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-24 19:16:46 +00:00
Родитель ccc7e6a6a1
Коммит 36a699b418
23 изменённых файлов: 52 добавлений и 114 удалений

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

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

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

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

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

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

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

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

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

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

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

@ -24,6 +24,7 @@ Test the confirmation-dialog component
</pre>
<script>
/** Test the confirmation-dialog component **/
let isWin = navigator.platform.includes("Win");
let options = {
title: "confirm-delete-dialog-title",
@ -65,7 +66,7 @@ add_task(async function test_initial_focus() {
add_task(async function test_tab_focus() {
gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("TAB");
synthesizeKey("VK_TAB", { shiftKey: !isWin });
is(gConfirmationDialog.shadowRoot.activeElement, cancelButton,
"After opening the dialog and tabbing once, the cancel button should be focused");
gConfirmationDialog.hide();
@ -86,7 +87,7 @@ add_task(async function test_enter_key_to_cancel() {
add_task(async function test_enter_key_to_confirm() {
let showPromise = gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("TAB");
synthesizeKey("VK_TAB", { shiftKey: !isWin });
sendKey("RETURN");
try {
await showPromise;

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

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

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

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

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

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

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

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

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

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

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

@ -30,8 +30,8 @@ Please refer to the existing [UA widgets documentation](https://firefox-source-d
### How to use existing Mozilla Custom Elements
The existing Mozilla Custom Elements are automatically imported into all chrome privileged documents.
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.
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.
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.
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.
@ -74,11 +74,10 @@ Just like with the UI widgets, [the `browser_all_files_referenced.js` will fail
### Using new domain-specific widgets
This is effectively the same as [using new design system components](#using-new-design-system-components).
You will need to import your widget into the relevant `html`/`xhtml` files via a `script` tag with `type="module"`:
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.
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
<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)

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

@ -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
`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"`:
```html
<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.
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).
**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.
## Common pitfalls

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

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

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

@ -393,7 +393,7 @@ var SelectTranslationsPanel = new (class {
);
}
await this.#openPopup(event, screenX, screenY);
this.#openPopup(event, screenX, screenY);
}
/**
@ -403,10 +403,7 @@ var SelectTranslationsPanel = new (class {
* @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.
*/
async #openPopup(event, screenX, screenY) {
await window.ensureCustomElements("moz-button-group");
await window.ensureCustomElements("moz-message-bar");
#openPopup(event, screenX, screenY) {
this.console?.log("Showing SelectTranslationsPanel");
const { panel } = this.elements;
panel.openPopupAtScreen(screenX, screenY, /* isContextMenu */ false, event);

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

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

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

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

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

@ -1,11 +1,8 @@
<!DOCTYPE HTML>
<html lang="en">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1813077
-->
<head>
<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>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script>
@ -14,36 +11,33 @@
outside of the task that imported them. This can create issues if writing
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);
ok(window.ensureCustomElements, "should be 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-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");
// Import a single custom element and assert it exists in the custom
// element registry
let modules = await window.ensureCustomElements("moz-support-link");
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");
// Create a custom element and assert that it gets imported/defined.
document.createElement("moz-support-link");
ok(registry.get("moz-support-link"), "moz-support-link should be defined after creation");
// Import multiple custom elements and assert they exist in the registry
modules = undefined;
// Create multiple custom elements and assert they exist in the registry.
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")
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-button-group"), "moz-button-group should be defined after importing it");
// Ensure there are no errors if the imported elements are imported
// again
modules = undefined;
modules = await window.ensureCustomElements("moz-support-link", "moz-toggle", "moz-button-group");
// Ensure there are no errors if the imported elements are created again.
document.createElement("moz-support-link");
document.createElement("moz-button-group");
document.createElement("moz-toggle");
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>
</head>

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

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

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

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

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

@ -806,49 +806,32 @@
"chrome://global/content/elements/autocomplete-input.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, () => {
Services.scriptloader.loadSubScript(script, window);
if (script.endsWith(".mjs")) {
ChromeUtils.importESModule(script, { global: "current" });
} else {
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
for (let script of [

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

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

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

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