Bug 1803678 - enable lazy loading of ESModule based moz- custom elements r=reusable-components-reviewers,pip-reviewers,credential-management-reviewers,translations-reviewers,kpatenio,issammani,mstriemer

Differential Revision: https://phabricator.services.mozilla.com/D207445
This commit is contained in:
Hanna Jones
2024-04-23 13:52:27 +00:00
parent 062a4c986b
commit cf0399a238
22 changed files with 56 additions and 112 deletions

View File

@@ -618,7 +618,6 @@ var gXPInstallObserver = {
break; break;
} }
case "addon-install-blocked": { case "addon-install-blocked": {
await window.ensureCustomElements("moz-support-link");
// Dismiss the progress notification. Note that this is bad if // Dismiss the progress notification. Note that this is bad if
// there are multiple simultaneous installs happening, see // there are multiple simultaneous installs happening, see
// bug 1329884 for a longer explanation. // bug 1329884 for a longer explanation.
@@ -1955,8 +1954,6 @@ var gUnifiedExtensions = {
supportPage = null, supportPage = null,
type = "warning", type = "warning",
}) { }) {
window.ensureCustomElements("moz-message-bar");
const messageBar = document.createElement("moz-message-bar"); const messageBar = document.createElement("moz-message-bar");
messageBar.setAttribute("type", type); messageBar.setAttribute("type", type);
messageBar.classList.add("unified-extensions-message-bar"); messageBar.classList.add("unified-extensions-message-bar");
@@ -1964,8 +1961,6 @@ var gUnifiedExtensions = {
messageBar.setAttribute("data-l10n-attrs", "heading, message"); messageBar.setAttribute("data-l10n-attrs", "heading, message");
if (supportPage) { if (supportPage) {
window.ensureCustomElements("moz-support-link");
const supportUrl = document.createElement("a", { const supportUrl = document.createElement("a", {
is: "moz-support-link", is: "moz-support-link",
}); });

View File

@@ -55,7 +55,6 @@ var StarUI = {
delete this.panel; delete this.panel;
this._createPanelIfNeeded(); this._createPanelIfNeeded();
var element = this._element("editBookmarkPanel"); var element = this._element("editBookmarkPanel");
window.ensureCustomElements("moz-button-group");
// initially the panel is hidden // initially the panel is hidden
// to avoid impacting startup / new window performance // to avoid impacting startup / new window performance
element.hidden = false; element.hidden = false;

View File

@@ -177,7 +177,6 @@ var gIdentityHandler = {
_popupInitialized: false, _popupInitialized: false,
_initializePopup() { _initializePopup() {
window.ensureCustomElements("moz-support-link");
if (!this._popupInitialized) { if (!this._popupInitialized) {
let wrapper = document.getElementById("template-identity-popup"); let wrapper = document.getElementById("template-identity-popup");
wrapper.replaceWith(wrapper.content); wrapper.replaceWith(wrapper.content);

View File

@@ -14,9 +14,6 @@ var gPermissionPanel = {
if (!this._popupInitialized) { if (!this._popupInitialized) {
let wrapper = document.getElementById("template-permission-popup"); let wrapper = document.getElementById("template-permission-popup");
wrapper.replaceWith(wrapper.content); wrapper.replaceWith(wrapper.content);
window.ensureCustomElements("moz-support-link");
this._popupInitialized = true; this._popupInitialized = true;
} }
}, },

View File

@@ -1377,7 +1377,6 @@ var gProtectionsHandler = {
let wrapper = document.getElementById("template-protections-popup"); let wrapper = document.getElementById("template-protections-popup");
this._protectionsPopup = wrapper.content.firstElementChild; this._protectionsPopup = wrapper.content.firstElementChild;
wrapper.replaceWith(wrapper.content); wrapper.replaceWith(wrapper.content);
window.ensureCustomElements("moz-support-link");
this.maybeSetMilestoneCounterText(); this.maybeSetMilestoneCounterText();
@@ -1591,8 +1590,6 @@ var gProtectionsHandler = {
// Add an observer to observe that the history has been cleared. // Add an observer to observe that the history has been cleared.
Services.obs.addObserver(this, "browser:purge-session-history"); Services.obs.addObserver(this, "browser:purge-session-history");
window.ensureCustomElements("moz-button-group", "moz-toggle");
}, },
uninit() { uninit() {

View File

@@ -403,7 +403,6 @@ export const ContentAnalysis = {
}, },
async showPanel(element, panelUI) { async showPanel(element, panelUI) {
element.ownerGlobal.ensureCustomElements("moz-support-link");
await element.ownerDocument.l10n.setAttributes( await element.ownerDocument.l10n.setAttributes(
lazy.PanelMultiView.getViewNode( lazy.PanelMultiView.getViewNode(
element.ownerDocument, element.ownerDocument,

View File

@@ -150,7 +150,6 @@ add_setup(async function () {
gLink.innerText = "gLink"; gLink.innerText = "gLink";
gLink.id = "gLink"; gLink.id = "gLink";
gMainView.appendChild(gLink); gMainView.appendChild(gLink);
await window.ensureCustomElements("moz-toggle");
gToggle = document.createElement("moz-toggle"); gToggle = document.createElement("moz-toggle");
gToggle.label = "Test label"; gToggle.label = "Test label";
gMainView.appendChild(gToggle); gMainView.appendChild(gToggle);

View File

@@ -97,8 +97,6 @@ var DownloadsPanel = {
window.addEventListener("unload", this.onWindowUnload); window.addEventListener("unload", this.onWindowUnload);
window.ensureCustomElements("moz-button-group");
// Load and resume active downloads if required. If there are downloads to // Load and resume active downloads if required. If there are downloads to
// be shown in the panel, they will be loaded asynchronously. // be shown in the panel, they will be loaded asynchronously.
DownloadsCommon.initializeAllDataLinks(); DownloadsCommon.initializeAllDataLinks();

View File

@@ -235,8 +235,6 @@ export class ExtensionControlledPopup {
return; return;
} }
win.ownerGlobal.ensureCustomElements("moz-support-link");
// Find the elements we need. // Find the elements we need.
let doc = win.document; let doc = win.document;
let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(doc); let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(doc);

View File

@@ -23,8 +23,6 @@ class ScreenshotsPreview extends HTMLElement {
// we get passed the <browser> as a param via TabDialogBox.open() // we get passed the <browser> as a param via TabDialogBox.open()
this.openerBrowser = window.arguments[0]; this.openerBrowser = window.arguments[0];
window.ensureCustomElements("moz-button");
let [downloadKey, copyKey] = let [downloadKey, copyKey] =
lazy.screenshotsLocalization.formatMessagesSync([ lazy.screenshotsLocalization.formatMessagesSync([
{ id: "screenshots-component-download-key" }, { id: "screenshots-component-download-key" },

View File

@@ -30,8 +30,8 @@ Please refer to the existing [UA widgets documentation](https://firefox-source-d
### How to use existing Mozilla Custom Elements ### How to use existing Mozilla Custom Elements
The existing Mozilla Custom Elements are automatically imported into all chrome privileged documents. The existing Mozilla Custom Elements are either [automatically imported](https://searchfox.org/mozilla-central/rev/d23849dd6d83edbe681d3b4828700256ea34a654/toolkit/content/customElements.js#853-878) into all chrome privileged documents, or are [lazy loaded](https://searchfox.org/mozilla-central/rev/d23849dd6d83edbe681d3b4828700256ea34a654/toolkit/content/customElements.js#789-809) and get automatically imported when first used.
These existing elements do not need to be imported individually via `<script>` tag or by using `window.ensureCustomElements()` when in a privileged main process document. In either case, these existing elements do not need to be imported individually via `<script>` tag.
As long as you are working in a chrome privileged document, you will have access to the existing Mozilla Custom Elements. As long as you are working in a chrome privileged document, you will have access to the existing Mozilla Custom Elements.
You can dynamically create one of the existing custom elements by using `document.createDocument("customElement)` or `document.createXULElement("customElement")` in the relevant JS file, or by using the custom element tag in the relevant XHTML document. You can dynamically create one of the existing custom elements by using `document.createDocument("customElement)` or `document.createXULElement("customElement")` in the relevant JS file, or by using the custom element tag in the relevant XHTML document.
For example, `document.createXULElement("checkbox")` creates an instance of [widgets/checkbox.js](https://searchfox.org/mozilla-central/source/toolkit/content/widgets/checkbox.js) while using `<checkbox>` declares an instance in the XUL document. For example, `document.createXULElement("checkbox")` creates an instance of [widgets/checkbox.js](https://searchfox.org/mozilla-central/source/toolkit/content/widgets/checkbox.js) while using `<checkbox>` declares an instance in the XUL document.
@@ -74,11 +74,10 @@ Just like with the UI widgets, [the `browser_all_files_referenced.js` will fail
### Using new domain-specific widgets ### Using new domain-specific widgets
This is effectively the same as [using new design system components](#using-new-design-system-components). This is effectively the same as [using new design system components](#using-new-design-system-components). In general you should be able to rely on these elements getting lazily loaded at the time of first use, similar to how existing custom elements are imported.
You will need to import your widget into the relevant `html`/`xhtml` files via a `script` tag with `type="module"`:
Outside of chrome privileged documents you may need to import your widget into the relevant `html`/`xhtml` files via a `script` tag with `type="module"`:
```html ```html
<script type="module" src="chrome://browser/content/<domain-directory>/<your-widget>.mjs"></script> <script type="module" src="chrome://browser/content/<domain-directory>/<your-widget>.mjs"></script>
``` ```
Or use `window.ensureCustomElements("<your-widget>")` as previously stated in [the using new design system components section.](#using-new-design-system-components)

View File

@@ -111,17 +111,16 @@ by updating [this array](https://searchfox.org/mozilla-central/rev/5c922d8b93b43
Once you've added a new component to `toolkit/content/widgets` and created Once you've added a new component to `toolkit/content/widgets` and created
`chrome://` URLs via `toolkit/content/jar.mn` you should be able to start using it `chrome://` URLs via `toolkit/content/jar.mn` you should be able to start using it
throughout Firefox. You can import the component into `html`/`xhtml` files via a throughout Firefox. In most cases, you should be able to rely on your custom element getting lazy loaded at the time of first use, provided you are working in a privileged context where `customElements.js` is available.
You can import the component directly into `html`/`xhtml` files via a
`script` tag with `type="module"`: `script` tag with `type="module"`:
```html ```html
<script type="module" src="chrome://global/content/elements/your-component-name.mjs"></script> <script type="module" src="chrome://global/content/elements/your-component-name.mjs"></script>
``` ```
If you are unable to import the new component in html, you can use [`ensureCustomElements()` in customElements.js](https://searchfox.org/mozilla-central/rev/31f5847a4494b3646edabbdd7ea39cb88509afe2/toolkit/content/customElements.js#865) in the relevant JS file. **Note** you will need to add your new widget to [this array in customElements.js](https://searchfox.org/mozilla-central/rev/cde3d4a8d228491e8b7f1bd94c63bbe039850696/toolkit/content/customElements.js#791-810) to ensure it gets lazy loaded on creation.
For example, [we use `window.ensureCustomElements("moz-button-group")` in `browser-siteProtections.js`](https://searchfox.org/mozilla-central/rev/31f5847a4494b3646edabbdd7ea39cb88509afe2/browser/base/content/browser-siteProtections.js#1749).
**Note** you will need to add your new widget to [the switch in importCustomElementFromESModule](https://searchfox.org/mozilla-central/rev/85b4f7363292b272eb9b606e00de2c37a6be73f0/toolkit/content/customElements.js#845-859) for `ensureCustomElements()` to work as expected.
Once [Bug 1803810](https://bugzilla.mozilla.org/show_bug.cgi?id=1803810) lands, this process will be simplified: you won't need to use `ensureCustomElements()` and you will [add your widget to the appropriate array in customElements.js instead of the switch statement](https://searchfox.org/mozilla-central/rev/85b4f7363292b272eb9b606e00de2c37a6be73f0/toolkit/content/customElements.js#818-841).
## Common pitfalls ## Common pitfalls

View File

@@ -1043,8 +1043,6 @@ var FullPageTranslationsPanel = new (class {
isFirstUserInteraction = null, isFirstUserInteraction = null,
} }
) { ) {
await window.ensureCustomElements("moz-button-group");
const { panel, appMenuButton } = this.elements; const { panel, appMenuButton } = this.elements;
const openedFromAppMenu = target.id === appMenuButton.id; const openedFromAppMenu = target.id === appMenuButton.id;
const { docLangTag } = await this.#getCachedDetectedLanguages(); const { docLangTag } = await this.#getCachedDetectedLanguages();
@@ -1109,10 +1107,6 @@ var FullPageTranslationsPanel = new (class {
return; return;
} }
const window =
gBrowser.selectedBrowser.browsingContext.top.embedderElement.ownerGlobal;
window.ensureCustomElements("moz-support-link");
const { button } = this.buttonElements; const { button } = this.buttonElements;
const { requestedTranslationPair } = const { requestedTranslationPair } =

View File

@@ -393,7 +393,14 @@ var SelectTranslationsPanel = new (class {
); );
} }
await this.#openPopup(event, screenX, screenY); await Promise.all([
this.#cachePlaceholderText(),
this.#initializeLanguageMenuLists(langPairPromise),
this.#registerSourceText(sourceText, langPairPromise),
]);
this.#maybeRequestTranslation();
this.#openPopup(event, screenX, screenY);
} }
/** /**
@@ -403,10 +410,7 @@ var SelectTranslationsPanel = new (class {
* @param {number} screenX - The x-axis location of the screen at which to open the popup. * @param {number} screenX - The x-axis location of the screen at which to open the popup.
* @param {number} screenY - The y-axis location of the screen at which to open the popup. * @param {number} screenY - The y-axis location of the screen at which to open the popup.
*/ */
async #openPopup(event, screenX, screenY) { #openPopup(event, screenX, screenY) {
await window.ensureCustomElements("moz-button-group");
await window.ensureCustomElements("moz-message-bar");
this.console?.log("Showing SelectTranslationsPanel"); this.console?.log("Showing SelectTranslationsPanel");
const { panel } = this.elements; const { panel } = this.elements;
panel.openPopupAtScreen(screenX, screenY, /* isContextMenu */ false, event); panel.openPopupAtScreen(screenX, screenY, /* isContextMenu */ false, event);

View File

@@ -339,8 +339,6 @@ export var ExtensionsUI = {
async showPermissionsPrompt(target, strings, icon) { async showPermissionsPrompt(target, strings, icon) {
let { browser, window } = getTabBrowser(target); let { browser, window } = getTabBrowser(target);
await window.ensureCustomElements("moz-support-link");
// Wait for any pending prompts to complete before showing the next one. // Wait for any pending prompts to complete before showing the next one.
let pending; let pending;
while ((pending = this.pendingNotifications.get(browser))) { while ((pending = this.pendingNotifications.get(browser))) {

View File

@@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
support-files = ["dummy_page.html"] support-files = ["dummy_page.html"]
["test_custom_element_ensure_custom_element.html"] ["test_custom_element_auto_import.html"]
["test_custom_element_htmlconstructor_chrome.html"] ["test_custom_element_htmlconstructor_chrome.html"]
support-files = [ support-files = [

View File

@@ -1,11 +1,8 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html lang="en"> <html lang="en">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1813077
-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test for customElements.ensureCustomElement</title> <title>Test for custom element auto import behavior</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script> <script>
@@ -14,36 +11,33 @@
outside of the task that imported them. This can create issues if writing outside of the task that imported them. This can create issues if writing
another test in this file. another test in this file.
*/ */
add_task(async function test_ensure_custom_elements() { add_task(async function test_custom_elements_auto_import() {
let registry = SpecialPowers.wrap(customElements); let registry = SpecialPowers.wrap(customElements);
ok(window.ensureCustomElements, "should be defined");
// Ensure the custom elements from ESModules are not defined. // Ensure the custom elements from ESModules are not defined.
is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it"); is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it");
is(registry.get("moz-support-link"), undefined, "moz-support-link should be undefined since we have not yet imported it"); is(registry.get("moz-support-link"), undefined, "moz-support-link should be undefined since we have not yet imported it");
is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it"); is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it");
// Import a single custom element and assert it exists in the custom // Create a custom element and assert that it gets imported/defined.
// element registry document.createElement("moz-support-link");
let modules = await window.ensureCustomElements("moz-support-link"); ok(registry.get("moz-support-link"), "moz-support-link should be defined after creation");
ok(registry.get("moz-support-link"), "moz-support-link should be defined after importing it");
is(modules, null, "There should not be a return value when using ensureCustomElements");
// Import multiple custom elements and assert they exist in the registry // Create multiple custom elements and assert they exist in the registry.
modules = undefined;
is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it"); is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it");
is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it") is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it")
modules = await window.ensureCustomElements("moz-toggle", "moz-button-group");
is(modules, null, "There should not be a return value when using ensureCustomElements"); document.createElement("moz-button-group");
document.createElement("moz-toggle");
ok(registry.get("moz-toggle"), "moz-toggle should be defined after importing it"); ok(registry.get("moz-toggle"), "moz-toggle should be defined after importing it");
ok(registry.get("moz-button-group"), "moz-button-group should be defined after importing it"); ok(registry.get("moz-button-group"), "moz-button-group should be defined after importing it");
// Ensure there are no errors if the imported elements are imported // Ensure there are no errors if the imported elements are created again.
// again document.createElement("moz-support-link");
modules = undefined; document.createElement("moz-button-group");
modules = await window.ensureCustomElements("moz-support-link", "moz-toggle", "moz-button-group"); document.createElement("moz-toggle");
ok(true, "The custom elements should not throw an error if imported again"); ok(true, "The custom elements should not throw an error if imported again");
is(modules, null, "There should not be a return value when using ensureCustomElements");
}) })
</script> </script>
</head> </head>

View File

@@ -1358,7 +1358,6 @@ export let FormAutofillPrompter = {
); );
const { ownerGlobal: win } = browser; const { ownerGlobal: win } = browser;
await win.ensureCustomElements("moz-support-link");
win.MozXULElement.insertFTLIfNeeded( win.MozXULElement.insertFTLIfNeeded(
"toolkit/formautofill/formAutofill.ftl" "toolkit/formautofill/formAutofill.ftl"
); );

View File

@@ -246,8 +246,6 @@ export var PictureInPicture = {
let panel = browser.ownerDocument.querySelector("#PictureInPicturePanel"); let panel = browser.ownerDocument.querySelector("#PictureInPicturePanel");
if (!panel) { if (!panel) {
browser.ownerGlobal.ensureCustomElements("moz-toggle");
browser.ownerGlobal.ensureCustomElements("moz-support-link");
let template = browser.ownerDocument.querySelector( let template = browser.ownerDocument.querySelector(
"#PictureInPicturePanelTemplate" "#PictureInPicturePanelTemplate"
); );

View File

@@ -806,49 +806,32 @@
"chrome://global/content/elements/autocomplete-input.js", "chrome://global/content/elements/autocomplete-input.js",
], ],
["editor", "chrome://global/content/elements/editor.js"], ["editor", "chrome://global/content/elements/editor.js"],
["moz-button", "chrome://global/content/elements/moz-button.mjs"],
[
"moz-button-group",
"chrome://global/content/elements/moz-button-group.mjs",
],
["moz-card", "chrome://global/content/elements/moz-card.mjs"],
["moz-five-star", "chrome://global/content/elements/moz-five-star.mjs"],
[
"moz-message-bar",
"chrome://global/content/elements/moz-message-bar.mjs",
],
["moz-page-nav", "chrome://global/content/elements/moz-page-nav.mjs"],
[
"moz-support-link",
"chrome://global/content/elements/moz-support-link.mjs",
],
["moz-toggle", "chrome://global/content/elements/moz-toggle.mjs"],
]) { ]) {
customElements.setElementCreationCallback(tag, () => { customElements.setElementCreationCallback(tag, () => {
if (script.endsWith(".mjs")) {
ChromeUtils.importESModule(script, { global: "current" });
} else {
Services.scriptloader.loadSubScript(script, window); Services.scriptloader.loadSubScript(script, window);
}
}); });
} }
// Bug 1813077: This is a workaround until Bug 1803810 lands
// which will give us the ability to load ESMs synchronously
// like the previous Services.scriptloader.loadSubscript() function
function importCustomElementFromESModule(name) {
switch (name) {
case "moz-button":
return import("chrome://global/content/elements/moz-button.mjs");
case "moz-button-group":
return import(
"chrome://global/content/elements/moz-button-group.mjs"
);
case "moz-message-bar":
return import("chrome://global/content/elements/moz-message-bar.mjs");
case "moz-support-link":
return import(
"chrome://global/content/elements/moz-support-link.mjs"
);
case "moz-toggle":
return import("chrome://global/content/elements/moz-toggle.mjs");
case "moz-card":
return import("chrome://global/content/elements/moz-card.mjs");
}
throw new Error(`Unknown custom element name (${name})`);
}
/*
This function explicitly returns null so that there is no confusion
about which custom elements from ES Modules have been loaded.
*/
window.ensureCustomElements = function (...elementNames) {
return Promise.all(
elementNames
.filter(name => !customElements.get(name))
.map(name => importCustomElementFromESModule(name))
)
.then(() => null)
.catch(console.error);
};
// Immediately load the following elements // Immediately load the following elements
for (let script of [ for (let script of [

View File

@@ -621,7 +621,7 @@
customElements.define("notification", MozElements.Notification); customElements.define("notification", MozElements.Notification);
async function createNotificationMessageElement() { async function createNotificationMessageElement() {
await window.ensureCustomElements("moz-message-bar"); document.createElement("moz-message-bar");
let MozMessageBar = await customElements.whenDefined("moz-message-bar"); let MozMessageBar = await customElements.whenDefined("moz-message-bar");
class NotificationMessage extends MozMessageBar { class NotificationMessage extends MozMessageBar {
static queries = { static queries = {
@@ -772,7 +772,6 @@
let buttonElem; let buttonElem;
if (button.hasOwnProperty("supportPage")) { if (button.hasOwnProperty("supportPage")) {
window.ensureCustomElements("moz-support-link");
buttonElem = document.createElement("a", { buttonElem = document.createElement("a", {
is: "moz-support-link", is: "moz-support-link",
}); });

View File

@@ -116,8 +116,6 @@
MozXULElement.insertFTLIfNeeded("toolkit/global/popupnotification.ftl"); MozXULElement.insertFTLIfNeeded("toolkit/global/popupnotification.ftl");
this.appendChild(this.constructor.fragment); this.appendChild(this.constructor.fragment);
window.ensureCustomElements("moz-button-group");
this.button = this.querySelector(".popup-notification-primary-button"); this.button = this.querySelector(".popup-notification-primary-button");
if ( if (
this.hasAttribute("buttonlabel") || this.hasAttribute("buttonlabel") ||