mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
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:
parent
29023d1a51
commit
2b3c5a5bf1
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
162
browser/components/customizableui/src/PanelWideWidgetTracker.jsm
Normal file
162
browser/components/customizableui/src/PanelWideWidgetTracker.jsm
Normal 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);
|
||||
},
|
||||
};
|
@ -8,4 +8,5 @@ EXTRA_PP_JS_MODULES += [
|
||||
'CustomizableUI.jsm',
|
||||
'CustomizableWidgets.jsm',
|
||||
'CustomizeMode.jsm',
|
||||
'PanelWideWidgetTracker.jsm',
|
||||
]
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user