Skip to content

MutationObserver flag to observe mutations in (open) shadow trees or shadow attachment, to facilitate polyfills of HTML syntax #1287

Description

@LeaVerou

What problem are you trying to solve?

It is currently exceedingly difficult (and slow) to reliably react to the existence of certain element types or element + attribute combinations across shadow roots.

This is necessary for use cases like:

  • Implementing HTML polyfills for new element types or new attributes
  • Augmenting built-in elements in a certain way (e.g. to implement certain behavior for a data- attribute)
  • Lazy loading of web components

The only existing tool is MutationObserver, however these use cases require observing mutations in shadow trees as well, which is currently incredibly difficult. All existing solutions have serious flaws: prohibitive performance, enforcing tightly coupled architectures, obtrusiveness, and/or miss too many cases.

What solutions exist today?

1. 🤷🏽‍♀️ Explicit opt-in

Use a regular document-wide mutation observer and simply accept that the polyfill will not work correctly when shadow trees are involved unless explicitly invoked on them by expecting components that use a shadow root to invoke a certain method, e.g. discover() (example) on their shadow root once they create it.

This only works in tightly coupled architectures. A third-party component cannot possibly know what such functions to invoke.

2. 🙈 Monkey patch

Wrap HTMLElement.prototype.attachShadow() with code that calls mutationObserver.observe() on it, like so:

let originalAttachShadow = HTMLElement.prototype.attachShadow;
HTMLElement.prototype.attachShadow = function(...args) {
	let node = originalAttachShadow.call(this, ...args);
	mutationObserver.observe(node, options);
	return node;
}

This is the direction this polyfill has gone.

This fails for:

  • declarative shadow DOM
  • pre-existing shadow roots

3. 🤕 Brute force

When the mutation observer detects any new elements, check if they have a shadowRoot and call observe() on that too. Loop over all existing elements and attach mutation observers to the shadow roots of those with one.

This is extremely wasteful, and still does not catch every case, notably failing for elements that do not have a shadow root when inspected, but get one later.

How would you solve it?

Option 1: New MutationObserverInit option

Add a new option to MutationObserverInit to observe shadow trees. Exact name & syntax TBB, but it needs to support:

  • Observing across all open trees
  • Observing across certain closed roots (passing them by reference)
  • Both of the above

Later expansions might be:

  • Distinguish open vs open-stylable
  • Max level of depth

Option 2: :has-shadow + observe shadow attachment

There is already a CSS WG issue for :has-shadow: w3c/csswg-drafts#11007

Then, we can add a shadowAttached (again, TBB) option to MutationObserverInit to observe shadow root attachment. That can just be a boolean option, since we can already detect whether a closed shadow root exists, so we don't need the additional granularity, passing root references etc.

If we can detect which elements have a shadow root + which elements get a shadow root, we have all the pieces of the puzzle that we need to put this together.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions