1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/customizableui/src/PanelWideWidgetTracker.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,176 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.10 + 1.11 +this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"]; 1.12 + 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", 1.15 + "resource:///modules/CustomizableUI.jsm"); 1.16 + 1.17 +let gModuleName = "[PanelWideWidgetTracker]"; 1.18 +#include logging.js 1.19 + 1.20 +let gPanel = CustomizableUI.AREA_PANEL; 1.21 +// We keep track of the widget placements for the panel locally: 1.22 +let gPanelPlacements = []; 1.23 + 1.24 +// All the wide widgets we know of: 1.25 +let gWideWidgets = new Set(); 1.26 +// All the widgets we know of: 1.27 +let gSeenWidgets = new Set(); 1.28 + 1.29 +let PanelWideWidgetTracker = { 1.30 + // Listeners used to validate panel contents whenever they change: 1.31 + onWidgetAdded: function(aWidgetId, aArea, aPosition) { 1.32 + if (aArea == gPanel) { 1.33 + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); 1.34 + let moveForward = this.shouldMoveForward(aWidgetId, aPosition); 1.35 + this.adjustWidgets(aWidgetId, moveForward); 1.36 + } 1.37 + }, 1.38 + onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) { 1.39 + if (aArea == gPanel) { 1.40 + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); 1.41 + let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition); 1.42 + this.adjustWidgets(aWidgetId, moveForward); 1.43 + } 1.44 + }, 1.45 + onWidgetRemoved: function(aWidgetId, aPrevArea) { 1.46 + if (aPrevArea == gPanel) { 1.47 + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); 1.48 + let pos = gPanelPlacements.indexOf(aWidgetId); 1.49 + this.adjustWidgets(aWidgetId, false); 1.50 + } 1.51 + }, 1.52 + onWidgetReset: function(aWidgetId) { 1.53 + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); 1.54 + }, 1.55 + // Listener to keep abreast of any new nodes. We use the DOM one because 1.56 + // we need access to the actual node's classlist, so we can't use the ones above. 1.57 + // Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones. 1.58 + onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) { 1.59 + if (!gSeenWidgets.has(aNode.id)) { 1.60 + if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) { 1.61 + gWideWidgets.add(aNode.id); 1.62 + } 1.63 + gSeenWidgets.add(aNode.id); 1.64 + } 1.65 + }, 1.66 + // When widgets get destroyed, we remove them from our sets of stuff we care about: 1.67 + onWidgetDestroyed: function(aWidgetId) { 1.68 + gSeenWidgets.delete(aWidgetId); 1.69 + gWideWidgets.delete(aWidgetId); 1.70 + }, 1.71 + shouldMoveForward: function(aWidgetId, aPosition) { 1.72 + let currentWidgetAtPosition = gPanelPlacements[aPosition + 1]; 1.73 + let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId); 1.74 + // We might now think we can move forward, but for that we need at least 2 more small 1.75 + // widgets to be present: 1.76 + if (rv) { 1.77 + let furtherWidgets = gPanelPlacements.slice(aPosition + 2); 1.78 + let realWidgets = 0; 1.79 + if (furtherWidgets.length >= 2) { 1.80 + while (furtherWidgets.length && realWidgets < 2) { 1.81 + let w = furtherWidgets.shift(); 1.82 + if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) { 1.83 + realWidgets++; 1.84 + } 1.85 + } 1.86 + } 1.87 + if (realWidgets < 2) { 1.88 + rv = false; 1.89 + } 1.90 + } 1.91 + return rv; 1.92 + }, 1.93 + adjustWidgets: function(aWidgetId, aMoveForwards) { 1.94 + if (this.adjusting) { 1.95 + return; 1.96 + } 1.97 + this.adjusting = true; 1.98 + let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))]; 1.99 + // If we're moving the wide widgets forwards (down/to the right in the panel) 1.100 + // we want to start with the last widgets. Otherwise we move widgets over other wide 1.101 + // widgets, which might mess up their order. Likewise, if moving backwards we should start with 1.102 + // the first widget and work our way down/right from there. 1.103 + let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b) 1.104 + widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a), 1.105 + gPanelPlacements.indexOf(b))); 1.106 + for (let widget of widgetsAffected) { 1.107 + this.adjustPosition(widget, aMoveForwards); 1.108 + } 1.109 + this.adjusting = false; 1.110 + }, 1.111 + // This function is called whenever an item gets moved in the menu panel. It 1.112 + // adjusts the position of widgets within the panel to prevent "gaps" between 1.113 + // wide widgets that could be filled up with single column widgets 1.114 + adjustPosition: function(aWidgetId, aMoveForwards) { 1.115 + // Make sure that there are n % columns = 0 narrow buttons before the widget. 1.116 + let placementIndex = gPanelPlacements.indexOf(aWidgetId); 1.117 + let prevSiblingCount = 0; 1.118 + let fixedPos = null; 1.119 + while (placementIndex--) { 1.120 + let thisWidgetId = gPanelPlacements[placementIndex]; 1.121 + if (gWideWidgets.has(thisWidgetId)) { 1.122 + continue; 1.123 + } 1.124 + let widgetStatus = this.checkWidgetStatus(thisWidgetId); 1.125 + if (!widgetStatus) { 1.126 + continue; 1.127 + } 1.128 + if (widgetStatus == "public-only") { 1.129 + fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex); 1.130 + prevSiblingCount = 0; 1.131 + } else { 1.132 + prevSiblingCount++; 1.133 + } 1.134 + } 1.135 + 1.136 + if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) { 1.137 + let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId); 1.138 + let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT); 1.139 + if (aMoveForwards && fixedPos == null) { 1.140 + // +1 because otherwise we'd count ourselves: 1.141 + desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1; 1.142 + } 1.143 + desiredPos += desiredChange; 1.144 + CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos); 1.145 + } 1.146 + }, 1.147 + 1.148 + /* 1.149 + * Check whether a widget id is actually known anywhere. 1.150 + * @returns false if the widget doesn't exist, 1.151 + * "public-only" if it's not shown in private windows 1.152 + * "real" if it does exist and is shown even in private windows 1.153 + */ 1.154 + checkWidgetStatus: function(aWidgetId) { 1.155 + let widgetWrapper = CustomizableUI.getWidget(aWidgetId); 1.156 + // This widget might not actually exist: 1.157 + if (!widgetWrapper) { 1.158 + return false; 1.159 + } 1.160 + // This widget might still not actually exist: 1.161 + if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL && 1.162 + widgetWrapper.instances.length == 0) { 1.163 + return false; 1.164 + } 1.165 + 1.166 + // Or it might only be there some of the time: 1.167 + if (widgetWrapper.provider == CustomizableUI.PROVIDER_API && 1.168 + widgetWrapper.showInPrivateBrowsing === false) { 1.169 + return "public-only"; 1.170 + } 1.171 + return "real"; 1.172 + }, 1.173 + 1.174 + init: function() { 1.175 + // Initialize our local placements copy and register the listener 1.176 + gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); 1.177 + CustomizableUI.addListener(this); 1.178 + }, 1.179 +};