Bug 1469902 - Migrate <tabbox> to a Custom Element draft
authorBrian Grinstead <bgrinstead@mozilla.com>
Wed, 20 Jun 2018 11:16:05 -0700
changeset 808786 3cb1aa3a82b6ff1e747654291861a79b9c994f63
parent 808784 26180a9edbb1942e25de3cf61e93e866b6252faa
push id113489
push userbgrinstead@mozilla.com
push dateWed, 20 Jun 2018 18:17:02 +0000
bugs1469902
milestone62.0a1
Bug 1469902 - Migrate <tabbox> to a Custom Element MozReview-Commit-ID: HNDiMYmKgkg
toolkit/content/customElements.js
toolkit/content/jar.mn
toolkit/content/widgets/tabbox.js
toolkit/content/xul.css
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -61,13 +61,14 @@ class MozXULElement extends XULElement {
 }
 
 // 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/tabbox.js",
 ]) {
   Services.scriptloader.loadSubScript(script, window);
 }
 
 }
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -98,13 +98,14 @@ toolkit.jar:
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    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/general.js           (widgets/general.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
+   content/global/elements/tabbox.js           (widgets/tabbox.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)
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/tabbox.js
@@ -0,0 +1,184 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+{
+
+class MozTabbox extends MozXULElement {
+  connectedCallback() {
+    this._handleMetaAltArrows = /Mac/.test(navigator.platform);
+
+    this._eventNode = this;
+
+    switch (this.getAttribute("eventnode")) {
+      case "parent":
+        this._eventNode = this.parentNode;
+        break;
+      case "window":
+        this._eventNode = window;
+        break;
+      case "document":
+        this._eventNode = document;
+        break;
+    }
+    Services.els.addSystemEventListener(this._eventNode, "keydown", this, false);
+    this.disconnectedCallback = this.disconnectedCallback.bind(this);
+    window.addEventListener("unload", this.disconnectedCallback, { once: true });
+  }
+
+  set handleCtrlTab(val) {
+    this.setAttribute("handleCtrlTab", val);
+    return val;
+  }
+
+  get handleCtrlTab() {
+    return (this.getAttribute("handleCtrlTab") != "false");
+  }
+  /**
+   * _tabs and _tabpanels are deprecated, they exist only for
+   * backwards compatibility.
+   */
+  get _tabs() {
+    return this.tabs;
+  }
+
+  get _tabpanels() {
+    return this.tabpanels;
+  }
+
+  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);
+  }
+
+  get tabpanels() {
+    return this.getElementsByTagNameNS(
+      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+      "tabpanels").item(0);
+  }
+
+  set selectedIndex(val) {
+    var tabs = this.tabs;
+    if (tabs)
+      tabs.selectedIndex = val;
+    this.setAttribute("selectedIndex", val);
+    return val;
+  }
+
+  get selectedIndex() {
+    var tabs = this.tabs;
+    return tabs ? tabs.selectedIndex : -1;
+  }
+
+  set selectedTab(val) {
+    if (val) {
+      var tabs = this.tabs;
+      if (tabs)
+        tabs.selectedItem = val;
+    }
+    return val;
+  }
+
+  get selectedTab() {
+    var tabs = this.tabs;
+    return tabs && tabs.selectedItem;
+  }
+
+  set selectedPanel(val) {
+    if (val) {
+      var tabpanels = this.tabpanels;
+      if (tabpanels)
+        tabpanels.selectedPanel = val;
+    }
+    return val;
+  }
+
+  get selectedPanel() {
+    var tabpanels = this.tabpanels;
+    return tabpanels && tabpanels.selectedPanel;
+  }
+
+  set eventNode(val) {
+    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;
+  }
+
+  get eventNode() {
+    return this._eventNode;
+  }
+
+  handleEvent(event) {
+    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;
+    }
+  }
+
+  disconnectedCallback() {
+    window.removeEventListener("unload", this.disconnectedCallback, { once: true });
+    Services.els.removeSystemEventListener(this._eventNode, "keydown", this, false);
+  }
+}
+
+customElements.define("tabbox", MozTabbox);
+
+}
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -653,17 +653,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;
 }