Bug 885579 - Narrow widgets dropped on a wide widget should place the narrow widget above the wide widget, r=jaws

This commit is contained in:
Gijs Kruitbosch 2013-09-17 22:33:02 +02:00
parent 29023d1a51
commit 2b3c5a5bf1
5 changed files with 200 additions and 96 deletions

View File

@ -10,6 +10,8 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PanelWideWidgetTracker",
"resource:///modules/PanelWideWidgetTracker.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
"resource:///modules/CustomizableWidgets.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
@ -155,6 +157,8 @@ let CustomizableUIInternal = {
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: panelPlacements
});
PanelWideWidgetTracker.init();
this.registerArea(CustomizableUI.AREA_NAVBAR, {
legacy: true,
type: CustomizableUI.TYPE_TOOLBAR,
@ -393,8 +397,10 @@ let CustomizableUIInternal = {
}
}
this.notifyListeners("onWidgetBeforeDOMChange", node, currentNode, container);
this.insertWidgetBefore(node, currentNode, container, aArea);
this._addParentFlex(node);
this.notifyListeners("onWidgetAfterDOMChange", node, currentNode, container);
if (gResetting) {
this.notifyListeners("onWidgetReset", id);
}
@ -1771,7 +1777,7 @@ let CustomizableUIInternal = {
getCustomizeTargetForArea: function(aArea, aWindow) {
let buildAreaNodes = gBuildAreas.get(aArea);
if (!buildAreaNodes) {
throw new Error("No build area nodes registered for " + aArea);
return null;
}
for (let node of buildAreaNodes) {
@ -1780,7 +1786,7 @@ let CustomizableUIInternal = {
}
}
throw new Error("Could not find any window nodes for area " + aArea);
return null;
},
reset: function() {
@ -2118,7 +2124,8 @@ function WidgetGroupWrapper(aWidget) {
}
let instance = aWidget.instances.get(aWindow.document);
if (!instance) {
if (!instance &&
(aWidget.showInPrivateBrowsing || !PrivateBrowsingUtils.isWindowPrivate(aWindow))) {
instance = CustomizableUIInternal.buildWidget(aWindow.document,
aWidget);
}
@ -2128,6 +2135,16 @@ function WidgetGroupWrapper(aWidget) {
return wrapper;
};
this.__defineGetter__("instances", function() {
// Can't use gBuildWindows here because some areas load lazily:
let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
if (!placement) {
return [];
}
let area = placement.area;
return [this.forWindow(node.ownerDocument.defaultView) for (node of gBuildAreas.get(area))];
});
this.__defineGetter__("areaType", function() {
return gAreas.get(aWidget.currentArea).get("type");
});
@ -2224,6 +2241,10 @@ function XULWidgetGroupWrapper(aWidgetId) {
return gAreas.get(placement.area).get("type");
});
this.__defineGetter__("instances", function() {
return [this.forWindow(win) for ([win,] of gBuildWindows)];
});
Object.freeze(this);
}

View File

@ -36,49 +36,6 @@ function setAttributes(aNode, aAttrs) {
}
}
// This function is called whenever an item gets moved in the menu panel. It
// adjusts the position of widgets within the panel to reduce single-column
// buttons from being placed in a row by themselves.
function adjustPosition(aNode) {
// TODO(bug 885574): Merge this constant with the one in CustomizeMode.jsm,
// maybe just use a pref for this.
const kColumnsInMenuPanel = 3;
let nodeId = aNode.id;
// If these are wrapped, we'll need to look for items before the wrapper instead:
if (aNode.parentNode.localName == "toolbarpaletteitem") {
aNode = aNode.parentNode;
}
// Make sure that there are n % columns = 0 narrow buttons before the widget.
let prevSibling = aNode.previousElementSibling;
let previousSiblingCount = 0;
while (prevSibling) {
let nodeToCheck = prevSibling.localName == "toolbarpaletteitem" ? prevSibling.firstChild : prevSibling;
if (!nodeToCheck.classList.contains(kWidePanelItemClass)) {
previousSiblingCount++;
}
prevSibling = prevSibling.previousElementSibling;
}
if (previousSiblingCount % kColumnsInMenuPanel) {
let previousElement = aNode.previousElementSibling;
if (!previousElement) {
return;
}
let nodeToCheck = previousElement.localName == "toolbarpaletteitem" ? previousElement.firstChild : previousElement;
if (nodeToCheck.classList.contains(kWidePanelItemClass)) {
return;
}
let position = CustomizableUI.getPlacementOfWidget(nodeId).position;
// We don't need to move all of the items in this pass, because
// this move will trigger adjustPosition to get called again. The
// function will stop recursing when it finds that there is no
// more work that is needed.
CustomizableUI.moveWidgetWithinArea(nodeId, position - 1);
}
}
const CustomizableWidgets = [{
id: "history-panelmenu",
type: "view",
@ -380,10 +337,6 @@ const CustomizableWidgets = [{
let listener = {
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
@ -395,10 +348,6 @@ const CustomizableWidgets = [{
}.bind(this),
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
@ -419,10 +368,6 @@ const CustomizableWidgets = [{
}.bind(this),
onWidgetMoved: function(aWidgetId, aArea) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
updateWidgetStyle(aArea);
@ -512,20 +457,12 @@ const CustomizableWidgets = [{
let listener = {
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
updateWidgetStyle(aArea);
}.bind(this),
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
// When a widget is demoted to the palette ('removed'), it's visual
@ -540,10 +477,6 @@ const CustomizableWidgets = [{
}.bind(this),
onWidgetMoved: function(aWidgetId, aArea) {
if (this.currentArea == CustomizableUI.AREA_PANEL) {
adjustPosition(node);
}
if (aWidgetId != this.id)
return;
updateWidgetStyle(aArea);

View File

@ -0,0 +1,162 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
let gModuleName = "[PanelWideWidgetTracker]";
#include logging.js
let gPanel = CustomizableUI.AREA_PANEL;
// We keep track of the widget placements for the panel locally:
let gPanelPlacements = [];
// All the wide widgets we know of:
let gWideWidgets = new Set();
// All the widgets we know of:
let gSeenWidgets = new Set();
// The class by which we recognize wide widgets:
const kWidePanelItemClass = "panel-combined-item";
let PanelWideWidgetTracker = {
// Listeners used to validate panel contents whenever they change:
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aArea == gPanel) {
let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
this.adjustWidgets(aWidgetId, aPosition, moveForward);
}
},
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
if (aArea == gPanel) {
let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
this.adjustWidgets(aWidgetId, Math.min(aOldPosition, aNewPosition), moveForward);
}
},
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aPrevArea == gPanel) {
let pos = gPanelPlacements.indexOf(aWidgetId);
this.adjustWidgets(aWidgetId, pos);
}
},
// Listener to keep abreast of any new nodes. We use the DOM one because
// we need access to the actual node's classlist, so we can't use the ones above.
// Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
if (!gSeenWidgets.has(aNode.id)) {
if (aNode.classList.contains(kWidePanelItemClass)) {
gWideWidgets.add(aNode.id);
}
gSeenWidgets.add(aNode.id);
}
},
// When widgets get destroyed, we remove them from our sets of stuff we care about:
onWidgetDestroyed: function(aWidgetId) {
gSeenWidgets.remove(aWidgetId);
gWideWidgets.remove(aWidgetId);
},
shouldMoveForward: function(aWidgetId, aPosition) {
let currentWidgetAtPosition = gPanelPlacements[aPosition];
return gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
},
adjustWidgets: function(aWidgetId, aPosition, aMoveForwards) {
if (this.adjustmentStack == 0) {
this.movingForward = aMoveForwards;
}
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
// First, make a list of all the widgets that are *after* the insertion/moving point.
let widgetsAffected = [];
for (let widget of gWideWidgets) {
let wideWidgetPos = gPanelPlacements.indexOf(widget);
// This would just be wideWidgetPos >= aPosition, except that if we start re-arranging
// widgets, we would re-enter here because obviously the wide widget ends up in its
// own position, and we'd never stop trying to rearrange things.
// So instead, we check the wide widget is after the insertion point *or*
// this is the first move, and the widget is exactly on the insertion point
if (wideWidgetPos > aPosition || (!this.adjustmentStack && wideWidgetPos == aPosition)) {
widgetsAffected.push(widget);
}
}
if (!widgetsAffected.length) {
return;
}
widgetsAffected.sort(function(a, b) gPanelPlacements.indexOf(a) < gPanelPlacements.indexOf(b));
this.adjustmentStack++;
this.adjustPosition(widgetsAffected[0]);
this.adjustmentStack--;
if (this.adjustmentStack == 0) {
delete this.movingForward;
}
},
// This function is called whenever an item gets moved in the menu panel. It
// adjusts the position of widgets within the panel to reduce single-column
// buttons from being placed in a row by themselves.
adjustPosition: function(aWidgetId) {
// TODO(bug 885574): Merge this constant with the one in CustomizeMode.jsm,
// maybe just use a pref for this.
const kColumnsInMenuPanel = 3;
// Make sure that there are n % columns = 0 narrow buttons before the widget.
let placementIndex = gPanelPlacements.indexOf(aWidgetId);
let prevSiblingCount = 0;
let fixedPos = null;
while (placementIndex--) {
let thisWidgetId = gPanelPlacements[placementIndex];
if (gWideWidgets.has(thisWidgetId)) {
continue;
}
let widgetWrapper = CustomizableUI.getWidget(gPanelPlacements[placementIndex]);
// This widget might not actually exist:
if (!widgetWrapper) {
continue;
}
// This widget might still not actually exist:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
widgetWrapper.instances.length == 0) {
continue;
}
// Or it might only be there some of the time:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
widgetWrapper.showInPrivateBrowsing === false) {
if (!fixedPos) {
fixedPos = placementIndex;
} else {
fixedPos = Math.min(fixedPos, placementIndex);
}
// We want to position ourselves before this item:
prevSiblingCount = 0;
} else {
prevSiblingCount++;
}
}
if (fixedPos !== null || prevSiblingCount % kColumnsInMenuPanel) {
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
if (this.movingForward) {
// Add 1 because we're moving forward, and we would otherwise count the widget itself.
desiredPos += (kColumnsInMenuPanel - (prevSiblingCount % kColumnsInMenuPanel)) + 1;
} else {
desiredPos -= prevSiblingCount % kColumnsInMenuPanel;
}
// We don't need to move all of the items in this pass, because
// this move will trigger adjustPosition to get called again. The
// function will stop recursing when it finds that there is no
// more work that is needed.
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
}
},
adjustmentStack: 0,
init: function() {
// Initialize our local placements copy and register the listener
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
CustomizableUI.addListener(this);
},
};

View File

@ -8,4 +8,5 @@ EXTRA_PP_JS_MODULES += [
'CustomizableUI.jsm',
'CustomizableWidgets.jsm',
'CustomizeMode.jsm',
'PanelWideWidgetTracker.jsm',
]

View File

@ -141,35 +141,22 @@ let gTests = [
run: function() {
let developerButton = document.getElementById("developer-button");
let zoomControls = document.getElementById("zoom-controls");
let expectedPlacementsAfterInsert = ["edit-controls",
"developer-button",
"new-window-button",
"privatebrowsing-button",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
let placementsAfterInsert = ["edit-controls",
"developer-button",
"new-window-button",
"privatebrowsing-button",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
simulateItemDrag(developerButton, zoomControls);
// Currently, the developer-button is placed after the zoom-controls, but it should be
// placed like expectedPlacementsAfterInsert describes.
todoAssertAreaPlacements(CustomizableUI.AREA_PANEL, expectedPlacementsAfterInsert);
let actualPlacementsAfterInsert = ["edit-controls",
"zoom-controls",
"developer-button",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
assertAreaPlacements(CustomizableUI.AREA_PANEL, actualPlacementsAfterInsert);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
// Check that the palette items are re-wrapped correctly.