Skip to content

Moving elements with shadow roots between documents #907

@caridy

Description

@caridy

follow up from #895

This issue has been discussed briefly multiple times (#716, #865 (comment)), without a clear consensus. We could not get to it during the recent F2F meeting, and instead we decided to try to resolved via an issue.

After reviewing the current state of the spec, and current implementations, I now believe that what @rniwa was saying was correct (not a surprise). Scoped Custom Element Registries are fundamentally incompatible with moving custom element instances between documents. Here is my take on this:

Current state of affairs

  1. Moving custom element instances between documents is not very common, but still possible. After creating a custom element somehow, in another document, the element can be moved into a new document without too much hazard (note: it is not hazard free, but the risk is pretty small).

  2. Moving custom element declarations between documents is not disallowed, but the creation of instances via document.createElement will throw, and creation of those via new is not different from point 1.

  3. Instances moved are hazardous because they could incur on invalid operations, e.g.: an instance that when connected, attempt to populate its shadow root (which is a common operation) where elements used by the shadow are not registered in the destination document (new owner document).

  4. Invariant: Any element created from markup or document.createElement must be registered in the document itself, and must produce an instance of the corresponding HTMLElement.

The problem with Scoped Registry

When an instance is moved (see 3), and any element created from markup or shadowRoot.createElement, from that point on, must produce an instance of the ownerDocument's corresponding HTMLElement. This is the fundamental problem, because that registry was most likely created and populated by the declaration, or instantiation of the custom element. E.g. of such broken code:

const registry = new CustomElementRegistry();
registry.define('x-child', SomeConstructor);
export class extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open', registry });
    }
    connectedCallback() {
        this.shadowRoot.innerHTML = '<x-child></x-child>'
    }
}

Proposed solution

This proposed solution assumes that moving custom elements with scoped registries is going to be a very very rare situation:

A. Use the same protection mechanism used today by the global registry. Fail with A newly constructed custom element belongs to a wrong document (Safari) or a similar error when needed.

B. Allow a new registry to be set into an existing shadow root (a setter on ShadowRoot.prototype.registry), that way the authors expecting elements to be moved between documents can do the extra work of redefining the registry for their necessary elements when an instances are moved around.

cc @justinfagnani @rniwa @leobalter @annevk

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions