diff --git a/browser/components/search/content/searchbar.js b/browser/components/search/content/searchbar.js index a62ccb21178e..ad28b467744c 100644 --- a/browser/components/search/content/searchbar.js +++ b/browser/components/search/content/searchbar.js @@ -9,46 +9,15 @@ // This is loaded into chrome windows with the subscript loader. Wrap in // a block to prevent accidentally leaking globals onto `window`. { -const inheritsMap = { - ".searchbar-textbox": ["disabled", "disableautocomplete", "searchengine", "src", "newlines"], - ".searchbar-search-button": ["addengines"], -}; - -function inheritAttribute(parent, child, attr) { - if (!parent.hasAttribute(attr)) { - child.removeAttribute(attr); - } else { - child.setAttribute(attr, parent.getAttribute(attr)); - } -} - /** * Defines the search bar element. */ class MozSearchbar extends MozXULElement { - static get observedAttributes() { - let unique = new Set(); - for (let i in inheritsMap) { - inheritsMap[i].forEach(attr => unique.add(attr)); - } - return Array.from(unique); - } - - attributeChangedCallback() { - this.inheritAttributes(); - } - - inheritAttributes() { - if (!this.isConnected) { - return; - } - - for (let sel in inheritsMap) { - let node = this.querySelector(sel); - for (let attr of inheritsMap[sel]) { - inheritAttribute(this, node, attr); - } - } + static get inheritedAttributes() { + return { + ".searchbar-textbox": "disabled,disableautocomplete,searchengine,src,newlines", + ".searchbar-search-button": "addengines", + }; } constructor() { @@ -72,9 +41,9 @@ class MozSearchbar extends MozXULElement { }; this.content = MozXULElement.parseXULToFragment(` - + - + @@ -93,7 +62,7 @@ class MozSearchbar extends MozXULElement { } this.appendChild(document.importNode(this.content, true)); - this.inheritAttributes(); + this.initializeAttributeInheritance(); window.addEventListener("unload", this.destroy); this._ignoreFocus = false; diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js index 8f3c3b1f1e9d..d49dc5282922 100644 --- a/toolkit/content/customElements.js +++ b/toolkit/content/customElements.js @@ -47,9 +47,120 @@ gXULDOMParser.forceEnableXULXBL(); const MozElements = {}; const MozElementMixin = Base => class MozElement extends Base { + /* + * A declarative way to wire up attribute inheritance and automatically generate + * the `observedAttributes` getter. For example, if you returned: + * { + * ".foo": "bar,baz=bat" + * } + * + * Then the base class will automatically return ["bar", "bat"] from `observedAttributes`, + * and set up an `attributeChangedCallback` to pass those attributes down onto an element + * matching the ".foo" selector. + * + * See the `inheritAttribute` function for more details on the attribute string format. + * + * @return {Object} + */ + static get inheritedAttributes() { + return null; + } + + /* + * Generate this array based on `inheritedAttributes`, if any. A class is free to override + * this if it needs to do something more complex or wants to opt out of this behavior. + */ + static get observedAttributes() { + let {inheritedAttributes} = this; + if (!inheritedAttributes) { + return []; + } + + let allAttributes = new Set(); + for (let sel in inheritedAttributes) { + for (let attrName of inheritedAttributes[sel].split(",")) { + allAttributes.add(attrName.split("=").pop()); + } + } + return [...allAttributes]; + } + + /* + * Provide default lifecycle callback for attribute changes that will inherit attributes + * based on the static `inheritedAttributes` Object. This can be overridden by callers. + */ + attributeChangedCallback(name, oldValue, newValue) { + if (!this.isConnectedAndReady || oldValue === newValue || !this.inheritedAttributesCache) { + return; + } + + this.inheritAttributes(); + } + + /* + * After setting content, calling this will cache the elements from selectors in the + * static `inheritedAttributes` Object. It'll also do an initial call to `this.inheritAttributes()`, + * so in the simple case, this is the only function you need to call. + * + * This should be called any time the children that are inheriting attributes changes. For instance, + * it's common in a connectedCallback to do something like: + * + * this.textContent = ""; + * this.append(MozXULElement.parseXULToFragment(` `, ["chrome://global/locale/notification.dtd"])); @@ -140,7 +110,7 @@ class MozPopupNotification extends MozXULElement { this.appendNotificationContent(popupnotificationcontent); } - this._updateAttributes(); + this.initializeAttributeInheritance(); } appendNotificationContent(el) { diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js index fe337925f21f..b01e8502528c 100644 --- a/toolkit/content/widgets/tree.js +++ b/toolkit/content/widgets/tree.js @@ -244,13 +244,11 @@ customElements.define("treecolpicker", MozTreecolPicker); class MozTreecol extends MozElements.BaseControl { - static get observedAttributes() { - return [ - "label", - "sortdirection", - "hideheader", - "crop", - ]; + static get inheritedAttributes() { + return { + ".treecol-sortdirection": "sortdirection,hidden=hideheader", + ".treecol-text": "value=label,crop", + }; } get content() { @@ -316,28 +314,7 @@ this.textContent = ""; this.appendChild(this.content); - - this._updateAttributes(); - } - - attributeChangedCallback() { - if (this.isConnectedAndReady) { - this._updateAttributes(); - } - } - - _updateAttributes() { - let image = this.querySelector(".treecol-sortdirection"); - let label = this.querySelector(".treecol-text"); - - this.inheritAttribute(image, "sortdirection"); - this.inheritAttribute(image, "hidden=hideheader"); - this.inheritAttribute(label, "value=label"); - - // Don't remove the attribute on the child if it's los on the host. - if (this.hasAttribute("crop")) { - this.inheritAttribute(label, "crop"); - } + this.initializeAttributeInheritance(); } set ordinal(val) { @@ -489,6 +466,12 @@ customElements.define("treecol", MozTreecol); class MozTreecols extends MozElements.BaseControl { + static get inheritedAttributes() { + return { + "treecolpicker": "tooltiptext=pickertooltiptext", + }; + } + connectedCallback() { if (this.delayConnectedCallback()) { return; @@ -498,11 +481,9 @@ this.appendChild(MozXULElement.parseXULToFragment(` `)); + this.initializeAttributeInheritance(); } - let treecolpicker = this.querySelector("treecolpicker"); - this.inheritAttribute(treecolpicker, "tooltiptext=pickertooltiptext"); - // Set resizeafter="farthest" on the splitters if nothing else has been // specified. Array.forEach(this.getElementsByTagName("splitter"), function(splitter) {