首页
/ Ladybird LibWeb 绑定生成器的 Realm 策略:实例成员默认使用接收者 Realm,静态成员与例外使用调用方 Realm

Ladybird LibWeb 绑定生成器的 Realm 策略:实例成员默认使用接收者 Realm,静态成员与例外使用调用方 Realm

2026-09-04 12:52:21作者:邬祺芯Juliet

本篇技术指南基于 Ladybird 浏览器的官方策略文档 REALM_POLICY.md,系统讲解 LibWeb 绑定生成器如何为每一个 WebIDL 成员做出显式的 realm(JS 执行域)决策:实例操作/属性默认使用校验后的接收者 realm(this_object_realm),静态操作/构造函数/命名空间成员使用调用方 realm,唯一的全局 opt-out 是 [NeedsCallerRealm] 扩展属性。读完本篇,你将掌握该策略的完整规则集、共享生成入口 member_realm_expr() 的实现与调用点、VM 层对 TypeError 构造域的限定机制,以及 WindowProxy 接收者 realm 的特殊解析逻辑,能够为编写或审查 LibWeb IDL 与生成代码提供准确依据。

一、为什么每个绑定成员都需要一次显式的 realm 决策

在 WebIDL 绑定生成中,"realm"(JS 执行域,见 LibJSJS::Realm)决定了 DOMException 包裹、返回值包装、Promise 创建/拒绝、wrapper 分配等行为发生在哪一个可观察域上。当页面存在多个执行域(主世界与隔离世界、跨 realm 传递的对象等)时,同一个对象上的成员调用可能在调用方 realm 与接收者所属 realm 之间产生不同的可观察行为——异常对象的身份、Promise 的原型链、instanceof 结果都会随 realm 选择而变化。

Ladybird 的策略文档开宗明义:生成的绑定代码对每个 IDL 成员只做一次显式的 realm 决策(Generated bindings make one explicit realm decision per IDL member)。这避免了生成器各发射器(emitter)中散落的临时策略,也保证了 DOMException 包裹、异常身份与返回值分配遵循同一个可观察 realm。

二、策略规则集:四类成员的 realm 决策

2.1 实例操作与属性:默认使用 this_object_realm

文档规定,实例操作(operations)和属性(attributes)默认使用校验后的接收者 realm,即 this_object_realm。该 realm 被用于:

  • 实现调用(implementation calls);
  • DOMException 包裹;
  • JavaScript 返回值包装(wrapping);
  • 对 promise 类型成员进行 promise 的创建与拒绝。

同时,生成的 pair-iterable、async-iterable、maplike、setlike 辅助代码也使用接收者 realm 来创建迭代器/容器以及包装值(对应发射器 iterables.py)。

2.2 静态操作、构造函数与命名空间成员:使用调用方 realm

静态操作(static operations)、构造函数(constructors)与命名空间成员(namespace members)没有接收者对象,因此直接使用调用方/当前 realm(realm)。

2.3 [RealmFreeConstructor]:不需要当前 realm 的自定义构造函数

[RealmFreeConstructor] 用于标记一个 [ImplementedInBindings] 构造函数,其自定义绑定辅助函数不需要当前 realm。注意一个精确的边界:生成的构造函数仍然使用调用方/当前 realm 来完成 WebIDL 转换、异常包裹、wrapper 分配与原型选择;只有对实现辅助函数的调用省略了 realm 参数

2.4 [NeedsCallerRealm]:实例成员的唯一 opt-out

[NeedsCallerRealm] 是实例成员的唯一退出机制:当其 Web 可见结果或实现算法被规范指定为使用调用方/当前 realm 时,在 IDL 上标注该属性。文档明确了两条约束:

  • 不存在 getter/setter 各自独立的 caller-realm 注解;
  • 属性级别的 [NeedsCallerRealm] 同时作用于生成的 getter 和 setter。

在仓库的 IDL 中可以看到大量真实用例。例如 History.idlpushState/replaceState 标注了 [NeedsCallerRealm]

undefined back();
undefined forward();
[NeedsCallerRealm] undefined pushState(any data, DOMString unused, optional USVString? url = null);
[NeedsCallerRealm] undefined replaceState(any data, DOMString unused, optional USVString? url = null);

Location.idl 则展示了属性级标注同时覆盖 getter/setter 的形态(hreforiginprotocolhosthostnameportpathnamesearchhash 等属性均带 [LegacyUnforgeable, NeedsCallerRealm])。

三、共享生成入口:realms.py 中的两个函数

策略文档指出,所有生成器代码应通过共享入口做出 realm 决策,而不应在个别发射器里临时拼写 realm 策略

The shared generator entry point is Meta/Generators/libweb_bindings/realms.py::member_realm_expr(). New generated member code should use that helper instead of spelling ad-hoc realm policy in individual emitters.

realms.py 全文只有两个函数,逻辑与文档一一对应:

def member_realm_expr(
    member: Union[Attribute, Operation, SpecialOperation],
    *,
    interface: Interface,
    is_static: bool = False,
) -> str:
    """Return the C++ realm expression used for member work.

    See Meta/Generators/libweb_bindings/REALM_POLICY.md. Instance members
    default to the validated receiver realm. Statics, constructors, namespaces,
    and explicit caller-realm opt-outs use the caller realm.
    """
    if is_static or interface.is_namespace:
        return "realm"

    if "NeedsCallerRealm" in member.extended_attributes:
        return "realm"

    return "this_object_realm"


def member_passes_realm_to_implementation(member: Union[Attribute, Operation, SpecialOperation]) -> bool:
    return "NeedsCallerRealm" in member.extended_attributes

即:静态成员或命名空间接口返回 "realm"(调用方 realm);标注了 [NeedsCallerRealm] 的实例成员返回 "realm";其余实例成员返回 "this_object_realm"

3.1 消费点:哪些发射器调用了 member_realm_expr

在生成器目录内检索 member_realm_expr / member_passes_realm_to_implementation 的调用点,可以看到策略被贯彻到各成员类型的发射器中:

发射器 用途
operations.py 普通操作与静态操作的 realm 参数(约 L451 使用 member_realm_expr;L83、L115 用 member_passes_realm_to_implementation 决定是否向实现传递 realm)
attributes.py 属性 getter(L327)与 setter(L695)各自的 realm 参数
named_and_indexed_properties.py 命名/索引属性 getter(L758、L806)与操作(L879)
overload_resolution.py 重载仲裁器中对 promise 型操作设置 promise_realm(L145)

四、生成代码如何落地:invoke_first_available 与 realm 参数的两种形态

operations.py 中的 implementation_operation_call()(L93-L121)为例,可以直观看到 realm 决策如何变成 C++ 代码。该函数根据成员签名,通过 Web::Bindings::invoke_first_available 生成"尝试多个候选签名"的 lambda 链:

Web::Bindings::invoke_first_available(*idl_object,
    & -> decltype(no_realm_call) { return no_realm_call; },
    & -> decltype(realm_call) { return realm_call; },
    ...)

其中候选调用包括"不传 realm"、"传 realm.global_object()"、"传 realm"、"传 vm"等形式;当成员标注了 [NeedsCallerRealm] 时,member_passes_realm_to_implementation() 返回 true,生成器会交换"带 realm 调用"与"不带 realm 调用"的优先级(L115-L116),从而让实现辅助函数显式收到调用方 realm 参数。

对于 promise 型操作,overload_resolution.pywrite_overload_arbiter()(L141-L156)还会按同一策略预置 promise_realm:若 member_realm_expr() 返回 "realm",直接取 *vm.current_realm();否则从 vm.this_value()this_value_realm() 解析接收者 realm(this 为 nullish 时回退到全局对象所在 realm)。这正对应文档中"promise 创建/拒绝发生在选定 realm"的规则。

五、对 WebIDL 默认措辞的有意偏离:接收者 realm 包裹 DOMException

文档明确说明,LibWeb 有意偏离 WebIDL 规范关于实例成员 DOMException 包裹的默认措辞:LibWeb 在接收者 realm 中包裹 DOMException,使 wrapper 分配、异常身份与返回值分配遵循同一个可观察 realm。

生成器为操作与 setter 的转换块(conversion blocks)还将普通 JS TypeError 的构造作用域限定在选定的成员 realm 上,并有两个重要的边界:

  1. VM 在转换重新进入作者脚本(author script)时清除该 TypeError 覆盖,因此嵌套脚本仍在其自身 realm 中抛出异常;
  2. 该机制刻意比改变 VM 的当前 realm 或执行上下文更窄——目的是让转换失败获得统一的 realm 身份,而不是让用户脚本在另一个 realm 下执行。

这一设计说明该策略不是简单"切换全局执行域",而是对异常/错误对象构造路径的局部限定。

六、WindowProxy 接收者:this_value_realm() 的解析规则

WindowProxy 这类接收者,this_object_realmBindings::this_value_realm() 提供。规则是:

  • 主世界(main-world)代理解析到当前激活的 [[Window]] 的 realm;
  • 非主世界代理保持在其自身 realm。

仓库中的实现印证了这一规则。Wrappable.cppthis_value_realm()(约 L214 起)的核心逻辑:

JS::Realm& this_value_realm(JS::Realm& fallback_realm, JS::Value this_value)
{
    if (!this_value.is_object())
        return fallback_realm;

    auto& object = this_value.as_object();
    if (auto* window_proxy = as_if<HTML::WindowProxy>(&object)) {
        auto& proxy_realm = object.shape().realm();
        if (!proxy_realm.host_defined())
            return fallback_realm;

        // A main-world WindowProxy is created once, in the initial about:blank
        // realm, then reused as globalThis across later navigations. Do not use
        // that stale shape realm for receiver-realm decisions: HTML defines a
        // WindowProxy's relevant realm as the realm of its current [[Window]].
        if (host_defined_wrapper_world(proxy_realm).is_main_world()) {
            if (auto window = window_proxy->window())
                return window->principal_realm();
        }
    }

    return object.shape().realm();
}

源码头注释解释了动机:主世界 WindowProxy 在初始 about:blank realm 中创建一次,之后跨导航复用为 globalThis;其 shape realm 是"陈旧的",因此接收者 realm 决策必须使用当前 [[Window]] 的 realm(window->principal_realm())。而隔离世界的 WindowProxy 在自身世界 realm 中创建,保持在代理自身 realm,保证跨世界包装(cross-world wrapping)的隔离性。该函数声明于 Wrappable.h

七、当前 [NeedsCallerRealm] 的退出类别

文档列出了目前使用 [NeedsCallerRealm] 的几类 API,理解这些类别有助于判断新 IDL 成员应使用哪个 realm:

  1. WebAssembly JS API 对象与命名空间函数:JS-API 算法在调用方 realm 中创建 ArrayBuffer、typed array、exports 对象与 promise;
  2. Promise / body / fetch / cache / credential / permission / serial / gamepad / media API:promise 能力(capability)与结果对象的分配对调用方可观察;
  3. 结构化序列化 APIpostMessageHistoryNavigation state):克隆/反序列化相对调用方/当前 realm 定义;
  4. 以调用方 JS 回调/构造函数/对象作为策略输入的 APICustomElementRegistry、Trusted Types、keyframes、CSS 属性注册;
  5. 显式保留为 caller-realm 行为的 WPT/规范豁免:包括 SourceBuffer.bufferedAbortSignal.abort()/timeout()

仓库 IDL 中可检索到这些类别的实际标注,例如 History.idlNavigation.idlCustomElementRegistry.idlMessagePort.idlLocation.idlTrustedTypePolicyFactory.idl 等文件均包含 [NeedsCallerRealm]

八、实践指引:为新 IDL 成员选择 realm 策略

结合上述规则与源码,在编写或审查 LibWeb IDL 及生成代码时可遵循以下决策路径:

  1. 默认不加任何扩展属性——实例操作/属性、迭代辅助代码将自动落在 this_object_realm,DOMException、返回值包装与 promise 创建均在接收者 realm 中进行;
  2. 静态操作/构造函数/命名空间成员——无需(也不能)标注,生成器经 member_realm_expr()is_static / interface.is_namespace 分支自动使用调用方 realm;
  3. 仅当规范算法明确要求调用方 realm 时,在成员上标注 [NeedsCallerRealm];对属性标注一次即同时作用于生成的 getter 与 setter;
  4. [ImplementedInBindings] 且辅助函数不需要 realm 的构造函数,标注 [RealmFreeConstructor]
  5. 新增生成器发射器时,realm 表达式一律调用 realms.pymember_realm_expr(),不要在各发射器中自行拼写策略。

需要强调的是该策略的边界:它只影响绑定层生成代码中异常/错误对象构造与包装所选择的 realm,以及传给实现辅助函数的 realm 参数;它不改变 VM 的当前 realm 或执行上下文,作者脚本始终在其自身 realm 中执行,嵌套脚本的异常抛出行为不受转换块中的 TypeError 域覆盖影响。

小结

Ladybird 的 LibWeb 绑定 realm 策略可以概括为三条主线:实例成员 → 接收者 realm(this_object_realm静态/构造/命名空间 → 调用方 realm[NeedsCallerRealm] 为实例成员的唯一 opt-out,并由 realms.pymember_realm_expr() 作为单一生成入口统一贯彻到操作、属性、命名/索引属性与重载仲裁等所有发射器中。配合 this_value_realm()WindowProxy 主世界代理的特殊解析(跟随当前 [[Window]] realm)以及对转换块 TypeError 构造域的窄化限定,LibWeb 在不切换 VM 执行域的前提下,为所有绑定成员提供了一致且可观察的 realm 身份。该策略文档(REALM_POLICY.md)本身即作为 realms.py 内联注释的权威引用,是维护 LibWeb 绑定层时最直接的规范依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341