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