Skip to content

developit/element-worklet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

Proposal: Element Worklet

Created: 2018-09-20 | Updated: 2021-01-15

This proposal outlines a (potentially impossible!) idea for moving entire Elements off the main thread.

Conceptually, think OOPIFs except well-suited to first-party content, and implemented as lightweight Worklets.

On the UI Thread: Element Worklet

The Custom Elements API is extended with support for registering Element Worklets. As with all Worklets, registration is done via a URL resolving to a Module Script. A single Element Worklet script can register multiple Custom Elements.

customElements.addModule('./lazy-image.mjs');

All Custom Elements controlled by a given Worklet are upgraded once the Worklet has been initialized, following the same lifecycle as Custom Elements defined on the main thread.

Aside from the URL-based Worklet script registration and instantiation, Custom Elements defined using Element Worklet function like a standard Custom Element:

<!-- declarative: -->
<lazy-image src="cat.jpeg">

<script>
  // imperative:
  const dog = document.createElement('lazy-image');
  dog.setAttribute('src', 'dog.png');
  document.body.appendChild(dog);
</script>

Pros & Cons

Pros Cons
  • Re-uses existing Element registry
  • Leverages Custom Element semantics
  • Well-defined lifecycle and upgrade model
  • Limited DOM API surface
  • No declarative registration **

** However, Element Worklet lends itself more to declarative registration than straight Custom Elements.

Inside an Element Worklet

Element Worklets build on the same foundation as the other existing types of Worklets. The module URL given to customElements.addModule() is immediately fetched and evaluated within a new ElementWorkletGlobalScope. This may occur on the rendering thread, in a background thread or a thread pool.

// lazy-image.mjs
class LazyImageElement extends WorkletElement {
  constructor() {
    // Light DOM is accessible to the worklet:
    this.getAttribute('src')  // cat.jpeg

    // ...but it is read-only
    this.firstElementChild.src = 'foo'  // Error

    // and scoped to the element (no parent references)
    this.parentNode  // null

    // in fact, there are no meaningful instance properties defined on WorkletElements at all.
    // to facilitate serialization, all data must flow through attributes or MessageChannel:

    // there's a corresponding `lazyImage.port` on the main thread representation.
    this.port.postMessage('hello');

    // As such, Shadow DOM is required in order to produce UI:
    this.shadow = this.createShadow();
    this.shadow.innerHTML = 'some stuff';
  }

  // all lifecycle functions the same as Custom Elements v1:

  static get observedAttributes() {
    return ['src']
  }

  attributeChangedCallback(name, oldValue, newValue) {}
  connectedCallback() {}
  disconnectedCallback() {}
}

// Registering an Element is the same as on the main thread:
customElements.define('lazy-image', LazyImageElement);

Data Flow: Custom Element properties are not reflected on a WorkletElement, only attributes. Attribute changes are observed the same as they are in Custom Elements, defined eagery by a static observedAttributes property on the element's constructor. Attribute changes changes invoke attributeChangedCallback() in the worklet, and may be batched.

Data Sharing: The main thread and worklet instances of a Custom Element each have a .port property, which are ports of an instance-specific MessageChannel that can be used for message passing. This is synonymous with the processor.port interface used by AudioWorkletNode/AudioWorkletProcessor.

Element Upgrade Process

When a Custom Element defined by an ElementWorklet is instantiated on the main thread, a new corresponding WorkletElement instance is created within its associated ElementWorkletGlobalScope:

  1. all unknown elements begin marked as unresolved (HTMLUnknownElement / :not(:resolved))
  2. customElements.addModule() is invoked and an ElementWorklet instantiated
  3. customElements.define() is invoked within the Worklet
  4. Elements in the document with the given Custom Element name are enqueued for upgrade

Upgrading Worklet-backed Elements is a two-phase process. First, the Element representation is created on the main thread and a MessagePort is created for it, with one port assigned as a port instance property. At this point, the Element is still considered to be in an unresolved state. Then, inside the Worklet, a corresponding WorkletElement is instantiated and assigned the other message port.

When a Worklet-backed Element is connected (via appendChild(), the parser or another means), the WorkletElement instance's connectedCallback() lifecycle method is invoked. As with a standard Custom Element, attributeChangedCallback() is also invoked with the initial value of each observed attribute. Once all the callbacks have been invoked, the Element's representation in the parent document is marked as resolved and the process is complete.

Use Cases

Ads: Advertisements currently use iframes for encapsulation, a technique of increasing cost as the effects of Spectre mitigations make their way into browsers. Element Worklet could provide a lightweight alternative to iframes for this use-case.

Third Party Embeds: Embedded content like comment widgets, chats and helpdesks all of these currently use some combination of same-origin scripting and iframes, usually mixing origins (eg: a script in the embedder context communicating with an iframe from the embedee's context). Moving from <script> + <iframe> to Element Worklet seems like a reasonable fit for this case.

AMP: The semantics defined in this proposal map reasonably well to <amp-script>, and a prototype of Element Worklet has been built using worker-dom, the library that underpins <amp-script>. AMP's approach is much more broadly applicable than Element Worklet, seeking to support arbitrary third-party code running in a sandboxed DOM environment. However, it's possible a solution like worker-dom would be able to leverage something like Element Worklet to simplify Element registration and upgrades, and to mitigate transfer overhead between threads.

Lazy Loading: Component-based frameworks and libraries strive to provide solutions for lazily downloading, instantiating and rendering portions of an application. This process is entirely implemented in userland, which has the unfortunate side effect of making it invisible to the browser. In certain scenarios, it may be possible to use Element Worklet as the underly mechanism for lazily loading and rendering pieces of a component-based User Interface.

UI Component Libraries: If this model can be shown to provide performance guarantees for Element registration, upgrade and rendering, it's possible a UI library would choose to provide their components as worklet-backed Elements through the use of one or more Element Worklets. This could have interesting implications for performance, since it would provide a way to impose performance guarantees. This is a safety net developers do not currently have for prebuilt modules. The (large) portions of a typical app that are defined by code installed from npm would have less ability to negatively impact the performance of first-party code.

Open Questions

  • What DOM APIs are available from within an ElementWorkletGlobalScope?
  • Can WorkletElement provide a sufficient API surface to allow current libraries and approaches to be reused with minimal modification?
  • Is the level of encapsulation illustrated in this proposal too limiting? Does it fail to meet the needs of use-cases like embedded video players?
  • Should Element Worklet provide an analog for Custom Element property getters/setters? Could custom properties (and/or methods?) defined on a WorkletElement subclass be reflected asynchronously to the main thread (in the style of Comlink? Without this, complex data types for Worklet-backed elements would need to be serialized as attributes.
  • Would it be possible to allow defining a priority when registering Element Worklet modules? This would unlock a host of use-cases in which worklet code could be considered untrusted.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published