Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
michael@0 | 7 | |
michael@0 | 8 | this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"]; |
michael@0 | 9 | |
michael@0 | 10 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 11 | XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", |
michael@0 | 12 | "resource:///modules/CustomizableUI.jsm"); |
michael@0 | 13 | |
michael@0 | 14 | let gModuleName = "[PanelWideWidgetTracker]"; |
michael@0 | 15 | #include logging.js |
michael@0 | 16 | |
michael@0 | 17 | let gPanel = CustomizableUI.AREA_PANEL; |
michael@0 | 18 | // We keep track of the widget placements for the panel locally: |
michael@0 | 19 | let gPanelPlacements = []; |
michael@0 | 20 | |
michael@0 | 21 | // All the wide widgets we know of: |
michael@0 | 22 | let gWideWidgets = new Set(); |
michael@0 | 23 | // All the widgets we know of: |
michael@0 | 24 | let gSeenWidgets = new Set(); |
michael@0 | 25 | |
michael@0 | 26 | let PanelWideWidgetTracker = { |
michael@0 | 27 | // Listeners used to validate panel contents whenever they change: |
michael@0 | 28 | onWidgetAdded: function(aWidgetId, aArea, aPosition) { |
michael@0 | 29 | if (aArea == gPanel) { |
michael@0 | 30 | gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); |
michael@0 | 31 | let moveForward = this.shouldMoveForward(aWidgetId, aPosition); |
michael@0 | 32 | this.adjustWidgets(aWidgetId, moveForward); |
michael@0 | 33 | } |
michael@0 | 34 | }, |
michael@0 | 35 | onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) { |
michael@0 | 36 | if (aArea == gPanel) { |
michael@0 | 37 | gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); |
michael@0 | 38 | let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition); |
michael@0 | 39 | this.adjustWidgets(aWidgetId, moveForward); |
michael@0 | 40 | } |
michael@0 | 41 | }, |
michael@0 | 42 | onWidgetRemoved: function(aWidgetId, aPrevArea) { |
michael@0 | 43 | if (aPrevArea == gPanel) { |
michael@0 | 44 | gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); |
michael@0 | 45 | let pos = gPanelPlacements.indexOf(aWidgetId); |
michael@0 | 46 | this.adjustWidgets(aWidgetId, false); |
michael@0 | 47 | } |
michael@0 | 48 | }, |
michael@0 | 49 | onWidgetReset: function(aWidgetId) { |
michael@0 | 50 | gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); |
michael@0 | 51 | }, |
michael@0 | 52 | // Listener to keep abreast of any new nodes. We use the DOM one because |
michael@0 | 53 | // we need access to the actual node's classlist, so we can't use the ones above. |
michael@0 | 54 | // Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones. |
michael@0 | 55 | onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) { |
michael@0 | 56 | if (!gSeenWidgets.has(aNode.id)) { |
michael@0 | 57 | if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) { |
michael@0 | 58 | gWideWidgets.add(aNode.id); |
michael@0 | 59 | } |
michael@0 | 60 | gSeenWidgets.add(aNode.id); |
michael@0 | 61 | } |
michael@0 | 62 | }, |
michael@0 | 63 | // When widgets get destroyed, we remove them from our sets of stuff we care about: |
michael@0 | 64 | onWidgetDestroyed: function(aWidgetId) { |
michael@0 | 65 | gSeenWidgets.delete(aWidgetId); |
michael@0 | 66 | gWideWidgets.delete(aWidgetId); |
michael@0 | 67 | }, |
michael@0 | 68 | shouldMoveForward: function(aWidgetId, aPosition) { |
michael@0 | 69 | let currentWidgetAtPosition = gPanelPlacements[aPosition + 1]; |
michael@0 | 70 | let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId); |
michael@0 | 71 | // We might now think we can move forward, but for that we need at least 2 more small |
michael@0 | 72 | // widgets to be present: |
michael@0 | 73 | if (rv) { |
michael@0 | 74 | let furtherWidgets = gPanelPlacements.slice(aPosition + 2); |
michael@0 | 75 | let realWidgets = 0; |
michael@0 | 76 | if (furtherWidgets.length >= 2) { |
michael@0 | 77 | while (furtherWidgets.length && realWidgets < 2) { |
michael@0 | 78 | let w = furtherWidgets.shift(); |
michael@0 | 79 | if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) { |
michael@0 | 80 | realWidgets++; |
michael@0 | 81 | } |
michael@0 | 82 | } |
michael@0 | 83 | } |
michael@0 | 84 | if (realWidgets < 2) { |
michael@0 | 85 | rv = false; |
michael@0 | 86 | } |
michael@0 | 87 | } |
michael@0 | 88 | return rv; |
michael@0 | 89 | }, |
michael@0 | 90 | adjustWidgets: function(aWidgetId, aMoveForwards) { |
michael@0 | 91 | if (this.adjusting) { |
michael@0 | 92 | return; |
michael@0 | 93 | } |
michael@0 | 94 | this.adjusting = true; |
michael@0 | 95 | let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))]; |
michael@0 | 96 | // If we're moving the wide widgets forwards (down/to the right in the panel) |
michael@0 | 97 | // we want to start with the last widgets. Otherwise we move widgets over other wide |
michael@0 | 98 | // widgets, which might mess up their order. Likewise, if moving backwards we should start with |
michael@0 | 99 | // the first widget and work our way down/right from there. |
michael@0 | 100 | let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b) |
michael@0 | 101 | widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a), |
michael@0 | 102 | gPanelPlacements.indexOf(b))); |
michael@0 | 103 | for (let widget of widgetsAffected) { |
michael@0 | 104 | this.adjustPosition(widget, aMoveForwards); |
michael@0 | 105 | } |
michael@0 | 106 | this.adjusting = false; |
michael@0 | 107 | }, |
michael@0 | 108 | // This function is called whenever an item gets moved in the menu panel. It |
michael@0 | 109 | // adjusts the position of widgets within the panel to prevent "gaps" between |
michael@0 | 110 | // wide widgets that could be filled up with single column widgets |
michael@0 | 111 | adjustPosition: function(aWidgetId, aMoveForwards) { |
michael@0 | 112 | // Make sure that there are n % columns = 0 narrow buttons before the widget. |
michael@0 | 113 | let placementIndex = gPanelPlacements.indexOf(aWidgetId); |
michael@0 | 114 | let prevSiblingCount = 0; |
michael@0 | 115 | let fixedPos = null; |
michael@0 | 116 | while (placementIndex--) { |
michael@0 | 117 | let thisWidgetId = gPanelPlacements[placementIndex]; |
michael@0 | 118 | if (gWideWidgets.has(thisWidgetId)) { |
michael@0 | 119 | continue; |
michael@0 | 120 | } |
michael@0 | 121 | let widgetStatus = this.checkWidgetStatus(thisWidgetId); |
michael@0 | 122 | if (!widgetStatus) { |
michael@0 | 123 | continue; |
michael@0 | 124 | } |
michael@0 | 125 | if (widgetStatus == "public-only") { |
michael@0 | 126 | fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex); |
michael@0 | 127 | prevSiblingCount = 0; |
michael@0 | 128 | } else { |
michael@0 | 129 | prevSiblingCount++; |
michael@0 | 130 | } |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) { |
michael@0 | 134 | let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId); |
michael@0 | 135 | let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT); |
michael@0 | 136 | if (aMoveForwards && fixedPos == null) { |
michael@0 | 137 | // +1 because otherwise we'd count ourselves: |
michael@0 | 138 | desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1; |
michael@0 | 139 | } |
michael@0 | 140 | desiredPos += desiredChange; |
michael@0 | 141 | CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos); |
michael@0 | 142 | } |
michael@0 | 143 | }, |
michael@0 | 144 | |
michael@0 | 145 | /* |
michael@0 | 146 | * Check whether a widget id is actually known anywhere. |
michael@0 | 147 | * @returns false if the widget doesn't exist, |
michael@0 | 148 | * "public-only" if it's not shown in private windows |
michael@0 | 149 | * "real" if it does exist and is shown even in private windows |
michael@0 | 150 | */ |
michael@0 | 151 | checkWidgetStatus: function(aWidgetId) { |
michael@0 | 152 | let widgetWrapper = CustomizableUI.getWidget(aWidgetId); |
michael@0 | 153 | // This widget might not actually exist: |
michael@0 | 154 | if (!widgetWrapper) { |
michael@0 | 155 | return false; |
michael@0 | 156 | } |
michael@0 | 157 | // This widget might still not actually exist: |
michael@0 | 158 | if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL && |
michael@0 | 159 | widgetWrapper.instances.length == 0) { |
michael@0 | 160 | return false; |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | // Or it might only be there some of the time: |
michael@0 | 164 | if (widgetWrapper.provider == CustomizableUI.PROVIDER_API && |
michael@0 | 165 | widgetWrapper.showInPrivateBrowsing === false) { |
michael@0 | 166 | return "public-only"; |
michael@0 | 167 | } |
michael@0 | 168 | return "real"; |
michael@0 | 169 | }, |
michael@0 | 170 | |
michael@0 | 171 | init: function() { |
michael@0 | 172 | // Initialize our local placements copy and register the listener |
michael@0 | 173 | gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel); |
michael@0 | 174 | CustomizableUI.addListener(this); |
michael@0 | 175 | }, |
michael@0 | 176 | }; |