summaryrefslogtreecommitdiffstats
path: root/browser/components/customizableui/content/toolbar.xml
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/customizableui/content/toolbar.xml')
-rw-r--r--browser/components/customizableui/content/toolbar.xml618
1 files changed, 618 insertions, 0 deletions
diff --git a/browser/components/customizableui/content/toolbar.xml b/browser/components/customizableui/content/toolbar.xml
new file mode 100644
index 000000000..4e6964c9f
--- /dev/null
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -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>