Skip to content

Update to support revamped proposal - #604

Closed
sorvell wants to merge 40 commits into
masterfrom
revamped
Closed

sorvell wants to merge 40 commits into
masterfrom
revamped

Conversation

@sorvell

@sorvell sorvell commented Dec 19, 2024

Copy link
Copy Markdown
Collaborator

Fixes: #603

New proposal: whatwg/html#10854

@sorvell sorvell changed the title Update to sport revamped proposal Update to support revamped proposal Dec 19, 2024
@sorvell

sorvell commented Feb 27, 2025 •

Copy link
Copy Markdown
Collaborator Author

To prevent future issues with conflicting with native implementations, let's consider offering a version of this that's more of a ponyfill. For example:

pony(document).createElement(tag, {customElements})
pony(element).customElements
pony(element).attachShadow({mode: 'open', customElements})

This might be fairly annoying in practice and there are some options:

  1. ponyfill or polyfill HTMLElement and CustomElementRegistry?
  2. ponyfill or polyfill relevant APIs, which include:
    • Node.customElements
    • Node.prototype.appendChild
    • Node.prototype.insertBefore
    • DocumentFragment.prototype.append
    • ShadowRoot,.prototype.setHTMLUnsafe
    • ShadowRoot.prototype.innerHTML
    • Element.prototype.insertAdjacentHTML
    • Element.prototype.setHTMLUnsafe
    • Element.prototype.append
    • Element.prototype.prepend
    • Element.prototype.insertAdjacentElement
    • Element.prototype.replaceChild
    • Element.prototype.replaceChildren
    • Element.prototype.replaceWith
    • Element.prototype.innerHTML
    • HTMLElement.prototype.attachShadow
    • HTMLElement.prototype.attachInternals
    • Document.prototype.createElement
    • Document.prototype.createElementNS
    • Document.prototype.importNode

Comment thread packages/scoped-custom-element-registry/src/types.d.ts Outdated
}

interface ShadowRoot {
readonly ['customElementRegistry']: CustomElementRegistry | null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice for reviewing and future readers to leave links to where public APIs are specced. I think this link would be good here: https://dom.spec.whatwg.org/#dom-documentorshadowroot-customelementregistry

Comment thread packages/scoped-custom-element-registry/src/types.d.ts Outdated
Comment thread packages/scoped-custom-element-registry/src/types.d.ts Outdated
Comment thread packages/scoped-custom-element-registry/src/types.d.ts Outdated
// > root avoids this.
const scopeForElement = new WeakMap<Node, Element | ShadowRoot>();

const registryForElement = new WeakMap<

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe choose a more descriptive name or add a comment. Maybe setRegistryForSubtree?

Also, does this implement a spec operation? If so, it'd be useful to have a link.

Comment thread packages/scoped-custom-element-registry/src/scoped-custom-element-registry.ts Outdated
Comment thread packages/scoped-custom-element-registry/src/types.d.ts
const {tagName, CustomElementClass} = getTestElement();
registry.define(tagName, CustomElementClass);
shadowRoot.innerHTML = `<${tagName}></${tagName}><div></div>`;
registry.initialize(shadowRoot);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about initializing a non-shadow root parent, or initializing a subtree where some elements already have registries?

Comment thread packages/scoped-custom-element-registry/src/scoped-custom-element-registry.ts Outdated
sorvell and others added 8 commits September 20, 2025 11:33
Co-authored-by: Justin Fagnani <justinfagnani@google.com>
Co-authored-by: Justin Fagnani <justinfagnani@google.com>
Co-authored-by: Justin Fagnani <justinfagnani@google.com>
Co-authored-by: Justin Fagnani <justinfagnani@google.com>
Co-authored-by: Justin Fagnani <justinfagnani@google.com>
* status reported in CustomElementRegistryPolyfill.inUse
* support DSD null registries via host attribute `polyfill-shadowrootcustomelementregistry`
* add additional tests to address feedback
* re-order some tests
* tracked scoped context now only supports valid customElementRegistry values
* added ability to test native impl on Safari + bail outs for known bugs
import {expect} from '@open-wc/testing';

// prettier-ignore
import {getTestTagName, getTestElement, getShadowRoot, getHTML, createTemplate} from './utils.js';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this import for?

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both checkout and setup-node are at version 6 atm

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v7, actually

- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 24.5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why not just 24 (latest; 24.11 atm)

? globalCustomElementRegistry
: optionsRegistry;
creationContext.push(customElementRegistry);
const el = createElement.call(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if options has an .is property, its ignored in createElement here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionally if .is is non-null and options.customElementRegistry exists, a NotSupportedError DOMException should be thrown. Im guessing that wouldn't happen here, because the registry is assigned after the createElement call

options?: boolean | ImportNodeOptions
): T {
const deep =
typeof options === 'boolean' ? options : !options?.selfOnly;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this ternary is wrong,importNode(node) results in deep = true, but it should be shallow, no?

registry as ShimmedCustomElementsRegistry
);
if (deep) {
node.childNodes.forEach((n) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When deep is true, I dont think this works correctly for <template> elements:

with polyfill:

<!doctype html>
<html>
  <body>
    <script>
      window.CustomElementRegistryPolyfill = { force: true };
    </script>
    <script src="polyfill.js"></script>
    <template id="t"><p>hello</p></template>
    <script>
      const t = document.getElementById("t");
      console.log(document.importNode(t, true).content.textContent); //❌ ""
      console.log(t.cloneNode(true).content.textContent); //❌ ""
    </script>
  </body>
</html>

without polyfill:

<!doctype html>
<html>
  <body>
    <template id="t"><p>hello</p></template>
    <script>
      const t = document.getElementById("t");
      console.log(document.importNode(t, true).content.textContent); //✅ "hello"
      console.log(t.cloneNode(true).content.textContent); //✅ "hello"
    </script>
  </body>
</html>

root as HTMLElement
);
}
root.childNodes.forEach((n) => this.upgrade(n));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include nested shadowroots, e.g.:

<!doctype html>
<body>
  <script>
    function check(label, tag) {
      const host = document.createElement("div");
      host.attachShadow({ mode: "open" }).innerHTML = `<${tag}></${tag}>`;

      class El extends HTMLElement {}
      customElements.define(tag, El);

      customElements.upgrade(host);

      console.log(
        label,
        "element inside shadow root upgraded?",
        host.shadowRoot.firstElementChild instanceof El,
      );
    }

    check("WITHOUT polyfill:", "x-native"); // ✅ true
  </script>

  <script>
    window.CustomElementRegistryPolyfill = { force: true };
  </script>
  <script src="scoped-custom-element-registry.js"></script>

  <script>
    check("WITH polyfill:   ", "x-polyfill"); // ❌ false
  </script>
</body>

@sorvell

sorvell commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Closing. This PR has been superseded by #668.

@sorvell sorvell closed this Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[scoped-custom-element-registry] Update to match new revamped proposal

5 participants