Summary
Using the Scoped Custom Element Registries API inside an isolated-world content script fails with:
Uncaught TypeError: Failed to construct 'HTMLElement': Illegal constructor
The same code works correctly when run from a MAIN-world content script.
Background
Isolated content scripts cannot access the page's window.customElements (the global CustomElementRegistry). This is expected and intentional for isolation. The Scoped Custom Element Registries API seemed like a natural fit — it was designed to allow element definitions in a separate registry without polluting the host page's namespace.
However, the WICG proposal mentions browser extensions only as a motivating example for naming conflict isolation, implicitly assuming a MAIN-world context where the extension and page share the same JS realm. It does not address isolated worlds at all.
The failure in isolated content scripts is a distinct, deeper problem: cross-realm HTMLElement inheritance. When a content script class extends HTMLElement, that HTMLElement comes from the extension's isolated JS realm — a different object identity from the document's HTMLElement. The scoped registry's constructor validation rejects it. The proposal itself acknowledges a related constraint:
"it must limit constructors by default to only looking up registrations from the global registry. If the constructor is not defined in the global registry, it will throw."
Reproduction
The following two approaches both fail in an isolated content script:
Approach 1 — attach registry via attachShadow option:
const scopedRegistry = new CustomElementRegistry();
scopedRegistry.define("my-counter", Counter);
const container = document.createElement("div");
container.attachShadow({
mode: "open",
customElementRegistry: scopedRegistry,
});
container.shadowRoot.innerHTML = `<my-counter startvalue="1" step="3"></my-counter>`;
document.body.appendChild(container);
Approach 2 — initialize after attaching shadow:
const scopedRegistry = new CustomElementRegistry();
scopedRegistry.define("my-counter", Counter);
const container = document.createElement("div");
container.attachShadow({ mode: "open", customElementRegistry: null });
container.shadowRoot.innerHTML = `<my-counter startvalue="1" step="3"></my-counter>`;
scopedRegistry.initialize(container.shadowRoot);
document.body.appendChild(container);
Both throw:
Uncaught TypeError: Failed to construct 'HTMLElement': Illegal constructor
Impact
Web Components / Custom Elements are increasingly common in modern UIs. Many browser extension use cases — UI injection, overlays, agent-assist panels — benefit from Shadow DOM encapsulation. Without working Custom Elements in isolated content scripts, developers must either:
- Run in MAIN world (losing isolation guarantees), or
- Use an iframe (complex, limited host-page integration).
Discussion
We'd love to understand whether supporting Custom Elements (including scoped registries) in isolated-world content scripts is something WECG would be open to exploring. The cross-realm HTMLElement issue seems like it would need to be addressed either at the spec level or through browser-specific adaptation of the isolated world environment — and we're happy to help think through the design if that's useful.
References
Browser Tested
- Chrome Version 149.0.7827.103 (Official Build) (arm64)
- Manifest V3, isolated world content script
Summary
Using the Scoped Custom Element Registries API inside an isolated-world content script fails with:
The same code works correctly when run from a MAIN-world content script.
Background
Isolated content scripts cannot access the page's
window.customElements(the globalCustomElementRegistry). This is expected and intentional for isolation. The Scoped Custom Element Registries API seemed like a natural fit — it was designed to allow element definitions in a separate registry without polluting the host page's namespace.However, the WICG proposal mentions browser extensions only as a motivating example for naming conflict isolation, implicitly assuming a MAIN-world context where the extension and page share the same JS realm. It does not address isolated worlds at all.
The failure in isolated content scripts is a distinct, deeper problem: cross-realm
HTMLElementinheritance. When a content script classextends HTMLElement, thatHTMLElementcomes from the extension's isolated JS realm — a different object identity from the document'sHTMLElement. The scoped registry's constructor validation rejects it. The proposal itself acknowledges a related constraint:Reproduction
The following two approaches both fail in an isolated content script:
Approach 1 — attach registry via
attachShadowoption:Approach 2 —
initializeafter attaching shadow:Both throw:
Impact
Web Components / Custom Elements are increasingly common in modern UIs. Many browser extension use cases — UI injection, overlays, agent-assist panels — benefit from Shadow DOM encapsulation. Without working Custom Elements in isolated content scripts, developers must either:
Discussion
We'd love to understand whether supporting Custom Elements (including scoped registries) in isolated-world content scripts is something WECG would be open to exploring. The cross-realm
HTMLElementissue seems like it would need to be addressed either at the spec level or through browser-specific adaptation of the isolated world environment — and we're happy to help think through the design if that's useful.References
Browser Tested