Bug 1502947 - Add helper function to MozXULElement for inheriting attributes;r=bgrins
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 30 Oct 2018 11:51:40 -0700
changeset 443508 2596473d8286f0f5d3e8d1df26eab7ef66df5658
parent 443507 aa35078cabaaef9c255b3bfaf195051456ab5d8e
child 443509 786d2f52f4569f30eeb10898ce2541c36054090c
push id109404
push userbgrinstead@mozilla.com
push dateTue, 30 Oct 2018 18:52:55 +0000
treeherdermozilla-inbound@2596473d8286 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1502947
milestone65.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 1502947 - Add helper function to MozXULElement for inheriting attributes;r=bgrins
toolkit/content/customElements.js
toolkit/content/tests/chrome/test_custom_element_base.xul
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -39,16 +39,49 @@ window.addEventListener("DOMContentLoade
   }
   gElementsPendingConnection.clear();
 }, { once: true, capture: true });
 
 const gXULDOMParser = new DOMParser();
 gXULDOMParser.forceEnableXULXBL();
 
 const MozElementMixin = Base => class MozElement extends Base {
+
+  /*
+   * Implements attribute inheritance by a child element. Uses XBL @inherit
+   * syntax of |to=from|.
+   *
+   * @param {element} child
+   *        A child element that inherits an attribute.
+   * @param {string} attr
+   *        An attribute to inherit. Optionally in the form of |to=from|, where
+   *        |to| is an attribute defined on custom element, whose value will be
+   *        inherited to |from| attribute, defined a child element. Note |from| may
+   *        take a special value of "text" to propogate attribute value as
+   *        a child's text.
+   */
+  inheritAttribute(child, attr) {
+    let attrName = attr;
+    let attrNewName = attr;
+    let split = attrName.split("=");
+    if (split.length == 2) {
+      attrName = split[1];
+      attrNewName = split[0];
+    }
+
+    if (attrNewName === "text") {
+      child.textContent =
+        this.hasAttribute(attrName) ? this.getAttribute(attrName) : "";
+    } else if (this.hasAttribute(attrName)) {
+      child.setAttribute(attrNewName, this.getAttribute(attrName));
+    } else {
+      child.removeAttribute(attrNewName);
+    }
+  }
+
   /**
    * Sometimes an element may not want to run connectedCallback logic during
    * parse. This could be because we don't want to initialize the element before
    * the element's contents have been fully parsed, or for performance reasons.
    * If you'd like to opt-in to this, then add this to the beginning of your
    * `connectedCallback` and `disconnectedCallback`:
    *
    *    if (this.delayConnectedCallback()) { return }
--- a/toolkit/content/tests/chrome/test_custom_element_base.xul
+++ b/toolkit/content/tests/chrome/test_custom_element_base.xul
@@ -11,27 +11,29 @@
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
   <button id="one"/>
   <simpleelement id="two" style="-moz-user-focus: normal;"/>
   <simpleelement id="three" disabled="true" style="-moz-user-focus: normal;"/>
   <button id="four"/>
+  <inherited-element foo="fuagra"></inherited-element>
 
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
   SimpleTest.waitForExplicitFinish();
 
   async function runTests() {
     ok(MozXULElement, "MozXULElement defined on the window");
     testMixin();
     testParseXULToFragment();
+    testInherits();
     await testCustomInterface();
 
     let htmlWin = await new Promise(resolve => {
       let htmlIframe = document.createElement("iframe");
       htmlIframe.src = "file_empty.xhtml";
       htmlIframe.onload = () => resolve(htmlIframe.contentWindow);
       document.documentElement.appendChild(htmlIframe);
     });
@@ -57,16 +59,63 @@
     let deck = document.documentElement.lastChild;
     ok(deck instanceof MozXULElement, "instance of MozXULElement");
     ok(deck instanceof XULElement, "instance of XULElement");
     is(deck.id, "foo", "attribute set");
     is(deck.selectedIndex, "0", "Custom Element is property attached");
     deck.remove();
   }
 
+  function testInherits() {
+    class InheritsElement extends MozXULElement {
+      static get observedAttributes() {
+        return [ "label", "foo", "boo", "bar" ];
+      }
+
+      attributeChangedCallback(name, oldValue, newValue) {
+        if (this.label && oldValue != newValue) {
+          this.inherit();
+        }
+      }
+
+      inherit() {
+        for (let attr of [ "text=label", "foo", "boo", "bardo=bar" ]) {
+          this.inheritAttribute(this.label, attr);
+        }
+      }
+
+      connectedCallback() {
+        this.append(MozXULElement.parseXULToFragment(`<label />`));
+        this.label = this.querySelector("label");
+        this.inherit();
+      }
+    }
+
+    customElements.define("inherited-element", InheritsElement);
+    let el = document.querySelector("inherited-element");
+    ok(el, "element exists");
+
+    is(el.label.getAttribute("foo"), "fuagra", "predefined attribute @foo");
+    ok(!el.label.hasAttribute("boo"), "predefined attribute @boo");
+    ok(!el.label.hasAttribute("bardo"), "predefined attribute @bardo");
+    ok(!el.label.textContent, "predefined attribute @label");
+
+    el.setAttribute("boo", "boo-test");
+    is(el.label.getAttribute("boo"), "boo-test",
+       "attribute inheritance: boo");
+
+    el.setAttribute("label", "label-test");
+    is(el.label.textContent, "label-test",
+       "attribute inheritance: text=label attribute change");
+
+    el.setAttribute("bar", "bar-test");
+    is(el.label.getAttribute("bardo"), "bar-test",
+       "attribute inheritance: bardo=bar");
+  }
+
   async function testCustomInterface() {
     class SimpleElement extends MozXULElement {
       get disabled() {
         return this.getAttribute("disabled") == "true";
       }
 
       set disabled(val) {
         if (val) this.setAttribute("disabled", "true");