WICG / webcomponents

Web Components specifications
Other
4.38k stars 374 forks source link

Isolated web components #1002

Open justinfagnani opened 1 year ago

justinfagnani commented 1 year ago

There have been a few ideas over the years of using web components as some sort of security/integrity boundary, or or using a new kind of web component (like declarative custom elements) as a security feature.

I think it would be useful to explore what a real isolation model for elements would look like, especially how it might differ from iframes and if it would actually offer ay advantages over iframes.

Because in the past this topic has needed a clear proposal, I'll put forward one possibility here:

Isolated Web Components

An isolated web component is one that cannot adversely effect the environment in which its run - the component implementation is (somewhat) untrusted. These might be components from a general marketplace, or components served from a third party (ex Tweet or YouTube embeds, Facebook like buttons, GitHub stars, etc).

Isolated components should not, or have limited ability to, interfere with the page they're in. ie, they should not get to choose a tag name or make a definition, they shouldn't be able to render over other content (unless given permission), they should ideally not run on the main thread, etc. But, they need to be more than iframes and integrate with layout and events more than iframes can.

Features:

Importing and registration

Isolated web components are registered by their user in a way that's independent of the definition itself. We can use the proposed declarative custom elements use of <define> element for this:

<define
    name="facespace-share"
    isolated
    constructor="ShareButton"
    src="https://facespace.com/modules/share-button.js">
</define>

<!-- usage: -->
<facespace-share></facespace-share>

where:

For use imperative JavaScript contexts, we also need an imperative API:

customElements.define('facespace-share', {
  isolated: true,
  ctor: 'ShareButton',
  src: 'https://facespace.com/modules/share-button.js'
});

I'm not sure if we can overload the second argument like this, so we may need a different method to call.

Implementing a component

An isolated component can be implemented like a normal web component. The goal is to be able to run mostly unmodified components in an isolated manner.

Sot the script may contain a definition like:

// my-element.js
class MyElement extends HTMLElement {
  constructor() {
    this.attachShadow({mode: open});
    this.shadowRoot.innerHTML = `
      <h1>Hello</h1>
      <slot></slot>
    `;
  }
}

This component can be used in a page with a definition like:

<define
    name="my-element"
    isolated
    constructor="MyElement"
    src="./my-element.js">
</define>

<my-element>
  <div>Some slotted content</div>
</my-element>

The component lives in a context with a separate document. In order for things to behave as expected, it will need to be connected to that document in some way. Possibly to a shadow root that has a host that acts as a proxy for the main page the isolated component is registered. Lifecycles will need to be driven by connecting and disconnecting the element, etc.

Proxying

We want isolated elements to be able to provide external APIs similar to regular elements. To do this we proxy the instance to the main thread. This will require a membrane-like system to handle methods, functions, promises, etc. Not all values will be proxyable.

Elements will need some facility to dispatch events to the main thread. Events could be proxied, but we also need to consider that event dispatch is synchronous. Can we pause the element worker while the event is dispatched?

Differences from iframes

rniwa commented 1 year ago

An isolated component can be implemented like a normal web component. The goal is to be able to run mostly unmodified components in an isolated manner.

I'm not certain that this should be a goal on its own. Isolated components are going to require its own construct one way or another since they're isolated from the rest of DOM tree so it would require some adjustments regardless.

justinfagnani commented 1 year ago

I'm not certain that this should be a goal on its own. Isolated components are going to require its own construct one way or another since they're isolated from the rest of DOM tree so it would require some adjustments regardless.

Yeah, I think when it comes to how exactly they're connected to their private DOM, events, proxies, and more, that will be true at the limit. What I'm meaning is that it's not an entirely different way of writing a web component - some simple components might be actually the same, but other should require incremental changes for main-thread components.

Looking for a better way to say this. Maybe that's it...