зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1527680
- Add MozElement `inheritedAttributes` helper for automatically inheriting attributes based on a map of selectors to attributes r=paolo
This allows elements to skip explicitly declaring `observedAttributes` and then imperatively calling `inheritAttribute` on the appropriate child nodes. For simple cases this means less boilerplate and moving this logic into the base class. This is an opt-in feature, so more complex cases can continue to manually implement inheriting behavior as before. Differential Revision: https://phabricator.services.mozilla.com/D19702 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
6888b2b9c6
Коммит
4bcb2ab728
|
@ -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(`
|
||||
<stringbundle src="chrome://browser/locale/search.properties"></stringbundle>
|
||||
<textbox class="searchbar-textbox" type="autocomplete" inputtype="search" placeholder="&searchInput.placeholder;" flex="1" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0" inherits="disabled,disableautocomplete,searchengine,src,newlines">
|
||||
<textbox class="searchbar-textbox" type="autocomplete" inputtype="search" placeholder="&searchInput.placeholder;" flex="1" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0">
|
||||
<box>
|
||||
<hbox class="searchbar-search-button" inherits="addengines" tooltiptext="&searchIcon.tooltip;">
|
||||
<hbox class="searchbar-search-button" tooltiptext="&searchIcon.tooltip;">
|
||||
<image class="searchbar-search-icon"></image>
|
||||
<image class="searchbar-search-icon-overlay"></image>
|
||||
</hbox>
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<string selector, string attributes>}
|
||||
*/
|
||||
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(`<label />`))
|
||||
* this.initializeAttributeInheritance();
|
||||
*
|
||||
*/
|
||||
initializeAttributeInheritance() {
|
||||
let {inheritedAttributes} = this.constructor;
|
||||
if (!inheritedAttributes) {
|
||||
return;
|
||||
}
|
||||
this._inheritedAttributesValuesCache = null;
|
||||
this.inheritedAttributesCache = new Map();
|
||||
for (let selector in inheritedAttributes) {
|
||||
let el = this.querySelector(selector);
|
||||
// Skip unmatched selectors in case an element omits some elements in certain cases:
|
||||
if (!el) {
|
||||
continue;
|
||||
}
|
||||
if (this.inheritedAttributesCache.has(el)) {
|
||||
console.error(`Error: duplicate element encountered with ${selector}`);
|
||||
}
|
||||
|
||||
this.inheritedAttributesCache.set(el, inheritedAttributes[selector]);
|
||||
}
|
||||
this.inheritAttributes();
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop through the static `inheritedAttributes` Map and inherit attributes to child elements.
|
||||
*
|
||||
* This usually won't need to be called directly - `this.initializeAttributeInheritance()` and
|
||||
* `this.attributeChangedCallback` will call it for you when appropriate.
|
||||
*/
|
||||
inheritAttributes() {
|
||||
let {inheritedAttributes} = this.constructor;
|
||||
if (!inheritedAttributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.inheritedAttributesCache) {
|
||||
console.error(`You must call this.initializeAttributeInheritance() for ${this.tagName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [ el, attrs ] of this.inheritedAttributesCache.entries()) {
|
||||
for (let attr of attrs.split(",")) {
|
||||
this.inheritAttribute(el, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implements attribute inheritance by a child element. Uses XBL @inherit
|
||||
* syntax of |to=from|.
|
||||
* syntax of |to=from|. This can be used directly, but for simple cases
|
||||
* you should use the inheritedAttributes getter and let the base class
|
||||
* handle this for you.
|
||||
*
|
||||
* @param {element} child
|
||||
* A child element that inherits an attribute.
|
||||
|
@ -74,13 +185,13 @@ const MozElementMixin = Base => class MozElement extends Base {
|
|||
// If our attribute hasn't changed since we last inherited, we don't want to
|
||||
// propagate it down to the child. This prevents overriding an attribute that's
|
||||
// been changed on the child (for instance, [checked]).
|
||||
if (!this._inheritedAttributesMap) {
|
||||
this._inheritedAttributesMap = new WeakMap();
|
||||
if (!this._inheritedAttributesValuesCache) {
|
||||
this._inheritedAttributesValuesCache = new WeakMap();
|
||||
}
|
||||
if (!this._inheritedAttributesMap.has(child)) {
|
||||
this._inheritedAttributesMap.set(child, {});
|
||||
if (!this._inheritedAttributesValuesCache.has(child)) {
|
||||
this._inheritedAttributesValuesCache.set(child, {});
|
||||
}
|
||||
let lastInheritedAttributes = this._inheritedAttributesMap.get(child);
|
||||
let lastInheritedAttributes = this._inheritedAttributesValuesCache.get(child);
|
||||
|
||||
if ((hasAttr && attrValue === lastInheritedAttributes[attrName]) ||
|
||||
(!hasAttr && !lastInheritedAttributes.hasOwnProperty(attrName))) {
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
<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>
|
||||
<inherited-element-declarative foo="fuagra"></inherited-element-declarative>
|
||||
<inherited-element-imperative foo="fuagra"></inherited-element-imperative>
|
||||
|
||||
|
||||
<!-- test code goes here -->
|
||||
|
@ -31,7 +32,7 @@
|
|||
testBaseControlMixin();
|
||||
testBaseText();
|
||||
testParseXULToFragment();
|
||||
testInherits();
|
||||
testInheritAttributes();
|
||||
await testCustomInterface();
|
||||
|
||||
let htmlWin = await new Promise(resolve => {
|
||||
|
@ -88,8 +89,26 @@
|
|||
deck.remove();
|
||||
}
|
||||
|
||||
function testInherits() {
|
||||
class InheritsElement extends MozXULElement {
|
||||
function testInheritAttributes() {
|
||||
class InheritsElementDeclarative extends MozXULElement {
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
"label": "text=label,foo,boo,bardo=bar",
|
||||
"unmatched": "foo", // Make sure we don't throw on unmatched selectors
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
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");
|
||||
|
||||
class InheritsElementImperative extends MozXULElement {
|
||||
static get observedAttributes() {
|
||||
return [ "label", "foo", "boo", "bar" ];
|
||||
}
|
||||
|
@ -113,49 +132,49 @@
|
|||
}
|
||||
}
|
||||
|
||||
customElements.define("inherited-element", InheritsElement);
|
||||
let el = document.querySelector("inherited-element");
|
||||
ok(el, "element exists");
|
||||
customElements.define("inherited-element-imperative", InheritsElementImperative);
|
||||
let imperativeEl = document.querySelector("inherited-element-imperative");
|
||||
ok(imperativeEl, "imperative inheritance 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");
|
||||
for (let el of [declarativeEl, imperativeEl]) {
|
||||
info(`Running checks for ${el.tagName}`);
|
||||
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("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("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: `=` mapping");
|
||||
el.setAttribute("bar", "bar-test");
|
||||
is(el.label.getAttribute("bardo"), "bar-test",
|
||||
"attribute inheritance: `=` mapping");
|
||||
|
||||
el.label.setAttribute("bardo", "changed-from-child");
|
||||
el.inherit();
|
||||
is(el.label.getAttribute("bardo"), "changed-from-child",
|
||||
"attribute inheritance: doesn't apply when host attr hasn't changed and child attr was changed");
|
||||
el.label.setAttribute("bardo", "changed-from-child");
|
||||
is(el.label.getAttribute("bardo"), "changed-from-child",
|
||||
"attribute inheritance: doesn't apply when host attr hasn't changed and child attr was changed");
|
||||
|
||||
el.label.removeAttribute("bardo");
|
||||
el.inherit();
|
||||
ok(!el.label.hasAttribute("bardo"),
|
||||
"attribute inheritance: doesn't apply when host attr hasn't changed and child attr was removed");
|
||||
el.label.removeAttribute("bardo");
|
||||
ok(!el.label.hasAttribute("bardo"),
|
||||
"attribute inheritance: doesn't apply when host attr hasn't changed and child attr was removed");
|
||||
|
||||
el.setAttribute("bar", "changed-from-host");
|
||||
is(el.label.getAttribute("bardo"), "changed-from-host",
|
||||
"attribute inheritance: does apply when host attr has changed and child attr was changed");
|
||||
el.setAttribute("bar", "changed-from-host");
|
||||
is(el.label.getAttribute("bardo"), "changed-from-host",
|
||||
"attribute inheritance: does apply when host attr has changed and child attr was changed");
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
async function testCustomInterface() {
|
||||
|
|
|
@ -61,56 +61,28 @@ MozElements.MozAutocompleteRichlistitem = class MozAutocompleteRichlistitem exte
|
|||
|
||||
this.textContent = "";
|
||||
this.appendChild(MozXULElement.parseXULToFragment(this._markup));
|
||||
this.initializeAttributeInheritance();
|
||||
|
||||
this._boundaryCutoff = null;
|
||||
this._inOverflow = false;
|
||||
|
||||
this._updateAttributes();
|
||||
this._adjustAcItem();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"actiontype",
|
||||
"current",
|
||||
"selected",
|
||||
"image",
|
||||
"type",
|
||||
];
|
||||
}
|
||||
|
||||
get inheritedAttributeMap() {
|
||||
if (!this.__inheritedAttributeMap) {
|
||||
this.__inheritedAttributeMap = new Map([
|
||||
[ this.querySelector(".ac-type-icon"), [ "selected", "current", "type" ] ],
|
||||
[ this.querySelector(".ac-site-icon"), [ "src=image", "selected", "type" ] ],
|
||||
[ this.querySelector(".ac-title"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-title-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-tags"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-tags-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-separator"), [ "selected", "actiontype", "type" ] ],
|
||||
[ this.querySelector(".ac-url"), [ "selected", "actiontype" ] ],
|
||||
[ this.querySelector(".ac-url-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-action"), [ "selected", "actiontype" ] ],
|
||||
[ this.querySelector(".ac-action-text"), [ "selected" ] ],
|
||||
]);
|
||||
}
|
||||
return this.__inheritedAttributeMap;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (this.isConnectedAndReady && oldValue != newValue &&
|
||||
this.constructor.observedAttributes.includes(name)) {
|
||||
this._updateAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
_updateAttributes() {
|
||||
for (let [ el, attrs ] of this.inheritedAttributeMap.entries()) {
|
||||
for (let attr of attrs) {
|
||||
this.inheritAttribute(el, attr);
|
||||
}
|
||||
}
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
".ac-type-icon": "selected,current,type",
|
||||
".ac-site-icon": "src=image,selected,type",
|
||||
".ac-title": "selected",
|
||||
".ac-title-text": "selected",
|
||||
".ac-tags": "selected",
|
||||
".ac-tags-text": "selected",
|
||||
".ac-separator": "selected,actiontype,type",
|
||||
".ac-url": "selected,actiontype",
|
||||
".ac-url-text": "selected",
|
||||
".ac-action": "selected,actiontype",
|
||||
".ac-action-text": "selected",
|
||||
};
|
||||
}
|
||||
|
||||
get _markup() {
|
||||
|
@ -942,31 +914,18 @@ class MozAutocompleteRichlistitemInsecureWarning extends MozElements.MozAutocomp
|
|||
this.classList.add("forceHandleUnderflow");
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"actiontype",
|
||||
"current",
|
||||
"selected",
|
||||
"image",
|
||||
"type",
|
||||
];
|
||||
}
|
||||
|
||||
get inheritedAttributeMap() {
|
||||
if (!this.__inheritedAttributeMap) {
|
||||
this.__inheritedAttributeMap = new Map([
|
||||
[ this.querySelector(".ac-type-icon"), [ "selected", "current", "type" ] ],
|
||||
[ this.querySelector(".ac-site-icon"), [ "src=image", "selected", "type" ] ],
|
||||
[ this.querySelector(".ac-title-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-tags-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-separator"), [ "selected", "actiontype", "type" ] ],
|
||||
[ this.querySelector(".ac-url"), [ "selected", "actiontype" ] ],
|
||||
[ this.querySelector(".ac-url-text"), [ "selected" ] ],
|
||||
[ this.querySelector(".ac-action"), [ "selected", "actiontype" ] ],
|
||||
[ this.querySelector(".ac-action-text"), [ "selected" ] ],
|
||||
]);
|
||||
}
|
||||
return this.__inheritedAttributeMap;
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
".ac-type-icon": "selected,current,type",
|
||||
".ac-site-icon": "src=image,selected,type",
|
||||
".ac-title-text": "selected",
|
||||
".ac-tags-text": "selected",
|
||||
".ac-separator": "selected,actiontype,type",
|
||||
".ac-url": "selected,actiontype",
|
||||
".ac-url-text": "selected",
|
||||
".ac-action": "selected,actiontype",
|
||||
".ac-action-text": "selected",
|
||||
};
|
||||
}
|
||||
|
||||
get _markup() {
|
||||
|
|
|
@ -111,18 +111,13 @@ MozXULElement.implementCustomInterface(MozMenuBase, [Ci.nsIDOMXULContainerElemen
|
|||
// The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>,
|
||||
// See SelectParentHelper.jsm.
|
||||
class MozMenuCaption extends MozMenuBase {
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"selected",
|
||||
"disabled",
|
||||
"checked",
|
||||
"image",
|
||||
"validate",
|
||||
"src",
|
||||
"label",
|
||||
"crop",
|
||||
"highlightable",
|
||||
];
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
".menu-iconic-left": "selected,disabled,checked",
|
||||
".menu-iconic-icon": "src=image,validate,src",
|
||||
".menu-iconic-text": "value=label,crop,highlightable",
|
||||
".menu-iconic-highlightable-text": "text=label,crop,highlightable",
|
||||
};
|
||||
}
|
||||
|
||||
_updateAttributes() {
|
||||
|
@ -148,17 +143,13 @@ class MozMenuCaption extends MozMenuBase {
|
|||
connectedCallback() {
|
||||
this.textContent = "";
|
||||
this.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<hbox class="menu-iconic-left" align="center" pack="center" inherits="selected,disabled,checked" role="none">
|
||||
<image class="menu-iconic-icon" inherits="src=image,validate,src" role="none"></image>
|
||||
<hbox class="menu-iconic-left" align="center" pack="center" role="none">
|
||||
<image class="menu-iconic-icon" role="none"></image>
|
||||
</hbox>
|
||||
<label class="menu-iconic-text" flex="1" inherits="value=label,crop,highlightable" crop="right" role="none"></label>
|
||||
<label class="menu-iconic-highlightable-text" inherits="text=label,crop,highlightable" crop="right" role="none"></label>
|
||||
<label class="menu-iconic-text" flex="1" crop="right" role="none"></label>
|
||||
<label class="menu-iconic-highlightable-text" crop="right" role="none"></label>
|
||||
`));
|
||||
this._inheritedAttributeMap = new Map();
|
||||
for (let el of this.querySelectorAll("[inherits]")) {
|
||||
this._inheritedAttributeMap.set(el, el.getAttribute("inherits").split(","));
|
||||
}
|
||||
this._updateAttributes();
|
||||
this.initializeAttributeInheritance();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,15 @@ class MozMenuList extends MenuBaseControl {
|
|||
}, { mozSystemGroup: true });
|
||||
}
|
||||
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
".menulist-icon": "src=image",
|
||||
".menulist-label": "value=label,crop,accesskey,highlightable",
|
||||
".menulist-highlightable-label": "text=label,crop,accesskey,highlightable",
|
||||
".menulist-dropmarker": "disabled,open",
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.delayConnectedCallback()) {
|
||||
return;
|
||||
|
@ -65,10 +74,9 @@ class MozMenuList extends MenuBaseControl {
|
|||
this.prepend(MozMenuList.fragment.cloneNode(true));
|
||||
this._labelBox = this.children[0];
|
||||
this._dropmarker = this.children[1];
|
||||
this.initializeAttributeInheritance();
|
||||
}
|
||||
|
||||
this._updateAttributes();
|
||||
|
||||
this.mSelectedInternal = null;
|
||||
this.mAttributeObserver = null;
|
||||
this.setInitialSelection();
|
||||
|
@ -361,41 +369,6 @@ class MozMenuList extends MenuBaseControl {
|
|||
this._dropmarker = null;
|
||||
}
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["label", "crop", "accesskey", "highlightable", "image", "disabled",
|
||||
"open"];
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
if (this.isConnectedAndReady) {
|
||||
this._updateAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
_updateAttributes() {
|
||||
if (!this._labelBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
let icon = this._labelBox.querySelector(".menulist-icon");
|
||||
this.inheritAttribute(icon, "src=image");
|
||||
|
||||
let label = this._labelBox.querySelector(".menulist-label");
|
||||
this.inheritAttribute(label, "value=label");
|
||||
this.inheritAttribute(label, "crop");
|
||||
this.inheritAttribute(label, "accesskey");
|
||||
this.inheritAttribute(label, "highlightable");
|
||||
|
||||
let highlightableLabel = this._labelBox.querySelector(".menulist-highlightable-label");
|
||||
this.inheritAttribute(highlightableLabel, "text=label");
|
||||
this.inheritAttribute(highlightableLabel, "crop");
|
||||
this.inheritAttribute(highlightableLabel, "accesskey");
|
||||
this.inheritAttribute(highlightableLabel, "highlightable");
|
||||
|
||||
this.inheritAttribute(this._dropmarker, "disabled");
|
||||
this.inheritAttribute(this._dropmarker, "open");
|
||||
}
|
||||
}
|
||||
|
||||
MenuBaseControl.implementCustomInterface(MozMenuList, [Ci.nsIDOMXULMenuListElement,
|
||||
|
|
|
@ -8,63 +8,34 @@
|
|||
// leaking to window scope.
|
||||
{
|
||||
class MozPopupNotification extends MozXULElement {
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"buttonaccesskey",
|
||||
"buttoncommand",
|
||||
"buttonhighlight",
|
||||
"buttonlabel",
|
||||
"closebuttoncommand",
|
||||
"closebuttonhidden",
|
||||
"dropmarkerhidden",
|
||||
"dropmarkerpopupshown",
|
||||
"endlabel",
|
||||
"icon",
|
||||
"iconclass",
|
||||
"label",
|
||||
"learnmoreclick",
|
||||
"learnmoreurl",
|
||||
"mainactiondisabled",
|
||||
"menucommand",
|
||||
"name",
|
||||
"origin",
|
||||
"origin",
|
||||
"popupid",
|
||||
"secondarybuttonaccesskey",
|
||||
"secondarybuttoncommand",
|
||||
"secondarybuttonhidden",
|
||||
"secondarybuttonlabel",
|
||||
"secondendlabel",
|
||||
"secondname",
|
||||
"warninghidden",
|
||||
"warninglabel",
|
||||
];
|
||||
}
|
||||
|
||||
_updateAttributes() {
|
||||
for (let [ el, attrs ] of this._inheritedAttributeMap.entries()) {
|
||||
for (let attr of attrs) {
|
||||
this.inheritAttribute(el, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get _inheritedAttributeMap() {
|
||||
if (!this.__inheritedAttributeMap) {
|
||||
this.__inheritedAttributeMap = new Map();
|
||||
for (let el of this.querySelectorAll("[inherits]")) {
|
||||
this.__inheritedAttributeMap.set(el, el.getAttribute("inherits").split(","));
|
||||
}
|
||||
}
|
||||
return this.__inheritedAttributeMap;
|
||||
static get inheritedAttributes() {
|
||||
return {
|
||||
".popup-notification-icon": "popupid,src=icon,class=iconclass",
|
||||
".popup-notification-origin": "value=origin,tooltiptext=origin",
|
||||
".popup-notification-description": "popupid",
|
||||
".popup-notification-description > span:first-of-type": "text=label,popupid",
|
||||
".popup-notification-description > b:first-of-type": "text=name,popupid",
|
||||
".popup-notification-description > span:nth-of-type(2)": "text=endlabel,popupid",
|
||||
".popup-notification-description > b:last-of-type": "text=secondname,popupid",
|
||||
".popup-notification-description > span:last-of-type": "secondendlabel,popupid",
|
||||
".popup-notification-closebutton": "oncommand=closebuttoncommand,hidden=closebuttonhidden",
|
||||
".popup-notification-learnmore-link": "onclick=learnmoreclick,href=learnmoreurl",
|
||||
".popup-notification-warning": "hidden=warninghidden,text=warninglabel",
|
||||
".popup-notification-button-container > .popup-notification-secondary-button":
|
||||
"oncommand=secondarybuttoncommand,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,hidden=secondarybuttonhidden",
|
||||
".popup-notification-button-container > toolbarseparator": "hidden=dropmarkerhidden",
|
||||
".popup-notification-dropmarker": "onpopupshown=dropmarkerpopupshown,hidden=dropmarkerhidden",
|
||||
".popup-notification-dropmarker > menupopup": "oncommand=menucommand",
|
||||
".popup-notification-primary-button": "oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,default=buttonhighlight,disabled=mainactiondisabled",
|
||||
};
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (!this._hasSlotted || oldValue === newValue) {
|
||||
if (!this._hasSlotted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateAttributes();
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
}
|
||||
|
||||
show() {
|
||||
|
@ -89,33 +60,32 @@ class MozPopupNotification extends MozXULElement {
|
|||
this.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<hbox class="popup-notification-header-container"></hbox>
|
||||
<hbox align="start" class="popup-notification-body-container">
|
||||
<image class="popup-notification-icon"
|
||||
inherits="popupid,src=icon,class=iconclass"/>
|
||||
<image class="popup-notification-icon"/>
|
||||
<vbox flex="1" pack="start" class="popup-notification-body">
|
||||
<hbox align="start">
|
||||
<vbox flex="1">
|
||||
<label class="popup-notification-origin header" inherits="value=origin,tooltiptext=origin" crop="center"></label>
|
||||
<label class="popup-notification-origin header" crop="center"></label>
|
||||
<!-- These need to be on the same line to avoid creating
|
||||
whitespace between them (whitespace is added in the
|
||||
localization file, if necessary). -->
|
||||
<description class="popup-notification-description" inherits="popupid"><html:span inherits="text=label,popupid"></html:span><html:b inherits="text=name,popupid"></html:b><html:span inherits="text=endlabel,popupid"></html:span><html:b inherits="text=secondname,popupid"></html:b><html:span inherits="text=secondendlabel,popupid"></html:span></description>
|
||||
<description class="popup-notification-description"><html:span></html:span><html:b></html:b><html:span></html:span><html:b></html:b><html:span></html:span></description>
|
||||
</vbox>
|
||||
<toolbarbutton class="messageCloseButton close-icon popup-notification-closebutton tabbable" inherits="oncommand=closebuttoncommand,hidden=closebuttonhidden" tooltiptext="&closeNotification.tooltip;"></toolbarbutton>
|
||||
<toolbarbutton class="messageCloseButton close-icon popup-notification-closebutton tabbable" tooltiptext="&closeNotification.tooltip;"></toolbarbutton>
|
||||
</hbox>
|
||||
<label class="text-link popup-notification-learnmore-link" inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</label>
|
||||
<label class="text-link popup-notification-learnmore-link">&learnMore;</label>
|
||||
<checkbox class="popup-notification-checkbox" oncommand="PopupNotifications._onCheckboxCommand(event)"></checkbox>
|
||||
<description class="popup-notification-warning" inherits="hidden=warninghidden,text=warninglabel"></description>
|
||||
<description class="popup-notification-warning"></description>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<hbox class="popup-notification-footer-container"></hbox>
|
||||
<hbox class="popup-notification-button-container panel-footer">
|
||||
<button class="popup-notification-button popup-notification-secondary-button" inherits="oncommand=secondarybuttoncommand,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,hidden=secondarybuttonhidden"></button>
|
||||
<toolbarseparator inherits="hidden=dropmarkerhidden"></toolbarseparator>
|
||||
<button type="menu" class="popup-notification-button popup-notification-dropmarker" aria-label="&moreActionsButton.accessibleLabel;" inherits="onpopupshown=dropmarkerpopupshown,hidden=dropmarkerhidden">
|
||||
<menupopup position="after_end" aria-label="&moreActionsButton.accessibleLabel;" inherits="oncommand=menucommand">
|
||||
<button class="popup-notification-button popup-notification-secondary-button"></button>
|
||||
<toolbarseparator></toolbarseparator>
|
||||
<button type="menu" class="popup-notification-button popup-notification-dropmarker" aria-label="&moreActionsButton.accessibleLabel;">
|
||||
<menupopup position="after_end" aria-label="&moreActionsButton.accessibleLabel;">
|
||||
</menupopup>
|
||||
</button>
|
||||
<button class="popup-notification-button popup-notification-primary-button" label="&defaultButton.label;" accesskey="&defaultButton.accesskey;" inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,default=buttonhighlight,disabled=mainactiondisabled"></button>
|
||||
<button class="popup-notification-button popup-notification-primary-button" label="&defaultButton.label;" accesskey="&defaultButton.accesskey;"></button>
|
||||
</hbox>
|
||||
`, ["chrome://global/locale/notification.dtd"]));
|
||||
|
||||
|
@ -140,7 +110,7 @@ class MozPopupNotification extends MozXULElement {
|
|||
this.appendNotificationContent(popupnotificationcontent);
|
||||
}
|
||||
|
||||
this._updateAttributes();
|
||||
this.initializeAttributeInheritance();
|
||||
}
|
||||
|
||||
appendNotificationContent(el) {
|
||||
|
|
|
@ -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(`
|
||||
<treecolpicker class="treecol-image" fixed="true"></treecolpicker>
|
||||
`));
|
||||
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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче