mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-31 19:47:32 +09:00
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
This commit is contained in:
parent
a951f19dfd
commit
177b28a898
1938 changed files with 0 additions and 0 deletions
4411
application/basilisk/components/customizableui/CustomizableUI.jsm
Normal file
4411
application/basilisk/components/customizableui/CustomizableUI.jsm
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2297
application/basilisk/components/customizableui/CustomizeMode.jsm
Normal file
2297
application/basilisk/components/customizableui/CustomizeMode.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,420 @@
|
|||
/* 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";
|
||||
|
||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||
|
||||
var gManagers = new WeakMap();
|
||||
|
||||
const kPaletteId = "customization-palette";
|
||||
const kPlaceholderClass = "panel-customization-placeholder";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DragPositionManager"];
|
||||
|
||||
function AreaPositionManager(aContainer) {
|
||||
// Caching the direction and bounds of the container for quick access later:
|
||||
let window = aContainer.ownerGlobal;
|
||||
this._dir = window.getComputedStyle(aContainer).direction;
|
||||
let containerRect = aContainer.getBoundingClientRect();
|
||||
this._containerInfo = {
|
||||
left: containerRect.left,
|
||||
right: containerRect.right,
|
||||
top: containerRect.top,
|
||||
width: containerRect.width
|
||||
};
|
||||
this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL;
|
||||
this._horizontalDistance = null;
|
||||
this.update(aContainer);
|
||||
}
|
||||
|
||||
AreaPositionManager.prototype = {
|
||||
_nodePositionStore: null,
|
||||
_wideCache: null,
|
||||
|
||||
update: function(aContainer) {
|
||||
this._nodePositionStore = new WeakMap();
|
||||
this._wideCache = new Set();
|
||||
let last = null;
|
||||
let singleItemHeight;
|
||||
for (let child of aContainer.children) {
|
||||
if (child.hidden) {
|
||||
continue;
|
||||
}
|
||||
let isNodeWide = this._checkIfWide(child);
|
||||
if (isNodeWide) {
|
||||
this._wideCache.add(child.id);
|
||||
}
|
||||
let coordinates = this._lazyStoreGet(child);
|
||||
// We keep a baseline horizontal distance between non-wide nodes around
|
||||
// for use when we can't compare with previous/next nodes
|
||||
if (!this._horizontalDistance && last && !isNodeWide) {
|
||||
this._horizontalDistance = coordinates.left - last.left;
|
||||
}
|
||||
// We also keep the basic height of non-wide items for use below:
|
||||
if (!isNodeWide && !singleItemHeight) {
|
||||
singleItemHeight = coordinates.height;
|
||||
}
|
||||
last = !isNodeWide ? coordinates : null;
|
||||
}
|
||||
if (this._inPanel) {
|
||||
this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT;
|
||||
} else {
|
||||
this._heightToWidthFactor = this._containerInfo.width / singleItemHeight;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the closest node in the container given the coordinates.
|
||||
* "Closest" is defined in a somewhat strange manner: we prefer nodes
|
||||
* which are in the same row over nodes that are in a different row.
|
||||
* In order to implement this, we use a weighted cartesian distance
|
||||
* where dy is more heavily weighted by a factor corresponding to the
|
||||
* ratio between the container's width and the height of its elements.
|
||||
*/
|
||||
find: function(aContainer, aX, aY, aDraggedItemId) {
|
||||
let closest = null;
|
||||
let minCartesian = Number.MAX_VALUE;
|
||||
let containerX = this._containerInfo.left;
|
||||
let containerY = this._containerInfo.top;
|
||||
for (let node of aContainer.children) {
|
||||
let coordinates = this._lazyStoreGet(node);
|
||||
let offsetX = coordinates.x - containerX;
|
||||
let offsetY = coordinates.y - containerY;
|
||||
let hDiff = offsetX - aX;
|
||||
let vDiff = offsetY - aY;
|
||||
// For wide widgets, we're always going to be further from the center
|
||||
// horizontally. Compensate:
|
||||
if (this.isWide(node)) {
|
||||
hDiff /= CustomizableUI.PANEL_COLUMN_COUNT;
|
||||
}
|
||||
// Then compensate for the height/width ratio so that we prefer items
|
||||
// which are in the same row:
|
||||
hDiff /= this._heightToWidthFactor;
|
||||
|
||||
let cartesianDiff = hDiff * hDiff + vDiff * vDiff;
|
||||
if (cartesianDiff < minCartesian) {
|
||||
minCartesian = cartesianDiff;
|
||||
closest = node;
|
||||
}
|
||||
}
|
||||
|
||||
// Now correct this node based on what we're dragging
|
||||
if (closest) {
|
||||
let doc = aContainer.ownerDocument;
|
||||
let draggedItem = doc.getElementById(aDraggedItemId);
|
||||
// If dragging a wide item, always pick the first item in a row:
|
||||
if (this._inPanel && draggedItem &&
|
||||
draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
|
||||
return this._firstInRow(closest);
|
||||
}
|
||||
let targetBounds = this._lazyStoreGet(closest);
|
||||
let farSide = this._dir == "ltr" ? "right" : "left";
|
||||
let outsideX = targetBounds[farSide];
|
||||
// Check if we're closer to the next target than to this one:
|
||||
// Only move if we're not targeting a node in a different row:
|
||||
if (aY > targetBounds.top && aY < targetBounds.bottom) {
|
||||
if ((this._dir == "ltr" && aX > outsideX) ||
|
||||
(this._dir == "rtl" && aX < outsideX)) {
|
||||
return closest.nextSibling || aContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
},
|
||||
|
||||
/**
|
||||
* "Insert" a "placeholder" by shifting the subsequent children out of the
|
||||
* way. We go through all the children, and shift them based on the position
|
||||
* they would have if we had inserted something before aBefore. We use CSS
|
||||
* transforms for this, which are CSS transitioned.
|
||||
*/
|
||||
insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) {
|
||||
let isShifted = false;
|
||||
let shiftDown = aWide;
|
||||
for (let child of aContainer.children) {
|
||||
// Don't need to shift hidden nodes:
|
||||
if (child.getAttribute("hidden") == "true") {
|
||||
continue;
|
||||
}
|
||||
// If this is the node before which we're inserting, start shifting
|
||||
// everything that comes after. One exception is inserting at the end
|
||||
// of the menupanel, in which case we do not shift the placeholders:
|
||||
if (child == aBefore && !child.classList.contains(kPlaceholderClass)) {
|
||||
isShifted = true;
|
||||
// If the node before which we're inserting is wide, we should
|
||||
// shift everything one row down:
|
||||
if (!shiftDown && this.isWide(child)) {
|
||||
shiftDown = true;
|
||||
}
|
||||
}
|
||||
// If we're moving items before a wide node that were already there,
|
||||
// it's possible it's not necessary to shift nodes
|
||||
// including & after the wide node.
|
||||
if (this.__undoShift) {
|
||||
isShifted = false;
|
||||
}
|
||||
if (isShifted) {
|
||||
// Conversely, if we're adding something before a wide node, for
|
||||
// simplicity's sake we move everything including the wide node down:
|
||||
if (this.__moveDown) {
|
||||
shiftDown = true;
|
||||
}
|
||||
if (aIsFromThisArea && !this._lastPlaceholderInsertion) {
|
||||
child.setAttribute("notransition", "true");
|
||||
}
|
||||
// Determine the CSS transform based on the next node:
|
||||
child.style.transform = this._getNextPos(child, shiftDown, aSize);
|
||||
} else {
|
||||
// If we're not shifting this node, reset the transform
|
||||
child.style.transform = "";
|
||||
}
|
||||
}
|
||||
if (aContainer.lastChild && aIsFromThisArea &&
|
||||
!this._lastPlaceholderInsertion) {
|
||||
// Flush layout:
|
||||
aContainer.lastChild.getBoundingClientRect();
|
||||
// then remove all the [notransition]
|
||||
for (let child of aContainer.children) {
|
||||
child.removeAttribute("notransition");
|
||||
}
|
||||
}
|
||||
delete this.__moveDown;
|
||||
delete this.__undoShift;
|
||||
this._lastPlaceholderInsertion = aBefore;
|
||||
},
|
||||
|
||||
isWide: function(aNode) {
|
||||
return this._wideCache.has(aNode.id);
|
||||
},
|
||||
|
||||
_checkIfWide: function(aNode) {
|
||||
return this._inPanel && aNode && aNode.firstChild &&
|
||||
aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset all the transforms in this container, optionally without
|
||||
* transitioning them.
|
||||
* @param aContainer the container in which to reset transforms
|
||||
* @param aNoTransition if truthy, adds a notransition attribute to the node
|
||||
* while resetting the transform.
|
||||
*/
|
||||
clearPlaceholders: function(aContainer, aNoTransition) {
|
||||
for (let child of aContainer.children) {
|
||||
if (aNoTransition) {
|
||||
child.setAttribute("notransition", true);
|
||||
}
|
||||
child.style.transform = "";
|
||||
if (aNoTransition) {
|
||||
// Need to force a reflow otherwise this won't work.
|
||||
child.getBoundingClientRect();
|
||||
child.removeAttribute("notransition");
|
||||
}
|
||||
}
|
||||
// We snapped back, so we can assume there's no more
|
||||
// "last" placeholder insertion point to keep track of.
|
||||
if (aNoTransition) {
|
||||
this._lastPlaceholderInsertion = null;
|
||||
}
|
||||
},
|
||||
|
||||
_getNextPos: function(aNode, aShiftDown, aSize) {
|
||||
// Shifting down is easy:
|
||||
if (this._inPanel && aShiftDown) {
|
||||
return "translate(0, " + aSize.height + "px)";
|
||||
}
|
||||
return this._diffWithNext(aNode, aSize);
|
||||
},
|
||||
|
||||
_diffWithNext: function(aNode, aSize) {
|
||||
let xDiff;
|
||||
let yDiff = null;
|
||||
let nodeBounds = this._lazyStoreGet(aNode);
|
||||
let side = this._dir == "ltr" ? "left" : "right";
|
||||
let next = this._getVisibleSiblingForDirection(aNode, "next");
|
||||
// First we determine the transform along the x axis.
|
||||
// Usually, there will be a next node to base this on:
|
||||
if (next) {
|
||||
let otherBounds = this._lazyStoreGet(next);
|
||||
xDiff = otherBounds[side] - nodeBounds[side];
|
||||
// If the next node is a wide item in the panel, check if we could maybe
|
||||
// just move further out in the same row, without snapping to the next
|
||||
// one. This happens, for example, if moving an item that's before a wide
|
||||
// node within its own row of items. There will be space to drop this
|
||||
// item within the row, and the rest of the items do not need to shift.
|
||||
if (this.isWide(next)) {
|
||||
let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds,
|
||||
this._firstInRow(aNode));
|
||||
// If this has the same sign as our original shift, we're still
|
||||
// snapping to the start of the row. In this case, we should move
|
||||
// everything after us a row down, so as not to display two nodes on
|
||||
// top of each other:
|
||||
// (we would be able to get away with checking for equality instead of
|
||||
// equal signs here, but one of these is based on the x coordinate of
|
||||
// the first item in row N and one on that for row N - 1, so this is
|
||||
// safer, as their margins might differ)
|
||||
if ((otherXDiff < 0) == (xDiff < 0)) {
|
||||
this.__moveDown = true;
|
||||
} else {
|
||||
// Otherwise, we succeeded and can move further out. This also means
|
||||
// we can stop shifting the rest of the content:
|
||||
xDiff = otherXDiff;
|
||||
this.__undoShift = true;
|
||||
}
|
||||
} else {
|
||||
// We set this explicitly because otherwise some strange difference
|
||||
// between the height and the actual difference between line creeps in
|
||||
// and messes with alignments
|
||||
yDiff = otherBounds.top - nodeBounds.top;
|
||||
}
|
||||
} else {
|
||||
// We don't have a sibling whose position we can use. First, let's see
|
||||
// if we're also the first item (which complicates things):
|
||||
let firstNode = this._firstInRow(aNode);
|
||||
if (aNode == firstNode) {
|
||||
// Maybe we stored the horizontal distance between non-wide nodes,
|
||||
// if not, we'll use the width of the incoming node as a proxy:
|
||||
xDiff = this._horizontalDistance || aSize.width;
|
||||
} else {
|
||||
// If not, we should be able to get the distance to the previous node
|
||||
// and use the inverse, unless there's no room for another node (ie we
|
||||
// are the last node and there's no room for another one)
|
||||
xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
|
||||
}
|
||||
}
|
||||
|
||||
// If we've not determined the vertical difference yet, check it here
|
||||
if (yDiff === null) {
|
||||
// If the next node is behind rather than in front, we must have moved
|
||||
// vertically:
|
||||
if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) {
|
||||
yDiff = aSize.height;
|
||||
} else {
|
||||
// Otherwise, we haven't
|
||||
yDiff = 0;
|
||||
}
|
||||
}
|
||||
return "translate(" + xDiff + "px, " + yDiff + "px)";
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to find the transform a node if there isn't a next node
|
||||
* to base that on.
|
||||
* @param aNode the node to transform
|
||||
* @param aNodeBounds the bounding rect info of this node
|
||||
* @param aFirstNodeInRow the first node in aNode's row
|
||||
*/
|
||||
_moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) {
|
||||
let next = this._getVisibleSiblingForDirection(aNode, "previous");
|
||||
let otherBounds = this._lazyStoreGet(next);
|
||||
let side = this._dir == "ltr" ? "left" : "right";
|
||||
let xDiff = aNodeBounds[side] - otherBounds[side];
|
||||
// If, however, this means we move outside the container's box
|
||||
// (i.e. the row in which this item is placed is full)
|
||||
// we should move it to align with the first item in the next row instead
|
||||
let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"];
|
||||
if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) ||
|
||||
(this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) {
|
||||
xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side];
|
||||
}
|
||||
return xDiff;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get position details from our cache. If the node is not yet cached, get its position
|
||||
* information and cache it now.
|
||||
* @param aNode the node whose position info we want
|
||||
* @return the position info
|
||||
*/
|
||||
_lazyStoreGet: function(aNode) {
|
||||
let rect = this._nodePositionStore.get(aNode);
|
||||
if (!rect) {
|
||||
// getBoundingClientRect() returns a DOMRect that is live, meaning that
|
||||
// as the element moves around, the rects values change. We don't want
|
||||
// that - we want a snapshot of what the rect values are right at this
|
||||
// moment, and nothing else. So we have to clone the values.
|
||||
let clientRect = aNode.getBoundingClientRect();
|
||||
rect = {
|
||||
left: clientRect.left,
|
||||
right: clientRect.right,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height,
|
||||
top: clientRect.top,
|
||||
bottom: clientRect.bottom,
|
||||
};
|
||||
rect.x = rect.left + rect.width / 2;
|
||||
rect.y = rect.top + rect.height / 2;
|
||||
Object.freeze(rect);
|
||||
this._nodePositionStore.set(aNode, rect);
|
||||
}
|
||||
return rect;
|
||||
},
|
||||
|
||||
_firstInRow: function(aNode) {
|
||||
// XXXmconley: I'm not entirely sure why we need to take the floor of these
|
||||
// values - it looks like, periodically, we're getting fractional pixels back
|
||||
// from lazyStoreGet. I've filed bug 994247 to investigate.
|
||||
let bound = Math.floor(this._lazyStoreGet(aNode).top);
|
||||
let rv = aNode;
|
||||
let prev;
|
||||
while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
|
||||
if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
|
||||
return rv;
|
||||
}
|
||||
rv = prev;
|
||||
}
|
||||
return rv;
|
||||
},
|
||||
|
||||
_getVisibleSiblingForDirection: function(aNode, aDirection) {
|
||||
let rv = aNode;
|
||||
do {
|
||||
rv = rv[aDirection + "Sibling"];
|
||||
} while (rv && rv.getAttribute("hidden") == "true")
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
var DragPositionManager = {
|
||||
start: function(aWindow) {
|
||||
let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar");
|
||||
areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
|
||||
areas.push(aWindow.document.getElementById(kPaletteId));
|
||||
for (let areaNode of areas) {
|
||||
let positionManager = gManagers.get(areaNode);
|
||||
if (positionManager) {
|
||||
positionManager.update(areaNode);
|
||||
} else {
|
||||
gManagers.set(areaNode, new AreaPositionManager(areaNode));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add: function(aWindow, aArea, aContainer) {
|
||||
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
|
||||
return;
|
||||
}
|
||||
|
||||
gManagers.set(aContainer, new AreaPositionManager(aContainer));
|
||||
},
|
||||
|
||||
remove: function(aWindow, aArea, aContainer) {
|
||||
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
|
||||
return;
|
||||
}
|
||||
|
||||
gManagers.delete(aContainer);
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
gManagers = new WeakMap();
|
||||
},
|
||||
|
||||
getManagerForArea: function(aArea) {
|
||||
return gManagers.get(aArea);
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(DragPositionManager);
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/* 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 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);
|
||||
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 = gPanelPlacements.filter((w) => 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 ? ((a, b) => a < b) : ((a, b) => a > b);
|
||||
widgetsAffected.sort((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);
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ScrollbarSampler"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var gSystemScrollbarWidth = null;
|
||||
|
||||
this.ScrollbarSampler = {
|
||||
getSystemScrollbarWidth: function() {
|
||||
if (gSystemScrollbarWidth !== null) {
|
||||
return Promise.resolve(gSystemScrollbarWidth);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this._sampleSystemScrollbarWidth().then(function(systemScrollbarWidth) {
|
||||
gSystemScrollbarWidth = systemScrollbarWidth;
|
||||
resolve(gSystemScrollbarWidth);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetSystemScrollbarWidth: function() {
|
||||
gSystemScrollbarWidth = null;
|
||||
},
|
||||
|
||||
_sampleSystemScrollbarWidth: function() {
|
||||
let hwin = Services.appShell.hiddenDOMWindow;
|
||||
let hdoc = hwin.document.documentElement;
|
||||
let iframe = hwin.document.createElementNS("http://www.w3.org/1999/xhtml",
|
||||
"html:iframe");
|
||||
iframe.setAttribute("srcdoc", '<body style="overflow-y: scroll"></body>');
|
||||
hdoc.appendChild(iframe);
|
||||
|
||||
let cwindow = iframe.contentWindow;
|
||||
let utils = cwindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
return new Promise(resolve => {
|
||||
cwindow.addEventListener("load", function onLoad(aEvent) {
|
||||
cwindow.removeEventListener("load", onLoad);
|
||||
let sbWidth = {};
|
||||
try {
|
||||
utils.getScrollbarSize(true, sbWidth, {});
|
||||
} catch (e) {
|
||||
Cu.reportError("Could not sample scrollbar size: " + e + " -- " +
|
||||
e.stack);
|
||||
sbWidth.value = 0;
|
||||
}
|
||||
// Minimum width of 10 so that we have enough padding:
|
||||
sbWidth.value = Math.max(sbWidth.value, 10);
|
||||
resolve(sbWidth.value);
|
||||
iframe.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
Object.freeze(this.ScrollbarSampler);
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<hbox id="customization-container" flex="1" hidden="true">
|
||||
<vbox flex="1" id="customization-palette-container">
|
||||
<label id="customization-header">
|
||||
&customizeMode.menuAndToolbars.header2;
|
||||
</label>
|
||||
<hbox id="customization-empty" hidden="true">
|
||||
<label>&customizeMode.menuAndToolbars.empty;</label>
|
||||
<label onclick="BrowserOpenAddonsMgr('addons://discover/');"
|
||||
onkeypress="BrowserOpenAddonsMgr('addons://discover/');"
|
||||
id="customization-more-tools"
|
||||
class="text-link">
|
||||
&customizeMode.menuAndToolbars.emptyLink;
|
||||
</label>
|
||||
</hbox>
|
||||
<vbox id="customization-palette" class="customization-palette"/>
|
||||
<spacer id="customization-spacer"/>
|
||||
<hbox id="customization-footer">
|
||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||
<button id="customization-titlebar-visibility-button" class="customizationmode-button"
|
||||
label="&customizeMode.titlebar;" type="checkbox"
|
||||
#NB: because oncommand fires after click, by the time we've fired, the checkbox binding
|
||||
# will already have switched the button's state, so this is correct:
|
||||
oncommand="gCustomizeMode.toggleTitlebar(this.hasAttribute('checked'))"/>
|
||||
#endif
|
||||
<button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
|
||||
<menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
|
||||
</button>
|
||||
<button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
|
||||
<panel type="arrow" id="customization-lwtheme-menu"
|
||||
onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
|
||||
position="topcenter bottomleft"
|
||||
flip="none"
|
||||
role="menu">
|
||||
<label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
|
||||
<label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
|
||||
<hbox id="customization-lwtheme-menu-footer">
|
||||
<toolbarbutton class="customization-lwtheme-menu-footeritem"
|
||||
label="&customizeMode.lwthemes.menuManage;"
|
||||
accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
|
||||
tabindex="0"
|
||||
oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
|
||||
<toolbarbutton class="customization-lwtheme-menu-footeritem"
|
||||
label="&customizeMode.lwthemes.menuGetMore;"
|
||||
accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
|
||||
tabindex="0"
|
||||
oncommand="gCustomizeMode.getMoreThemes(event);"/>
|
||||
</hbox>
|
||||
</panel>
|
||||
</button>
|
||||
|
||||
<spacer id="customization-footer-spacer"/>
|
||||
<button id="customization-undo-reset-button"
|
||||
class="customizationmode-button"
|
||||
hidden="true"
|
||||
oncommand="gCustomizeMode.undoReset();"
|
||||
label="&undoCmd.label;"/>
|
||||
<button id="customization-reset-button"
|
||||
oncommand="gCustomizeMode.reset();"
|
||||
label="&customizeMode.restoreDefaults;"
|
||||
class="customizationmode-button"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="customization-panel-container">
|
||||
<vbox id="customization-panelWrapper">
|
||||
<html:style html:type="text/html" scoped="scoped">
|
||||
@import url(chrome://global/skin/popup.css);
|
||||
</html:style>
|
||||
<box class="panel-arrowbox">
|
||||
<box flex="1"/>
|
||||
<image class="panel-arrow" side="top"/>
|
||||
</box>
|
||||
<box class="panel-arrowcontent" side="top" flex="1">
|
||||
<hbox id="customization-panelHolder"/>
|
||||
<box class="panel-inner-arrowcontentfooter" hidden="true"/>
|
||||
</box>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# 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/.
|
||||
|
||||
browser.jar:
|
||||
content/browser/customizableui/panelUI.css
|
||||
content/browser/customizableui/panelUI.js
|
||||
content/browser/customizableui/panelUI.xml
|
||||
content/browser/customizableui/toolbar.xml
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/* 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/. */
|
||||
|
||||
.panel-viewstack[viewtype="main"] > .panel-clickcapturer {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.panel-mainview,
|
||||
.panel-viewcontainer,
|
||||
.panel-viewstack {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-viewstack {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.panel-subviews {
|
||||
-moz-stack-sizing: ignore;
|
||||
transform: translateX(0);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-subviews[panelopen] {
|
||||
transition: transform var(--panelui-subview-transition-duration);
|
||||
}
|
||||
|
||||
.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
|
||||
transition: height var(--panelui-subview-transition-duration);
|
||||
}
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<panel id="PanelUI-popup"
|
||||
role="group"
|
||||
type="arrow"
|
||||
hidden="true"
|
||||
flip="slide"
|
||||
position="bottomcenter topright"
|
||||
noautofocus="true">
|
||||
<panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
|
||||
<panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
|
||||
<vbox id="PanelUI-contents-scroller">
|
||||
<vbox id="PanelUI-contents" class="panelUI-grid"/>
|
||||
</vbox>
|
||||
|
||||
<footer id="PanelUI-footer">
|
||||
<toolbarbutton id="PanelUI-update-status"
|
||||
oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
|
||||
wrap="true"
|
||||
hidden="true"/>
|
||||
<hbox id="PanelUI-footer-fxa">
|
||||
<hbox id="PanelUI-fxa-status"
|
||||
defaultlabel="&fxaSignIn.label;"
|
||||
signedinTooltiptext="&fxaSignedIn.tooltip;"
|
||||
tooltiptext="&fxaSignedIn.tooltip;"
|
||||
errorlabel="&fxaSignInError.label;"
|
||||
unverifiedlabel="&fxaUnverified.label;"
|
||||
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
|
||||
<image id="PanelUI-fxa-avatar"/>
|
||||
<toolbarbutton id="PanelUI-fxa-label"
|
||||
fxabrandname="&syncBrand.fxAccount.label;"/>
|
||||
</hbox>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="PanelUI-fxa-icon"
|
||||
oncommand="gSyncUI.doSync();"
|
||||
closemenu="none">
|
||||
<observes element="sync-status" attribute="syncstatus"/>
|
||||
<observes element="sync-status" attribute="tooltiptext"/>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
|
||||
<hbox id="PanelUI-footer-inner">
|
||||
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
|
||||
exitLabel="&appMenuCustomizeExit.label;"
|
||||
tooltiptext="&appMenuCustomize.tooltip;"
|
||||
exitTooltiptext="&appMenuCustomizeExit.tooltip;"
|
||||
closemenu="none"
|
||||
oncommand="gCustomizeMode.toggle();"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
|
||||
closemenu="none"
|
||||
tooltiptext="&appMenuHelp.tooltip;"
|
||||
oncommand="PanelUI.showHelpView(this);"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="PanelUI-quit"
|
||||
#ifdef XP_WIN
|
||||
label="&quitApplicationCmdWin2.label;"
|
||||
tooltiptext="&quitApplicationCmdWin2.tooltip;"
|
||||
#else
|
||||
#ifdef XP_MACOSX
|
||||
label="&quitApplicationCmdMac2.label;"
|
||||
#else
|
||||
label="&quitApplicationCmd.label;"
|
||||
#endif
|
||||
#endif
|
||||
command="cmd_quitApplication"/>
|
||||
</hbox>
|
||||
</footer>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-history" flex="1">
|
||||
<label value="&appMenuHistory.label;" class="panel-subview-header"/>
|
||||
<vbox class="panel-subview-body">
|
||||
<toolbarbutton id="appMenuViewHistorySidebar"
|
||||
label="&appMenuHistory.viewSidebar.label;"
|
||||
type="checkbox"
|
||||
class="subviewbutton"
|
||||
key="key_gotoHistory"
|
||||
oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
|
||||
<observes element="viewHistorySidebar" attribute="checked"/>
|
||||
</toolbarbutton>
|
||||
<toolbarbutton id="appMenuClearRecentHistory"
|
||||
label="&appMenuHistory.clearRecent.label;"
|
||||
class="subviewbutton"
|
||||
command="Tools:Sanitize"/>
|
||||
<toolbarbutton id="appMenuRestoreLastSession"
|
||||
label="&appMenuHistory.restoreSession.label;"
|
||||
class="subviewbutton"
|
||||
command="Browser:RestoreLastSession"/>
|
||||
<menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
|
||||
<vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
|
||||
<menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
|
||||
<vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
|
||||
<menuseparator id="PanelUI-historyItems-separator"/>
|
||||
<vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
|
||||
</vbox>
|
||||
<toolbarbutton id="PanelUI-historyMore"
|
||||
class="panel-subview-footer subviewbutton"
|
||||
label="&appMenuHistory.showAll.label;"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
|
||||
<label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
|
||||
<vbox class="panel-subview-body">
|
||||
<!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
|
||||
<!-- When Sync is ready to sync -->
|
||||
<vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
|
||||
<vbox id="PanelUI-remotetabs-buttons">
|
||||
<toolbarbutton id="PanelUI-remotetabs-view-sidebar"
|
||||
class="subviewbutton"
|
||||
observes="viewTabsSidebar"
|
||||
label="&appMenuRemoteTabs.sidebar.label;"/>
|
||||
<toolbarbutton id="PanelUI-remotetabs-syncnow"
|
||||
observes="sync-status"
|
||||
class="subviewbutton"
|
||||
oncommand="gSyncUI.doSync();"
|
||||
closemenu="none"/>
|
||||
<menuseparator id="PanelUI-remotetabs-separator"/>
|
||||
</vbox>
|
||||
<deck id="PanelUI-remotetabs-deck">
|
||||
<!-- Sync is ready to Sync and the "tabs" engine is enabled -->
|
||||
<vbox id="PanelUI-remotetabs-tabspane">
|
||||
<vbox id="PanelUI-remotetabs-tabslist"
|
||||
notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
|
||||
/>
|
||||
</vbox>
|
||||
<!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
|
||||
<hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
|
||||
<vbox class="PanelUI-remotetabs-instruction-box">
|
||||
<hbox pack="center">
|
||||
<image class="fxaSyncIllustration" alt=""/>
|
||||
</hbox>
|
||||
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
|
||||
<hbox pack="center">
|
||||
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
|
||||
label="&appMenuRemoteTabs.openprefs.label;"
|
||||
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<!-- Sync is ready to Sync but we are still fetching the tabs to show -->
|
||||
<vbox id="PanelUI-remotetabs-fetching">
|
||||
<!-- Show intentionally blank panel, see bug 1239845 -->
|
||||
</vbox>
|
||||
<!-- Sync has only 1 (ie, this) device connected -->
|
||||
<hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
|
||||
<vbox class="PanelUI-remotetabs-instruction-box">
|
||||
<hbox pack="center">
|
||||
<image class="fxaSyncIllustration" alt=""/>
|
||||
</hbox>
|
||||
<label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
|
||||
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
|
||||
<!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
|
||||
<label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
<!-- a box to ensure contained boxes are centered horizonally -->
|
||||
<hbox pack="center" flex="1">
|
||||
<!-- When Sync is not configured -->
|
||||
<vbox id="PanelUI-remotetabs-setupsync"
|
||||
flex="1"
|
||||
align="center"
|
||||
class="PanelUI-remotetabs-instruction-box"
|
||||
observes="sync-setup-state">
|
||||
<image class="fxaSyncIllustration" alt=""/>
|
||||
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
|
||||
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
|
||||
label="&appMenuRemoteTabs.signin.label;"
|
||||
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
|
||||
</vbox>
|
||||
<!-- When Sync needs re-authentication. This uses the exact same messaging
|
||||
as "Sync is not configured" but remains a separate box so we get
|
||||
the goodness of observing broadcasters to manage the hidden states -->
|
||||
<vbox id="PanelUI-remotetabs-reauthsync"
|
||||
flex="1"
|
||||
align="center"
|
||||
class="PanelUI-remotetabs-instruction-box"
|
||||
observes="sync-reauth-state">
|
||||
<image class="fxaSyncIllustration" alt=""/>
|
||||
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
|
||||
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
|
||||
label="&appMenuRemoteTabs.signin.label;"
|
||||
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
|
||||
<label value="&bookmarksMenu.label;" class="panel-subview-header"/>
|
||||
<vbox class="panel-subview-body">
|
||||
<toolbarbutton id="panelMenuBookmarkThisPage"
|
||||
class="subviewbutton"
|
||||
observes="bookmarkThisPageBroadcaster"
|
||||
command="Browser:AddBookmarkAs"
|
||||
onclick="PanelUI.hide();"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="panelMenu_viewBookmarksSidebar"
|
||||
label="&viewBookmarksSidebar2.label;"
|
||||
class="subviewbutton"
|
||||
key="viewBookmarksSidebarKb"
|
||||
oncommand="SidebarUI.toggle('viewBookmarksSidebar'); PanelUI.hide();">
|
||||
<observes element="viewBookmarksSidebar" attribute="checked"/>
|
||||
</toolbarbutton>
|
||||
<toolbarbutton id="panelMenu_viewBookmarksToolbar"
|
||||
label="&viewBookmarksToolbar.label;"
|
||||
type="checkbox"
|
||||
toolbarId="PersonalToolbar"
|
||||
class="subviewbutton"
|
||||
oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="panelMenu_bookmarksToolbar"
|
||||
label="&personalbarCmd.label;"
|
||||
class="subviewbutton cui-withicon"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
|
||||
<toolbarbutton id="panelMenu_unsortedBookmarks"
|
||||
label="&otherBookmarksCmd.label;"
|
||||
class="subviewbutton cui-withicon"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
|
||||
<toolbarseparator class="small-separator"/>
|
||||
<toolbaritem id="panelMenu_bookmarksMenu"
|
||||
orient="vertical"
|
||||
smoothscroll="false"
|
||||
onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
|
||||
oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
|
||||
flatList="true"
|
||||
tooltip="bhTooltip">
|
||||
<!-- bookmarks menu items will go here -->
|
||||
</toolbaritem>
|
||||
</vbox>
|
||||
<toolbarbutton id="panelMenu_showAllBookmarks"
|
||||
label="&showAllBookmarks2.label;"
|
||||
class="subviewbutton panel-subview-footer"
|
||||
command="Browser:ShowAllBookmarks"
|
||||
onclick="PanelUI.hide();"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
|
||||
<label value="&feedsMenu2.label;" class="panel-subview-header"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-containers" flex="1">
|
||||
<label value="&containersMenu.label;" class="panel-subview-header"/>
|
||||
<vbox id="PanelUI-containersItems"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
|
||||
<label value="&helpMenu.label;" class="panel-subview-header"/>
|
||||
<vbox id="PanelUI-helpItems" class="panel-subview-body"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-developer" flex="1">
|
||||
<label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
|
||||
<vbox id="PanelUI-developerItems" class="panel-subview-body"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-sidebar" flex="1">
|
||||
<label value="&appMenuSidebars.label;" class="panel-subview-header"/>
|
||||
<vbox id="PanelUI-sidebarItems" class="panel-subview-body"/>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-characterEncodingView" flex="1">
|
||||
<label value="&charsetMenu2.label;" class="panel-subview-header"/>
|
||||
<vbox class="panel-subview-body">
|
||||
<vbox id="PanelUI-characterEncodingView-pinned"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox id="PanelUI-characterEncodingView-charsets"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox>
|
||||
<label id="PanelUI-characterEncodingView-autodetect-label"/>
|
||||
<vbox id="PanelUI-characterEncodingView-autodetect"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-panicView" flex="1">
|
||||
<vbox class="panel-subview-body">
|
||||
<hbox id="PanelUI-panic-timeframe">
|
||||
<image id="PanelUI-panic-timeframe-icon" alt=""/>
|
||||
<vbox flex="1">
|
||||
<hbox id="PanelUI-panic-header">
|
||||
<image id="PanelUI-panic-timeframe-icon-small" alt=""/>
|
||||
<description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
|
||||
</hbox>
|
||||
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
|
||||
<radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
|
||||
value="5" class="subviewradio"/>
|
||||
<radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
|
||||
value="2" class="subviewradio"/>
|
||||
<radio id="PanelUI-panic-day" label="&panicButton.view.day;"
|
||||
value="6" class="subviewradio"/>
|
||||
</radiogroup>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="PanelUI-panic-explanations">
|
||||
<label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
|
||||
|
||||
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
|
||||
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
|
||||
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
|
||||
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
|
||||
|
||||
<label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
|
||||
</vbox>
|
||||
<button id="PanelUI-panic-view-button"
|
||||
label="&panicButton.view.forgetButton;"/>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
</panelmultiview>
|
||||
<!-- These menupopups are located here to prevent flickering,
|
||||
see bug 492960 comment 20. -->
|
||||
<menupopup id="customizationPanelItemContextMenu">
|
||||
<menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
|
||||
closemenu="single"
|
||||
class="customize-context-moveToToolbar"
|
||||
accesskey="&customizeMenu.moveToToolbar.accesskey;"
|
||||
label="&customizeMenu.moveToToolbar.label;"/>
|
||||
<menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
|
||||
closemenu="single"
|
||||
class="customize-context-removeFromPanel"
|
||||
accesskey="&customizeMenu.removeFromMenu.accesskey;"
|
||||
label="&customizeMenu.removeFromMenu.label;"/>
|
||||
<menuseparator/>
|
||||
<menuitem command="cmd_CustomizeToolbars"
|
||||
class="viewCustomizeToolbar"
|
||||
accesskey="&viewCustomizeToolbar.accesskey;"
|
||||
label="&viewCustomizeToolbar.label;"/>
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="customizationPaletteItemContextMenu">
|
||||
<menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
|
||||
class="customize-context-addToToolbar"
|
||||
accesskey="&customizeMenu.addToToolbar.accesskey;"
|
||||
label="&customizeMenu.addToToolbar.label;"/>
|
||||
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
|
||||
class="customize-context-addToPanel"
|
||||
accesskey="&customizeMenu.addToPanel.accesskey;"
|
||||
label="&customizeMenu.addToPanel.label;"/>
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="customizationPanelContextMenu">
|
||||
<menuitem command="cmd_CustomizeToolbars"
|
||||
accesskey="&customizeMenu.addMoreItems.accesskey;"
|
||||
label="&customizeMenu.addMoreItems.label;"/>
|
||||
</menupopup>
|
||||
</panel>
|
||||
|
||||
<panel id="widget-overflow"
|
||||
role="group"
|
||||
type="arrow"
|
||||
noautofocus="true"
|
||||
context="toolbar-context-menu"
|
||||
position="bottomcenter topright"
|
||||
hidden="true">
|
||||
<vbox id="widget-overflow-scroller">
|
||||
<vbox id="widget-overflow-list" class="widget-overflow-list"
|
||||
overflowfortoolbar="nav-bar"/>
|
||||
</vbox>
|
||||
</panel>
|
||||
|
||||
<panel id="panic-button-success-notification"
|
||||
type="arrow"
|
||||
position="bottomcenter topright"
|
||||
hidden="true"
|
||||
role="alert"
|
||||
orient="vertical">
|
||||
<hbox id="panic-button-success-header">
|
||||
<image id="panic-button-success-icon" alt=""/>
|
||||
<vbox>
|
||||
<description>&panicButton.thankyou.msg1;</description>
|
||||
<description>&panicButton.thankyou.msg2;</description>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<button label="&panicButton.thankyou.buttonlabel;"
|
||||
id="panic-button-success-closebutton"
|
||||
oncommand="PanicButtonNotifier.close()"/>
|
||||
</panel>
|
||||
|
|
@ -0,0 +1,558 @@
|
|||
/* 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
|
||||
"resource:///modules/ScrollbarSampler.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
/**
|
||||
* Maintains the state and dispatches events for the main menu panel.
|
||||
*/
|
||||
|
||||
const PanelUI = {
|
||||
/** Panel events that we listen for. **/
|
||||
get kEvents() {
|
||||
return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
|
||||
},
|
||||
/**
|
||||
* Used for lazily getting and memoizing elements from the document. Lazy
|
||||
* getters are set in init, and memoizing happens after the first retrieval.
|
||||
*/
|
||||
get kElements() {
|
||||
return {
|
||||
contents: "PanelUI-contents",
|
||||
mainView: "PanelUI-mainView",
|
||||
multiView: "PanelUI-multiView",
|
||||
helpView: "PanelUI-helpView",
|
||||
menuButton: "PanelUI-menu-button",
|
||||
panel: "PanelUI-popup",
|
||||
scroller: "PanelUI-contents-scroller"
|
||||
};
|
||||
},
|
||||
|
||||
_initialized: false,
|
||||
init: function() {
|
||||
for (let [k, v] of Object.entries(this.kElements)) {
|
||||
// Need to do fresh let-bindings per iteration
|
||||
let getKey = k;
|
||||
let id = v;
|
||||
this.__defineGetter__(getKey, function() {
|
||||
delete this[getKey];
|
||||
return this[getKey] = document.getElementById(id);
|
||||
});
|
||||
}
|
||||
|
||||
this.menuButton.addEventListener("mousedown", this);
|
||||
this.menuButton.addEventListener("keypress", this);
|
||||
this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
|
||||
window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
|
||||
CustomizableUI.addListener(this);
|
||||
this._initialized = true;
|
||||
},
|
||||
|
||||
_eventListenersAdded: false,
|
||||
_ensureEventListenersAdded: function() {
|
||||
if (this._eventListenersAdded)
|
||||
return;
|
||||
this._addEventListeners();
|
||||
},
|
||||
|
||||
_addEventListeners: function() {
|
||||
for (let event of this.kEvents) {
|
||||
this.panel.addEventListener(event, this);
|
||||
}
|
||||
|
||||
this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
|
||||
this._eventListenersAdded = true;
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
for (let event of this.kEvents) {
|
||||
this.panel.removeEventListener(event, this);
|
||||
}
|
||||
this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
|
||||
this.menuButton.removeEventListener("mousedown", this);
|
||||
this.menuButton.removeEventListener("keypress", this);
|
||||
window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
|
||||
CustomizableUI.removeListener(this);
|
||||
this._overlayScrollListenerBoundFn = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize mode extracts the mainView and puts it somewhere else while the
|
||||
* user customizes. Upon completion, this function can be called to put the
|
||||
* panel back to where it belongs in normal browsing mode.
|
||||
*
|
||||
* @param aMainView
|
||||
* The mainView node to put back into place.
|
||||
*/
|
||||
setMainView: function(aMainView) {
|
||||
this._ensureEventListenersAdded();
|
||||
this.multiView.setMainView(aMainView);
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the menu panel if it's closed, or closes it if it's
|
||||
* open.
|
||||
*
|
||||
* @param aEvent the event that triggers the toggle.
|
||||
*/
|
||||
toggle: function(aEvent) {
|
||||
// Don't show the panel if the window is in customization mode,
|
||||
// since this button doubles as an exit path for the user in this case.
|
||||
if (document.documentElement.hasAttribute("customizing")) {
|
||||
return;
|
||||
}
|
||||
this._ensureEventListenersAdded();
|
||||
if (this.panel.state == "open") {
|
||||
this.hide();
|
||||
} else if (this.panel.state == "closed") {
|
||||
this.show(aEvent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the menu panel. If the event target has a child with the
|
||||
* toolbarbutton-icon attribute, the panel will be anchored on that child.
|
||||
* Otherwise, the panel is anchored on the event target itself.
|
||||
*
|
||||
* @param aEvent the event (if any) that triggers showing the menu.
|
||||
*/
|
||||
show: function(aEvent) {
|
||||
return new Promise(resolve => {
|
||||
this.ensureReady().then(() => {
|
||||
if (this.panel.state == "open" ||
|
||||
document.documentElement.hasAttribute("customizing")) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
|
||||
if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
|
||||
updateEditUIVisibility();
|
||||
}
|
||||
|
||||
let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
|
||||
if (personalBookmarksPlacement &&
|
||||
personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
|
||||
PlacesToolbarHelper.customizeChange();
|
||||
}
|
||||
|
||||
let anchor;
|
||||
if (!aEvent ||
|
||||
aEvent.type == "command") {
|
||||
anchor = this.menuButton;
|
||||
} else {
|
||||
anchor = aEvent.target;
|
||||
}
|
||||
|
||||
this.panel.addEventListener("popupshown", function onPopupShown() {
|
||||
this.removeEventListener("popupshown", onPopupShown);
|
||||
resolve();
|
||||
});
|
||||
|
||||
let iconAnchor =
|
||||
document.getAnonymousElementByAttribute(anchor, "class",
|
||||
"toolbarbutton-icon");
|
||||
this.panel.openPopup(iconAnchor || anchor);
|
||||
}, (reason) => {
|
||||
console.error("Error showing the PanelUI menu", reason);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* If the menu panel is being shown, hide it.
|
||||
*/
|
||||
hide: function() {
|
||||
if (document.documentElement.hasAttribute("customizing")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel.hidePopup();
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
// Ignore context menus and menu button menus showing and hiding:
|
||||
if (aEvent.type.startsWith("popup") &&
|
||||
aEvent.target != this.panel) {
|
||||
return;
|
||||
}
|
||||
switch (aEvent.type) {
|
||||
case "popupshowing":
|
||||
this._adjustLabelsForAutoHyphens();
|
||||
// Fall through
|
||||
case "popupshown":
|
||||
// Fall through
|
||||
case "popuphiding":
|
||||
// Fall through
|
||||
case "popuphidden":
|
||||
this._updatePanelButton(aEvent.target);
|
||||
break;
|
||||
case "mousedown":
|
||||
if (aEvent.button == 0)
|
||||
this.toggle(aEvent);
|
||||
break;
|
||||
case "keypress":
|
||||
this.toggle(aEvent);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
get isReady() {
|
||||
return !!this._isReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* Registering the menu panel is done lazily for performance reasons. This
|
||||
* method is exposed so that CustomizationMode can force panel-readyness in the
|
||||
* event that customization mode is started before the panel has been opened
|
||||
* by the user.
|
||||
*
|
||||
* @param aCustomizing (optional) set to true if this was called while entering
|
||||
* customization mode. If that's the case, we trust that customization
|
||||
* mode will handle calling beginBatchUpdate and endBatchUpdate.
|
||||
*
|
||||
* @return a Promise that resolves once the panel is ready to roll.
|
||||
*/
|
||||
ensureReady: function(aCustomizing=false) {
|
||||
if (this._readyPromise) {
|
||||
return this._readyPromise;
|
||||
}
|
||||
this._readyPromise = Task.spawn(function*() {
|
||||
if (!this._initialized) {
|
||||
yield new Promise(resolve => {
|
||||
let delayedStartupObserver = (aSubject, aTopic, aData) => {
|
||||
if (aSubject == window) {
|
||||
Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
|
||||
});
|
||||
}
|
||||
|
||||
this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
|
||||
getLocale());
|
||||
if (!this._scrollWidth) {
|
||||
// In order to properly center the contents of the panel, while ensuring
|
||||
// that we have enough space on either side to show a scrollbar, we have to
|
||||
// do a bit of hackery. In particular, we calculate a new width for the
|
||||
// scroller, based on the system scrollbar width.
|
||||
this._scrollWidth =
|
||||
(yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
|
||||
let cstyle = window.getComputedStyle(this.scroller);
|
||||
let widthStr = cstyle.width;
|
||||
// Get the calculated padding on the left and right sides of
|
||||
// the scroller too. We'll use that in our final calculation so
|
||||
// that if a scrollbar appears, we don't have the contents right
|
||||
// up against the edge of the scroller.
|
||||
let paddingLeft = cstyle.paddingLeft;
|
||||
let paddingRight = cstyle.paddingRight;
|
||||
let calcStr = [widthStr, this._scrollWidth,
|
||||
paddingLeft, paddingRight].join(" + ");
|
||||
this.scroller.style.width = "calc(" + calcStr + ")";
|
||||
}
|
||||
|
||||
if (aCustomizing) {
|
||||
CustomizableUI.registerMenuPanel(this.contents);
|
||||
} else {
|
||||
this.beginBatchUpdate();
|
||||
try {
|
||||
CustomizableUI.registerMenuPanel(this.contents);
|
||||
} finally {
|
||||
this.endBatchUpdate();
|
||||
}
|
||||
}
|
||||
this._updateQuitTooltip();
|
||||
this.panel.hidden = false;
|
||||
this._isReady = true;
|
||||
}.bind(this)).then(null, Cu.reportError);
|
||||
|
||||
return this._readyPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch the panel to the main view if it's not already
|
||||
* in that view.
|
||||
*/
|
||||
showMainView: function() {
|
||||
this._ensureEventListenersAdded();
|
||||
this.multiView.showMainView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch the panel to the help view if it's not already
|
||||
* in that view.
|
||||
*/
|
||||
showHelpView: function(aAnchor) {
|
||||
this._ensureEventListenersAdded();
|
||||
this.multiView.showSubView("PanelUI-helpView", aAnchor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a subview in the panel with a given ID.
|
||||
*
|
||||
* @param aViewId the ID of the subview to show.
|
||||
* @param aAnchor the element that spawned the subview.
|
||||
* @param aPlacementArea the CustomizableUI area that aAnchor is in.
|
||||
*/
|
||||
showSubView: Task.async(function*(aViewId, aAnchor, aPlacementArea) {
|
||||
this._ensureEventListenersAdded();
|
||||
let viewNode = document.getElementById(aViewId);
|
||||
if (!viewNode) {
|
||||
Cu.reportError("Could not show panel subview with id: " + aViewId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aAnchor) {
|
||||
Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aPlacementArea == CustomizableUI.AREA_PANEL) {
|
||||
this.multiView.showSubView(aViewId, aAnchor);
|
||||
} else if (!aAnchor.open) {
|
||||
aAnchor.open = true;
|
||||
|
||||
let tempPanel = document.createElement("panel");
|
||||
tempPanel.setAttribute("type", "arrow");
|
||||
tempPanel.setAttribute("id", "customizationui-widget-panel");
|
||||
tempPanel.setAttribute("class", "cui-widget-panel");
|
||||
tempPanel.setAttribute("viewId", aViewId);
|
||||
if (aAnchor.getAttribute("tabspecific")) {
|
||||
tempPanel.setAttribute("tabspecific", true);
|
||||
}
|
||||
if (this._disableAnimations) {
|
||||
tempPanel.setAttribute("animate", "false");
|
||||
}
|
||||
tempPanel.setAttribute("context", "");
|
||||
document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
|
||||
// If the view has a footer, set a convenience class on the panel.
|
||||
tempPanel.classList.toggle("cui-widget-panelWithFooter",
|
||||
viewNode.querySelector(".panel-subview-footer"));
|
||||
|
||||
let multiView = document.createElement("panelmultiview");
|
||||
multiView.setAttribute("id", "customizationui-widget-multiview");
|
||||
multiView.setAttribute("nosubviews", "true");
|
||||
tempPanel.appendChild(multiView);
|
||||
multiView.setAttribute("mainViewIsSubView", "true");
|
||||
multiView.setMainView(viewNode);
|
||||
viewNode.classList.add("cui-widget-panelview");
|
||||
|
||||
let viewShown = false;
|
||||
let panelRemover = () => {
|
||||
viewNode.classList.remove("cui-widget-panelview");
|
||||
if (viewShown) {
|
||||
CustomizableUI.removePanelCloseListeners(tempPanel);
|
||||
tempPanel.removeEventListener("popuphidden", panelRemover);
|
||||
|
||||
let evt = new CustomEvent("ViewHiding", {detail: viewNode});
|
||||
viewNode.dispatchEvent(evt);
|
||||
}
|
||||
aAnchor.open = false;
|
||||
|
||||
this.multiView.appendChild(viewNode);
|
||||
tempPanel.remove();
|
||||
};
|
||||
|
||||
// Emit the ViewShowing event so that the widget definition has a chance
|
||||
// to lazily populate the subview with things.
|
||||
let detail = {
|
||||
blockers: new Set(),
|
||||
addBlocker(aPromise) {
|
||||
this.blockers.add(aPromise);
|
||||
},
|
||||
};
|
||||
|
||||
let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
|
||||
viewNode.dispatchEvent(evt);
|
||||
|
||||
let cancel = evt.defaultPrevented;
|
||||
if (detail.blockers.size) {
|
||||
try {
|
||||
let results = yield Promise.all(detail.blockers);
|
||||
cancel = cancel || results.some(val => val === false);
|
||||
} catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
panelRemover();
|
||||
return;
|
||||
}
|
||||
|
||||
viewShown = true;
|
||||
CustomizableUI.addPanelCloseListeners(tempPanel);
|
||||
tempPanel.addEventListener("popuphidden", panelRemover);
|
||||
|
||||
let iconAnchor =
|
||||
document.getAnonymousElementByAttribute(aAnchor, "class",
|
||||
"toolbarbutton-icon");
|
||||
|
||||
if (iconAnchor && aAnchor.id) {
|
||||
iconAnchor.setAttribute("consumeanchor", aAnchor.id);
|
||||
}
|
||||
tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* NB: The enable- and disableSingleSubviewPanelAnimations methods only
|
||||
* affect the hiding/showing animations of single-subview panels (tempPanel
|
||||
* in the showSubView method).
|
||||
*/
|
||||
disableSingleSubviewPanelAnimations: function() {
|
||||
this._disableAnimations = true;
|
||||
},
|
||||
|
||||
enableSingleSubviewPanelAnimations: function() {
|
||||
this._disableAnimations = false;
|
||||
},
|
||||
|
||||
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
|
||||
if (aContainer != this.contents) {
|
||||
return;
|
||||
}
|
||||
if (aWasRemoval) {
|
||||
aNode.removeAttribute("auto-hyphens");
|
||||
}
|
||||
},
|
||||
|
||||
onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
|
||||
if (aContainer != this.contents) {
|
||||
return;
|
||||
}
|
||||
if (!aIsRemoval &&
|
||||
(this.panel.state == "open" ||
|
||||
document.documentElement.hasAttribute("customizing"))) {
|
||||
this._adjustLabelsForAutoHyphens(aNode);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Signal that we're about to make a lot of changes to the contents of the
|
||||
* panels all at once. For performance, we ignore the mutations.
|
||||
*/
|
||||
beginBatchUpdate: function() {
|
||||
this._ensureEventListenersAdded();
|
||||
this.multiView.ignoreMutations = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Signal that we're done making bulk changes to the panel. We now pay
|
||||
* attention to mutations. This automatically synchronizes the multiview
|
||||
* container with whichever view is displayed if the panel is open.
|
||||
*/
|
||||
endBatchUpdate: function(aReason) {
|
||||
this._ensureEventListenersAdded();
|
||||
this.multiView.ignoreMutations = false;
|
||||
},
|
||||
|
||||
_adjustLabelsForAutoHyphens: function(aNode) {
|
||||
let toolbarButtons = aNode ? [aNode] :
|
||||
this.contents.querySelectorAll(".toolbarbutton-1");
|
||||
for (let node of toolbarButtons) {
|
||||
let label = node.getAttribute("label");
|
||||
if (!label) {
|
||||
continue;
|
||||
}
|
||||
if (label.includes("\u00ad")) {
|
||||
node.setAttribute("auto-hyphens", "off");
|
||||
} else {
|
||||
node.removeAttribute("auto-hyphens");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the anchor node into the open or closed state, depending
|
||||
* on the state of the panel.
|
||||
*/
|
||||
_updatePanelButton: function() {
|
||||
this.menuButton.open = this.panel.state == "open" ||
|
||||
this.panel.state == "showing";
|
||||
},
|
||||
|
||||
_onHelpViewShow: function(aEvent) {
|
||||
// Call global menu setup function
|
||||
buildHelpMenu();
|
||||
|
||||
let helpMenu = document.getElementById("menu_HelpPopup");
|
||||
let items = this.getElementsByTagName("vbox")[0];
|
||||
let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
|
||||
let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
// Remove all buttons from the view
|
||||
while (items.firstChild) {
|
||||
items.removeChild(items.firstChild);
|
||||
}
|
||||
|
||||
// Add the current set of menuitems of the Help menu to this view
|
||||
let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
|
||||
let fragment = document.createDocumentFragment();
|
||||
for (let node of menuItems) {
|
||||
if (node.hidden)
|
||||
continue;
|
||||
let button = document.createElementNS(NSXUL, "toolbarbutton");
|
||||
// Copy specific attributes from a menuitem of the Help menu
|
||||
for (let attrName of attrs) {
|
||||
if (!node.hasAttribute(attrName))
|
||||
continue;
|
||||
button.setAttribute(attrName, node.getAttribute(attrName));
|
||||
}
|
||||
button.setAttribute("class", "subviewbutton");
|
||||
fragment.appendChild(button);
|
||||
}
|
||||
items.appendChild(fragment);
|
||||
},
|
||||
|
||||
_updateQuitTooltip: function() {
|
||||
if (AppConstants.platform == "win") {
|
||||
return;
|
||||
}
|
||||
|
||||
let tooltipId = AppConstants.platform == "macosx" ?
|
||||
"quit-button.tooltiptext.mac" :
|
||||
"quit-button.tooltiptext.linux2";
|
||||
|
||||
let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
|
||||
let stringArgs = [brands.GetStringFromName("brandShortName")];
|
||||
|
||||
let key = document.getElementById("key_quitApplication");
|
||||
stringArgs.push(ShortcutUtils.prettifyShortcut(key));
|
||||
let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
|
||||
let quitButton = document.getElementById("PanelUI-quit");
|
||||
quitButton.setAttribute("tooltiptext", tooltipString);
|
||||
},
|
||||
|
||||
_overlayScrollListenerBoundFn: null,
|
||||
_overlayScrollListener: function(aMQL) {
|
||||
ScrollbarSampler.resetSystemScrollbarWidth();
|
||||
this._scrollWidth = null;
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
|
||||
|
||||
/**
|
||||
* Gets the currently selected locale for display.
|
||||
* @return the selected locale or "en-US" if none is selected
|
||||
*/
|
||||
function getLocale() {
|
||||
try {
|
||||
let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry);
|
||||
return chromeRegistry.getSelectedLocale("browser");
|
||||
} catch (ex) {
|
||||
return "en-US";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<bindings id="browserPanelUIBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="panelmultiview">
|
||||
<resources>
|
||||
<stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
|
||||
</resources>
|
||||
<content>
|
||||
<xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
|
||||
<xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
|
||||
<xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
|
||||
|
||||
<!-- Used to capture click events over the PanelUI-mainView if we're in
|
||||
subview mode. That way, any click on the PanelUI-mainView causes us
|
||||
to revert to the mainView mode, whereupon PanelUI-click-capture then
|
||||
allows click events to go through it. -->
|
||||
<xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
|
||||
|
||||
<!-- We manually set display: none (via a CSS attribute selector) on the
|
||||
subviews that are not being displayed. We're using this over a deck
|
||||
because a deck assumes the size of its largest child, regardless of
|
||||
whether or not it is shown. That's not good for our case, since we
|
||||
want to allow each subview to be uniquely sized. -->
|
||||
<xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
|
||||
<children includes="panelview"/>
|
||||
</xul:vbox>
|
||||
</xul:stack>
|
||||
</xul:box>
|
||||
</content>
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<field name="_clickCapturer" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
|
||||
</field>
|
||||
<field name="_viewContainer" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
|
||||
</field>
|
||||
<field name="_mainViewContainer" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
|
||||
</field>
|
||||
<field name="_subViews" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "subViews");
|
||||
</field>
|
||||
<field name="_viewStack" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
|
||||
</field>
|
||||
<field name="_panel" readonly="true">
|
||||
this.parentNode;
|
||||
</field>
|
||||
|
||||
<field name="_currentSubView">null</field>
|
||||
<field name="_anchorElement">null</field>
|
||||
<field name="_mainViewHeight">0</field>
|
||||
<field name="_subViewObserver">null</field>
|
||||
<field name="__transitioning">false</field>
|
||||
<field name="_ignoreMutations">false</field>
|
||||
|
||||
<property name="showingSubView" readonly="true"
|
||||
onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
|
||||
<property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
|
||||
<property name="_mainView" readonly="true"
|
||||
onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
|
||||
<property name="showingSubViewAsMainView" readonly="true"
|
||||
onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
|
||||
|
||||
<property name="ignoreMutations">
|
||||
<getter>
|
||||
return this._ignoreMutations;
|
||||
</getter>
|
||||
<setter><![CDATA[
|
||||
this._ignoreMutations = val;
|
||||
if (!val && this._panel.state == "open") {
|
||||
if (this.showingSubView) {
|
||||
this._syncContainerWithSubView();
|
||||
} else {
|
||||
this._syncContainerWithMainView();
|
||||
}
|
||||
}
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="_transitioning">
|
||||
<getter>
|
||||
return this.__transitioning;
|
||||
</getter>
|
||||
<setter><![CDATA[
|
||||
this.__transitioning = val;
|
||||
if (val) {
|
||||
this.setAttribute("transitioning", "true");
|
||||
} else {
|
||||
this.removeAttribute("transitioning");
|
||||
}
|
||||
]]></setter>
|
||||
</property>
|
||||
<constructor><![CDATA[
|
||||
this._clickCapturer.addEventListener("click", this);
|
||||
this._panel.addEventListener("popupshowing", this);
|
||||
this._panel.addEventListener("popupshown", this);
|
||||
this._panel.addEventListener("popuphidden", this);
|
||||
this._subViews.addEventListener("overflow", this);
|
||||
this._mainViewContainer.addEventListener("overflow", this);
|
||||
|
||||
// Get a MutationObserver ready to react to subview size changes. We
|
||||
// only attach this MutationObserver when a subview is being displayed.
|
||||
this._subViewObserver =
|
||||
new MutationObserver(this._syncContainerWithSubView.bind(this));
|
||||
this._mainViewObserver =
|
||||
new MutationObserver(this._syncContainerWithMainView.bind(this));
|
||||
|
||||
this._mainViewContainer.setAttribute("panelid",
|
||||
this._panel.id);
|
||||
|
||||
if (this._mainView) {
|
||||
this.setMainView(this._mainView);
|
||||
}
|
||||
this.setAttribute("viewtype", "main");
|
||||
]]></constructor>
|
||||
|
||||
<destructor><![CDATA[
|
||||
if (this._mainView) {
|
||||
this._mainView.removeAttribute("mainview");
|
||||
}
|
||||
this._mainViewObserver.disconnect();
|
||||
this._subViewObserver.disconnect();
|
||||
this._panel.removeEventListener("popupshowing", this);
|
||||
this._panel.removeEventListener("popupshown", this);
|
||||
this._panel.removeEventListener("popuphidden", this);
|
||||
this._subViews.removeEventListener("overflow", this);
|
||||
this._mainViewContainer.removeEventListener("overflow", this);
|
||||
this._clickCapturer.removeEventListener("click", this);
|
||||
]]></destructor>
|
||||
|
||||
<method name="setMainView">
|
||||
<parameter name="aNewMainView"/>
|
||||
<body><![CDATA[
|
||||
if (this._mainView) {
|
||||
this._mainViewObserver.disconnect();
|
||||
this._subViews.appendChild(this._mainView);
|
||||
this._mainView.removeAttribute("mainview");
|
||||
}
|
||||
this._mainViewId = aNewMainView.id;
|
||||
aNewMainView.setAttribute("mainview", "true");
|
||||
this._mainViewContainer.appendChild(aNewMainView);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="showMainView">
|
||||
<body><![CDATA[
|
||||
if (this.showingSubView) {
|
||||
let viewNode = this._currentSubView;
|
||||
let evt = document.createEvent("CustomEvent");
|
||||
evt.initCustomEvent("ViewHiding", true, true, viewNode);
|
||||
viewNode.dispatchEvent(evt);
|
||||
|
||||
viewNode.removeAttribute("current");
|
||||
this._currentSubView = null;
|
||||
|
||||
this._subViewObserver.disconnect();
|
||||
|
||||
this._setViewContainerHeight(this._mainViewHeight);
|
||||
|
||||
this.setAttribute("viewtype", "main");
|
||||
}
|
||||
|
||||
this._shiftMainView();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="showSubView">
|
||||
<parameter name="aViewId"/>
|
||||
<parameter name="aAnchor"/>
|
||||
<body><![CDATA[
|
||||
Task.spawn(function*() {
|
||||
let viewNode = this.querySelector("#" + aViewId);
|
||||
viewNode.setAttribute("current", true);
|
||||
// Emit the ViewShowing event so that the widget definition has a chance
|
||||
// to lazily populate the subview with things.
|
||||
let detail = {
|
||||
blockers: new Set(),
|
||||
addBlocker(aPromise) {
|
||||
this.blockers.add(aPromise);
|
||||
},
|
||||
};
|
||||
|
||||
let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
|
||||
viewNode.dispatchEvent(evt);
|
||||
|
||||
let cancel = evt.defaultPrevented;
|
||||
if (detail.blockers.size) {
|
||||
try {
|
||||
let results = yield Promise.all(detail.blockers);
|
||||
cancel = cancel || results.some(val => val === false);
|
||||
} catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentSubView = viewNode;
|
||||
|
||||
// Now we have to transition the panel. There are a few parts to this:
|
||||
//
|
||||
// 1) The main view content gets shifted so that the center of the anchor
|
||||
// node is at the left-most edge of the panel.
|
||||
// 2) The subview deck slides in so that it takes up almost all of the
|
||||
// panel.
|
||||
// 3) If the subview is taller then the main panel contents, then the panel
|
||||
// must grow to meet that new height. Otherwise, it must shrink.
|
||||
//
|
||||
// All three of these actions make use of CSS transformations, so they
|
||||
// should all occur simultaneously.
|
||||
this.setAttribute("viewtype", "subview");
|
||||
this._shiftMainView(aAnchor);
|
||||
|
||||
this._mainViewHeight = this._viewStack.clientHeight;
|
||||
|
||||
let newHeight = this._heightOfSubview(viewNode, this._subViews);
|
||||
this._setViewContainerHeight(newHeight);
|
||||
|
||||
this._subViewObserver.observe(viewNode, {
|
||||
attributes: true,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}.bind(this));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_setViewContainerHeight">
|
||||
<parameter name="aHeight"/>
|
||||
<body><![CDATA[
|
||||
let container = this._viewContainer;
|
||||
this._transitioning = true;
|
||||
|
||||
let onTransitionEnd = () => {
|
||||
container.removeEventListener("transitionend", onTransitionEnd);
|
||||
this._transitioning = false;
|
||||
};
|
||||
|
||||
container.addEventListener("transitionend", onTransitionEnd);
|
||||
container.style.height = `${aHeight}px`;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_shiftMainView">
|
||||
<parameter name="aAnchor"/>
|
||||
<body><![CDATA[
|
||||
if (aAnchor) {
|
||||
// We need to find the edge of the anchor, relative to the main panel.
|
||||
// Then we need to add half the width of the anchor. This is the target
|
||||
// that we need to transition to.
|
||||
let anchorRect = aAnchor.getBoundingClientRect();
|
||||
let mainViewRect = this._mainViewContainer.getBoundingClientRect();
|
||||
let center = aAnchor.clientWidth / 2;
|
||||
let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
|
||||
let edge;
|
||||
if (direction == "ltr") {
|
||||
edge = anchorRect.left - mainViewRect.left;
|
||||
} else {
|
||||
edge = mainViewRect.right - anchorRect.right;
|
||||
}
|
||||
|
||||
// If the anchor is an element on the far end of the mainView we
|
||||
// don't want to shift the mainView too far, we would reveal empty
|
||||
// space otherwise.
|
||||
let cstyle = window.getComputedStyle(document.documentElement, null);
|
||||
let exitSubViewGutterWidth =
|
||||
cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
|
||||
let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
|
||||
let target = Math.min(maxShift, edge + center);
|
||||
|
||||
let neg = direction == "ltr" ? "-" : "";
|
||||
this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
|
||||
aAnchor.setAttribute("panel-multiview-anchor", true);
|
||||
} else {
|
||||
this._mainViewContainer.style.transform = "";
|
||||
if (this.anchorElement)
|
||||
this.anchorElement.removeAttribute("panel-multiview-anchor");
|
||||
}
|
||||
this.anchorElement = aAnchor;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
|
||||
// Shouldn't act on e.g. context menus being shown from within the panel.
|
||||
return;
|
||||
}
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
if (aEvent.originalTarget == this._clickCapturer) {
|
||||
this.showMainView();
|
||||
}
|
||||
break;
|
||||
case "overflow":
|
||||
if (aEvent.target.localName == "vbox") {
|
||||
// Resize the right view on the next tick.
|
||||
if (this.showingSubView) {
|
||||
setTimeout(this._syncContainerWithSubView.bind(this), 0);
|
||||
} else if (!this.transitioning) {
|
||||
setTimeout(this._syncContainerWithMainView.bind(this), 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "popupshowing":
|
||||
this.setAttribute("panelopen", "true");
|
||||
// Bug 941196 - The panel can get taller when opening a subview. Disabling
|
||||
// autoPositioning means that the panel won't jump around if an opened
|
||||
// subview causes the panel to exceed the dimensions of the screen in the
|
||||
// direction that the panel originally opened in. This property resets
|
||||
// every time the popup closes, which is why we have to set it each time.
|
||||
this._panel.autoPosition = false;
|
||||
this._syncContainerWithMainView();
|
||||
|
||||
this._mainViewObserver.observe(this._mainView, {
|
||||
attributes: true,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
break;
|
||||
case "popupshown":
|
||||
this._setMaxHeight();
|
||||
break;
|
||||
case "popuphidden":
|
||||
this.removeAttribute("panelopen");
|
||||
this._mainView.style.removeProperty("height");
|
||||
this.showMainView();
|
||||
this._mainViewObserver.disconnect();
|
||||
break;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_shouldSetPosition">
|
||||
<body><![CDATA[
|
||||
return this.getAttribute("nosubviews") == "true";
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_shouldSetHeight">
|
||||
<body><![CDATA[
|
||||
return this.getAttribute("nosubviews") != "true";
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_setMaxHeight">
|
||||
<body><![CDATA[
|
||||
if (!this._shouldSetHeight())
|
||||
return;
|
||||
|
||||
// Ignore the mutation that'll fire when we set the height of
|
||||
// the main view.
|
||||
this.ignoreMutations = true;
|
||||
this._mainView.style.height =
|
||||
this.getBoundingClientRect().height + "px";
|
||||
this.ignoreMutations = false;
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="_adjustContainerHeight">
|
||||
<body><![CDATA[
|
||||
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
|
||||
let height;
|
||||
if (this.showingSubViewAsMainView) {
|
||||
height = this._heightOfSubview(this._mainView);
|
||||
} else {
|
||||
height = this._mainView.scrollHeight;
|
||||
}
|
||||
this._viewContainer.style.height = height + "px";
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="_syncContainerWithSubView">
|
||||
<body><![CDATA[
|
||||
// Check that this panel is still alive:
|
||||
if (!this._panel || !this._panel.parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.ignoreMutations && this.showingSubView) {
|
||||
let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
|
||||
this._viewContainer.style.height = newHeight + "px";
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="_syncContainerWithMainView">
|
||||
<body><![CDATA[
|
||||
// Check that this panel is still alive:
|
||||
if (!this._panel || !this._panel.parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._shouldSetPosition()) {
|
||||
this._panel.adjustArrowPosition();
|
||||
}
|
||||
|
||||
if (this._shouldSetHeight()) {
|
||||
this._adjustContainerHeight();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- Call this when the height of one of your views (the main view or a
|
||||
subview) changes and you want the heights of the multiview and panel
|
||||
to be the same as the view's height.
|
||||
If the caller can give a hint of the expected height change with the
|
||||
optional aExpectedChange parameter, it prevents flicker. -->
|
||||
<method name="setHeightToFit">
|
||||
<parameter name="aExpectedChange"/>
|
||||
<body><![CDATA[
|
||||
// Set the max-height to zero, wait until the height is actually
|
||||
// updated, and then remove it. If it's not removed, weird things can
|
||||
// happen, like widgets in the panel won't respond to clicks even
|
||||
// though they're visible.
|
||||
let count = 5;
|
||||
let height = getComputedStyle(this).height;
|
||||
if (aExpectedChange)
|
||||
this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
|
||||
else
|
||||
this.style.maxHeight = "0";
|
||||
let interval = setInterval(() => {
|
||||
if (height != getComputedStyle(this).height || --count == 0) {
|
||||
clearInterval(interval);
|
||||
this.style.removeProperty("max-height");
|
||||
}
|
||||
}, 0);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_heightOfSubview">
|
||||
<parameter name="aSubview"/>
|
||||
<parameter name="aContainerToCheck"/>
|
||||
<body><![CDATA[
|
||||
function getFullHeight(element) {
|
||||
// XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
|
||||
// that works with overflow: auto elements. Fortunately for us,
|
||||
// we have exactly 1 (potentially) scrolling element in here (the subview body),
|
||||
// and rounding 1 value is OK - rounding more than 1 and adding them means we get
|
||||
// off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
|
||||
// So, use scrollHeight *only* if the element is vertically scrollable.
|
||||
let height;
|
||||
let elementCS;
|
||||
if (element.scrollTopMax) {
|
||||
height = element.scrollHeight;
|
||||
// Bounding client rects include borders, scrollHeight doesn't:
|
||||
elementCS = win.getComputedStyle(element);
|
||||
height += parseFloat(elementCS.borderTopWidth) +
|
||||
parseFloat(elementCS.borderBottomWidth);
|
||||
} else {
|
||||
height = element.getBoundingClientRect().height;
|
||||
if (height > 0) {
|
||||
elementCS = win.getComputedStyle(element);
|
||||
}
|
||||
}
|
||||
if (elementCS) {
|
||||
// Include margins - but not borders or paddings because they
|
||||
// were dealt with above.
|
||||
height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
let win = aSubview.ownerDocument.defaultView;
|
||||
let body = aSubview.querySelector(".panel-subview-body");
|
||||
let height = getFullHeight(body || aSubview);
|
||||
if (body) {
|
||||
let header = aSubview.querySelector(".panel-subview-header");
|
||||
let footer = aSubview.querySelector(".panel-subview-footer");
|
||||
height += (header ? getFullHeight(header) : 0) +
|
||||
(footer ? getFullHeight(footer) : 0);
|
||||
}
|
||||
if (aContainerToCheck) {
|
||||
let containerCS = win.getComputedStyle(aContainerToCheck);
|
||||
height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
|
||||
}
|
||||
return Math.ceil(height);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="panelview">
|
||||
<implementation>
|
||||
<property name="panelMultiView" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (this.parentNode.localName != "panelmultiview") {
|
||||
return document.getBindingParent(this.parentNode);
|
||||
}
|
||||
|
||||
return this.parentNode;
|
||||
]]></getter>
|
||||
</property>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
@ -0,0 +1,618 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<bindings id="browserToolbarBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="toolbar" role="xul:toolbar">
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/toolbar.css"/>
|
||||
</resources>
|
||||
<implementation>
|
||||
<field name="overflowedDuringConstruction">null</field>
|
||||
|
||||
<constructor><![CDATA[
|
||||
let scope = {};
|
||||
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
|
||||
// Add an early overflow event listener that will mark if the
|
||||
// toolbar overflowed during construction.
|
||||
if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
|
||||
this.addEventListener("overflow", this);
|
||||
this.addEventListener("underflow", this);
|
||||
}
|
||||
|
||||
if (document.readyState == "complete") {
|
||||
this._init();
|
||||
} else {
|
||||
// Need to wait until XUL overlays are loaded. See bug 554279.
|
||||
let self = this;
|
||||
document.addEventListener("readystatechange", function onReadyStateChange() {
|
||||
if (document.readyState != "complete")
|
||||
return;
|
||||
document.removeEventListener("readystatechange", onReadyStateChange, false);
|
||||
self._init();
|
||||
}, false);
|
||||
}
|
||||
]]></constructor>
|
||||
|
||||
<method name="_init">
|
||||
<body><![CDATA[
|
||||
let scope = {};
|
||||
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
|
||||
let CustomizableUI = scope.CustomizableUI;
|
||||
|
||||
// Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
|
||||
// attributes, just in case they accidentally get restored from
|
||||
// persistence from a user that's been upgrading and downgrading.
|
||||
if (CustomizableUI.isBuiltinToolbar(this.id)) {
|
||||
const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
|
||||
for (let [attribute, value] of kAttributes) {
|
||||
if (this.getAttribute(attribute) != value) {
|
||||
this.setAttribute(attribute, value);
|
||||
document.persist(this.id, attribute);
|
||||
}
|
||||
if (this.toolbox) {
|
||||
if (this.toolbox.getAttribute(attribute) != value) {
|
||||
this.toolbox.setAttribute(attribute, value);
|
||||
document.persist(this.toolbox.id, attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Searching for the toolbox palette in the toolbar binding because
|
||||
// toolbars are constructed first.
|
||||
let toolbox = this.toolbox;
|
||||
if (toolbox && !toolbox.palette) {
|
||||
for (let node of toolbox.children) {
|
||||
if (node.localName == "toolbarpalette") {
|
||||
// Hold on to the palette but remove it from the document.
|
||||
toolbox.palette = node;
|
||||
toolbox.removeChild(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pass the current set of children for comparison with placements:
|
||||
let children = Array.from(this.childNodes)
|
||||
.filter(node => node.getAttribute("skipintoolbarset") != "true" && node.id)
|
||||
.map(node => node.id);
|
||||
CustomizableUI.registerToolbarNode(this, children);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
if (aEvent.type == "overflow" && aEvent.detail > 0) {
|
||||
if (this.overflowable && this.overflowable.initialized) {
|
||||
this.overflowable.onOverflow(aEvent);
|
||||
} else {
|
||||
this.overflowedDuringConstruction = aEvent;
|
||||
}
|
||||
} else if (aEvent.type == "underflow" && aEvent.detail > 0) {
|
||||
this.overflowedDuringConstruction = null;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="insertItem">
|
||||
<parameter name="aId"/>
|
||||
<parameter name="aBeforeElt"/>
|
||||
<parameter name="aWrapper"/>
|
||||
<body><![CDATA[
|
||||
if (aWrapper) {
|
||||
Cu.reportError("Can't insert " + aId + ": using insertItem " +
|
||||
"no longer supports wrapper elements.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Hack, the customizable UI code makes this be the last position
|
||||
let pos = null;
|
||||
if (aBeforeElt) {
|
||||
let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
|
||||
if (beforeInfo.area != this.id) {
|
||||
Cu.reportError("Can't insert " + aId + " before " +
|
||||
aBeforeElt.id + " which isn't in this area (" +
|
||||
this.id + ").");
|
||||
return null;
|
||||
}
|
||||
pos = beforeInfo.position;
|
||||
}
|
||||
|
||||
CustomizableUI.addWidgetToArea(aId, this.id, pos);
|
||||
return this.ownerDocument.getElementById(aId);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="toolbarName"
|
||||
onget="return this.getAttribute('toolbarname');"
|
||||
onset="this.setAttribute('toolbarname', val); return val;"/>
|
||||
|
||||
<property name="customizationTarget" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (this._customizationTarget)
|
||||
return this._customizationTarget;
|
||||
|
||||
let id = this.getAttribute("customizationtarget");
|
||||
if (id)
|
||||
this._customizationTarget = document.getElementById(id);
|
||||
|
||||
if (this._customizationTarget)
|
||||
this._customizationTarget.insertItem = this.insertItem.bind(this);
|
||||
else
|
||||
this._customizationTarget = this;
|
||||
|
||||
return this._customizationTarget;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<property name="toolbox" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (this._toolbox)
|
||||
return this._toolbox;
|
||||
|
||||
let toolboxId = this.getAttribute("toolboxid");
|
||||
if (toolboxId) {
|
||||
let toolbox = document.getElementById(toolboxId);
|
||||
if (toolbox) {
|
||||
if (toolbox.externalToolbars.indexOf(this) == -1)
|
||||
toolbox.externalToolbars.push(this);
|
||||
|
||||
this._toolbox = toolbox;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._toolbox && this.parentNode &&
|
||||
this.parentNode.localName == "toolbox") {
|
||||
this._toolbox = this.parentNode;
|
||||
}
|
||||
|
||||
return this._toolbox;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<property name="currentSet">
|
||||
<getter><![CDATA[
|
||||
let currentWidgets = new Set();
|
||||
for (let node of this.customizationTarget.children) {
|
||||
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
|
||||
if (realNode.getAttribute("skipintoolbarset") != "true") {
|
||||
currentWidgets.add(realNode.id);
|
||||
}
|
||||
}
|
||||
if (this.getAttribute("overflowing") == "true") {
|
||||
let overflowTarget = this.getAttribute("overflowtarget");
|
||||
let overflowList = this.ownerDocument.getElementById(overflowTarget);
|
||||
for (let node of overflowList.children) {
|
||||
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
|
||||
if (realNode.getAttribute("skipintoolbarset") != "true") {
|
||||
currentWidgets.add(realNode.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
|
||||
return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
// Get list of new and old ids:
|
||||
let newVal = (val || '').split(',').filter(x => x);
|
||||
let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
|
||||
|
||||
// Get a list of items only in the new list
|
||||
let newIds = newVal.filter(id => oldIds.indexOf(id) == -1);
|
||||
CustomizableUI.beginBatchUpdate();
|
||||
try {
|
||||
for (let newId of newIds) {
|
||||
oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
|
||||
let nextId = newId;
|
||||
let pos;
|
||||
do {
|
||||
// Get the next item
|
||||
nextId = newVal[newVal.indexOf(nextId) + 1];
|
||||
// Figure out where it is in the old list
|
||||
pos = oldIds.indexOf(nextId);
|
||||
// If it's not in the old list, repeat:
|
||||
} while (pos == -1 && nextId);
|
||||
if (pos == -1) {
|
||||
pos = null; // We didn't find anything, insert at the end
|
||||
}
|
||||
CustomizableUI.addWidgetToArea(newId, this.id, pos);
|
||||
}
|
||||
|
||||
let currentIds = this.currentSet.split(',');
|
||||
let removedIds = currentIds.filter(id => newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1);
|
||||
for (let removedId of removedIds) {
|
||||
CustomizableUI.removeWidgetFromArea(removedId);
|
||||
}
|
||||
} finally {
|
||||
CustomizableUI.endBatchUpdate();
|
||||
}
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="toolbar-menubar-stub">
|
||||
<implementation>
|
||||
<property name="toolbox" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (this._toolbox)
|
||||
return this._toolbox;
|
||||
|
||||
if (this.parentNode && this.parentNode.localName == "toolbox") {
|
||||
this._toolbox = this.parentNode;
|
||||
}
|
||||
|
||||
return this._toolbox;
|
||||
]]></getter>
|
||||
</property>
|
||||
<property name="currentSet" readonly="true">
|
||||
<getter><![CDATA[
|
||||
return this.getAttribute("defaultset");
|
||||
]]></getter>
|
||||
</property>
|
||||
<method name="insertItem">
|
||||
<body><![CDATA[
|
||||
return null;
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
|
||||
verbatim copies of their toolkit counterparts - they just inherit from
|
||||
the customizableui's toolbar binding instead of toolkit's. We're currently
|
||||
OK with the maintainance burden of having two copies of a binding, since
|
||||
the long term goal is to move the customization framework into toolkit. -->
|
||||
|
||||
<binding id="toolbar-menubar-autohide"
|
||||
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
|
||||
<implementation>
|
||||
<constructor>
|
||||
this._setInactive();
|
||||
</constructor>
|
||||
<destructor>
|
||||
this._setActive();
|
||||
</destructor>
|
||||
|
||||
<field name="_inactiveTimeout">null</field>
|
||||
|
||||
<field name="_contextMenuListener"><![CDATA[({
|
||||
toolbar: this,
|
||||
contextMenu: null,
|
||||
|
||||
get active () {
|
||||
return !!this.contextMenu;
|
||||
},
|
||||
|
||||
init: function (event) {
|
||||
let node = event.target;
|
||||
while (node != this.toolbar) {
|
||||
if (node.localName == "menupopup")
|
||||
return;
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
let contextMenuId = this.toolbar.getAttribute("context");
|
||||
if (!contextMenuId)
|
||||
return;
|
||||
|
||||
this.contextMenu = document.getElementById(contextMenuId);
|
||||
if (!this.contextMenu)
|
||||
return;
|
||||
|
||||
this.contextMenu.addEventListener("popupshown", this, false);
|
||||
this.contextMenu.addEventListener("popuphiding", this, false);
|
||||
this.toolbar.addEventListener("mousemove", this, false);
|
||||
},
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "popupshown":
|
||||
this.toolbar.removeEventListener("mousemove", this, false);
|
||||
break;
|
||||
case "popuphiding":
|
||||
case "mousemove":
|
||||
this.toolbar._setInactiveAsync();
|
||||
this.toolbar.removeEventListener("mousemove", this, false);
|
||||
this.contextMenu.removeEventListener("popuphiding", this, false);
|
||||
this.contextMenu.removeEventListener("popupshown", this, false);
|
||||
this.contextMenu = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})]]></field>
|
||||
|
||||
<method name="_setInactive">
|
||||
<body><![CDATA[
|
||||
this.setAttribute("inactive", "true");
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_setInactiveAsync">
|
||||
<body><![CDATA[
|
||||
this._inactiveTimeout = setTimeout(function (self) {
|
||||
if (self.getAttribute("autohide") == "true") {
|
||||
self._inactiveTimeout = null;
|
||||
self._setInactive();
|
||||
}
|
||||
}, 0, this);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_setActive">
|
||||
<body><![CDATA[
|
||||
if (this._inactiveTimeout) {
|
||||
clearTimeout(this._inactiveTimeout);
|
||||
this._inactiveTimeout = null;
|
||||
}
|
||||
this.removeAttribute("inactive");
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="DOMMenuBarActive" action="this._setActive();"/>
|
||||
<handler event="popupshowing" action="this._setActive();"/>
|
||||
<handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
|
||||
<handler event="DOMMenuBarInactive"><![CDATA[
|
||||
if (!this._contextMenuListener.active)
|
||||
this._setInactiveAsync();
|
||||
]]></handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="toolbar-drag"
|
||||
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
|
||||
<implementation>
|
||||
<field name="_dragBindingAlive">true</field>
|
||||
<constructor><![CDATA[
|
||||
if (!this._draggableStarted) {
|
||||
this._draggableStarted = true;
|
||||
try {
|
||||
let tmp = {};
|
||||
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
|
||||
let draggableThis = new tmp.WindowDraggingElement(this);
|
||||
draggableThis.mouseDownCheck = function(e) {
|
||||
return this._dragBindingAlive;
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
]]></constructor>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
|
||||
<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
|
||||
and immediately direct such content elsewhere. -->
|
||||
<binding id="addonbar-delegating">
|
||||
<implementation>
|
||||
<constructor><![CDATA[
|
||||
// Reading these immediately so nobody messes with them anymore:
|
||||
this._delegatingToolbar = this.getAttribute("toolbar-delegate");
|
||||
this._wasCollapsed = this.getAttribute("collapsed") == "true";
|
||||
// Leaving those in here to unbreak some code:
|
||||
if (document.readyState == "complete") {
|
||||
this._init();
|
||||
} else {
|
||||
// Need to wait until XUL overlays are loaded. See bug 554279.
|
||||
let self = this;
|
||||
document.addEventListener("readystatechange", function onReadyStateChange() {
|
||||
if (document.readyState != "complete")
|
||||
return;
|
||||
document.removeEventListener("readystatechange", onReadyStateChange, false);
|
||||
self._init();
|
||||
}, false);
|
||||
}
|
||||
]]></constructor>
|
||||
|
||||
<method name="_init">
|
||||
<body><![CDATA[
|
||||
// Searching for the toolbox palette in the toolbar binding because
|
||||
// toolbars are constructed first.
|
||||
let toolbox = this.toolbox;
|
||||
if (toolbox && !toolbox.palette) {
|
||||
for (let node of toolbox.children) {
|
||||
if (node.localName == "toolbarpalette") {
|
||||
// Hold on to the palette but remove it from the document.
|
||||
toolbox.palette = node;
|
||||
toolbox.removeChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pass the current set of children for comparison with placements:
|
||||
let children = [];
|
||||
for (let node of this.childNodes) {
|
||||
if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
|
||||
// Force everything to be removable so that buildArea can chuck stuff
|
||||
// out if the user has customized things / we've been here before:
|
||||
if (!this._whiteListed.has(node.id)) {
|
||||
node.setAttribute("removable", "true");
|
||||
}
|
||||
children.push(node);
|
||||
}
|
||||
}
|
||||
CustomizableUI.registerToolbarNode(this, children);
|
||||
let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
|
||||
for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
|
||||
this._currentSetMigrated.add(migratedItem);
|
||||
}
|
||||
this.evictNodes();
|
||||
// We can't easily use |this| or strong bindings for the observer fn here
|
||||
// because that creates leaky circular references when the node goes away,
|
||||
// and XBL destructors are unreliable.
|
||||
let mutationObserver = new MutationObserver(function(mutations) {
|
||||
if (!mutations.length) {
|
||||
return;
|
||||
}
|
||||
let toolbar = mutations[0].target;
|
||||
// Can't use our own attribute because we might not have one if we're set to
|
||||
// collapsed
|
||||
let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
|
||||
if (!toolbar._isModifying && !areCustomizing) {
|
||||
toolbar.evictNodes();
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(this, {childList: true});
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="evictNodes">
|
||||
<body><![CDATA[
|
||||
this._isModifying = true;
|
||||
let i = this.childNodes.length;
|
||||
while (i--) {
|
||||
let node = this.childNodes[i];
|
||||
if (this.childNodes[i].id) {
|
||||
this.evictNode(this.childNodes[i]);
|
||||
} else {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
this._isModifying = false;
|
||||
this._updateMigratedSet();
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="evictNode">
|
||||
<parameter name="aNode"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
|
||||
return;
|
||||
}
|
||||
const kItemMaxWidth = 100;
|
||||
let oldParent = aNode.parentNode;
|
||||
aNode.setAttribute("removable", "true");
|
||||
this._currentSetMigrated.add(aNode.id);
|
||||
|
||||
let movedOut = false;
|
||||
if (!this._wasCollapsed) {
|
||||
try {
|
||||
let nodeWidth = aNode.getBoundingClientRect().width;
|
||||
if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
|
||||
throw new Error(aNode.id + " is too big (" + nodeWidth +
|
||||
"px wide), moving to the palette");
|
||||
}
|
||||
CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
|
||||
movedOut = true;
|
||||
} catch (ex) {
|
||||
// This will throw if the node is too big, or can't be moved there for
|
||||
// some reason. Report this:
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* We won't have moved the widget if either the add-on bar was collapsed,
|
||||
* or if it was too wide to be inserted into the navbar. */
|
||||
if (!movedOut) {
|
||||
try {
|
||||
CustomizableUI.removeWidgetFromArea(aNode.id);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
aNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Surprise: addWidgetToArea(palette) will get you nothing if the palette
|
||||
// is not constructed yet. Fix:
|
||||
if (aNode.parentNode == oldParent) {
|
||||
let palette = this.toolbox.palette;
|
||||
if (palette && oldParent != palette) {
|
||||
palette.appendChild(aNode);
|
||||
}
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="insertItem">
|
||||
<parameter name="aId"/>
|
||||
<parameter name="aBeforeElt"/>
|
||||
<parameter name="aWrapper"/>
|
||||
<body><![CDATA[
|
||||
if (aWrapper) {
|
||||
Cu.reportError("Can't insert " + aId + ": using insertItem " +
|
||||
"no longer supports wrapper elements.");
|
||||
return null;
|
||||
}
|
||||
|
||||
let widget = CustomizableUI.getWidget(aId);
|
||||
widget = widget && widget.forWindow(window);
|
||||
let node = widget && widget.node;
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this._isModifying = true;
|
||||
// Temporarily add it here so it can have a width, then ditch it:
|
||||
this.appendChild(node);
|
||||
this.evictNode(node);
|
||||
this._isModifying = false;
|
||||
this._updateMigratedSet();
|
||||
// We will now have moved stuff around; kick off some events
|
||||
// so add-ons know we've just moved their stuff:
|
||||
// XXXgijs: only in this window. It's hard to know for sure what's the right
|
||||
// thing to do here - typically insertItem is used on each window, so
|
||||
// this seems to make the most sense, even if some of the effects of
|
||||
// evictNode might affect multiple windows.
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
|
||||
CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
|
||||
return node;
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="getMigratedItems">
|
||||
<body><![CDATA[
|
||||
return [... this._currentSetMigrated];
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="_updateMigratedSet">
|
||||
<body><![CDATA[
|
||||
let newMigratedItems = this.getMigratedItems().join(',');
|
||||
if (this.getAttribute("migratedset") != newMigratedItems) {
|
||||
this.setAttribute("migratedset", newMigratedItems);
|
||||
this.ownerDocument.persist(this.id, "migratedset");
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<property name="customizationTarget" readonly="true">
|
||||
<getter><![CDATA[
|
||||
return this;
|
||||
]]></getter>
|
||||
</property>
|
||||
<property name="currentSet">
|
||||
<getter><![CDATA[
|
||||
return Array.from(this.children, node => node.id).join(",");
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
let v = val.split(',');
|
||||
let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
|
||||
!CustomizableUI.isSpecialWidget(x) &&
|
||||
!this._currentSetMigrated.has(x)));
|
||||
for (let newButton of newButtons) {
|
||||
this._currentSetMigrated.add(newButton);
|
||||
this.insertItem(newButton);
|
||||
}
|
||||
this._updateMigratedSet();
|
||||
]]></setter>
|
||||
</property>
|
||||
<property name="toolbox" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (!this._toolbox && this.parentNode &&
|
||||
this.parentNode.localName == "toolbox") {
|
||||
this._toolbox = this.parentNode;
|
||||
}
|
||||
|
||||
return this._toolbox;
|
||||
]]></getter>
|
||||
</property>
|
||||
<field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
|
||||
<field name="_isModifying">false</field>
|
||||
<field name="_currentSetMigrated">new Set()</field>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
||||
21
application/basilisk/components/customizableui/moz.build
Normal file
21
application/basilisk/components/customizableui/moz.build
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'content',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'CustomizableUI.jsm',
|
||||
'CustomizableWidgets.jsm',
|
||||
'CustomizeMode.jsm',
|
||||
'DragPositionManager.jsm',
|
||||
'PanelWideWidgetTracker.jsm',
|
||||
'ScrollbarSampler.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
|
||||
DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue