gecko/browser/components/customizableui/PanelWideWidgetTracker.jsm

177 lines
6.8 KiB
JavaScript

/* 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");
var gModuleName = "[PanelWideWidgetTracker]";
#include logging.js
var gPanel = CustomizableUI.AREA_PANEL;
// We keep track of the widget placements for the panel locally:
var gPanelPlacements = [];
// All the wide widgets we know of:
var gWideWidgets = new Set();
// All the widgets we know of:
var gSeenWidgets = new Set();
var PanelWideWidgetTracker = {
// Listeners used to validate panel contents whenever they change:
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aPrevArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let pos = gPanelPlacements.indexOf(aWidgetId);
this.adjustWidgets(aWidgetId, false);
}
},
onWidgetReset: function(aWidgetId) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
},
// 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(CustomizableUI.WIDE_PANEL_CLASS)) {
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.delete(aWidgetId);
gWideWidgets.delete(aWidgetId);
},
shouldMoveForward: function(aWidgetId, aPosition) {
let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
// We might now think we can move forward, but for that we need at least 2 more small
// widgets to be present:
if (rv) {
let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
let realWidgets = 0;
if (furtherWidgets.length >= 2) {
while (furtherWidgets.length && realWidgets < 2) {
let w = furtherWidgets.shift();
if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
realWidgets++;
}
}
}
if (realWidgets < 2) {
rv = false;
}
}
return rv;
},
adjustWidgets: function(aWidgetId, aMoveForwards) {
if (this.adjusting) {
return;
}
this.adjusting = true;
let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))];
// If we're moving the wide widgets forwards (down/to the right in the panel)
// we want to start with the last widgets. Otherwise we move widgets over other wide
// widgets, which might mess up their order. Likewise, if moving backwards we should start with
// the first widget and work our way down/right from there.
let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b)
widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a),
gPanelPlacements.indexOf(b)));
for (let widget of widgetsAffected) {
this.adjustPosition(widget, aMoveForwards);
}
this.adjusting = false;
},
// This function is called whenever an item gets moved in the menu panel. It
// adjusts the position of widgets within the panel to prevent "gaps" between
// wide widgets that could be filled up with single column widgets
adjustPosition: function(aWidgetId, aMoveForwards) {
// 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 widgetStatus = this.checkWidgetStatus(thisWidgetId);
if (!widgetStatus) {
continue;
}
if (widgetStatus == "public-only") {
fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
prevSiblingCount = 0;
} else {
prevSiblingCount++;
}
}
if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
if (aMoveForwards && fixedPos == null) {
// +1 because otherwise we'd count ourselves:
desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
}
desiredPos += desiredChange;
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
}
},
/*
* Check whether a widget id is actually known anywhere.
* @returns false if the widget doesn't exist,
* "public-only" if it's not shown in private windows
* "real" if it does exist and is shown even in private windows
*/
checkWidgetStatus: function(aWidgetId) {
let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
// This widget might not actually exist:
if (!widgetWrapper) {
return false;
}
// This widget might still not actually exist:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
widgetWrapper.instances.length == 0) {
return false;
}
// Or it might only be there some of the time:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
widgetWrapper.showInPrivateBrowsing === false) {
return "public-only";
}
return "real";
},
init: function() {
// Initialize our local placements copy and register the listener
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
CustomizableUI.addListener(this);
},
};