Bug 1469902 - Migrate <tabbox> to a Custom Element;r=dao
authorBrian Grinstead <bgrinstead@mozilla.com>
Tue, 28 Aug 2018 16:07:28 +0000
changeset 491329 373020acb0864c4b10ac2a777d143bfd0fca78a0
parent 491328 48ca12e943f5f8514dcc01e4ba9ec1888dce8485
child 491330 c8df42395ed823eb81b84d124008a3b9094254de
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1469902
milestone63.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 1469902 - Migrate <tabbox> to a Custom Element;r=dao MozReview-Commit-ID: HNDiMYmKgkg Differential Revision: https://phabricator.services.mozilla.com/D3989
toolkit/content/customElements.js
toolkit/content/jar.mn
toolkit/content/widgets/tabbox.js
toolkit/content/widgets/tabbox.xml
toolkit/content/xul.css
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -122,16 +122,17 @@ function getInterfaceProxy(obj) {
 
 // Attach the base class to the window so other scripts can use it:
 window.MozXULElement = MozXULElement;
 
 for (let script of [
   "chrome://global/content/elements/stringbundle.js",
   "chrome://global/content/elements/general.js",
   "chrome://global/content/elements/textbox.js",
+  "chrome://global/content/elements/tabbox.js",
 ]) {
   Services.scriptloader.loadSubScript(script, window);
 }
 
 customElements.setElementCreationCallback("printpreview-toolbar", type => {
   Services.scriptloader.loadSubScript(
     "chrome://global/content/printPreviewToolbar.js", window);
 });
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -97,15 +97,16 @@ toolkit.jar:
    content/global/bindings/toolbar.xml         (widgets/toolbar.xml)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/tree.xml            (widgets/tree.xml)
    content/global/bindings/videocontrols.xml   (widgets/videocontrols.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
    content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
+   content/global/elements/tabbox.js           (widgets/tabbox.js)
    content/global/elements/textbox.js          (widgets/textbox.js)
    content/global/elements/videocontrols.js    (widgets/videocontrols.js)
 #ifdef XP_MACOSX
    content/global/macWindowMenu.js
 #endif
    content/global/gmp-sources/openh264.json    (gmp-sources/openh264.json)
    content/global/gmp-sources/widevinecdm.json (gmp-sources/widevinecdm.json)
copy from toolkit/content/widgets/tabbox.xml
copy to toolkit/content/widgets/tabbox.js
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.js
@@ -1,784 +1,184 @@
-<?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/. -->
-
+/* 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="tabBindings"
-          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="tabbox">
-    <implementation>
-      <property name="handleCtrlTab">
-        <setter>
-        <![CDATA[
-          this.setAttribute("handleCtrlTab", val);
-          return val;
-        ]]>
-        </setter>
-        <getter>
-        <![CDATA[
-          return (this.getAttribute("handleCtrlTab") != "false");
-        ]]>
-        </getter>
-      </property>
-
-      <field name="_handleMetaAltArrows" readonly="true">
-        /Mac/.test(navigator.platform)
-      </field>
-
-      <!-- _tabs and _tabpanels are deprecated, they exist only for
-           backwards compatibility. -->
-      <property name="_tabs" readonly="true" onget="return this.tabs;"/>
-      <property name="_tabpanels" readonly="true" onget="return this.tabpanels;"/>
+"use strict";
 
-      <property name="tabs" readonly="true">
-        <getter>
-        <![CDATA[
-          if (this.hasAttribute("tabcontainer")) {
-            return document.getElementById(this.getAttribute("tabcontainer"));
-          }
-          return this.getElementsByTagNameNS(
-              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-              "tabs").item(0);
-        ]]>
-        </getter>
-      </property>
-
-      <property name="tabpanels" readonly="true">
-        <getter>
-        <![CDATA[
-          return this.getElementsByTagNameNS(
-              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-              "tabpanels").item(0);
-        ]]>
-        </getter>
-      </property>
+{
 
-      <property name="selectedIndex">
-        <getter>
-        <![CDATA[
-          var tabs = this.tabs;
-          return tabs ? tabs.selectedIndex : -1;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          var tabs = this.tabs;
-          if (tabs)
-            tabs.selectedIndex = val;
-          this.setAttribute("selectedIndex", val);
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedTab">
-        <getter>
-        <![CDATA[
-          var tabs = this.tabs;
-          return tabs && tabs.selectedItem;
-        ]]>
-        </getter>
+class MozTabbox extends MozXULElement {
+  constructor() {
+    super();
+    this._handleMetaAltArrows = /Mac/.test(navigator.platform);
+    this.disconnectedCallback = this.disconnectedCallback.bind(this);
+  }
 
-        <setter>
-        <![CDATA[
-          if (val) {
-            var tabs = this.tabs;
-            if (tabs)
-              tabs.selectedItem = val;
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedPanel">
-        <getter>
-        <![CDATA[
-          var tabpanels = this.tabpanels;
-          return tabpanels && tabpanels.selectedPanel;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          if (val) {
-            var tabpanels = this.tabpanels;
-            if (tabpanels)
-              tabpanels.selectedPanel = val;
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <method name="handleEvent">
-        <parameter name="event"/>
-        <body>
-        <![CDATA[
-          if (!event.isTrusted) {
-            // Don't let untrusted events mess with tabs.
-            return;
-          }
-
-          // Don't check if the event was already consumed because tab
-          // navigation should always work for better user experience.
+  connectedCallback() {
+    switch (this.getAttribute("eventnode")) {
+      case "parent":
+        this._eventNode = this.parentNode;
+        break;
+      case "window":
+        this._eventNode = window;
+        break;
+      case "document":
+        this._eventNode = document;
+        break;
+      default:
+        this._eventNode = this;
+    }
 
-          switch (event.keyCode) {
-            case event.DOM_VK_TAB:
-              if (event.ctrlKey && !event.altKey && !event.metaKey)
-                if (this.tabs && this.handleCtrlTab) {
-                  this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
-                  event.preventDefault();
-                }
-              break;
-            case event.DOM_VK_PAGE_UP:
-              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
-                  this.tabs) {
-                this.tabs.advanceSelectedTab(-1, true);
-                event.preventDefault();
-              }
-              break;
-            case event.DOM_VK_PAGE_DOWN:
-              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
-                  this.tabs) {
-                this.tabs.advanceSelectedTab(1, true);
-                event.preventDefault();
-              }
-              break;
-            case event.DOM_VK_LEFT:
-              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
-                if (this.tabs && this._handleMetaAltArrows) {
-                  var offset = window.getComputedStyle(this)
-                                     .direction == "ltr" ? -1 : 1;
-                  this.tabs.advanceSelectedTab(offset, true);
-                  event.preventDefault();
-                }
-              break;
-            case event.DOM_VK_RIGHT:
-              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
-                if (this.tabs && this._handleMetaAltArrows) {
-                  offset = window.getComputedStyle(this)
-                                     .direction == "ltr" ? 1 : -1;
-                  this.tabs.advanceSelectedTab(offset, true);
-                  event.preventDefault();
-                }
-              break;
-          }
-        ]]>
-        </body>
-      </method>
+    Services.els.addSystemEventListener(this._eventNode, "keydown", this, false);
+    window.addEventListener("unload", this.disconnectedCallback, { once: true });
+  }
 
-      <field name="_eventNode">this</field>
-
-      <property name="eventNode" onget="return this._eventNode;">
-        <setter>
-          <![CDATA[
-            if (val != this._eventNode) {
-              const nsIEventListenerService =
-                Ci.nsIEventListenerService;
-              let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                          .getService(nsIEventListenerService);
-              els.addSystemEventListener(val, "keydown", this, false);
-              els.removeSystemEventListener(this._eventNode, "keydown", this, false);
-              this._eventNode = val;
-            }
-            return val;
-          ]]>
-        </setter>
-      </property>
+  disconnectedCallback() {
+    window.removeEventListener("unload", this.disconnectedCallback);
+    Services.els.removeSystemEventListener(this._eventNode, "keydown", this, false);
+  }
 
-      <constructor>
-        switch (this.getAttribute("eventnode")) {
-          case "parent": this._eventNode = this.parentNode; break;
-          case "window": this._eventNode = window; break;
-          case "document": this._eventNode = document; break;
-        }
-        let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                    .getService(Ci.nsIEventListenerService);
-        els.addSystemEventListener(this._eventNode, "keydown", this, false);
-      </constructor>
-
-      <destructor>
-        let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                    .getService(Ci.nsIEventListenerService);
-        els.removeSystemEventListener(this._eventNode, "keydown", this, false);
-      </destructor>
-    </implementation>
-  </binding>
-
-  <binding id="tabs"
-           extends="chrome://global/content/bindings/general.xml#basecontrol">
-    <content>
-      <xul:spacer class="tabs-left"/>
-      <children/>
-      <xul:spacer class="tabs-right" flex="1"/>
-    </content>
-
-    <implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement">
-      <constructor>
-      <![CDATA[
-
-        if (!this.hasAttribute("orient"))
-          this.setAttribute("orient", "horizontal");
-
-        if (this.tabbox && this.tabbox.hasAttribute("selectedIndex")) {
-          let selectedIndex = parseInt(this.tabbox.getAttribute("selectedIndex"));
-          this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
-          return;
-        }
+  set handleCtrlTab(val) {
+    this.setAttribute("handleCtrlTab", val);
+    return val;
+  }
 
-        var children = this.children;
-        var length = children.length;
-        for (var i = 0; i < length; i++) {
-          if (children[i].getAttribute("selected") == "true") {
-            this.selectedIndex = i;
-            return;
-          }
-        }
-
-        var value = this.value;
-        if (value)
-          this.value = value;
-        else
-          this.selectedIndex = 0;
-      ]]>
-      </constructor>
-
-      <!-- nsIDOMXULRelatedElement -->
-      <method name="getRelatedElement">
-        <parameter name="aTabElm"/>
-        <body>
-        <![CDATA[
-          if (!aTabElm)
-            return null;
+  get handleCtrlTab() {
+    return (this.getAttribute("handleCtrlTab") != "false");
+  }
+  /**
+   * _tabs and _tabpanels are deprecated, they exist only for
+   * backwards compatibility.
+   */
+  get _tabs() {
+    return this.tabs;
+  }
 
-          let tabboxElm = this.tabbox;
-          if (!tabboxElm)
-            return null;
-
-          let tabpanelsElm = tabboxElm.tabpanels;
-          if (!tabpanelsElm)
-            return null;
-
-          // Get linked tab panel by 'linkedpanel' attribute on the given tab
-          // element.
-          let linkedPanelId = aTabElm.linkedPanel;
-          if (linkedPanelId) {
-            let ownerDoc = this.ownerDocument;
-
-            // XXX bug 565858: if XUL tab element is anonymous element then
-            // suppose linked tab panel is hosted within the same XBL binding
-            // and search it by ID attribute inside an anonymous content of
-            // the binding. This is not robust assumption since tab elements may
-            // live outside a tabbox element so that for example tab elements
-            // can be explicit content but tab panels can be anonymous.
-
-            let bindingParent = ownerDoc.getBindingParent(aTabElm);
-            if (bindingParent)
-              return ownerDoc.getAnonymousElementByAttribute(bindingParent,
-                                                             "id",
-                                                             linkedPanelId);
-
-            return ownerDoc.getElementById(linkedPanelId);
-          }
+  get _tabpanels() {
+    return this.tabpanels;
+  }
 
-          // otherwise linked tabpanel element has the same index as the given
-          // tab element.
-          let tabElmIdx = this.getIndexOfItem(aTabElm);
-          return tabpanelsElm.children[tabElmIdx];
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <property name="itemCount" readonly="true"
-                onget="return this.children.length"/>
-
-      <property name="value" onget="return this.getAttribute('value');">
-        <setter>
-          <![CDATA[
-            this.setAttribute("value", val);
-            var children = this.children;
-            for (var c = children.length - 1; c >= 0; c--) {
-              if (children[c].value == val) {
-                this.selectedIndex = c;
-                break;
-              }
-            }
-            return val;
-          ]]>
-        </setter>
-      </property>
-
-      <field name="_tabbox">null</field>
-      <property name="tabbox" readonly="true">
-        <getter><![CDATA[
-          // Memoize the result in a field rather than replacing this property,
-          // so that it can be reset along with the binding.
-          if (this._tabbox) {
-            return this._tabbox;
-          }
-
-          let parent = this.parentNode;
-          while (parent) {
-            if (parent.localName == "tabbox") {
-              break;
-            }
-            parent = parent.parentNode;
-          }
-
-          return this._tabbox = parent;
-        ]]></getter>
-      </property>
+  get tabs() {
+    if (this.hasAttribute("tabcontainer")) {
+      return document.getElementById(this.getAttribute("tabcontainer"));
+    }
+    return this.getElementsByTagNameNS(
+      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+      "tabs").item(0);
+  }
 
-      <!-- _tabbox is deprecated, it exists only for backwards compatibility. -->
-      <field name="_tabbox" readonly="true"><![CDATA[
-        this.tabbox;
-      ]]></field>
-
-      <property name="selectedIndex">
-        <getter>
-        <![CDATA[
-          const tabs = this.children;
-          for (var i = 0; i < tabs.length; i++) {
-            if (tabs[i].selected)
-              return i;
-          }
-          return -1;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          var tab = this.getItemAtIndex(val);
-          if (tab) {
-            Array.forEach(this.children, function(aTab) {
-              if (aTab.selected && aTab != tab)
-                aTab._selected = false;
-            });
-            tab._selected = true;
+  get tabpanels() {
+    return this.getElementsByTagNameNS(
+      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+      "tabpanels").item(0);
+  }
 
-            this.setAttribute("value", tab.value);
-
-            let linkedPanel = this.getRelatedElement(tab);
-            if (linkedPanel) {
-              this.tabbox.setAttribute("selectedIndex", val);
+  set selectedIndex(val) {
+    var tabs = this.tabs;
+    if (tabs)
+      tabs.selectedIndex = val;
+    this.setAttribute("selectedIndex", val);
+    return val;
+  }
 
-              // This will cause an onselect event to fire for the tabpanel
-              // element.
-              this.tabbox.tabpanels.selectedPanel = linkedPanel;
-            }
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedItem">
-        <getter>
-        <![CDATA[
-          const tabs = this.children;
-          for (var i = 0; i < tabs.length; i++) {
-            if (tabs[i].selected)
-              return tabs[i];
-          }
-          return null;
-        ]]>
-        </getter>
+  get selectedIndex() {
+    var tabs = this.tabs;
+    return tabs ? tabs.selectedIndex : -1;
+  }
 
-        <setter>
-        <![CDATA[
-          if (val && !val.selected)
-            // The selectedIndex setter ignores invalid values
-            // such as -1 if |val| isn't one of our child nodes.
-            this.selectedIndex = this.getIndexOfItem(val);
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <method name="getIndexOfItem">
-        <parameter name="item"/>
-        <body>
-        <![CDATA[
-          return Array.indexOf(this.children, item);
-        ]]>
-        </body>
-      </method>
-
-      <method name="getItemAtIndex">
-        <parameter name="index"/>
-        <body>
-        <![CDATA[
-          return this.children.item(index);
-        ]]>
-        </body>
-      </method>
+  set selectedTab(val) {
+    if (val) {
+      var tabs = this.tabs;
+      if (tabs)
+        tabs.selectedItem = val;
+    }
+    return val;
+  }
 
-      <method name="_selectNewTab">
-        <parameter name="aNewTab"/>
-        <parameter name="aFallbackDir"/>
-        <parameter name="aWrap"/>
-        <body>
-        <![CDATA[
-          var requestedTab = aNewTab;
-          while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
-            aNewTab = aFallbackDir == -1 ? aNewTab.previousElementSibling : aNewTab.nextElementSibling;
-            if (!aNewTab && aWrap)
-              aNewTab = aFallbackDir == -1 ? this.children[this.children.length - 1] :
-                                             this.children[0];
-            if (!aNewTab || aNewTab == requestedTab)
-              return;
-          }
+  get selectedTab() {
+    var tabs = this.tabs;
+    return tabs && tabs.selectedItem;
+  }
 
-          var isTabFocused = false;
-          try {
-            isTabFocused =
-              (document.commandDispatcher.focusedElement == this.selectedItem);
-          } catch (e) {}
-          this.selectedItem = aNewTab;
-          if (isTabFocused) {
-            aNewTab.focus();
-          } else if (this.getAttribute("setfocus") != "false") {
-            let selectedPanel = this.tabbox.selectedPanel;
-            document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);
-
-            // Make sure that the focus doesn't move outside the tabbox
-            if (this.tabbox) {
-              try {
-                let el = document.commandDispatcher.focusedElement;
-                while (el && el != this.tabbox.tabpanels) {
-                  if (el == this.tabbox || el == selectedPanel)
-                    return;
-                  el = el.parentNode;
-                }
-                aNewTab.focus();
-              } catch (e) {
-              }
-            }
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="_canAdvanceToTab">
-        <parameter name="aTab"/>
-        <body>
-        <![CDATA[
-          return true;
-        ]]>
-        </body>
-      </method>
+  set selectedPanel(val) {
+    if (val) {
+      var tabpanels = this.tabpanels;
+      if (tabpanels)
+        tabpanels.selectedPanel = val;
+    }
+    return val;
+  }
 
-      <method name="advanceSelectedTab">
-        <parameter name="aDir"/>
-        <parameter name="aWrap"/>
-        <body>
-        <![CDATA[
-          var startTab = this.selectedItem;
-          var next = startTab[aDir == -1 ? "previousSibling" : "nextSibling"];
-          if (!next && aWrap) {
-            next = aDir == -1 ? this.children[this.children.length - 1] :
-                                this.children[0];
-          }
-          if (next && next != startTab) {
-            this._selectNewTab(next, aDir, aWrap);
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="appendItem">
-        <parameter name="label"/>
-        <parameter name="value"/>
-        <body>
-        <![CDATA[
-          var tab = document.createXULElement("tab");
-          tab.setAttribute("label", label);
-          tab.setAttribute("value", value);
-          this.appendChild(tab);
-          return tab;
-        ]]>
-        </body>
-      </method>
-    </implementation>
-
-#ifdef MOZ_WIDGET_GTK
-    <handlers>
-      <handler event="DOMMouseScroll">
-      <![CDATA[
-        if (event.detail > 0)
-          this.advanceSelectedTab(1, false);
-        else
-          this.advanceSelectedTab(-1, false);
-
-        event.stopPropagation();
-      ]]>
-      </handler>
-    </handlers>
-#endif
-  </binding>
+  get selectedPanel() {
+    var tabpanels = this.tabpanels;
+    return tabpanels && tabpanels.selectedPanel;
+  }
 
-  <binding id="tabpanels">
-    <implementation implements="nsIDOMXULRelatedElement">
-      <!-- nsIDOMXULRelatedElement -->
-      <method name="getRelatedElement">
-        <parameter name="aTabPanelElm"/>
-        <body>
-        <![CDATA[
-          if (!aTabPanelElm)
-            return null;
-
-          let tabboxElm = this.tabbox;
-          if (!tabboxElm)
-            return null;
-
-          let tabsElm = tabboxElm.tabs;
-          if (!tabsElm)
-            return null;
-
-          // Return tab element having 'linkedpanel' attribute equal to the id
-          // of the tab panel or the same index as the tab panel element.
-          let tabpanelIdx = Array.indexOf(this.children, aTabPanelElm);
-          if (tabpanelIdx == -1)
-            return null;
-
-          let tabElms = tabsElm.children;
-          let tabElmFromIndex = tabElms[tabpanelIdx];
+  set eventNode(val) {
+    if (val != this._eventNode) {
+      Services.els.addSystemEventListener(val, "keydown", this, false);
+      Services.els.removeSystemEventListener(this._eventNode, "keydown", this, false);
+      this._eventNode = val;
+    }
+    return val;
+  }
 
-          let tabpanelId = aTabPanelElm.id;
-          if (tabpanelId) {
-            for (let idx = 0; idx < tabElms.length; idx++) {
-              var tabElm = tabElms[idx];
-              if (tabElm.linkedPanel == tabpanelId)
-                return tabElm;
-            }
-          }
-
-          return tabElmFromIndex;
-        ]]>
-        </body>
-      </method>
+  get eventNode() {
+    return this._eventNode;
+  }
 
-      <!-- public -->
-      <field name="_tabbox">null</field>
-      <property name="tabbox" readonly="true">
-        <getter><![CDATA[
-          // Memoize the result in a field rather than replacing this property,
-          // so that it can be reset along with the binding.
-          if (this._tabbox) {
-            return this._tabbox;
-          }
-
-          let parent = this.parentNode;
-          while (parent) {
-            if (parent.localName == "tabbox") {
-              break;
-            }
-            parent = parent.parentNode;
-          }
-
-          return this._tabbox = parent;
-        ]]></getter>
-      </property>
+  handleEvent(event) {
+    if (!event.isTrusted) {
+      // Don't let untrusted events mess with tabs.
+      return;
+    }
 
-      <field name="_selectedPanel">this.children.item(this.selectedIndex)</field>
-
-      <property name="selectedIndex">
-        <getter>
-        <![CDATA[
-          var indexStr = this.getAttribute("selectedIndex");
-          return indexStr ? parseInt(indexStr) : -1;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          if (val < 0 || val >= this.children.length)
-            return val;
-
-          var panel = this._selectedPanel;
-          this._selectedPanel = this.children[val];
-          this.setAttribute("selectedIndex", val);
-          if (this._selectedPanel != panel) {
-            var event = document.createEvent("Events");
-            event.initEvent("select", true, true);
-            this.dispatchEvent(event);
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedPanel">
-        <getter>
-          <![CDATA[
-            return this._selectedPanel;
-          ]]>
-        </getter>
-
-        <setter>
-          <![CDATA[
-            var selectedIndex = -1;
-            for (var panel = val; panel != null; panel = panel.previousElementSibling)
-              ++selectedIndex;
-            this.selectedIndex = selectedIndex;
-            return val;
-          ]]>
-        </setter>
-      </property>
-    </implementation>
-  </binding>
-
-  <binding id="tab" display="xul:button"
-           extends="chrome://global/content/bindings/general.xml#basetext">
-    <content>
-      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
-        <xul:image class="tab-icon"
-                   xbl:inherits="validate,src=image"
-                   role="presentation"/>
-        <xul:label class="tab-text"
-                   xbl:inherits="value=label,accesskey,crop,disabled"
-                   flex="1"
-                   role="presentation"/>
-      </xul:hbox>
-    </content>
-
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <property name="value" onset="this.setAttribute('value', val); return val;"
-                             onget="return this.getAttribute('value');"/>
-      <property name="control" readonly="true">
-        <getter>
-          <![CDATA[
-            var parent = this.parentNode;
-            if (parent instanceof Ci.nsIDOMXULSelectControlElement)
-              return parent;
-            return null;
-          ]]>
-        </getter>
-      </property>
-
-      <property name="selected" readonly="true"
-                onget="return this.getAttribute('selected') == 'true';"/>
-
-      <property name="_selected">
-        <setter><![CDATA[
-          if (val) {
-            this.setAttribute("selected", "true");
-            this.setAttribute("visuallyselected", "true");
-          } else {
-            this.removeAttribute("selected");
-            this.removeAttribute("visuallyselected");
-          }
-
-          return val;
-        ]]></setter>
-      </property>
-
-      <property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
-                                   onset="this.setAttribute('linkedpanel', val); return val;"/>
+    // Don't check if the event was already consumed because tab
+    // navigation should always work for better user experience.
 
-      <field name="arrowKeysShouldWrap" readonly="true">
-        /Mac/.test(navigator.platform)
-      </field>
-      <property name="TelemetryStopwatch" readonly="true">
-        <getter><![CDATA[
-          let module = {};
-          ChromeUtils.import("resource://gre/modules/TelemetryStopwatch.jsm", module);
-          Object.defineProperty(this, "TelemetryStopwatch", {
-            configurable: true,
-            enumerable: true,
-            writable: true,
-            value: module.TelemetryStopwatch
-          });
-          return module.TelemetryStopwatch;
-        ]]></getter>
-      </property>
-    </implementation>
-
-    <handlers>
-      <handler event="mousedown" button="0">
-      <![CDATA[
-        if (this.disabled)
-          return;
-
-        if (this != this.parentNode.selectedItem) { // Not selected yet
-          let stopwatchid = this.parentNode.getAttribute("stopwatchid");
-          if (stopwatchid) {
-            this.TelemetryStopwatch.start(stopwatchid);
+    switch (event.keyCode) {
+      case event.DOM_VK_TAB:
+        if (event.ctrlKey && !event.altKey && !event.metaKey)
+          if (this.tabs && this.handleCtrlTab) {
+            this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
+            event.preventDefault();
           }
-
-          // Call this before setting the 'ignorefocus' attribute because this
-          // will pass on focus if the formerly selected tab was focused as well.
-          this.parentNode._selectNewTab(this);
-
-          var isTabFocused = false;
-          try {
-            isTabFocused = (document.commandDispatcher.focusedElement == this);
-          } catch (e) {}
-
-          // Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
-          // focus the tab; we only want tabs to be focusable by the mouse if
-          // they are already focused. After a short timeout we'll reset
-          // '-moz-user-focus' so that tabs can be focused by keyboard again.
-          if (!isTabFocused) {
-            this.setAttribute("ignorefocus", "true");
-            setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
+        break;
+      case event.DOM_VK_PAGE_UP:
+        if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
+          this.tabs) {
+          this.tabs.advanceSelectedTab(-1, true);
+          event.preventDefault();
+        }
+        break;
+      case event.DOM_VK_PAGE_DOWN:
+        if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
+          this.tabs) {
+          this.tabs.advanceSelectedTab(1, true);
+          event.preventDefault();
+        }
+        break;
+      case event.DOM_VK_LEFT:
+        if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
+          if (this.tabs && this._handleMetaAltArrows) {
+            var offset = window.getComputedStyle(this)
+              .direction == "ltr" ? -1 : 1;
+            this.tabs.advanceSelectedTab(offset, true);
+            event.preventDefault();
           }
+        break;
+      case event.DOM_VK_RIGHT:
+        if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
+          if (this.tabs && this._handleMetaAltArrows) {
+            offset = window.getComputedStyle(this)
+              .direction == "ltr" ? 1 : -1;
+            this.tabs.advanceSelectedTab(offset, true);
+            event.preventDefault();
+          }
+        break;
+    }
+  }
+}
 
-          if (stopwatchid) {
-            this.TelemetryStopwatch.finish(stopwatchid);
-          }
-        }
-        // Otherwise this tab is already selected and we will fall
-        // through to mousedown behavior which sets focus on the current tab,
-        // Only a click on an already selected tab should focus the tab itself.
-      ]]>
-      </handler>
-
-      <handler event="keydown" keycode="VK_LEFT" group="system" preventdefault="true">
-      <![CDATA[
-        var direction = window.getComputedStyle(this.parentNode).direction;
-        this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1, this.arrowKeysShouldWrap);
-      ]]>
-      </handler>
-
-      <handler event="keydown" keycode="VK_RIGHT" group="system" preventdefault="true">
-      <![CDATA[
-        var direction = window.getComputedStyle(this.parentNode).direction;
-        this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1, this.arrowKeysShouldWrap);
-      ]]>
-      </handler>
+customElements.define("tabbox", MozTabbox);
 
-      <handler event="keydown" keycode="VK_UP" group="system" preventdefault="true">
-      <![CDATA[
-        this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
-      ]]>
-      </handler>
-
-      <handler event="keydown" keycode="VK_DOWN" group="system" preventdefault="true">
-      <![CDATA[
-        this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
-      ]]>
-      </handler>
-
-      <handler event="keydown" keycode="VK_HOME" group="system" preventdefault="true">
-      <![CDATA[
-        this.parentNode._selectNewTab(this.parentNode.children[0]);
-      ]]>
-      </handler>
-
-      <handler event="keydown" keycode="VK_END" group="system" preventdefault="true">
-      <![CDATA[
-        var tabs = this.parentNode.children;
-        this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
-      ]]>
-      </handler>
-    </handlers>
-  </binding>
-
-</bindings>
+}
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -3,218 +3,16 @@
    - 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="tabBindings"
           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="tabbox">
-    <implementation>
-      <property name="handleCtrlTab">
-        <setter>
-        <![CDATA[
-          this.setAttribute("handleCtrlTab", val);
-          return val;
-        ]]>
-        </setter>
-        <getter>
-        <![CDATA[
-          return (this.getAttribute("handleCtrlTab") != "false");
-        ]]>
-        </getter>
-      </property>
-
-      <field name="_handleMetaAltArrows" readonly="true">
-        /Mac/.test(navigator.platform)
-      </field>
-
-      <!-- _tabs and _tabpanels are deprecated, they exist only for
-           backwards compatibility. -->
-      <property name="_tabs" readonly="true" onget="return this.tabs;"/>
-      <property name="_tabpanels" readonly="true" onget="return this.tabpanels;"/>
-
-      <property name="tabs" readonly="true">
-        <getter>
-        <![CDATA[
-          if (this.hasAttribute("tabcontainer")) {
-            return document.getElementById(this.getAttribute("tabcontainer"));
-          }
-          return this.getElementsByTagNameNS(
-              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-              "tabs").item(0);
-        ]]>
-        </getter>
-      </property>
-
-      <property name="tabpanels" readonly="true">
-        <getter>
-        <![CDATA[
-          return this.getElementsByTagNameNS(
-              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-              "tabpanels").item(0);
-        ]]>
-        </getter>
-      </property>
-
-      <property name="selectedIndex">
-        <getter>
-        <![CDATA[
-          var tabs = this.tabs;
-          return tabs ? tabs.selectedIndex : -1;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          var tabs = this.tabs;
-          if (tabs)
-            tabs.selectedIndex = val;
-          this.setAttribute("selectedIndex", val);
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedTab">
-        <getter>
-        <![CDATA[
-          var tabs = this.tabs;
-          return tabs && tabs.selectedItem;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          if (val) {
-            var tabs = this.tabs;
-            if (tabs)
-              tabs.selectedItem = val;
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="selectedPanel">
-        <getter>
-        <![CDATA[
-          var tabpanels = this.tabpanels;
-          return tabpanels && tabpanels.selectedPanel;
-        ]]>
-        </getter>
-
-        <setter>
-        <![CDATA[
-          if (val) {
-            var tabpanels = this.tabpanels;
-            if (tabpanels)
-              tabpanels.selectedPanel = val;
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <method name="handleEvent">
-        <parameter name="event"/>
-        <body>
-        <![CDATA[
-          if (!event.isTrusted) {
-            // Don't let untrusted events mess with tabs.
-            return;
-          }
-
-          // Don't check if the event was already consumed because tab
-          // navigation should always work for better user experience.
-
-          switch (event.keyCode) {
-            case event.DOM_VK_TAB:
-              if (event.ctrlKey && !event.altKey && !event.metaKey)
-                if (this.tabs && this.handleCtrlTab) {
-                  this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
-                  event.preventDefault();
-                }
-              break;
-            case event.DOM_VK_PAGE_UP:
-              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
-                  this.tabs) {
-                this.tabs.advanceSelectedTab(-1, true);
-                event.preventDefault();
-              }
-              break;
-            case event.DOM_VK_PAGE_DOWN:
-              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
-                  this.tabs) {
-                this.tabs.advanceSelectedTab(1, true);
-                event.preventDefault();
-              }
-              break;
-            case event.DOM_VK_LEFT:
-              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
-                if (this.tabs && this._handleMetaAltArrows) {
-                  var offset = window.getComputedStyle(this)
-                                     .direction == "ltr" ? -1 : 1;
-                  this.tabs.advanceSelectedTab(offset, true);
-                  event.preventDefault();
-                }
-              break;
-            case event.DOM_VK_RIGHT:
-              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
-                if (this.tabs && this._handleMetaAltArrows) {
-                  offset = window.getComputedStyle(this)
-                                     .direction == "ltr" ? 1 : -1;
-                  this.tabs.advanceSelectedTab(offset, true);
-                  event.preventDefault();
-                }
-              break;
-          }
-        ]]>
-        </body>
-      </method>
-
-      <field name="_eventNode">this</field>
-
-      <property name="eventNode" onget="return this._eventNode;">
-        <setter>
-          <![CDATA[
-            if (val != this._eventNode) {
-              const nsIEventListenerService =
-                Ci.nsIEventListenerService;
-              let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                          .getService(nsIEventListenerService);
-              els.addSystemEventListener(val, "keydown", this, false);
-              els.removeSystemEventListener(this._eventNode, "keydown", this, false);
-              this._eventNode = val;
-            }
-            return val;
-          ]]>
-        </setter>
-      </property>
-
-      <constructor>
-        switch (this.getAttribute("eventnode")) {
-          case "parent": this._eventNode = this.parentNode; break;
-          case "window": this._eventNode = window; break;
-          case "document": this._eventNode = document; break;
-        }
-        let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                    .getService(Ci.nsIEventListenerService);
-        els.addSystemEventListener(this._eventNode, "keydown", this, false);
-      </constructor>
-
-      <destructor>
-        let els = Cc["@mozilla.org/eventlistenerservice;1"]
-                    .getService(Ci.nsIEventListenerService);
-        els.removeSystemEventListener(this._eventNode, "keydown", this, false);
-      </destructor>
-    </implementation>
-  </binding>
-
   <binding id="tabs"
            extends="chrome://global/content/bindings/general.xml#basecontrol">
     <content>
       <xul:spacer class="tabs-left"/>
       <children/>
       <xul:spacer class="tabs-right" flex="1"/>
     </content>
 
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -551,17 +551,16 @@ deck {
 
 stack {
   display: -moz-stack;
 }
 
 /********** tabbox *********/
 
 tabbox {
-  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabbox");
   -moz-box-orient: vertical;
 }
 
 tabs {
   -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabs");
   -moz-box-orient: horizontal;
 }