1. 11 Worklets
    1. 11.1 简介
      1. 11.1.1 动机
      2. 11.1.2 代码幂等性
      3. 11.1.3 推测性评估
    2. 11.2 示例
      1. 11.2.1 加载脚本
      2. 11.2.2 注册类并调用其方法
    3. 11.3 基础设施
      1. 11.3.1 全局作用域
        1. 11.3.1.1 代理和事件循环
      2. 11.3.2 Worklet
      3. 11.3.3 Worklet 的生命周期

11 Worklets

11.1 简介

Worklets 是一部分规范基础设施,可用于运行独立于主 JavaScript 执行环境的脚本,同时不需要任何特定实现模型。

此处指定的 worklet 基础设施不能被 Web 开发人员直接使用。相反,其他规范在此基础上构建,以创建直接可用的 worklet 类型,专门用于在浏览器实现管道特定部分中运行。

11.1.1 动机

允许扩展渲染或实现管道其他敏感部分(如音频输出)的扩展点非常困难。如果扩展点以对 Window 上可用 API 的完全访问权限来完成,引擎将需要放弃之前对那些阶段中可能发生的事情的假设。例如,在布局阶段,渲染引擎假设不会修改任何 DOM。

此外,在 Window 环境中定义扩展点会将用户代理限制在与 Window 对象相同的线程中执行工作。(除非实现添加了复杂、高开销的基础设施来允许线程安全 API,以及线程连接保证。)

Worklets 旨在允许扩展点,同时保持用户代理当前依赖的保证。这是通过基于 WorkletGlobalScope 子类的新的全局环境来完成的。

Worklets 类似于 Web 工作线程。但是,它们

由于 worklets 的开销相对较高,因此最好谨慎使用。因此,预期给定 WorkletGlobalScope 将在多个独立脚本之间共享。(这类似于单个 Window 如何在多个独立脚本之间共享。)

Worklets 是一项通用技术,可用于不同的用例。一些 worklets(例如在 CSS 绘画 API 中定义的 worklets)提供旨在用于无状态、幂等和短运行计算的扩展点,这些扩展点具有下一节中描述的特殊考虑因素。其他 worklets(例如在 Web 音频 API 中定义的 worklets)用于有状态、长时间运行的操作。 [CSSPAINT] [WEBAUDIO]

11.1.2 代码幂等性

一些使用 worklets 的规范旨在允许用户代理在多个线程上并行执行工作,或根据需要在线程之间移动工作。在这些规范中,用户代理可能会以 实现定义 的顺序调用 Web 开发人员提供的类上的方法。

因此,为了防止互操作性问题,在这些 WorkletGlobalScope 上注册类的作者应该使他们的代码幂等。也就是说,类上的方法或方法集对于给定特定输入应该产生相同的输出。

本规范使用以下技术来鼓励作者以幂等的方式编写代码

这些限制共同有助于防止两个不同的脚本使用 全局对象 的属性共享状态。

此外,使用 worklets 并打算允许 实现定义 行为的规范必须遵守以下内容

11.1.3 推测性评估

一些使用 worklets 的规范可以根据用户代理的状态调用 Web 开发人员提供的类上的方法。为了提高线程之间的并发性,用户代理可能会基于潜在的未来状态推测性地调用方法。

在这些规范中,用户代理可能会在任何时候调用这些方法,并使用任何参数,而不仅仅是与用户代理的当前状态相对应的参数。这种推测性评估的结果不会立即显示,但可以缓存以供用户代理状态与推测状态匹配时使用。这可以提高用户代理和 worklet 线程之间的并发性。

因此,为了防止用户代理之间发生互操作性风险,在这些 WorkletGlobalScope 上注册类的作者应该使他们的代码无状态。也就是说,调用方法的唯一影响应该是其结果,而不是任何副作用,例如更新可变状态。

鼓励 代码幂等性 的相同技术也鼓励作者编写无状态代码。

11.2 示例

对于这些示例,我们将使用一个假 worklet。Window 对象提供两个 Worklet 实例,每个实例都在其自己的 FakeWorkletGlobalScope 集合中运行代码

window.fakeWorklet1
返回其中一个假 worklet。
window.fakeWorklet2
返回另一个假 worklet。

这两者的 worklet 全局作用域类型 都设置为 FakeWorkletGlobalScope,其 worklet 目标类型 都设置为 "fakeworklet"。用户代理应该为每个 worklet 创建至少两个 FakeWorkletGlobalScope 实例。

"fakeworklet" 实际上不是根据 Fetch 的有效 目标。但这说明了真正的 worklets 通常如何具有它们自己的 worklet 类型特定的目标。 [FETCH]


FakeWorkletGlobalScope 内部,以下全局方法可用

registerFake(type, classConstructor)
注册由 classConstructor 给出的 JavaScript 类,以便在用户代理稍后想要执行由 type 指定的操作时使用。

11.2.1 加载脚本

要将脚本加载到 假 worklet 1 中,Web 开发人员会编写

window.fakeWorklet1.addModule('script1.mjs');
window.fakeWorklet1.addModule('script2.mjs');

请注意,哪个脚本先完成获取并运行取决于网络计时:可能是 script1.mjsscript2.mjs。这对于为加载到 worklets 中而编写的、编写良好的脚本来说通常并不重要,如果它们遵循有关准备进行 推测性评估 的建议。

如果 Web 开发人员想要仅在脚本成功运行并加载到某些 worklets 中后执行任务,他们可以编写

Promise.all([
    window.fakeWorklet1.addModule('script1.mjs'),
    window.fakeWorklet2.addModule('script2.mjs')
]).then(() => {
    // Do something which relies on those scripts being loaded.
});

关于脚本加载的另一个重要要点是,加载的脚本可以在每个 Worklet 中的多个 WorkletGlobalScope 中运行,如关于 代码幂等性 的部分所述。特别是,上述关于 假 worklet 1假 worklet 2 的规范要求这样做。因此,请考虑以下场景

// script.mjs
console.log("Hello from a FakeWorkletGlobalScope!");
// app.mjs
window.fakeWorklet1.addModule("script.mjs");

这可能导致用户代理控制台输出如下内容

[fakeWorklet1#1] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#4] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#2] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#3] Hello from a FakeWorkletGlobalScope!

如果用户代理在某个时候决定终止和重启 FakeWorkletGlobalScope 的第三个实例,控制台将在发生这种情况时再次打印 [fakeWorklet1#3] Hello from a FakeWorkletGlobalScope!

11.2.2 注册类并调用其方法

假设 Web 开发人员使用我们假 worklet 的意图之一是允许他们自定义高度复杂的布尔否定过程。他们可能会注册他们的自定义内容,如下所示

// script.mjs
registerFake('negation-processor', class {
  process(arg) {
    return !arg;
  }
});
// app.mjs
window.fakeWorklet1.addModule("script.mjs");

为了利用这些注册的类,假 worklet 的规范可以定义一个 查找真值的相反值 算法,给定一个 Worklet worklet,它会调用作为类型 "negation-processor" 注册到 worklet 的全局作用域之一上的任何类的 process 方法,并使用 true 作为参数,然后以某种方式使用结果。

11.3 基础设施

11.3.1 全局作用域

WorkletGlobalScope 的子类用于创建 全局对象,其中加载到特定 Worklet 中的代码可以执行。

其他规范旨在子类化 WorkletGlobalScope,添加注册类的 API 以及特定于其工作线程类型的其他 API。

11.3.1.1 代理和事件循环

每个 WorkletGlobalScope 都包含在它自己的 工作线程代理 中,该代理具有其对应的 事件循环。但是,在实践中,这些代理和事件循环的实现预计将与大多数其他代理和事件循环不同。

每个 WorkletGlobalScope 都存在一个 工作线程代理,因为理论上,实现可以使用一个独立的线程来处理每个 WorkletGlobalScope 实例,并且允许这种级别的并行性最好使用代理来完成。但是,由于它们的 [[CanBlock]] 值为 false,因此代理和线程之间没有一对一的必要性。这允许实现自由地在任何线程上执行加载到工作线程中的脚本,包括运行来自其他具有 [[CanBlock]] 为 false 的代理的代码的线程,例如 相同来源窗口代理(“主线程”)的线程。将此与 专用工作线程代理 相比,它们对 [[CanBlock]] 的真实值实际上要求它们获得一个专用的操作系统线程。

工作线程的 事件循环 也是有点特殊的。它们仅用于与 addModule() 相关的任务,即用户代理调用作者定义的方法的任务,以及微任务。因此,即使 事件循环处理模型 指定所有事件循环都持续运行,实现也可以使用更简单的策略来实现可观察的等效结果,该策略只是 调用 作者提供的函数,然后依赖于该过程来 执行微任务检查点

11.3.2 Worklet

Worklet

在所有当前引擎中支持。

Firefox76+Safari14.1+Chrome65+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

Worklet 类提供了将模块脚本添加到其关联的 WorkletGlobalScope 的功能。然后,用户代理可以创建在 WorkletGlobalScope 上注册的类并调用它们的方法。

创建 Worklet 实例的规范必须为给定实例指定以下内容

await worklet.addModule(moduleURL[, { credentials }])

moduleURL 给出的 模块脚本 加载并执行到所有 worklet全局范围 中。它还可以在此过程中创建额外的全局范围,具体取决于工作线程类型。返回的 Promise 在脚本已成功加载并运行在所有全局范围中后将完成。

credentials 选项可以设置为 凭据模式 来修改脚本获取过程。它默认为“same-origin”。

任何 获取 脚本或其依赖项的失败都会导致返回的 Promise 被拒绝,并使用一个 "AbortError" DOMException。任何解析脚本或其依赖项的错误都会导致返回的 Promise 被拒绝,并使用解析期间生成的异常。

11.3.3 工作线程的生命周期

Worklet 的生命周期没有特殊注意事项;它与它所属的对象绑定,例如 Window

WorkletGlobalScope 的生命周期至少与包含它的 工作线程全局范围Document 绑定。特别是,销毁 Document终止 对应的 WorkletGlobalScope 并允许它被垃圾回收。

此外,用户代理可以在任何时候 终止 给定的 WorkletGlobalScope,除非定义相应工作线程类型的规范另有规定。例如,如果 工作线程代理事件循环 没有排队的 任务,或者如果用户代理没有计划使用工作线程的挂起操作,或者如果用户代理检测到异常操作(例如无限循环或超过设定的时间限制的回调),它们可能会终止它们。

最后,特定工作线程类型的规范可以提供有关何时 创建 给定工作线程类型的 WorkletGlobalScope 的更多具体细节。例如,它们可能会在调用工作线程代码的特定过程中创建它们,就像在 示例 中一样。