Bug 1546024 - Clear the _inheritedElements cache on chrome custom elements when re-calling initializeAttributeInheritance r=surkov
authorBrian Grinstead <bgrinstead@mozilla.com>
Mon, 22 Apr 2019 13:52:17 +0000
changeset 470351 4bc97c0629fad26ca8739c69bd5fe36e746b18ff
parent 470350 f0be6dbacdb0145d2991780ed9c88d2973d72797
child 470352 32e046bfdeab2df91e89a584c718346c1a09712c
push id35903
push useropoprus@mozilla.com
push dateMon, 22 Apr 2019 21:46:44 +0000
treeherdermozilla-central@a11bd690638f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov
bugs1546024
milestone68.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 1546024 - Clear the _inheritedElements cache on chrome custom elements when re-calling initializeAttributeInheritance r=surkov Otherwise we can end up setting the proper attribute on removed children when elements get disconnected and reconnected. Differential Revision: https://phabricator.services.mozilla.com/D28306
toolkit/content/customElements.js
toolkit/content/tests/chrome/test_custom_element_base.xul
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -306,16 +306,19 @@ MozElements.MozElementMixin = Base => {
   *
   */
   initializeAttributeInheritance() {
     let {flippedInheritedAttributes} = this.constructor;
     if (!flippedInheritedAttributes) {
       return;
     }
 
+    // Clear out any existing cached elements:
+    this._inheritedElements = null;
+
     this.initializedAttributeInheritance = true;
     for (let attr in flippedInheritedAttributes) {
       if (this.hasAttribute(attr)) {
         this.inheritAttribute(flippedInheritedAttributes[attr], attr);
       }
     }
   }
 
--- a/toolkit/content/tests/chrome/test_custom_element_base.xul
+++ b/toolkit/content/tests/chrome/test_custom_element_base.xul
@@ -128,16 +128,17 @@
       static get inheritedAttributes() {
         return {
           "label": "text=label,foo,empty-string,bardo=bar",
           "unmatched": "foo", // Make sure we don't throw on unmatched selectors
         };
       }
 
       connectedCallback() {
+        this.textContent = "";
         this.append(MozXULElement.parseXULToFragment(`<label />`));
         this.label = this.querySelector("label");
         this.initializeAttributeInheritance();
       }
     }
     customElements.define("inherited-element-declarative", InheritsElementDeclarative);
     let declarativeEl = document.querySelector("inherited-element-declarative");
     ok(declarativeEl, "declarative inheritance element exists");
@@ -145,25 +146,29 @@
     class InheritsElementDerived extends InheritsElementDeclarative {
       static get inheritedAttributes() {
         return { label: "renamedfoo=foo" };
       }
     }
     customElements.define("inherited-element-derived", InheritsElementDerived);
 
     class InheritsElementShadowDOMDeclarative extends MozXULElement {
+      constructor() {
+        super();
+        this.attachShadow({ mode: "open" });
+      }
       static get inheritedAttributes() {
         return {
           "label": "text=label,foo,empty-string,bardo=bar",
           "unmatched": "foo", // Make sure we don't throw on unmatched selectors
         };
       }
 
       connectedCallback() {
-        this.attachShadow({ mode: "open" });
+        this.shadowRoot.textContent = "";
         this.shadowRoot.append(MozXULElement.parseXULToFragment(`<label />`));
         this.label = this.shadowRoot.querySelector("label");
         this.initializeAttributeInheritance();
       }
     }
     customElements.define("inherited-element-shadowdom-declarative", InheritsElementShadowDOMDeclarative);
     let shadowDOMDeclarativeEl = document.querySelector("inherited-element-shadowdom-declarative");
     ok(shadowDOMDeclarativeEl, "declarative inheritance element with shadow DOM exists");
@@ -187,28 +192,31 @@
           "bar": [[ "label", "bardo" ]],
         };
         for (let attr of InheritsElementImperative.observedAttributes) {
           this.inheritAttribute(map[attr], attr);
         }
       }
 
       connectedCallback() {
+        // Typically `initializeAttributeInheritance` handles this for us:
+        this._inheritedElements = null;
+
+        this.textContent = "";
         this.append(MozXULElement.parseXULToFragment(`<label />`));
         this.label = this.querySelector("label");
         this.inherit();
       }
     }
 
     customElements.define("inherited-element-imperative", InheritsElementImperative);
     let imperativeEl = document.querySelector("inherited-element-imperative");
     ok(imperativeEl, "imperative inheritance element exists");
 
-    for (let el of [declarativeEl, shadowDOMDeclarativeEl, imperativeEl]) {
-      info(`Running checks for ${el.tagName}`);
+    function checkElement(el) {
       is(el.label.getAttribute("foo"), "fuagra", "predefined attribute @foo");
       ok(el.label.hasAttribute("empty-string"), "predefined attribute @empty-string");
       ok(!el.label.hasAttribute("bardo"), "predefined attribute @bardo");
       ok(!el.label.textContent, "predefined attribute @label");
 
       el.setAttribute("empty-string", "not-empty-anymore");
       is(el.label.getAttribute("empty-string"), "not-empty-anymore",
         "attribute inheritance: empty-string");
@@ -235,16 +243,28 @@
 
       el.removeAttribute("bar");
       ok(!el.label.hasAttribute("bardo"),
         "attribute inheritance: does apply when host attr has been removed");
 
       el.setAttribute("bar", "changed-from-host-2");
       is(el.label.getAttribute("bardo"), "changed-from-host-2",
         "attribute inheritance: does apply when host attr has changed after being removed");
+
+      // Restore to the original state so this can be ran again with the same element:
+      el.removeAttribute("label");
+      el.removeAttribute("bar");
+    }
+
+    for (let el of [declarativeEl, shadowDOMDeclarativeEl, imperativeEl]) {
+      info(`Running checks for ${el.tagName}`);
+      checkElement(el);
+      info(`Remove and re-add ${el.tagName} to make sure attribute inheritance still works`);
+      el.replaceWith(el);
+      checkElement(el);
     }
 
     let derivedEl = document.querySelector("inherited-element-derived");
     ok(derivedEl, "derived inheritance element exists");
     ok(!derivedEl.label.hasAttribute("foo"),
        "attribute inheritance: base class attribute is not applied in derived class that overrides it");
     ok(derivedEl.label.hasAttribute("renamedfoo"),
        "attribute inheritance: attribute defined in derived class is present");