[WICG/webcomponents] Isolated web components (Issue #1002)

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](https://github.com/WICG/webcomponents/issues/72) 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:

- Components run in an iframe-like context with separate globals
- The box of the component is sized (like seamless iframes were)
- Instances are proxied across worker boundary. Properties and methods are accessible.
- Components can have slots
- Components are styleable with cross-frame inherited properties (?), ::part()
- Components can not paint outside of their box, like `contain: content`
- Some events may be able to bubble out, or be dispatched on the host element

## 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:

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

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

where:

- `name`: the local name of the element. The user defines this and the component should not be able to see the name.
- `isolated`: indicates an isolated web components
- `src`: the URL to load the component definition from. (may also be be a blob URL for use in bundles)
- `constructor`: The name of the constructor in the source script to use when creating new instances.

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

```ts
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:

```ts
// 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:

```html
<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

- Sizing based on content
- Slot support
- CSS properties inherit, and #918 
- Instances and (some) events are proxied

-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/1002
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/1002@github.com>

Received on Wednesday, 19 April 2023 20:10:14 UTC