Bug 1590573, create a unified approach for caching fragments in our Custom Elements r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D58866

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Emma Malysz 2020-04-07 23:56:45 +00:00
Родитель 8485fe5358
Коммит b657384896
25 изменённых файлов: 471 добавлений и 373 удалений

Просмотреть файл

@ -8,6 +8,34 @@
// a block to prevent accidentally leaking globals onto `window`. // a block to prevent accidentally leaking globals onto `window`.
{ {
class MozTabbrowserTab extends MozElements.MozTab { class MozTabbrowserTab extends MozElements.MozTab {
static get markup() {
return `
<stack class="tab-stack" flex="1">
<vbox class="tab-background">
<hbox class="tab-line"/>
<spacer flex="1" class="tab-background-inner"/>
<hbox class="tab-bottom-line"/>
</vbox>
<hbox class="tab-loading-burst"/>
<hbox class="tab-content" align="center">
<hbox class="tab-throbber" layer="true"/>
<hbox class="tab-icon-pending"/>
<image class="tab-icon-image" validate="never" role="presentation"/>
<image class="tab-sharing-icon-overlay" role="presentation"/>
<image class="tab-icon-overlay" role="presentation"/>
<hbox class="tab-label-container"
onoverflow="this.setAttribute('textoverflow', 'true');"
onunderflow="this.removeAttribute('textoverflow');"
flex="1">
<label class="tab-text tab-label" role="presentation"/>
</hbox>
<image class="tab-icon-sound" role="presentation"/>
<image class="tab-close-button close-icon" role="presentation"/>
</hbox>
</stack>
`;
}
constructor() { constructor() {
super(); super();
@ -69,37 +97,6 @@
}; };
} }
get fragment() {
if (!this.constructor.hasOwnProperty("_fragment")) {
this.constructor._fragment = MozXULElement.parseXULToFragment(`
<stack class="tab-stack" flex="1">
<vbox class="tab-background">
<hbox class="tab-line"/>
<spacer flex="1" class="tab-background-inner"/>
<hbox class="tab-bottom-line"/>
</vbox>
<hbox class="tab-loading-burst"/>
<hbox class="tab-content" align="center">
<hbox class="tab-throbber" layer="true"/>
<hbox class="tab-icon-pending"/>
<image class="tab-icon-image" validate="never" role="presentation"/>
<image class="tab-sharing-icon-overlay" role="presentation"/>
<image class="tab-icon-overlay" role="presentation"/>
<hbox class="tab-label-container"
onoverflow="this.setAttribute('textoverflow', 'true');"
onunderflow="this.removeAttribute('textoverflow');"
flex="1">
<label class="tab-text tab-label" role="presentation"/>
</hbox>
<image class="tab-icon-sound" role="presentation"/>
<image class="tab-close-button close-icon" role="presentation"/>
</hbox>
</stack>
`);
}
return document.importNode(this.constructor._fragment, true);
}
connectedCallback() { connectedCallback() {
this.initialize(); this.initialize();
} }
@ -110,7 +107,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild(this.fragment); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
this.setAttribute("context", "tabContextMenu"); this.setAttribute("context", "tabContextMenu");
this._initialized = true; this._initialized = true;

Просмотреть файл

@ -941,44 +941,45 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
break; break;
} }
}, },
get markup() {
return `
<vbox class="panel-subview-body">
<hbox id="PanelUI-panic-timeframe">
<image id="PanelUI-panic-timeframe-icon" alt=""/>
<vbox flex="1">
<description data-l10n-id="panic-main-timeframe-desc" id="PanelUI-panic-mainDesc"></description>
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
<radio id="PanelUI-panic-5min" data-l10n-id="panic-button-5min" selected="true"
value="5" class="subviewradio"/>
<radio id="PanelUI-panic-2hr" data-l10n-id="panic-button-2hr"
value="2" class="subviewradio"/>
<radio id="PanelUI-panic-day" data-l10n-id="panic-button-day"
value="6" class="subviewradio"/>
</radiogroup>
</vbox>
</hbox>
<vbox id="PanelUI-panic-explanations">
<label id="PanelUI-panic-actionlist-main-label" data-l10n-id="panic-button-action-desc"></label>
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-tabs-and-windows"></label>
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-cookies"></label>
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-history"></label>
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-open-new-window"></label>
<label id="PanelUI-panic-warning" data-l10n-id="panic-button-undo-warning"></label>
</vbox>
<button id="PanelUI-panic-view-button"
data-l10n-id="panic-button-forget-button"/>
</vbox>
`;
},
onViewShowing(aEvent) { onViewShowing(aEvent) {
let win = aEvent.target.ownerGlobal; let win = aEvent.target.ownerGlobal;
let doc = win.document; let doc = win.document;
let eventBlocker = null; let eventBlocker = null;
if (!doc.querySelector("#PanelUI-panic-timeframe")) { if (!doc.querySelector("#PanelUI-panic-timeframe")) {
win.MozXULElement.insertFTLIfNeeded("browser/panicButton.ftl"); win.MozXULElement.insertFTLIfNeeded("browser/panicButton.ftl");
let frag = win.MozXULElement.parseXULToFragment(` aEvent.target.appendChild(this.constructor.fragment);
<vbox class="panel-subview-body">
<hbox id="PanelUI-panic-timeframe">
<image id="PanelUI-panic-timeframe-icon" alt=""/>
<vbox flex="1">
<description data-l10n-id="panic-main-timeframe-desc" id="PanelUI-panic-mainDesc"></description>
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
<radio id="PanelUI-panic-5min" data-l10n-id="panic-button-5min" selected="true"
value="5" class="subviewradio"/>
<radio id="PanelUI-panic-2hr" data-l10n-id="panic-button-2hr"
value="2" class="subviewradio"/>
<radio id="PanelUI-panic-day" data-l10n-id="panic-button-day"
value="6" class="subviewradio"/>
</radiogroup>
</vbox>
</hbox>
<vbox id="PanelUI-panic-explanations">
<label id="PanelUI-panic-actionlist-main-label" data-l10n-id="panic-button-action-desc"></label>
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-tabs-and-windows"></label>
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-cookies"></label>
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-history"></label>
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-open-new-window"></label>
<label id="PanelUI-panic-warning" data-l10n-id="panic-button-undo-warning"></label>
</vbox>
<button id="PanelUI-panic-view-button"
data-l10n-id="panic-button-forget-button"/>
</vbox>
`);
aEvent.target.appendChild(frag);
eventBlocker = doc.l10n.translateElements([aEvent.target]); eventBlocker = doc.l10n.translateElements([aEvent.target]);
} }

Просмотреть файл

@ -502,6 +502,20 @@ DownloadsSubview.Button = class extends DownloadsViewUI.DownloadElementShell {
} }
} }
static get markup() {
return `
<image class="toolbarbutton-icon" validate="always"/>
<vbox class="toolbarbutton-text" flex="1">
<label crop="end"/>
<label class="status-text status-full" crop="end"/>
<label class="status-text status-open" crop="end"/>
<label class="status-text status-retry" crop="end"/>
<label class="status-text status-show" crop="end"/>
</vbox>
<toolbarbutton class="action-button"/>
`;
}
// DownloadElementShell // DownloadElementShell
connect() { connect() {
let document = this.element.ownerDocument; let document = this.element.ownerDocument;
@ -510,17 +524,9 @@ DownloadsSubview.Button = class extends DownloadsViewUI.DownloadElementShell {
); );
if (!downloadsSubviewItemFragment) { if (!downloadsSubviewItemFragment) {
let MozXULElement = document.defaultView.MozXULElement; let MozXULElement = document.defaultView.MozXULElement;
downloadsSubviewItemFragment = MozXULElement.parseXULToFragment(` downloadsSubviewItemFragment = MozXULElement.parseXULToFragment(
<image class="toolbarbutton-icon" validate="always"/> this.markup
<vbox class="toolbarbutton-text" flex="1"> );
<label crop="end"/>
<label class="status-text status-full" crop="end"/>
<label class="status-text status-open" crop="end"/>
<label class="status-text status-retry" crop="end"/>
<label class="status-text status-show" crop="end"/>
</vbox>
<toolbarbutton class="action-button"/>
`);
gDownloadsSubviewItemFragments.set( gDownloadsSubviewItemFragments.set(
document, document,
downloadsSubviewItemFragment downloadsSubviewItemFragment

Просмотреть файл

@ -180,6 +180,28 @@ DownloadsViewUI.DownloadElementShell.prototype = {
return !!this._active; return !!this._active;
}, },
get markup() {
return `
<hbox class="downloadMainArea" flex="1" align="center">
<stack>
<image class="downloadTypeIcon" validate="always"/>
<image class="downloadBlockedBadge" />
</stack>
<vbox class="downloadContainer" flex="1" pack="center">
<description class="downloadTarget" crop="center"/>
<description class="downloadDetails downloadDetailsNormal"
crop="end"/>
<description class="downloadDetails downloadDetailsHover"
crop="end"/>
<description class="downloadDetails downloadDetailsButtonHover"
crop="end"/>
</vbox>
</hbox>
<toolbarseparator />
<button class="downloadButton"/>
`;
},
connect() { connect() {
let document = this.element.ownerDocument; let document = this.element.ownerDocument;
let downloadListItemFragment = gDownloadListItemFragments.get(document); let downloadListItemFragment = gDownloadListItemFragments.get(document);
@ -189,25 +211,7 @@ DownloadsViewUI.DownloadElementShell.prototype = {
// actions based on the check if originaltarget was not a button. // actions based on the check if originaltarget was not a button.
if (!downloadListItemFragment) { if (!downloadListItemFragment) {
let MozXULElement = document.defaultView.MozXULElement; let MozXULElement = document.defaultView.MozXULElement;
downloadListItemFragment = MozXULElement.parseXULToFragment(` downloadListItemFragment = MozXULElement.parseXULToFragment(this.markup);
<hbox class="downloadMainArea" flex="1" align="center">
<stack>
<image class="downloadTypeIcon" validate="always"/>
<image class="downloadBlockedBadge" />
</stack>
<vbox class="downloadContainer" flex="1" pack="center">
<description class="downloadTarget" crop="center"/>
<description class="downloadDetails downloadDetailsNormal"
crop="end"/>
<description class="downloadDetails downloadDetailsHover"
crop="end"/>
<description class="downloadDetails downloadDetailsButtonHover"
crop="end"/>
</vbox>
</hbox>
<toolbarseparator />
<button class="downloadButton"/>
`);
gDownloadListItemFragments.set(document, downloadListItemFragment); gDownloadListItemFragments.set(document, downloadListItemFragment);
} }
this.element.setAttribute("active", true); this.element.setAttribute("active", true);

Просмотреть файл

@ -3093,32 +3093,40 @@ function getLocalHandlerApp(aFile) {
return localHandlerApp; return localHandlerApp;
} }
// eslint-disable-next-line no-undef
let gHandlerListItemFragment = MozXULElement.parseXULToFragment(`
<richlistitem>
<hbox flex="1" equalsize="always">
<hbox class="typeContainer" flex="1" align="center">
<image class="typeIcon" width="16" height="16"
src="moz-icon://goat?size=16"/>
<label class="typeDescription" flex="1" crop="end"/>
</hbox>
<hbox class="actionContainer" flex="1" align="center">
<image class="actionIcon" width="16" height="16"/>
<label class="actionDescription" flex="1" crop="end"/>
</hbox>
<hbox class="actionsMenuContainer" flex="1">
<menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1">
<menupopup/>
</menulist>
</hbox>
</hbox>
</richlistitem>
`);
/** /**
* This is associated to <richlistitem> elements in the handlers view. * This is associated to <richlistitem> elements in the handlers view.
*/ */
class HandlerListItem { class HandlerListItem {
static get fragment() {
if (!this.hasOwnProperty("_fragment")) {
this._fragment = MozXULElement.parseXULToFragment(this.markup);
}
return document.importNode(this._fragment, true);
}
static get markup() {
return `
<richlistitem>
<hbox flex="1" equalsize="always">
<hbox class="typeContainer" flex="1" align="center">
<image class="typeIcon" width="16" height="16"
src="moz-icon://goat?size=16"/>
<label class="typeDescription" flex="1" crop="end"/>
</hbox>
<hbox class="actionContainer" flex="1" align="center">
<image class="actionIcon" width="16" height="16"/>
<label class="actionDescription" flex="1" crop="end"/>
</hbox>
<hbox class="actionsMenuContainer" flex="1">
<menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1">
<menupopup/>
</menulist>
</hbox>
</hbox>
</richlistitem>
`;
}
static forNode(node) { static forNode(node) {
return gNodeToObjectMap.get(node); return gNodeToObjectMap.get(node);
} }
@ -3139,7 +3147,7 @@ class HandlerListItem {
} }
createNode(list) { createNode(list) {
list.appendChild(document.importNode(gHandlerListItemFragment, true)); list.appendChild(this.constructor.fragment);
this.node = list.lastChild; this.node = list.lastChild;
gNodeToObjectMap.set(this.node, this); gNodeToObjectMap.set(this.node, this);
} }

Просмотреть файл

@ -95,7 +95,7 @@
return this._oneOffButtons; return this._oneOffButtons;
} }
get _markup() { static get markup() {
return ` return `
<hbox class="search-panel-header search-panel-current-engine"> <hbox class="search-panel-header search-panel-current-engine">
<image class="searchbar-engine-image"></image> <image class="searchbar-engine-image"></image>

Просмотреть файл

@ -21,6 +21,25 @@
}; };
} }
static get markup() {
return `
<stringbundle src="chrome://browser/locale/search.properties"></stringbundle>
<hbox class="searchbar-search-button" tooltiptext="&searchIcon.tooltip;">
<image class="searchbar-search-icon"></image>
<image class="searchbar-search-icon-overlay"></image>
</hbox>
<html:input class="searchbar-textbox" is="autocomplete-input" type="search" placeholder="&searchInput.placeholder;" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0"/>
<menupopup class="textbox-contextmenu"></menupopup>
<hbox class="search-go-container">
<image class="search-go-button urlbar-icon" hidden="true" onclick="handleSearchCommand(event);" tooltiptext="&contentSearchSubmit.tooltip;"></image>
</hbox>
`;
}
static get entities() {
return ["chrome://browser/locale/browser.dtd"];
}
constructor() { constructor() {
super(); super();
@ -44,22 +63,6 @@
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]), QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
}; };
this.content = MozXULElement.parseXULToFragment(
`
<stringbundle src="chrome://browser/locale/search.properties"></stringbundle>
<hbox class="searchbar-search-button" tooltiptext="&searchIcon.tooltip;">
<image class="searchbar-search-icon"></image>
<image class="searchbar-search-icon-overlay"></image>
</hbox>
<html:input class="searchbar-textbox" is="autocomplete-input" type="search" placeholder="&searchInput.placeholder;" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0"/>
<menupopup class="textbox-contextmenu"></menupopup>
<hbox class="search-go-container">
<image class="search-go-button urlbar-icon" hidden="true" onclick="handleSearchCommand(event);" tooltiptext="&contentSearchSubmit.tooltip;"></image>
</hbox>
`,
["chrome://browser/locale/browser.dtd"]
);
this._ignoreFocus = false; this._ignoreFocus = false;
this._engines = null; this._engines = null;
} }
@ -70,7 +73,7 @@
return; return;
} }
this.appendChild(document.importNode(this.content, true)); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
// Don't go further if in Customize mode. // Don't go further if in Customize mode.

Просмотреть файл

@ -5,10 +5,8 @@
"use strict"; "use strict";
class MozTranslationNotification extends MozElements.Notification { class MozTranslationNotification extends MozElements.Notification {
connectedCallback() { static get markup() {
this.appendChild( return `
MozXULElement.parseXULToFragment(
`
<hbox anonid="details" align="center" flex="1"> <hbox anonid="details" align="center" flex="1">
<image class="translate-infobar-element messageImage"/> <image class="translate-infobar-element messageImage"/>
<panel anonid="welcomePanel" class="translation-welcome-panel" type="arrow" align="start"> <panel anonid="welcomePanel" class="translation-welcome-panel" type="arrow" align="start">
@ -82,13 +80,18 @@ class MozTranslationNotification extends MozElements.Notification {
class="messageCloseButton close-icon tabbable" class="messageCloseButton close-icon tabbable"
tooltiptext="&closeNotification.tooltip;" tooltiptext="&closeNotification.tooltip;"
oncommand="this.parentNode.closeCommand();"/> oncommand="this.parentNode.closeCommand();"/>
`, `;
[ }
"chrome://global/locale/notification.dtd",
"chrome://browser/locale/translation.dtd", static get entities() {
] return [
) "chrome://global/locale/notification.dtd",
); "chrome://browser/locale/translation.dtd",
];
}
connectedCallback() {
this.appendChild(this.constructor.fragment);
for (let [propertyName, selector] of [ for (let [propertyName, selector] of [
["details", "[anonid=details]"], ["details", "[anonid=details]"],

Просмотреть файл

@ -81,15 +81,8 @@
} }
MozElements.MozAutocompleteProfileListitem = class MozAutocompleteProfileListitem extends MozAutocompleteProfileListitemBase { MozElements.MozAutocompleteProfileListitem = class MozAutocompleteProfileListitem extends MozAutocompleteProfileListitemBase {
connectedCallback() { static get markup() {
if (this.delayConnectedCallback()) { return `
return;
}
this.textContent = "";
this.appendChild(
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box"> <div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box">
<div class="profile-label-col profile-item-col"> <div class="profile-label-col profile-item-col">
<span class="profile-label-affix"></span> <span class="profile-label-affix"></span>
@ -99,8 +92,17 @@
<span class="profile-comment"></span> <span class="profile-comment"></span>
</div> </div>
</div> </div>
`) `;
); }
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this._itemBox = this.querySelector(".autofill-item-box"); this._itemBox = this.querySelector(".autofill-item-box");
this._labelAffix = this.querySelector(".profile-label-affix"); this._labelAffix = this.querySelector(".profile-label-affix");
@ -158,6 +160,15 @@
); );
class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase { class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-warning"></div>
<div class="autofill-footer-row autofill-button"></div>
</div>
`;
}
constructor() { constructor() {
super(); super();
@ -180,14 +191,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-warning"></div>
<div class="autofill-footer-row autofill-button"></div>
</div>
`)
);
this._itemBox = this.querySelector(".autofill-footer"); this._itemBox = this.querySelector(".autofill-footer");
this._optionButton = this.querySelector(".autofill-button"); this._optionButton = this.querySelector(".autofill-button");
@ -312,16 +316,18 @@
); );
class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase { class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-insecure-item"></div>
`;
}
connectedCallback() { connectedCallback() {
if (this.delayConnectedCallback()) { if (this.delayConnectedCallback()) {
return; return;
} }
this.textContent = ""; this.textContent = "";
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-insecure-item"></div>
`)
);
this._itemBox = this.querySelector(".autofill-insecure-item"); this._itemBox = this.querySelector(".autofill-insecure-item");
@ -353,6 +359,14 @@
); );
class MozAutocompleteProfileListitemClearButton extends MozAutocompleteProfileListitemBase { class MozAutocompleteProfileListitemClearButton extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-button"></div>
</div>
`;
}
constructor() { constructor() {
super(); super();
@ -371,13 +385,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-button"></div>
</div>
`)
);
this._itemBox = this.querySelector(".autofill-item-box"); this._itemBox = this.querySelector(".autofill-item-box");
this._clearBtn = this.querySelector(".autofill-button"); this._clearBtn = this.querySelector(".autofill-button");

Просмотреть файл

@ -10,18 +10,8 @@
customElements.define( customElements.define(
"printpreview-toolbar", "printpreview-toolbar",
class PrintPreviewToolbar extends MozXULElement { class PrintPreviewToolbar extends MozXULElement {
constructor() { static get markup() {
super(); return `
this.disconnectedCallback = this.disconnectedCallback.bind(this);
}
connectedCallback() {
window.addEventListener("unload", this.disconnectedCallback, {
once: true,
});
MozXULElement.insertFTLIfNeeded("toolkit/printing/printPreview.ftl");
this.appendChild(
MozXULElement.parseXULToFragment(`
<button id="print-preview-print" oncommand="this.parentNode.print();" data-l10n-id="printpreview-print"/> <button id="print-preview-print" oncommand="this.parentNode.print();" data-l10n-id="printpreview-print"/>
<button id="print-preview-pageSetup" oncommand="this.parentNode.doPageSetup();" data-l10n-id="printpreview-page-setup"/> <button id="print-preview-pageSetup" oncommand="this.parentNode.doPageSetup();" data-l10n-id="printpreview-page-setup"/>
<vbox align="center" pack="center"> <vbox align="center" pack="center">
@ -71,8 +61,19 @@ customElements.define(
<toolbarseparator class="toolbarseparator-primary"/> <toolbarseparator class="toolbarseparator-primary"/>
<button id="print-preview-toolbar-close-button" oncommand="PrintUtils.exitPrintPreview();" data-l10n-id="printpreview-close"/> <button id="print-preview-toolbar-close-button" oncommand="PrintUtils.exitPrintPreview();" data-l10n-id="printpreview-close"/>
<data id="print-preview-custom-scale-prompt-title" data-l10n-id="printpreview-custom-scale-prompt-title"/> <data id="print-preview-custom-scale-prompt-title" data-l10n-id="printpreview-custom-scale-prompt-title"/>
`) `;
); }
constructor() {
super();
this.disconnectedCallback = this.disconnectedCallback.bind(this);
}
connectedCallback() {
window.addEventListener("unload", this.disconnectedCallback, {
once: true,
});
MozXULElement.insertFTLIfNeeded("toolkit/printing/printPreview.ftl");
this.appendChild(this.constructor.fragment);
this.mPrintButton = document.getElementById("print-preview-print"); this.mPrintButton = document.getElementById("print-preview-print");

Просмотреть файл

@ -469,6 +469,48 @@
} }
} }
/**
* Used by custom elements for caching fragments. We now would be
* caching once per class while also supporting subclasses.
*
* If available, returns the cached fragment.
* Otherwise, creates it.
*
* Example:
*
* class ElementA extends MozXULElement {
* static get markup() {
* return `<hbox class="example"`;
* }
*
* static get entities() {
* // Optional field for parseXULToFragment
* return `["chrome://global/locale/notification.dtd"]`;
* }
*
* connectedCallback() {
* this.appendChild(this.constructor.fragment);
* }
* }
*
* @return {importedNode} The imported node that has not been
* inserted into document tree.
*/
static get fragment() {
if (!this.hasOwnProperty("_fragment")) {
let markup = this.markup;
if (markup) {
this._fragment = MozXULElement.parseXULToFragment(
markup,
this.entities
);
} else {
throw new Error("Markup is null");
}
}
return document.importNode(this._fragment, true);
}
/** /**
* Allows eager deterministic construction of XUL elements with XBL attached, by * Allows eager deterministic construction of XUL elements with XBL attached, by
* parsing an element tree and returning a DOM fragment to be inserted in the * parsing an element tree and returning a DOM fragment to be inserted in the

Просмотреть файл

@ -25,7 +25,7 @@
this.setAttribute("consumeoutsideclicks", "never"); this.setAttribute("consumeoutsideclicks", "never");
this.textContent = ""; this.textContent = "";
this.appendChild(MozXULElement.parseXULToFragment(this._markup)); this.appendChild(this.constructor.fragment);
/** /**
* This is the default number of rows that we give the autocomplete * This is the default number of rows that we give the autocomplete
@ -108,7 +108,7 @@
return this._richlistbox; return this._richlistbox;
} }
get _markup() { static get markup() {
return ` return `
<richlistbox class="autocomplete-richlistbox" flex="1"/> <richlistbox class="autocomplete-richlistbox" flex="1"/>
`; `;

Просмотреть файл

@ -62,7 +62,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild(MozXULElement.parseXULToFragment(this._markup)); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
this._boundaryCutoff = null; this._boundaryCutoff = null;
@ -83,7 +83,7 @@
}; };
} }
get _markup() { static get markup() {
return ` return `
<image class="ac-type-icon"/> <image class="ac-type-icon"/>
<image class="ac-site-icon"/> <image class="ac-site-icon"/>
@ -553,7 +553,7 @@
}; };
} }
get _markup() { static get markup() {
return ` return `
<image class="ac-type-icon"/> <image class="ac-type-icon"/>
<image class="ac-site-icon"/> <image class="ac-site-icon"/>
@ -624,7 +624,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild(MozXULElement.parseXULToFragment(this._markup)); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
this._adjustAcItem(); this._adjustAcItem();
} }
@ -638,7 +638,7 @@
}; };
} }
get _markup() { static get markup() {
return ` return `
<div xmlns="http://www.w3.org/1999/xhtml" <div xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"

Просмотреть файл

@ -8,6 +8,16 @@
// leaking to window scope. // leaking to window scope.
{ {
class MozCheckbox extends MozElements.BaseText { class MozCheckbox extends MozElements.BaseText {
static get markup() {
return `
<image class="checkbox-check"/>
<hbox class="checkbox-label-box" flex="1">
<image class="checkbox-icon"/>
<label class="checkbox-label" flex="1"/>
</hbox>
`;
}
constructor() { constructor() {
super(); super();
@ -41,23 +51,8 @@
return; return;
} }
if (!MozCheckbox.contentFragment) {
let content = `
<image class="checkbox-check"/>
<hbox class="checkbox-label-box" flex="1">
<image class="checkbox-icon"/>
<label class="checkbox-label" flex="1"/>
</hbox>
`;
MozCheckbox.contentFragment = MozXULElement.parseXULToFragment(content);
}
this.textContent = ""; this.textContent = "";
let fragment = this.ownerDocument.importNode( this.appendChild(this.constructor.fragment);
MozCheckbox.contentFragment,
true
);
this.appendChild(fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
} }

Просмотреть файл

@ -38,28 +38,9 @@
]); ]);
const TOPIC_MAC_APP_ACTIVATE = "mac_app_activate"; const TOPIC_MAC_APP_ACTIVATE = "mac_app_activate";
class MozFindbar extends XULElement { class MozFindbar extends MozXULElement {
constructor() { static get markup() {
super(); return `
MozXULElement.insertFTLIfNeeded("toolkit/main-window/findbar.ftl");
this.destroy = this.destroy.bind(this);
// We have to guard against `this.close` being |null| due to an unknown
// issue, which is tracked in bug 957999.
this.addEventListener(
"keypress",
event => {
if (event.keyCode == event.DOM_VK_ESCAPE) {
if (this.close) {
this.close();
}
event.preventDefault();
}
},
true
);
this.content = MozXULElement.parseXULToFragment(`
<hbox anonid="findbar-container" class="findbar-container" flex="1" align="center"> <hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
<hbox anonid="findbar-textbox-wrapper" align="stretch"> <hbox anonid="findbar-textbox-wrapper" align="stretch">
<html:input anonid="findbar-textbox" class="findbar-textbox findbar-find-fast" /> <html:input anonid="findbar-textbox" class="findbar-textbox findbar-find-fast" />
@ -86,7 +67,28 @@
</hbox> </hbox>
<toolbarbutton anonid="find-closebutton" class="findbar-closebutton close-icon" <toolbarbutton anonid="find-closebutton" class="findbar-closebutton close-icon"
data-l10n-id="findbar-find-button-close" oncommand="close();" /> data-l10n-id="findbar-find-button-close" oncommand="close();" />
`); `;
}
constructor() {
super();
MozXULElement.insertFTLIfNeeded("toolkit/main-window/findbar.ftl");
this.destroy = this.destroy.bind(this);
// We have to guard against `this.close` being |null| due to an unknown
// issue, which is tracked in bug 957999.
this.addEventListener(
"keypress",
event => {
if (event.keyCode == event.DOM_VK_ESCAPE) {
if (this.close) {
this.close();
}
event.preventDefault();
}
},
true
);
} }
connectedCallback() { connectedCallback() {
@ -95,7 +97,7 @@
// findbar into a new window). // findbar into a new window).
this.setAttribute("noanim", "true"); this.setAttribute("noanim", "true");
this.hidden = true; this.hidden = true;
this.appendChild(document.importNode(this.content, true)); this.appendChild(this.constructor.fragment);
/** /**
* Please keep in sync with toolkit/modules/FindBarContent.jsm * Please keep in sync with toolkit/modules/FindBarContent.jsm

Просмотреть файл

@ -348,17 +348,8 @@
}); });
MozElements.Notification = class Notification extends MozXULElement { MozElements.Notification = class Notification extends MozXULElement {
constructor() { static get markup() {
super(); return `
this.persistence = 0;
this.priority = 0;
this.timeout = 0;
}
connectedCallback() {
this.appendChild(
MozXULElement.parseXULToFragment(
`
<hbox class="messageDetails" align="center" flex="1" <hbox class="messageDetails" align="center" flex="1"
oncommand="this.parentNode._doButtonCommand(event);"> oncommand="this.parentNode._doButtonCommand(event);">
<image class="messageImage"/> <image class="messageImage"/>
@ -369,10 +360,22 @@
class="messageCloseButton close-icon tabbable" class="messageCloseButton close-icon tabbable"
tooltiptext="&closeNotification.tooltip;" tooltiptext="&closeNotification.tooltip;"
oncommand="this.parentNode.dismiss();"/> oncommand="this.parentNode.dismiss();"/>
`, `;
["chrome://global/locale/notification.dtd"] }
)
); static get entities() {
return ["chrome://global/locale/notification.dtd"];
}
constructor() {
super();
this.persistence = 0;
this.priority = 0;
this.timeout = 0;
}
connectedCallback() {
this.appendChild(this.constructor.fragment);
for (let [propertyName, selector] of [ for (let [propertyName, selector] of [
["messageDetails", ".messageDetails"], ["messageDetails", ".messageDetails"],

Просмотреть файл

@ -64,14 +64,8 @@
this.hidden = false; this.hidden = false;
} }
slotContents() { static get markup() {
if (this._hasSlotted) { return `
return;
}
this._hasSlotted = true;
this.appendChild(
MozXULElement.parseXULToFragment(
`
<hbox class="popup-notification-header-container"></hbox> <hbox class="popup-notification-header-container"></hbox>
<hbox align="start" class="popup-notification-body-container"> <hbox align="start" class="popup-notification-body-container">
<image class="popup-notification-icon"/> <image class="popup-notification-icon"/>
@ -101,10 +95,19 @@
</button> </button>
<button class="popup-notification-button popup-notification-primary-button" label="&defaultButton.label;" accesskey="&defaultButton.accesskey;"></button> <button class="popup-notification-button popup-notification-primary-button" label="&defaultButton.label;" accesskey="&defaultButton.accesskey;"></button>
</hbox> </hbox>
`, `;
["chrome://global/locale/notification.dtd"] }
)
); static get entities() {
return ["chrome://global/locale/notification.dtd"];
}
slotContents() {
if (this._hasSlotted) {
return;
}
this._hasSlotted = true;
this.appendChild(this.constructor.fragment);
this.button = this.querySelector(".popup-notification-primary-button"); this.button = this.querySelector(".popup-notification-primary-button");
this.secondaryButton = this.querySelector( this.secondaryButton = this.querySelector(

Просмотреть файл

@ -466,21 +466,17 @@
customElements.define("radiogroup", MozRadiogroup); customElements.define("radiogroup", MozRadiogroup);
let gRadioFrag = null;
function getRadioFragment() {
if (!gRadioFrag) {
gRadioFrag = MozXULElement.parseXULToFragment(`
<image class="radio-check"></image>
<hbox class="radio-label-box" align="center" flex="1">
<image class="radio-icon"></image>
<label class="radio-label" flex="1"></label>
</hbox>
`);
}
return document.importNode(gRadioFrag, true);
}
class MozRadio extends MozElements.BaseText { class MozRadio extends MozElements.BaseText {
static get markup() {
return `
<image class="radio-check"></image>
<hbox class="radio-label-box" align="center" flex="1">
<image class="radio-icon"></image>
<label class="radio-label" flex="1"></label>
</hbox>
`;
}
static get inheritedAttributes() { static get inheritedAttributes() {
return { return {
".radio-check": "disabled,selected", ".radio-check": "disabled,selected",
@ -513,7 +509,7 @@
this.connectedOnce = true; this.connectedOnce = true;
// If the caller didn't provide custom content then append the default: // If the caller didn't provide custom content then append the default:
if (!this.firstElementChild) { if (!this.firstElementChild) {
this.appendChild(getRadioFragment()); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
} }
} }

Просмотреть файл

@ -59,6 +59,17 @@
}; };
} }
static get markup() {
// TODO: Bug 1534799 - Convert string to Fluent and use manual DOM construction
return `
<image class="textbox-search-clear" label="&searchTextBox.clear.label;"/>
`;
}
static get entities() {
return ["chrome://global/locale/textcontext.dtd"];
}
connectedCallback() { connectedCallback() {
if (this.delayConnectedCallback()) { if (this.delayConnectedCallback()) {
return; return;
@ -84,13 +95,7 @@
searchBtn.className = "textbox-search-icon"; searchBtn.className = "textbox-search-icon";
searchBtn.addEventListener("click", e => this._iconClick(e)); searchBtn.addEventListener("click", e => this._iconClick(e));
// TODO: Bug 1534799 - Convert string to Fluent and use manual DOM construction let clearBtn = this.constructor.fragment;
let clearBtn = MozXULElement.parseXULToFragment(
`
<image class="textbox-search-clear" label="&searchTextBox.clear.label;"/>
`,
["chrome://global/locale/textcontext.dtd"]
);
clearBtn = this._searchClearIcon = clearBtn.querySelector( clearBtn = this._searchClearIcon = clearBtn.querySelector(
".textbox-search-clear" ".textbox-search-clear"
); );

Просмотреть файл

@ -272,6 +272,15 @@
customElements.define("tabpanels", MozTabpanels); customElements.define("tabpanels", MozTabpanels);
MozElements.MozTab = class MozTab extends MozElements.BaseText { MozElements.MozTab = class MozTab extends MozElements.BaseText {
static get markup() {
return `
<hbox class="tab-middle box-inherit" flex="1">
<image class="tab-icon" role="presentation"></image>
<label class="tab-text" flex="1" role="presentation"></label>
</hbox>
`;
}
constructor() { constructor() {
super(); super();
@ -289,22 +298,10 @@
}; };
} }
get fragment() {
if (!this._fragment) {
this._fragment = MozXULElement.parseXULToFragment(`
<hbox class="tab-middle box-inherit" flex="1">
<image class="tab-icon" role="presentation"></image>
<label class="tab-text" flex="1" role="presentation"></label>
</hbox>
`);
}
return this.ownerDocument.importNode(this._fragment, true);
}
connectedCallback() { connectedCallback() {
if (!this._initialized) { if (!this._initialized) {
this.textContent = ""; this.textContent = "";
this.appendChild(this.fragment); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
this._initialized = true; this._initialized = true;
} }

Просмотреть файл

@ -198,6 +198,19 @@
customElements.define("treechildren", MozTreeChildren); customElements.define("treechildren", MozTreeChildren);
class MozTreecolPicker extends MozElements.BaseControl { class MozTreecolPicker extends MozElements.BaseControl {
static get entities() {
return ["chrome://global/locale/tree.dtd"];
}
static get markup() {
return `
<image class="tree-columnpicker-icon"></image>
<menupopup anonid="popup">
<menuseparator anonid="menuseparator"></menuseparator>
<menuitem anonid="menuitem" label="&restoreColumnOrder.label;"></menuitem>
</menupopup>
`;
}
constructor() { constructor() {
super(); super();
@ -235,18 +248,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(
`
<image class="tree-columnpicker-icon"></image>
<menupopup anonid="popup">
<menuseparator anonid="menuseparator"></menuseparator>
<menuitem anonid="menuitem" label="&restoreColumnOrder.label;"></menuitem>
</menupopup>
`,
["chrome://global/locale/tree.dtd"]
)
);
} }
buildPopup(aPopup) { buildPopup(aPopup) {
@ -308,11 +310,11 @@
}; };
} }
get content() { static get markup() {
return MozXULElement.parseXULToFragment(` return `
<label class="treecol-text" flex="1" crop="right"></label> <label class="treecol-text" flex="1" crop="right"></label>
<image class="treecol-sortdirection"></image> <image class="treecol-sortdirection"></image>
`); `;
} }
constructor() { constructor() {
@ -373,7 +375,7 @@
} }
this.textContent = ""; this.textContent = "";
this.appendChild(this.content); this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
if (this.hasAttribute("ordinal")) { if (this.hasAttribute("ordinal")) {
this.style.MozBoxOrdinalGroup = this.getAttribute("ordinal"); this.style.MozBoxOrdinalGroup = this.getAttribute("ordinal");
@ -559,6 +561,12 @@
}; };
} }
static get markup() {
return `
<treecolpicker class="treecol-image" fixed="true"></treecolpicker>
`;
}
connectedCallback() { connectedCallback() {
if (this.delayConnectedCallback()) { if (this.delayConnectedCallback()) {
return; return;
@ -567,11 +575,7 @@
this.setAttribute("slot", "treecols"); this.setAttribute("slot", "treecols");
if (!this.querySelector("treecolpicker")) { if (!this.querySelector("treecolpicker")) {
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(`
<treecolpicker class="treecol-image" fixed="true"></treecolpicker>
`)
);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
} }
@ -590,6 +594,28 @@
class MozTree extends MozElements.BaseControlMixin( class MozTree extends MozElements.BaseControlMixin(
MozElements.MozElementMixin(XULTreeElement) MozElements.MozElementMixin(XULTreeElement)
) { ) {
static get markup() {
return `
<html:link rel="stylesheet" href="chrome://global/content/widgets.css" />
<html:slot name="treecols"></html:slot>
<stack class="tree-stack" flex="1">
<hbox class="tree-rows" flex="1">
<hbox flex="1" class="tree-bodybox">
<html:slot name="treechildren"></html:slot>
</hbox>
<scrollbar height="0" minwidth="0" minheight="0" orient="vertical"
class="hidevscroll-scrollbar scrollbar-topmost"
></scrollbar>
</hbox>
<html:input class="tree-input" type="text" hidden="true"/>
</stack>
<hbox class="hidehscroll-box">
<scrollbar orient="horizontal" flex="1" increment="16" class="scrollbar-topmost" ></scrollbar>
<scrollcorner class="hidevscroll-scrollcorner"></scrollcorner>
</hbox>
`;
}
constructor() { constructor() {
super(); super();
@ -599,26 +625,9 @@
this.NATURAL_ORDER = 1; // The original order, which is the DOM ordering this.NATURAL_ORDER = 1; // The original order, which is the DOM ordering
this.attachShadow({ mode: "open" }); this.attachShadow({ mode: "open" });
let fragment = MozXULElement.parseXULToFragment(` let handledElements = this.constructor.fragment.querySelectorAll(
<html:link rel="stylesheet" href="chrome://global/content/widgets.css" /> "scrollbar,scrollcorner"
<html:slot name="treecols"></html:slot> );
<stack class="tree-stack" flex="1">
<hbox class="tree-rows" flex="1">
<hbox flex="1" class="tree-bodybox">
<html:slot name="treechildren"></html:slot>
</hbox>
<scrollbar height="0" minwidth="0" minheight="0" orient="vertical"
class="hidevscroll-scrollbar scrollbar-topmost"
></scrollbar>
</hbox>
<html:input class="tree-input" type="text" hidden="true"/>
</stack>
<hbox class="hidehscroll-box">
<scrollbar orient="horizontal" flex="1" increment="16" class="scrollbar-topmost" ></scrollbar>
<scrollcorner class="hidevscroll-scrollcorner"></scrollcorner>
</hbox>
`);
let handledElements = fragment.querySelectorAll("scrollbar,scrollcorner");
let stopAndPrevent = e => { let stopAndPrevent = e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -630,7 +639,7 @@
el.addEventListener("dblclick", stopProp); el.addEventListener("dblclick", stopProp);
el.addEventListener("command", stopProp); el.addEventListener("command", stopProp);
} }
this.shadowRoot.appendChild(fragment); this.shadowRoot.appendChild(this.constructor.fragment);
} }
static get inheritedAttributes() { static get inheritedAttributes() {

Просмотреть файл

@ -522,7 +522,7 @@
this._wizard = this.getRootNode().host; this._wizard = this.getRootNode().host;
this.textContent = ""; this.textContent = "";
this.appendChild(MozXULElement.parseXULToFragment(this._markup)); this.appendChild(this.constructor.fragment);
MozXULElement.insertFTLIfNeeded("toolkit/global/wizard.ftl"); MozXULElement.insertFTLIfNeeded("toolkit/global/wizard.ftl");
@ -556,7 +556,7 @@
: null; : null;
} }
get _markup() { static get _markup() {
if (AppConstants.platform == "macosx") { if (AppConstants.platform == "macosx") {
return ` return `
<vbox flex="1"> <vbox flex="1">

Просмотреть файл

@ -2170,20 +2170,31 @@ class FiveStarRating extends HTMLElement {
customElements.define("five-star-rating", FiveStarRating); customElements.define("five-star-rating", FiveStarRating);
class ContentSelectDropdown extends HTMLElement { class ContentSelectDropdown extends HTMLElement {
static get markup() {
return `
<menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
<menupopup rolluponmousewheel="true" activateontab="true"
position="after_start" level="parent"/>
</menulist>
`;
}
static get fragment() {
if (!this.constructor.hasOwnProperty("_fragment")) {
this.constructor._fragment = MozXULElement.parseXULToFragment(
this.constructor.markup
);
}
return document.importNode(this.constructor._fragment, true);
}
connectedCallback() { connectedCallback() {
if (this.children.length) { if (this.children.length) {
return; return;
} }
// This creates the menulist and menupopup elements needed for the inline // This creates the menulist and menupopup elements needed for the inline
// browser to support <select> elements and context menus. // browser to support <select> elements and context menus.
this.appendChild( this.appendChild(this.constructor.fragment);
MozXULElement.parseXULToFragment(`
<menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
<menupopup rolluponmousewheel="true" activateontab="true"
position="after_start" level="parent"/>
</menulist>
`)
);
} }
} }
customElements.define("content-select-dropdown", ContentSelectDropdown); customElements.define("content-select-dropdown", ContentSelectDropdown);

Просмотреть файл

@ -19,6 +19,17 @@
); );
class AddonAbuseReportsXULFrame extends MozXULElement { class AddonAbuseReportsXULFrame extends MozXULElement {
static get markup() {
return `
<browser id="abuse-report-xulframe-overlay-inner"
type="content"
disablehistory="true"
transparent="true"
flex="1">
</browser>
`;
}
constructor() { constructor() {
super(); super();
this.report = null; this.report = null;
@ -30,16 +41,7 @@
connectedCallback() { connectedCallback() {
this.textContent = ""; this.textContent = "";
const content = MozXULElement.parseXULToFragment(` this.appendChild(this.constructor.fragment);
<browser id="abuse-report-xulframe-overlay-inner"
type="content"
disablehistory="true"
transparent="true"
flex="1">
</browser>
`);
this.appendChild(content);
const browser = this.querySelector("browser"); const browser = this.querySelector("browser");
this.promiseBrowserLoaded = new Promise(resolve => { this.promiseBrowserLoaded = new Promise(resolve => {

Просмотреть файл

@ -37,19 +37,21 @@ const { PrivateBrowsingUtils } = ChromeUtils.import(
); );
class MozHandler extends window.MozElements.MozRichlistitem { class MozHandler extends window.MozElements.MozRichlistitem {
static get markup() {
return `
<vbox pack="center">
<image height="32" width="32"/>
</vbox>
<vbox flex="1">
<label class="name"/>
<label class="description"/>
</vbox>
`;
}
connectedCallback() { connectedCallback() {
this.textContent = ""; this.textContent = "";
this.appendChild( this.appendChild(this.constructor.fragment);
window.MozXULElement.parseXULToFragment(`
<vbox pack="center">
<image height="32" width="32"/>
</vbox>
<vbox flex="1">
<label class="name"/>
<label class="description"/>
</vbox>
`)
);
this.initializeAttributeInheritance(); this.initializeAttributeInheritance();
} }