diff options
Diffstat (limited to 'browser/components/customizableui/content/toolbar.xml')
-rw-r--r-- | browser/components/customizableui/content/toolbar.xml | 618 |
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> |