Bug 1527105 - Convert menucaption to Custom Element r=paolo
authorBrian Grinstead <bgrinstead@mozilla.com>
Tue, 12 Feb 2019 19:08:25 +0000
changeset 458790 7a797eeffeba
parent 458789 090304ca37d9
child 458791 1b9f4cb229fa
push id35548
push useropoprus@mozilla.com
push dateWed, 13 Feb 2019 09:48:26 +0000
treeherdermozilla-central@93e37c529818 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo
bugs1527105
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1527105 - Convert menucaption to Custom Element r=paolo Differential Revision: https://phabricator.services.mozilla.com/D19439
toolkit/content/customElements.js
toolkit/content/jar.mn
toolkit/content/widgets/menu.js
toolkit/content/widgets/menu.xml
toolkit/content/xul.css
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -378,16 +378,17 @@ customElements.setElementCreationCallbac
 });
 
 // For now, don't load any elements in the extension dummy document.
 // We will want to load <browser> when that's migrated (bug 1441935).
 const isDummyDocument = document.documentURI == "chrome://extensions/content/dummy.xul";
 if (!isDummyDocument) {
   for (let script of [
     "chrome://global/content/elements/general.js",
+    "chrome://global/content/elements/menu.js",
     "chrome://global/content/elements/notificationbox.js",
     "chrome://global/content/elements/popupnotification.js",
     "chrome://global/content/elements/radio.js",
     "chrome://global/content/elements/richlistbox.js",
     "chrome://global/content/elements/autocomplete-richlistitem.js",
     "chrome://global/content/elements/textbox.js",
     "chrome://global/content/elements/tabbox.js",
     "chrome://global/content/elements/tree.js",
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -85,16 +85,17 @@ toolkit.jar:
    content/global/bindings/tree.xml            (widgets/tree.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
    content/global/elements/autocomplete-richlistitem.js       (widgets/autocomplete-richlistitem.js)
    content/global/elements/browser-custom-element.js          (widgets/browser-custom-element.js)
    content/global/elements/datetimebox.js      (widgets/datetimebox.js)
    content/global/elements/findbar.js          (widgets/findbar.js)
    content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
+   content/global/elements/menu.js             (widgets/menu.js)
    content/global/elements/notificationbox.js  (widgets/notificationbox.js)
    content/global/elements/pluginProblem.js    (widgets/pluginProblem.js)
    content/global/elements/radio.js            (widgets/radio.js)
    content/global/elements/richlistbox.js      (widgets/richlistbox.js)
    content/global/elements/marquee.css         (widgets/marquee.css)
    content/global/elements/marquee.js          (widgets/marquee.js)
    content/global/elements/menulist.js         (widgets/menulist.js)
    content/global/elements/popupnotification.js  (widgets/popupnotification.js)
copy from toolkit/content/widgets/menu.xml
copy to toolkit/content/widgets/menu.js
--- a/toolkit/content/widgets/menu.xml
+++ b/toolkit/content/widgets/menu.js
@@ -1,225 +1,166 @@
-<?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/. -->
-<!-- globals XULMenuElement -->
+/* 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="menuitemBindings"
-   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">
+"use strict";
 
-  <binding id="menuitem-base"
-           extends="chrome://global/content/bindings/general.xml#basetext">
-    <implementation implements="nsIDOMXULSelectControlItemElement, nsIDOMXULContainerItemElement">
-      <property name="value" onset="this.setAttribute('value', val); return val;"
-                             onget="return this.getAttribute('value');"/>
-      <!-- nsIDOMXULSelectControlItemElement -->
-      <property name="selected" readonly="true"
-                onget="return this.getAttribute('selected') == 'true';"/>
-      <property name="control" readonly="true">
-        <getter>
-          <![CDATA[
-            var parent = this.parentNode;
-            // Return the parent if it is a menu or menulist.
-            if (parent && parent.parentNode instanceof XULMenuElement) {
-              return parent.parentNode;
-            }
-            return null;
-          ]]>
-        </getter>
-      </property>
+// This is loaded into all XUL windows. Wrap in a block to prevent
+// leaking to window scope.
+{
+class MozMenuItemBase extends MozElements.BaseText {
+  // nsIDOMXULSelectControlItemElement
+  set value(val) {
+    this.setAttribute("value", val);
+  }
+  get value() {
+    return this.getAttribute("value");
+  }
 
-      <!-- nsIDOMXULContainerItemElement -->
-      <property name="parentContainer" readonly="true">
-        <getter>
-          for (var parent = this.parentNode; parent; parent = parent.parentNode) {
-            if (parent instanceof XULMenuElement) {
-              return parent;
-            }
-          }
-          return null;
-        </getter>
-      </property>
-    </implementation>
-  </binding>
+  // nsIDOMXULSelectControlItemElement
+  get selected() {
+    return this.getAttribute("selected") == "true";
+  }
 
-  <binding id="menu-base"
-           extends="chrome://global/content/bindings/menu.xml#menuitem-base">
+  // nsIDOMXULSelectControlItemElement
+  get control() {
+    var parent = this.parentNode;
+    // Return the parent if it is a menu or menulist.
+    if (parent && parent.parentNode instanceof XULMenuElement) {
+      return parent.parentNode;
+    }
+    return null;
+  }
 
-    <implementation implements="nsIDOMXULContainerElement">
-      <property name="open" onget="return this.hasAttribute('open');">
-        <setter><![CDATA[
-          this.openMenu(val);
-          return val;
-        ]]></setter>
-      </property>
+  // nsIDOMXULContainerItemElement
+  get parentContainer() {
+    for (var parent = this.parentNode; parent; parent = parent.parentNode) {
+      if (parent instanceof XULMenuElement) {
+        return parent;
+      }
+    }
+    return null;
+  }
+}
 
-      <!-- nsIDOMXULContainerElement interface -->
-      <method name="appendItem">
-        <parameter name="aLabel"/>
-        <parameter name="aValue"/>
-        <body>
-          var menupopup = this.menupopup;
-          if (!menupopup) {
-            menupopup = this.ownerDocument.createXULElement("menupopup");
-            this.appendChild(menupopup);
-          }
+MozXULElement.implementCustomInterface(MozMenuItemBase, [Ci.nsIDOMXULSelectControlItemElement, Ci.nsIDOMXULContainerItemElement]);
 
-          var menuitem = this.ownerDocument.createXULElement("menuitem");
-          menuitem.setAttribute("label", aLabel);
-          menuitem.setAttribute("value", aValue);
+class MozMenuBase extends MozMenuItemBase {
+  set open(val) {
+    this.openMenu(val);
+    return val;
+  }
 
-          return menupopup.appendChild(menuitem);
-        </body>
-      </method>
+  get open() {
+    return this.hasAttribute("open");
+  }
 
-      <property name="itemCount" readonly="true">
-        <getter>
-          var menupopup = this.menupopup;
-          return menupopup ? menupopup.children.length : 0;
-        </getter>
-      </property>
+  get itemCount() {
+    var menupopup = this.menupopup;
+    return menupopup ? menupopup.children.length : 0;
+  }
+
+  get menupopup() {
+    const XUL_NS =
+      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-      <method name="getIndexOfItem">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          var menupopup = this.menupopup;
-          if (menupopup) {
-            var items = menupopup.children;
-            var length = items.length;
-            for (var index = 0; index < length; ++index) {
-              if (items[index] == aItem)
-                return index;
-            }
-          }
-          return -1;
-        ]]>
-        </body>
-      </method>
+    for (var child = this.firstElementChild; child; child = child.nextElementSibling) {
+      if (child.namespaceURI == XUL_NS && child.localName == "menupopup")
+        return child;
+    }
+    return null;
+  }
 
-      <method name="getItemAtIndex">
-        <parameter name="aIndex"/>
-        <body>
-        <![CDATA[
-          var menupopup = this.menupopup;
-          if (!menupopup || aIndex < 0 || aIndex >= menupopup.children.length)
-            return null;
+  appendItem(aLabel, aValue) {
+    var menupopup = this.menupopup;
+    if (!menupopup) {
+      menupopup = this.ownerDocument.createXULElement("menupopup");
+      this.appendChild(menupopup);
+    }
+
+    var menuitem = this.ownerDocument.createXULElement("menuitem");
+    menuitem.setAttribute("label", aLabel);
+    menuitem.setAttribute("value", aValue);
 
-          return menupopup.children[aIndex];
-        ]]>
-        </body>
-      </method>
-
-      <property name="menupopup" readonly="true">
-        <getter>
-        <![CDATA[
-          const XUL_NS =
-            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-          for (var child = this.firstElementChild; child; child = child.nextElementSibling) {
-            if (child.namespaceURI == XUL_NS && child.localName == "menupopup")
-              return child;
-          }
-          return null;
-        ]]>
-        </getter>
-      </property>
-    </implementation>
-  </binding>
+    return menupopup.appendChild(menuitem);
+  }
 
-  <binding id="menu"
-           extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
-      <xul:hbox class="menu-accel-container" anonid="accel">
-        <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
-      </xul:hbox>
-      <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
-        <xul:image/>
-      </xul:hbox>
-      <children includes="menupopup"/>
-    </content>
-  </binding>
+  getIndexOfItem(aItem) {
+    var menupopup = this.menupopup;
+    if (menupopup) {
+      var items = menupopup.children;
+      var length = items.length;
+      for (var index = 0; index < length; ++index) {
+        if (items[index] == aItem)
+          return index;
+      }
+    }
+    return -1;
+  }
 
-  <binding id="menuitem" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
-    <content>
-      <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
-      <xul:hbox class="menu-accel-container" anonid="accel">
-        <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
-      </xul:hbox>
-    </content>
-  </binding>
+  getItemAtIndex(aIndex) {
+    var menupopup = this.menupopup;
+    if (!menupopup || aIndex < 0 || aIndex >= menupopup.children.length)
+      return null;
 
-  <binding id="menu-menubar"
-           extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
-      <children includes="menupopup"/>
-    </content>
-  </binding>
+    return menupopup.children[aIndex];
+  }
+}
+
+MozXULElement.implementCustomInterface(MozMenuBase, [Ci.nsIDOMXULContainerElement]);
 
-  <binding id="menu-menubar-iconic"
-           extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:image class="menubar-left" xbl:inherits="src=image"/>
-      <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
-      <children includes="menupopup"/>
-    </content>
-  </binding>
+// The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>,
+// See SelectParentHelper.jsm.
+class MozMenuCaption extends MozMenuBase {
+  static get observedAttributes() {
+    return [
+      "selected",
+      "disabled",
+      "checked",
+      "image",
+      "validate",
+      "src",
+      "label",
+      "crop",
+      "highlightable",
+    ];
+  }
 
-  <binding id="menuitem-iconic" extends="chrome://global/content/bindings/menu.xml#menuitem">
-    <content>
-      <xul:hbox class="menu-iconic-left" align="center" pack="center"
-                xbl:inherits="selected,_moz-menuactive,disabled,checked">
-        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,validate"/>
-      </xul:hbox>
-      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
-      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
-      <xul:hbox class="menu-accel-container" anonid="accel">
-        <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
-      </xul:hbox>
-    </content>
-  </binding>
+  _updateAttributes() {
+    if (!this._inheritedAttributeMap) {
+      return;
+    }
 
-  <binding id="menuitem-iconic-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
-    <content>
-      <xul:hbox class="menu-iconic-left" align="center" pack="center"
-                xbl:inherits="selected,disabled,checked">
-        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate"/>
-      </xul:hbox>
-      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
-      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
-    </content>
-  </binding>
+    for (let [ el, attrs ] of this._inheritedAttributeMap.entries()) {
+      for (let attr of attrs) {
+        this.inheritAttribute(el, attr);
+      }
+    }
+  }
+
+  attributeChangedCallback(name, oldValue, newValue) {
+    if (oldValue === newValue) {
+      return;
+    }
+
+    this._updateAttributes();
+  }
 
-  <binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:hbox class="menu-iconic-left" align="center" pack="center"
-                xbl:inherits="selected,disabled,checked">
-        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
-      </xul:hbox>
-      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop,highlightable" crop="right"/>
-      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,highlightable" crop="right"/>
-    </content>
-  </binding>
+  connectedCallback() {
+    this.textContent = "";
+    this.appendChild(MozXULElement.parseXULToFragment(`
+      <hbox class="menu-iconic-left" align="center" pack="center" inherits="selected,disabled,checked" role="none">
+        <image class="menu-iconic-icon" inherits="src=image,validate,src" role="none"></image>
+      </hbox>
+      <label class="menu-iconic-text" flex="1" inherits="value=label,crop,highlightable" crop="right" role="none"></label>
+      <label class="menu-iconic-highlightable-text" inherits="text=label,crop,highlightable" crop="right" role="none"></label>
+    `));
+    this._inheritedAttributeMap = new Map();
+    for (let el of this.querySelectorAll("[inherits]")) {
+      this._inheritedAttributeMap.set(el, el.getAttribute("inherits").split(","));
+    }
+    this._updateAttributes();
+  }
+}
 
-  <binding id="menu-iconic"
-           extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:hbox class="menu-iconic-left" align="center" pack="center">
-        <xul:image class="menu-iconic-icon" xbl:inherits="src=image"/>
-      </xul:hbox>
-      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
-      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
-      <xul:hbox class="menu-accel-container" anonid="accel">
-        <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
-      </xul:hbox>
-      <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
-        <xul:image/>
-      </xul:hbox>
-      <children includes="menupopup|template"/>
-    </content>
-  </binding>
-</bindings>
+customElements.define("menucaption", MozMenuCaption);
+}
--- a/toolkit/content/widgets/menu.xml
+++ b/toolkit/content/widgets/menu.xml
@@ -189,27 +189,16 @@
                 xbl:inherits="selected,disabled,checked">
         <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate"/>
       </xul:hbox>
       <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
       <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
     </content>
   </binding>
 
-  <binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
-    <content>
-      <xul:hbox class="menu-iconic-left" align="center" pack="center"
-                xbl:inherits="selected,disabled,checked">
-        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
-      </xul:hbox>
-      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop,highlightable" crop="right"/>
-      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,highlightable" crop="right"/>
-    </content>
-  </binding>
-
   <binding id="menu-iconic"
            extends="chrome://global/content/bindings/menu.xml#menu-base">
     <content>
       <xul:hbox class="menu-iconic-left" align="center" pack="center">
         <xul:image class="menu-iconic-icon" xbl:inherits="src=image"/>
       </xul:hbox>
       <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
       <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -286,20 +286,16 @@ menuitem.menuitem-iconic {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
 }
 
 menuitem[type="checkbox"],
 menuitem[type="radio"] {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
 }
 
-menucaption {
-  -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption");
-}
-
 .menu-text {
   -moz-box-flex: 1;
 }
 
 /********* popup & menupopup ***********/
 
 /* <popup> is deprecated.  Only <menupopup> and <tooltip> are still valid. */