Web IDL 绑定

注意

需要记录索引和命名设置器/创建器/删除器的设置。

基于两方面内容,在构建时生成 Web IDL 绑定:实际的 Web IDL 文件和一个配置文 件,该文件列出了有关如何将 Web IDL 反映到 Gecko 内部代码的一些元数据。

所有 Web IDL 文件都应放在 dom/webidl 中,并添加到该目录中 moz.build 文件中的列表中。

请注意,如果您添加新的接口,则 dom/tests/mochitest/general/test_interfaces.html 中的测试很可能会失败。这表示您需要获得 DOM 专家 的审查。不要急于将您的接口添加到 moz.build 列表中而无需审查;这只会惹恼 DOM 专家,他们会让您无论如何都要进行审查。

配置文件 dom/bindings/Bindings.conf 本质上是一个 Python 字典,它将接口名称映射到有关接口的信息,称为 *描述符*。这里有各种可能的选项可以处理各种边缘情况,但大多数描述符可以非常简单。

所有生成的代码都放置在 mozilla::dom 命名空间中。对于每个接口,都会创建一个命名空间,其名称为接口名称加上 Binding,并且与该接口绑定相关的所有内容都位于该命名空间中。

dom/bindings 中有各种辅助对象和实用程序方法,它们也都在 mozilla::dom 命名空间中,并且它们的头文件都导出到 mozilla/dom(通过构建过程放置在 $OBJDIR/dist/include 中)。

向类添加 Web IDL 绑定

要为接口 MyInterface 添加 Web IDL 绑定到类 mozilla::dom::MyInterface(该类应该实现该接口),您需要执行以下操作

  1. 如果您的接口不继承自任何其他接口,请从 nsWrapperCache 继承并将其连接到循环收集器,以便它能够正确地跟踪包装器缓存。请注意,如果您的对象只能创建,而不能从其他对象获取,则您可能不需要执行此操作。如果您也继承自 nsISupports,请确保 nsISupports 在您的父类列表中位于 nsWrapperCache 之前。如果您的接口 *确实* 继承自另一个接口,只需从另一个接口对应的 C++ 类型继承即可。

    如果您确实需要连接循环收集,则在也继承自 nsISupports 的常见情况下,它将如下所示

    // Add strong pointers your class holds here. If you do, change to using
    // NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE.
    NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MyClass)
    NS_IMPL_CYCLE_COLLECTING_ADDREF(MyClass)
    NS_IMPL_CYCLE_COLLECTING_RELEASE(MyClass)
    NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MyClass)
      NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
      NS_INTERFACE_MAP_ENTRY(nsISupports)
    NS_INTERFACE_MAP_END
    
  2. 如果您的类不继承自实现 GetParentObject 的类,则添加一个名为该名称的函数,该函数对于给定类的实例,每次返回相同的对象(除非您编写了处理父对象通过重新设置 JS 包装器(如节点)而更改的显式代码)。其思想是遍历 GetParentObject 链最终会到达一个 Window,以便每个 Web IDL 对象都与特定的 Window 相关联。例如,nsINode::GetParentObject 返回节点的所有者文档。 GetParentObject 的返回类型无关紧要,除了它必须从 nsISupports 单独继承或具有相应的 ToSupports 方法,该方法可以从中生成一个 nsISupports。(这允许通过该类的非显式构造函数之一,通过编译器将返回值隐式转换为 ParentObject 实例。)如果预计会快速创建许多 MyInterface 实例,则 GetParentObject 的返回值本身也应继承自 nsWrapperCache 以获得最佳性能。在允许将结果对象与随机全局对象关联以出于安全目的的情况下,允许从 GetParentObject 返回 null;这通常不适用于公开给 Web 内容的内容。同样,如果您不需要包装器缓存,则无需执行此操作。从 GetParentObject 返回的实际类型必须在从您的实现头文件中包含的头文件中定义,以便该类型的定义对绑定代码可见。

  3. dom/webidl 中添加 MyInterface 的 Web IDL,并添加到 dom/webidl/moz.build 中的列表中。

  4. dom/bindings/Bindings.conf 中添加一个条目,设置有关接口实现的一些基本信息。如果 C++ 类型不是 mozilla::dom::MyInterface,则需要将 'nativeType' 设置为正确的类型。如果该类型不在通过替换 '::' 为 '/' 并附加 '.h' 获得的头文件中,则添加相应的 'headerFile' 注解(或 HeaderFile 注解到 .webidl 文件中)。如果您不必设置任何注解,则您也不需要添加条目,代码生成器将在此处简单地假设默认值。请注意,通常不建议使用 'headerFile' 注解。如果确实使用它,则需要确保您的头文件包含 Func 注解所需的所有头文件。

  5. 为新接口作为参数或返回值具有的任何非 Web IDL 接口,在 Bindings.conf 中添加外部接口条目。

  6. mozilla::dom::MyInterface 上实现 WrapObject 覆盖,该覆盖只是调用 mozilla::dom::MyInterface_Binding::Wrap。请注意,如果您的 C++ 类型正在实现多个不同的 Web IDL 接口,则需要在此处选择要调用的 mozilla::dom::MyInterface_Binding::Wrap。例如,请参阅 AudioContext::WrapObject

  7. mozilla::dom::MyInterface 上公开接口所需的任何方法。这些方法可以是内联的、虚函数的、具有任何调用约定等等,只要它们具有正确的参数类型和返回类型即可。您可以通过运行 mach webidl-example MyInterface 来查看函数声明应该是什么样子。这将在您的 objdir 中的 dom/bindings 中生成两个文件:MyInterface-example.hMyInterface-example.cpp,它们展示了使用继承自 nsISupports 并具有包装器缓存的类的接口的基本实现。

请参阅此 将 window.performance.* 迁移到 Web IDL 绑定的示例补丁

注意

如果您的对象只能通过创建它来反映到 JS 中,而不能通过从某个地方检索它来反映到 JS 中,则可以跳过上述步骤 1 和 2,而是将 'wrapperCache': False 添加到您的描述符中。您需要将返回对象的功能标记为 [NewObject] 在 Web IDL 中。如果您的对象不是引用计数的,则返回它的函数的返回值应返回一个 UniquePtr。

Web IDL 结构的 C++ 反射

Web IDL 操作(方法)的 C++ 反射

Web IDL 操作会被转换为对底层 C++ 对象的方法调用。返回值类型和参数类型将根据如下所述确定。除了这些之外,所有允许抛出异常的方法都将在其参数列表中追加一个ErrorResult&参数。使用某些 Web IDL 类型(如anyobject)的非静态方法将在参数列表的开头添加一个JSContext*参数。静态方法将传递一个const GlobalObject&以表示相关的全局对象,并且可以通过调用其上的Context()方法获取JSContext*

C++ 方法的名称就是 Web IDL 操作的名称,只是第一个字母转换为大写。

Web IDL 重载会被转换为 C++ 重载:它们只是调用名称相同但签名不同的 C++ 方法。

例如,这个 Web IDL

interface MyInterface
{
  undefined doSomething(long number);
  double doSomething(MyInterface? otherInstance);

  [Throws]
  MyInterface doSomethingElse(optional long maybeNumber);
  [Throws]
  undefined doSomethingElse(MyInterface otherInstance);

  undefined doTheOther(any something);

  undefined doYetAnotherThing(optional boolean actuallyDoIt = false);

  static undefined staticOperation(any arg);
};

将需要以下方法声明

class MyClass
{
  void DoSomething(int32_t a number);
  double DoSomething(MyClass* aOtherInstance);

  already_AddRefed<MyInterface> DoSomethingElse(Optional<int32_t> aMaybeNumber,
                                                ErrorResult& rv);
  void DoSomethingElse(MyClass& aOtherInstance, ErrorResult& rv);

  void DoTheOther(JSContext* cx, JS::Handle<JS::Value> aSomething);

  void DoYetAnotherThing(bool aActuallyDoIt);

  static void StaticOperation(const GlobalObject& aGlobal, JS::Handle<JS::Value> aSomething);
}

Web IDL 属性的 C++ 反射

Web IDL 属性会被转换为底层 C++ 对象上的 getter 和 setter 方法调用对。只读属性只有 getter,没有 setter。

getter 的名称是属性的名称,第一个字母转换为大写。如果满足以下任何条件,则会在其前面添加Get

  1. 属性类型是可空的。

  2. getter 可以抛出异常。

  3. 属性的返回值通过 C++ 中的输出参数返回。

getter 的方法签名就像一个没有参数并且返回值类型为属性类型操作一样。

setter 的名称是Set后跟属性名称,第一个字母转换为大写。方法签名就像一个返回值为未定义并且只有一个参数(类型为属性类型)的操作一样。

Web IDL 构造函数的 C++ 反射

Web IDL 构造函数会被转换为名为Constructor的静态类方法。此方法的参数将是 Web IDL 构造函数的参数,并在前面添加一个const GlobalObject&以表示相关的全局对象。对于非工作线程的情况,全局对象通常是 DOM 窗口的内部窗口,构造函数附加到该窗口上。如果由于某些参数类型也需要JSContext*,它将位于全局对象之后。对于MyInterface的构造函数的返回值与返回MyInterface实例的方法的返回值完全相同。构造函数始终允许抛出异常。

例如,此 IDL

interface MyInterface {
  constructor();
  constructor(unsigned long someNumber);
};

将在MyClass中需要以下声明

class MyClass {
  // Various nsISupports stuff or whatnot
  static
  already_AddRefed<MyClass> Constructor(const GlobalObject& aGlobal,
                                        ErrorResult& rv);
  static
  already_AddRefed<MyClass> Constructor(const GlobalObject& aGlobal,
                                        uint32_t aSomeNumber,
                                        ErrorResult& rv);
};

Web IDL 类型的 C++ 反射

Web IDL 类型的精确 C++ 表示形式可能取决于它们的使用方式(例如,返回值、参数以及序列或字典成员可能都有不同的表示形式)。

除非另有说明,否则一种类型只有一种表示形式。此外,除非另有说明,否则可空类型将通过在基本类型周围包装Nullable<>来表示。

在所有情况下,没有默认值的可选参数都将通过在参数类型的表示形式周围包装const Optional<>&来表示。如果参数类型是 C++ 引用,它还将在过程中变为NonNull<>,围绕对象的实际类型。具有默认值的可选参数仅由参数类型本身表示,如果实际上没有传入参数,则将其设置为默认值。

可变参数的 Web IDL 参数将被视为const Sequence<>&,围绕实际的参数类型。

这是一个表格,有关更多详细信息和说明,请参阅下面的具体部分。

Web IDL 类型 参数类型 返回值类型 字典/成员类型
any JS::Handle<JS::Value> JS::MutableHandle<JS::Value> JS::Value
boolean bool bool bool
byte int8_t int8_t int8_t
ByteString const nsACString& nsCString& (outparam)
nsACString& (outparam)
nsCString
Date mozilla::dom::Date
DOMString const nsAString& mozilla::dom::DOMString& (outparam)
nsAString& (outparam)
nsString& (outparam)
nsString
UTF8String const nsACString& (outparam) nsACString& nsCString
double double double double
float float float float
interface
non-nullable
Foo& already_addRefed<Foo> OwningNonNull<Foo>
interface
nullable
Foo* already_addRefed<Foo>
Foo*
RefPtr<Foo>
long int32_t int32_t int32_t
long long int64_t int64_t int64_t
object JS::Handle<JSObject*> JS::MutableHandle<JSObject*> JSObject*
octet uint8_t uint8_t uint8_t
sequence const Sequence<T>& nsTArray<T>& (outparam)
short int16_t int16_t int16_t
unrestricted double double double double
unrestricted float float float float
unsigned long uint32_t uint32_t uint32_t
unsigned long long uint64_t uint64_t uint64_t
unsigned short uint16_t uint16_t uint16_t
USVString const nsAString& mozilla::dom::DOMString& (outparam)
nsAString& (outparam)
nsString& (outparam)
nsString

any

any 有三种不同的表示方式,具体取决于使用场景

  • any 参数将变为JS::Handle<JS::Value>。它们将位于传入的 JSContext 的隔间中。

  • any 返回值将变为附加到参数列表的JS::MutableHandle<JS::Value>输出参数。它位于所有 IDL 参数之后,但在方法的ErrorResult&(如果有)之前。返回值可以位于任何隔间中;绑定将根据需要将其包装到上下文隔间中。

  • any 字典成员和序列元素将变为JS::Value。字典成员和序列元素将由将序列或字典放入堆栈的人保证标记,使用SequenceRooterDictionaryRooter

使用any的方法始终获得JSContext*参数。

例如,这个 Web IDL

interface Test {
  attribute any myAttr;
  any myMethod(any arg1, sequence<any> arg2, optional any arg3);
};

将对应于以下 C++ 函数声明

void MyAttr(JSContext* cx, JS::MutableHandle<JS::Value> retval);
void SetMyAttr(JSContext* cx, JS::Handle<JS::Value> value);
void MyMethod(JSContext* cx, JS::Handle<JS::Value> arg1,
              const Sequence<JS::Value>& arg2,
              const Optional<JS::Handle<JS::Value>>& arg3,
              JS::MutableHandle<JS::Value> retval);

boolean

Web IDL 类型boolean用 C++ bool表示。

例如,这个 Web IDL

interface Test {
  attribute boolean myAttr;
  boolean myMethod(optional boolean arg);
};

将对应于以下 C++ 函数声明

bool MyAttr();
void SetMyAttr(bool value);
bool MyMethod(const Optional<bool>& arg);

整数类型

整数 Web IDL 类型映射到相应的 C99 stdint 类型。

例如,这个 Web IDL

interface Test {
  attribute short myAttr;
  long long myMethod(unsigned long? arg);
};

将对应于以下 C++ 函数声明

int16_t MyAttr();
void SetMyAttr(int16_t value);
int64_t MyMethod(const Nullable<uint32_t>& arg);

浮点类型

浮点 Web IDL 类型映射到同名的 C++ 类型。因此floatunrestricted float变为 C++ float,而doubleunrestricted double变为 C++ double

例如,这个 Web IDL

interface Test {
  float myAttr;
  double myMethod(unrestricted double? arg);
};

将对应于以下 C++ 函数声明

float MyAttr();
void SetMyAttr(float value);
double MyMethod(const Nullable<double>& arg);

DOMString

字符串有三种不同的反射方式,具体取决于使用场景

  • 字符串参数将变为const nsAString&

  • 字符串返回值将变为附加到参数列表的mozilla::dom::DOMString&输出参数。它位于所有 IDL 参数之后,但在方法的ErrorResult&(如果有)之前。请注意,这允许被调用者根据需要将其方法声明为接受nsAString&nsString&

  • 序列、字典、拥有联合和可变参数中的字符串将变为nsString

可空字符串由与不可空字符串相同的类型表示,但字符串将针对DOMStringIsNull()返回 true。可以使用SetDOMStringToNull在输出参数上返回 null 作为字符串值(如果它是nsAString)或在DOMString上调用SetNull()

例如,这个 Web IDL

interface Test {
  DOMString myAttr;
  [Throws]
  DOMString myMethod(sequence<DOMString> arg1, DOMString? arg2, optional DOMString arg3);
};

将对应于以下 C++ 函数声明

void GetMyAttr(nsString& retval);
void SetMyAttr(const nsAString& value);
void MyMethod(const Sequence<nsString>& arg1, const nsAString& arg2,
              const Optional<nsAString>& arg3, nsString& retval, ErrorResult& rv);

USVString

USVString的反射方式与DOMString相同。

UTF8String

UTF8String是内容保证有效的 UTF-8 字符串。它不是 Web IDL 规范中的标准,但其可观察对象与USVString相同。

当规范允许使用USVString但您想将字符串作为 UTF-8 而不是 UTF-16 处理时,它非常合适。

它有三种不同的反射方式,具体取决于使用场景

  • 参数将变为const nsACString&

  • 返回值将变为附加到参数列表的nsACString&输出参数。它位于所有 IDL 参数之后,但在方法的ErrorResult&(如果有)之前。

  • 在序列、字典拥有联合和可变参数中,它将变为nsCString

可空UTF8String由与不可空字符串相同的类型表示,但字符串将针对IsVoid()返回 true。可以使用SetIsVoid()在输出参数上返回 null 作为字符串值。

ByteString

ByteString有三种不同的反射方式,具体取决于使用场景

  • ByteString参数将变为const nsACString&

  • ByteString返回值将变为附加到参数列表的nsCString&输出参数。它位于所有 IDL 参数之后,但在方法的ErrorResult&(如果有)之前。

  • ByteString在序列、字典拥有联合和可变参数中将变为nsCString

可空ByteString由与不可空字符串相同的类型表示,但字符串将针对IsVoid()返回 true。可以使用SetIsVoid()在输出参数上返回 null 作为字符串值。

object

object 有三种不同的表示方式,具体取决于使用场景。

  • object 参数会转换为 JS::Handle<JSObject*>。它们将位于传入的 JSContext 的隔离区中。

  • object 返回值会转换为一个 JS::MutableHandle<JSObject*> 输出参数,并追加到参数列表的末尾。它位于所有 IDL 参数之后,但在方法的 ErrorResult&(如果有)之前。返回值可以在任何隔离区中;绑定会根据需要将其包装到上下文隔离区中。

  • object 字典成员和序列元素会转换为 JSObject*。字典成员和序列元素保证会被将序列或字典放到栈上的实体进行标记,使用 SequenceRooterDictionaryRooter

使用 object 的方法始终会获得一个 JSContext* 参数。

例如,这个 Web IDL

interface Test {
  object myAttr;
  object myMethod(object arg1, object? arg2, sequence<object> arg3, optional object arg4,
                  optional object? arg5);
};

将对应于以下 C++ 函数声明

void GetMyAttr(JSContext* cx, JS::MutableHandle<JSObject*> retval);
void SetMyAttr(JSContext* cx, JS::Handle<JSObject*> value);
void MyMethod(JSContext* cx, JS::Handle<JSObject*> arg1, JS::Handle<JSObject*> arg2,
              const Sequence<JSObject*>& arg3,
              const Optional<JS::Handle<JSObject*>>& arg4,
              const Optional<JS::Handle<JSObject*>>& arg5,
              JS::MutableHandle<JSObject*> retval);

接口类型

Web IDL 绑定中有四种接口类型。回调接口用于表示浏览器代码可以调用的脚本对象。外部接口用于表示尚未转换为 Web IDL 绑定的对象。Web IDL 接口用于表示 Web IDL 绑定对象。“SpiderMonkey” 接口用于表示 JavaScript 引擎原生实现的对象(例如,类型化数组)。

回调接口

在 C++ 中,回调接口表示为继承自 mozilla::dom::CallbackInterface 的对象,其名称(在 mozilla::dom 命名空间中)与 Web IDL 中回调接口的名称匹配。确切的表示方式取决于类型的使用方式。

  • 可空参数会转换为 Foo*

  • 不可空参数会转换为 Foo&

  • 返回值会转换为 already_AddRefed<Foo>Foo*,具体取决于需求。指针形式更受青睐,因为它会生成更快的代码,但仅当返回值未进行引用计数时才能使用(因此,仅当被调用者至少在绑定方法返回之前一直保持返回值存活时才能使用)。

  • 序列、字典、拥有联合体和可变参数中的 Web IDL 回调接口,如果可空则表示为 RefPtr<Foo>,否则表示为 OwningNonNull<Foo>

如果接口是单操作接口,则该对象会公开两个方法,这两个方法都会调用相同的底层 JS 可调用对象。第一个方法允许调用者传入一个 this 对象,而第二个方法则默认为 undefined 作为 this 值。在这两种情况下,仅当回调接口由 JS 可调用对象实现时才会使用 this 值。如果它由一个对象实现,并且该对象具有一个名称与操作匹配的属性,则始终使用该对象本身作为 this

如果接口不是单操作接口,则它会为每个 IDL 方法/getter/setter 公开一个方法。

方法的签名对应于抛出 IDL 方法/getter/setter 的签名,并带有一个额外的尾随 mozilla::dom::CallbackObject::ExceptionHandling aExceptionHandling 参数,默认为 eReportExceptions。如果 aReportExceptions 设置为 eReportExceptions,则方法会在返回之前报告 JS 异常。如果 aReportExceptions 设置为 eRethrowExceptions,则 JS 异常将被存储在 ErrorResult 中,并在栈展开到设置 ErrorResult 的位置时报告。

例如,这个 Web IDL

callback interface MyCallback {
  attribute long someNumber;
  short someMethod(DOMString someString);
};
callback interface MyOtherCallback {
  // single-operation interface
  short doSomething(Node someNode);
};
interface MyInterface {
  attribute MyCallback foo;
  attribute MyCallback? bar;
};

将在 mozilla::dom 命名空间中生成这些 C++ 类声明。

class MyCallback : public CallbackInterface
{
  int32_t GetSomeNumber(ErrorResult& rv, ExceptionHandling aExceptionHandling = eReportExceptions);
  void SetSomeNumber(int32_t arg, ErrorResult& rv,
                     ExceptionHandling aExceptionHandling = eReportExceptions);
  int16_t SomeMethod(const nsAString& someString, ErrorResult& rv,
                     ExceptionHandling aExceptionHandling = eReportExceptions);
};

class MyOtherCallback : public CallbackInterface
{
public:
  int16_t
  DoSomething(nsINode& someNode, ErrorResult& rv,
              ExceptionHandling aExceptionHandling = eReportExceptions);

  template<typename T>
  int16_t
  DoSomething(const T& thisObj, nsINode& someNode, ErrorResult& rv,
              ExceptionHandling aExceptionHandling = eReportExceptions);
};

以及在 MyInterface 的实现上进行这些 C++ 函数声明。

already_AddRefed<MyCallback> GetFoo();
void SetFoo(MyCallback&);
already_AddRefed<MyCallback> GetBar();
void SetBar(MyCallback*);

MyCallback 的使用者可以像这样使用它。

void
SomeClass::DoSomethingWithCallback(MyCallback& aCallback)
{
  ErrorResult rv;
  int32_t number = aCallback.GetSomeNumber(rv);
  if (rv.Failed()) {
    // The error has already been reported to the JS console; you can handle
    // things however you want here.
    return;
  }

  // For some reason we want to catch and rethrow exceptions from SetSomeNumber, say.
  aCallback.SetSomeNumber(2*number, rv, eRethrowExceptions);
  if (rv.Failed()) {
    // The exception is now stored on rv. This code MUST report
    // it usefully; otherwise it will assert.
  }
}
外部接口

在 C++ 中,外部接口表示为 XPConnect 知道如何解包的对象。这可以表示 XPCOM 接口(无论是否在 XPIDL 中声明)或表示某种类型,并且存在可转换的原生解包函数。要使用的 C++ 类型应为 Bindings.conf 文件中外部接口列出的 nativeType。确切的表示方式取决于类型的使用方式。

  • 参数会转换为 nsIFoo*

  • 返回值可以是 already_AddRefed<nsIFoo>nsIFoo*,具体取决于需求。指针形式更受青睐,因为它会生成更快的代码,但仅当返回值未进行引用计数时才能使用(因此,仅当被调用者至少在绑定方法返回之前一直保持返回值存活时才能使用)。

  • 序列、字典、拥有联合体和可变参数中的外部接口表示为 RefPtr<nsIFoo>

Web IDL 接口

在 C++ 中,Web IDL 接口表示为 C++ 类。所涉及的类必须是引用计数的,或者必须在 Bindings.conf 中明确注释为由 JS 对象直接拥有。如果该类继承自 nsISupports,则规范的 nsISupports 必须位于对象的继承链的主要位置。如果接口具有父接口,则对应于父接口的 C++ 类必须位于对象的继承链的主要位置。这保证了可以将 void* 存储在 JSObject 中,然后可以将其 reinterpret_cast 为对应于对象实现的接口的任何类。要使用的 C++ 类型应为 Bindings.conf 文件中接口列出的 nativeType,或者如果未列出则为 mozilla::dom::InterfaceName。确切的表示方式取决于类型的使用方式。

  • 可空参数会转换为 Foo*

  • 不可空参数会转换为 Foo&

  • 返回值会转换为 already_AddRefed<Foo>Foo*,具体取决于需求。指针形式更受青睐,因为它会生成更快的代码,但仅当返回值未进行引用计数时才能使用(因此,仅当被调用者至少在绑定方法返回之前一直保持返回值存活时才能使用)。

  • 序列、字典、拥有联合体和可变参数中的 Web IDL 接口,如果可空则表示为 RefPtr<Foo>,否则表示为 OwningNonNull<Foo>

例如,这个 Web IDL

interface MyInterface {
  attribute MyInterface myAttr;
  undefined passNullable(MyInterface? arg);
  MyInterface? doSomething(sequence<MyInterface> arg);
  MyInterface doTheOther(sequence<MyInterface?> arg);
  readonly attribute MyInterface? nullableAttr;
  readonly attribute MyInterface someOtherAttr;
  readonly attribute MyInterface someYetOtherAttr;
};

将对应于这些 C++ 函数声明。

already_AddRefed<MyClass> MyAttr();
void SetMyAttr(MyClass& value);
void PassNullable(MyClass* arg);
already_AddRefed<MyClass> doSomething(const Sequence<OwningNonNull<MyClass>>& arg);
already_AddRefed<MyClass> doTheOther(const Sequence<RefPtr<MyClass>>& arg);
already_Addrefed<MyClass> GetNullableAttr();
MyClass* SomeOtherAttr();
MyClass* SomeYetOtherAttr(); // Don't have to return already_AddRefed!
“SpiderMonkey” 接口

类型化数组、数组缓冲区和数组缓冲区视图参数由 TypedArray.h 中的对象表示。例如,此 Web IDL

interface Test {
  undefined passTypedArrayBuffer(ArrayBuffer arg);
  undefined passTypedArray(ArrayBufferView arg);
  undefined passInt16Array(Int16Array? arg);
}

将对应于以下 C++ 函数声明

void PassTypedArrayBuffer(const ArrayBuffer& arg);
void PassTypedArray(const ArrayBufferView& arg);
void PassInt16Array(const Nullable<Int16Array>& arg);

类型化数组返回值会转换为一个 JS::MutableHandle<JSObject*> 输出参数,并追加到参数列表的末尾。它位于所有 IDL 参数之后,但在方法的 ErrorResult&(如果有)之前。返回值可以在任何隔离区中;绑定会根据需要将其包装到上下文隔离区中。

类型化数组存储一个 JSObject*,因此需要正确地进行根化。栈上的类型化数组可以声明为 RootedTypedArray<TypedArrayType>(例如 RootedTypedArray<Int16Array>)。堆上的类型化数组需要进行跟踪。

字典类型

字典参数表示为对结构体的常量引用,该结构体的名称为 mozilla::dom 命名空间中的字典名称。该结构体为字典的每个成员都有一个成员,其名称相同,但第一个字母大写并在前面加上“m”。必需的或具有默认值的成员具有本文档中相应 Web IDL 类型下所述的类型。不是必需的且没有默认值的成员具有用 Optional<> 包裹的这些类型。

字典返回值表示为输出参数,其类型为对上述结构体的非常量引用,所有具有默认值的成员都预先初始化为这些默认值。

请注意,可选字典参数始终会被 IDL 解析器和代码生成器强制具有空字典的默认值,因此字典参数永远不会用 Optional<> 包裹。

如有必要,可以通过调用其 Init() 方法,在 C++ 代码中从 JS::Value 直接初始化字典。执行此操作的使用者应将其字典声明为 RootedDictionary<DictionaryName>。执行此操作时,如果传入的 JS::ValueJS::NullValue(),则允许传入空 JSContext*。同样,可以通过使用字典作为第二个参数调用 ToJSValue,在 C++ 中将字典结构体转换为 JS::Value。如果 Init()ToJSValue() 返回 false,则它们通常会在 JSContext 上设置一个挂起的异常;报告这些异常是调用者的责任。

例如,这个 Web IDL

dictionary Dict {
  long foo = 5;
  DOMString bar;
};

interface Test {
  undefined initSomething(optional Dict arg = {});
};

将对应于此 C++ 函数声明。

void InitSomething(const Dict& arg);

并且 Dict 结构体将如下所示。

struct Dict {
  bool Init(JSContext* aCx, JS::Handle<JS::Value> aVal, const char* aSourceDescription = "value");

  Optional<nsString> mBar;
  int32_t mFoo;
}

请注意,字典成员在结构体中按字母顺序排序。

用于处理字典的 API

字典和字典成员上有一些有用的方法,您可以使用这些方法快速确定有用的信息。

  • member.WasPassed() - 顾名思义,是否传递了特定成员?(例如,if (arg.foo.WasPassed() { /* do nice things!*/ }

  • dictionary.IsAnyMemberPresent() - 非常适合检查是否需要执行任何操作。(例如,if (!arg.IsAnyMemberPresent()) return; // nothing to do

  • member.Value() - 获取已传递成员的实际数据/值。(例如,mBar.Assign(args.mBar.value())

使用以上所有内容的示例实现。

void
MyInterface::InitSomething(const Dict& aArg){
  if (!aArg.IsAnyMemberPresent()) {
    return; // nothing to do!
  }
  if (aArg.mBar.WasPassed() && !mBar.Equals(aArg.mBar.value())) {
    mBar.Assign(aArg.mBar.Value());
  }
}

枚举类型

Web IDL 枚举类型表示为 C++ 枚举类。C++ 枚举的值通过获取 Web IDL 枚举中的字符串,将所有非字母数字替换为下划线,并将第一个字母大写来命名,其中空字符串是一个特例,它将成为值 _empty

对于名为 MyEnum 的 Web IDL 枚举,C++ 枚举命名为 MyEnum 并放置在 mozilla::dom 命名空间中,而值则放置在 mozilla::dom::MyEnum 命名空间中。

枚举类的类型会自动选择为可以容纳所有值的最小无符号整数类型。在实践中,这始终是 uint8_t,因为 Web IDL 枚举往往不超过 255 个值。

例如,这个 Web IDL

enum MyEnum {
  "something",
  "something-else",
  "",
  "another"
};

将生成此 C++ 枚举声明。

enum class MyEnum : uint8_t {
  Something,
  Something_else,
  _empty,
  Another
};

mozilla::dom::GetEnumString 是一个模板化辅助函数,在 BindingUtils.h 中声明并导出到 mozilla/dom/BindingUtils.h,可用于将枚举值转换为其对应的字符串值。它返回一个包含字符串值的 const nsCString&

mozilla::dom::StringToEnumBindingUtils.h 中的一个模板化辅助函数,并导出到 mozilla/dom/BindingUtils.h,可用于将字符串转换为相应的枚举值。它需要使用枚举类作为模板参数,并返回一个 mozilla::Maybe<Enum>。如果作为参数传入的字符串值不是枚举的字符串值之一,则它返回 mozilla::Nothing(),否则它在 mozilla::Maybe 中返回正确的枚举值。

mozilla::dom::WebIDLEnumSerializerBindingIPCUtils.h 中的一个模板化别名,导出到 mozilla/dom/BindingIPCUtils.h,以实现具有正确 WebIDL 枚举验证的 IPC 序列化程序。它使用为每个 WebIDL 枚举生成的 mozilla::MaxContinuousEnumValue 来实现验证。

mozilla::dom::MakeWebIDLEnumeratedRangeBindingUtils.h 中的一个模板化辅助函数,并导出到 mozilla/dom/BindingUtils.h,可用于为 WebIDL 枚举创建 mozilla::EnumeratedRange

回调函数类型

回调函数表示为一个对象,继承自 mozilla::dom::CallbackFunction,其名称在 mozilla::dom 命名空间中与 Web IDL 中回调函数的名称匹配。如果类型是可空的,则传入指针;否则传入引用。

该对象公开了两个 Call 方法,这两个方法都调用底层的 JS 可调用对象。第一个 Call 方法与声明方式与回调函数相同的抛出方法具有相同的签名,并带有一个额外的尾随 mozilla::dom::CallbackObject::ExceptionHandling aExceptionHandling 参数,默认为 eReportExceptions,调用它将使用 undefined 作为 this 值来调用可调用对象。第二个 Call 方法允许将显式 this 值作为第一个参数传入。此第二个调用方法是第一个参数类型的模板,因此 this 值可以以最方便的形式传入,只要它是可以被 XPConnect 包装的类型或 Web IDL 接口类型即可。

如果 aReportExceptions 设置为 eReportExceptions,则 Call 方法将在返回前报告 JS 异常。如果 aReportExceptions 设置为 eRethrowExceptions,则 JS 异常将存储在 ErrorResult 中,并在堆栈展开到设置 ErrorResult 的位置时报告。

例如,这个 Web IDL

callback MyCallback = long (MyInterface arg1, boolean arg2);
interface MyInterface {
  attribute MyCallback foo;
  attribute MyCallback? bar;
};

将导致在 mozilla::dom 命名空间中出现此 C++ 类声明

class MyCallback : public CallbackFunction
{
public:
  int32_t
  Call(MyInterface& arg1, bool arg2, ErrorResult& rv,
       ExceptionHandling aExceptionHandling = eReportExceptions);

  template<typename T>
  int32_t
  Call(const T& thisObj, MyInterface& arg1, bool arg2, ErrorResult& rv,
       ExceptionHandling aExceptionHandling = eReportExceptions);
};

以及在 MyInterface 类中的这些 C++ 函数声明

already_AddRefed<MyCallback> GetFoo();
void SetFoo(MyCallback&);
already_AddRefed<MyCallback> GetBar();
void SetBar(MyCallback*);

MyCallback 的使用者可以像这样使用它。

void
SomeClass::DoSomethingWithCallback(MyCallback& aCallback, MyInterface& aInterfaceInstance)
{
  ErrorResult rv;
  int32_t number = aCallback.Call(aInterfaceInstance, false, rv);
  if (rv.Failed()) {
    // The error has already been reported to the JS console; you can handle
    // things however you want here.
    return;
  }

  // Now for some reason we want to catch and rethrow exceptions from the callback,
  // and use "this" as the this value for the call to JS.
  number = aCallback.Call(*this, true, rv, eRethrowExceptions);
  if (rv.Failed()) {
    // The exception is now stored on rv.  This code MUST report
    // it usefully; otherwise it will assert.
  }
}

序列

序列参数由 const Sequence<T>& 表示,其中 T 取决于 Web IDL 序列中元素的类型。

序列返回值由附加到参数列表中的 nsTArray<T> 输出参数表示,其中 T 是 Web IDL 序列元素的返回类型。它位于所有 IDL 参数之后,但在 ErrorResult&(如果有)之前,用于该方法。

数组

IDL 数组对象尚不支持。关于这些内容的规范可能会发生很大变化。

联合类型

联合类型反映为 mozilla::dom 命名空间中的结构体。联合结构体有两种:一种不保持其成员存活(为“非拥有”),另一种保持存活(为“拥有”)。非拥有联合的常量引用用于普通参数。拥有联合用于字典、序列和可变参数。联合返回值成为非 const 拥有联合输出参数。结构体的名称是联合中类型名称的连接,并在它们之间插入“Or”,对于拥有结构体,则在前面加上“Owning”。例如,此 IDL

undefined passUnion((object or long) arg);
(object or long) receiveUnion();
undefined passSequenceOfUnions(sequence<(object or long)> arg);
undefined passOtherUnion((HTMLDivElement or ArrayBuffer or EventInit) arg);

将对应于这些 C++ 函数声明

void PassUnion(const ObjectOrLong& aArg);
void ReceiveUnion(OwningObjectObjectOrLong& aArg);
void PassSequenceOfUnions(const Sequence<OwningObjectOrLong>& aArg);
void PassOtherUnion(const HTMLDivElementOrArrayBufferOrEventInit& aArg);

联合结构体公开了访问器,用于测试它们是否为给定类型以及获取该类型的數據。它们还公开了设置器,用于将联合设置为特定类型并返回联合内部存储的引用,该存储可以存储该类型。唯一的例外是 object 类型,它使用稍微不同的设置器形式,其中 JSObject* 直接传入。例如,ObjectOrLong 将具有以下方法

bool IsObject() const;
JSObject* GetAsObject() const;
void SetToObject(JSContext*, JSObject*);
bool IsLong() const;
int32_t GetAsLong() const;
int32_t& SetAsLong()

在堆栈上使用的拥有联合应声明为 RootedUnion<UnionType>,例如 RootedUnion<OwningObjectOrLong>

Date

Web IDL Date 类型由 mozilla::dom::Date 结构体表示。

Web IDL 声明的 C++ 反射

Web IDL 声明(maplike/setlike/iterable)将转换为它们声明的接口上的属性和函数集。每个都有一组不同的辅助函数。此外,对于 iterable,接口开发人员需要实现 C++ 函数。

Maplike

示例接口

interface StringToLongMap {
  maplike<DOMString, long>;
};

此接口的绑定将生成映射的存储结构,以及从 C++ 访问该结构的辅助函数。生成的 C++ API 将如下所示

namespace StringToLongMapBinding {
namespace MaplikeHelpers {
void Clear(mozilla::dom::StringToLongMap* self, ErrorResult& aRv);
bool Delete(mozilla::dom::StringToLongMap* self, const nsAString& aKey, ErrorResult& aRv);
bool Has(mozilla::dom::StringToLongMap* self, const nsAString& aKey, ErrorResult& aRv);
void Set(mozilla::dom::StringToLongMap* self, const nsAString& aKey, int32_t aValue, ErrorResult& aRv);
} // namespace MaplikeHelpers
} // namespace StringToLongMapBindings

Setlike

示例接口

interface StringSet {
  setlike<DOMString>;
};

此接口的绑定将生成集合的存储结构,以及从 c++ 访问该结构的辅助函数。生成的 C++ API 将如下所示

namespace StringSetBinding {
namespace SetlikeHelpers {
void Clear(mozilla::dom::StringSet* self, ErrorResult& aRv);
bool Delete(mozilla::dom::StringSet* self, const nsAString& aKey, ErrorResult& aRv);
bool Has(mozilla::dom::StringSet* self, const nsAString& aKey, ErrorResult& aRv);
void Add(mozilla::dom::StringSet* self, const nsAString& aKey, ErrorResult& aRv);
} // namespace SetlikeHelpers
}

Iterable

与 maplike 和 setlike 不同,iterable 没有任何 C++ 辅助函数,因为接口的 iterable 数据支持结构由开发人员决定。考虑到这一点,生成的 iterable 绑定期望包装器对象为接口提供某些方法进行访问。

Iterable 接口有不同的要求,具体取决于它们是单值迭代器还是对值迭代器。

单值迭代器示例接口

interface LongIterable {
  iterable<long>;
  getter long(unsigned long index);
  readonly attribute unsigned long length;
};

对于单值迭代器接口,我们根据规范的要求将其视为 索引获取器。有关构建此类结构的更多信息,请参阅 索引获取器实现部分

对值迭代器示例接口

interface StringAndLongIterable {
  iterable<DOMString, long>;
};

此对值迭代器接口的绑定要求在 C++ 对象中实现以下方法

class StringAndLongIterable {
public:
  // Returns the number of items in the iterable storage
  size_t GetIterableLength();
  // Returns key of pair at aIndex in iterable storage
  nsAString& GetKeyAtIndex(uint32_t aIndex);
  // Returns value of pair at aIndex in iterable storage
  uint32_t& GetValueAtIndex(uint32_t aIndex);
}

字符串化器

Web IDL 中命名的字符串化器操作将只调用相应的 C++ 方法。

Web IDL 中的匿名字符串化器将调用名为 Stringify 的 C++ 方法。因此,例如,给定此 IDL

interface FirstInterface {
  stringifier;
};

interface SecondInterface {
  stringifier DOMString getStringRepresentation();
};

相应的 C++ 将是

class FirstInterface {
public:
  void Stringify(nsAString& aResult);
};

class SecondInterface {
public:
  void GetStringRepresentation(nsAString& aResult);
};

旧版调用者

仅支持匿名旧版调用者,并将调用名为 LegacyCall 的 C++ 方法。这将把 JS“this”值作为第一个参数传递,然后传递给实际操作的参数。如果任何操作参数需要 JSContext,则会传递 JSContext。因此,例如,给定此 IDL

interface InterfaceWithCall {
  legacycaller long (float arg);
};

相应的 C++ 将是

class InterfaceWithCall {
public:
  int32_t LegacyCall(JS::Handle<JS::Value> aThisVal, float aArgument);
};

命名获取器

如果接口具有命名获取器,则绑定将期望 C++ 实现上的几种方法

  • 一个 NamedGetter 方法。它接受一个属性名称并返回命名获取器声明返回的任何类型。它还有一个布尔输出参数,用于指示是否应该存在具有该名称的属性。

  • 一个 NameIsEnumerable 方法。它接受一个属性名称并返回一个布尔值,指示该属性是否可枚举。

  • 一个 GetSupportedNames 方法。它接受一个无符号整数,该整数对应于传递给 iterate 代理陷阱的标志,并返回属性名称列表。对于此方法的实现,重要的标志是 JSITER_HIDDEN。如果设置了该标志,则调用需要返回所有支持的属性名称。如果没有设置,则调用需要仅返回可枚举的属性名称。

NameIsEnumerableGetSupportedNames 方法需要就哪些名称是可枚举的达成一致。NamedGetterGetSupportedNames 方法需要就哪些名称受支持达成一致。

因此,例如,给定此 IDL

interface InterfaceWithNamedGetter {
  getter long(DOMString arg);
};

相应的 C++ 将是

class InterfaceWithNamedGetter
{
public:
  int32_t NamedGetter(const nsAString& aName, bool& aFound);
  bool NameIsEnumerable(const nsAString& aName);
  undefined GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames);
};

索引获取器

如果接口具有索引获取器,则绑定将期望 C++ 实现上的以下方法

  • 一个 IndexedGetter 方法。它接受一个整数索引值并返回索引获取器声明返回的任何类型。它还有一个布尔输出参数,用于指示是否应该存在具有该索引的属性。实现必须正确设置此输出参数。如果输出参数设置为 false,则保证忽略返回值。

因此,例如,给定此 IDL

interface InterfaceWithIndexedGetter {
  getter long(unsigned long index);
  readonly attribute unsigned long length;
};

相应的 C++ 将是

class InterfaceWithIndexedGetter
{
public:
  uint32_t Length() const;
  int32_t IndexedGetter(uint32_t aIndex, bool& aFound) const;
};

从 Web IDL 方法、获取器和设置器抛出异常

明确标记为允许抛出的 Web IDL 方法、获取器和设置器具有 ErrorResult& 参数作为其最后一个参数。要抛出异常,只需在 ErrorResult& 上调用 Throw() 并从您的 C++ 返回到绑定代码即可。

在规范要求抛出 TypeError 的情况下,您应该使用 ErrorResult::ThrowTypeError() 而不是调用 Throw()

自定义扩展属性

我们的 Web IDL 解析器和代码生成器识别 Web IDL 规范中不存在的几个扩展属性。

[Alias=propName]

此扩展属性可以在方法上指定,并指示另一个具有指定名称的属性也将出现在接口原型对象上,并且将与方法的属性具有相同的 Function 对象值。例如

interface MyInterface {
  [Alias=performSomething] undefined doSomething();
};

MyInterface.prototype.performSomething 将与 MyInterface.prototype.doSomething 具有相同的 Function 对象值。

可以在一个方法上使用多个 [Alias] 扩展属性。[Alias] 不能用于静态方法,也不能用于全局接口(例如 Window)上的方法。

除了常规属性名称外,别名的名称还可以是 Symbol.iterator。这可以通过编写 [Alias="@@iterator"] 来指定。

[BindingAlias=propName]

此扩展属性可以在属性上指定,并指示另一个具有指定名称的属性也将出现在接口原型对象上,并将调用属性 getter 和 setter 的相同底层 C++ 实现。这比对两个属性使用相同的 BinaryName 更有效,因为它在它们之间共享绑定胶水代码。这些属性在 JavaScript 中仍然具有单独的 getter/setter 函数,因此从 Web 使用者的角度来看,就像您在接口上实际声明了两个单独的属性一样。例如

interface MyInterface {
  [BindingAlias=otherAttr] readonly attribute boolean attr;
};

MyInterface.prototype.otherAttrMyInterface.prototype.attr 都将存在,具有单独的 getter/setter 函数,但调用实现 MyInterface 的对象上的相同绑定胶水代码和实现函数。

可以在单个属性上使用多个 [BindingAlias] 扩展属性。

[BindingTemplate=(name, value)]

此扩展属性可以指定在属性上,并导致该属性的 getter 和 setter 转发到一个通用的生成实现,该实现与所有其他具有相同 name 参数值的 [BindingTemplate] 的属性共享。Bindings.conf 中的 TemplatedAttributes 字典需要包含一个名为 name 的模板定义。调用通用生成实现时,将传递 value 作为参数。

这针对的是非常特殊的用例,其中一个接口具有大量属性,所有这些属性都具有相同的类型,并且我们有一个对所有这些属性通用的本机实现,并且通常在实现中使用基于属性名称的一些 ID。所有使用相同模板的属性都必须具有大部分相同的扩展属性,除了少数允许不同的属性([BindingTemplate][BindingAlias][Pure]、[Pref] 和 [Func],以及 getter 和 setter 是否抛出异常的注释)。

[ChromeOnly]

此扩展属性可以指定在接口上的任何方法、属性或常量上,或在接口整体上指定。它也可以指定在字典成员上。

标记为 [ChromeOnly] 的接口成员仅在 Chrome 窗口中公开(特别是,不会公开给网页)。从网页内容的角度来看,就好像接口成员根本不存在一样。这些成员公开给通过 Xrays 使用内容对象的 Chrome 脚本。

如果在整个接口上指定,则其功能类似于 [Func],只是绑定代码会自动检查调用者脚本是否具有系统权限(是 Chrome 还是从 Chrome 页面启动的工作线程),而不是调用 C++ 实现来确定是否在全局上公开接口对象。这意味着通过 Xrays 访问内容全局将在其上显示 [ChromeOnly] 接口对象。

如果在字典成员上指定,则字典成员将仅在系统特权代码中出现。

此扩展属性可以与 [Func][Pref] 一起指定。如果指定了多个,则所有条件都需要测试为真,接口或接口成员才能公开。

[Pref=prefname]

此扩展属性可以指定在接口上的任何方法、属性或常量上,或在接口整体上指定。它也可以指定在字典成员上。它接受一个值,该值必须是从 StaticPrefs 公开的布尔首选项的名称。StaticPrefs 函数的调用方式将根据扩展属性的值计算得出,点将替换为下划线(如下例中的 StaticPrefs::my_pref_name())。

如果在接口成员上指定,则只有在首选项设置为 true 时,才会公开相关的接口成员。如何使用此功能的示例

interface MyInterface {
  attribute long alwaysHere;
  [Pref="my.pref.name"] attribute long onlyHereIfEnabled;
};

如果在整个接口上指定,则其功能类似于 [Func],只是绑定将直接检查首选项的值,而根本不调用接口的 C++ 实现。当启用检查很简单并且希望将 prefname 保留为 Web IDL 声明时,这很有用。

如果在字典成员上指定,则当 pref 设置为 false 时,Web 可观察的行为就像字典没有定义该名称的成员一样。这意味着在 JS 端不会发生任何可观察的属性获取。在 C++ 端,行为就像传入的对象没有具有相关名称的属性一样:字典成员将为 !Passed() 或具有默认值(如果存在默认值)。

如何使用此功能的示例

[Pref="my.pref.name"]
interface MyConditionalInterface {
};

此扩展属性可以与 [ChromeOnly][Func] 一起指定。如果指定了多个,则所有条件都需要测试为真,接口或接口成员才能公开。

[Func="funcname"]

此扩展属性可以指定在接口上的任何方法、属性或常量上,或在接口整体上指定。它也可以指定在字典成员上。它接受一个值,该值必须是静态函数的名称。

如果在接口成员上指定,则只有在指定的函数返回 true 时,才会公开相关的接口成员。如何使用此功能的示例

interface MyInterface {
  attribute long alwaysHere;
  [Func="MyClass::StuffEnabled"] attribute long onlyHereIfEnabled;
};

该函数使用两个参数调用:正在发生操作的 JSContext 和如果函数返回 true 则将在其上定义该对象全局的 JSObject。特别是,在 Xray 案例中,JSContext 位于调用者隔间(通常是 Chrome),但 JSObject 位于目标隔间(通常是内容)。这允许方法实现在其检查中选择它关心的隔间。

上述 IDL 还需要以下 C++ 代码

class MyClass {
  static bool StuffEnabled(JSContext* cx, JSObject* obj);
};

如果在整个接口上指定,则只有在指定的函数返回 true 时,DOM 窗口上此接口的接口对象的查找才能找到它。对于只能通过构造函数创建的对象,这允许完全禁用功能并使其看起来像该功能根本未实现。

如果在字典成员上指定,则当函数返回 false 时,Web 可观察的行为就像字典没有定义该名称的成员一样。这意味着在 JS 端不会发生任何可观察的属性获取。在 C++ 端,行为就像传入的对象没有具有相关名称的属性一样:字典成员将为 !Passed() 或具有默认值(如果存在默认值)。

如何使用 [Func] 的示例

[Func="MyClass::MyConditionalInterfaceEnabled"]
interface MyConditionalInterface {
};

在这种情况下,C++ 函数传递了一个 JS::Handle<JSObject*>。因此,在这种情况下,C++ 将如下所示

class MyClass {
  static bool MyConditionalInterfaceEnabled(JSContext* cx, JS::Handle<JSObject*> obj);
};

就像在接口成员案例中一样,JSContext 位于调用者隔间,但 JSObject 是将在此处定义属性的实际对象。在 Xray 案例中,这意味着 obj 位于目标隔间(通常是内容),而 cx 通常是 Chrome。

此扩展属性可以与 [ChromeOnly][Pref] 一起指定。如果指定了多个,则所有条件都需要测试为真,接口或接口成员才能公开。

绑定代码将包含 [Func] 所需的头文件,除非接口使用非默认头文件。如果使用非默认头文件,则该头文件需要执行 [Func] 注释所需的任何头文件包含。

[Throws][GetterThrows][SetterThrows]

用于将方法或属性标记为允许 C++ 被调用方抛出异常。这会导致绑定生成器,在许多情况下会导致 JIT,生成额外的代码来处理可能的异常。可能抛出异常的方法和属性会获得一个 ErrorResult& 参数。

[Throws] 适用于方法和属性;对于属性,这意味着 getter 和 setter 都可以抛出异常。[GetterThrows] 仅适用于属性。[SetterThrows] 仅适用于非只读属性。

对于标记为 [JSImplementation] 的接口,假定所有方法和属性都能够抛出异常,并且不需要标记为抛出异常。

[DependsOn]

用于方法或属性以指示返回值依赖于什么。可能的值是

  • Everything

    此值实际上无法显式指定;这是未指定 [DependsOn] 时获得的默认值。这意味着我们对返回值的依赖关系一无所知,因此无法重新排列可能在方法或属性周围更改值的其它代码。

  • DOMState

    返回值取决于“DOM”的状态,我们指的是通过 Web IDL 指定的所有对象。返回值保证不依赖于 JS 堆或其他 JS 引擎数据结构的状态,并且保证不会更改,除非执行了一些带有 [Affects=Everything] 的函数。

  • DeviceState

    返回值取决于我们正在运行的设备的状态(例如,系统时钟)。返回值保证不受 Gecko 本身内部运行的任何代码的影响,但即使在调用之间没有运行任何 Gecko 代码,我们也可能每次调用方法或 getter 时都会获得一个新值。

  • Nothing

    返回值是一个永不更改的常量。此值不能用于非只读属性,因为拥有一个值永不更改的非只读属性没有意义。

当与 [Affects=Nothing] 结合使用时,除了 Everything 之外的值,可以由 JIT 用于对 IDL 属性和方法的返回值执行循环提升和公共子表达式消除。

[Affects]

用于方法或属性 getter 以指示调用函数时可能受哪些状态影响。就目前而言,假设属性 setter 会影响所有内容。可能的值是

  • Everything

    此值实际上无法显式指定;这是未指定 [Affects] 时获得的默认值。这意味着调用方法或 getter 可能会更改 DOM 或 JS 堆中的任何可变状态。

  • Nothing

    调用方法或 getter 对 DOM 或 JS 堆均无副作用。

带有 [Affects=Nothing] 的方法和属性 getter 允许抛出异常,只要它们以确定性方式抛出即可。在方法的情况下,是否抛出异常允许依赖于参数,只要使用相同参数调用方法始终会抛出或不抛出异常即可。

当与 [DependsOn] 值(除了 Everything 之外)一起使用时,Nothing 值可以由 JIT 用于对 IDL 属性和方法的返回值执行循环提升和公共子表达式消除,以及过去可能依赖于系统状态但没有副作用的 DOM 方法的代码移动。

[Pure]

这是 [Affects=Nothing, DependsOn=DOMState] 的别名。以这种方式标记的属性/方法承诺,只要没有任何 [Affects=Everything] 执行,它们将保持返回相同的值。

[Constant]

这是 [Affects=Nothing, DependsOn=Nothing] 的别名。用于标记那些本可以被 [Pure] 注释且始终返回相同值的只读属性或方法。仅当绝对保证属性 getter 的返回值从 JS 引擎的角度来看始终相同时,才应使用此标记。

规范的 [SameObject] 扩展属性是 [Constant] 的别名,但只能应用于返回对象的项,而 [Constant] 可以用于任何类型的返回值。

[NeedResolve]

用于标记具有自定义解析钩子的接口。此注释将导致在对象上发生属性查找时,调用底层 C++ 类上的 DoResolve 方法。此方法的签名为:bool DoResolve(JSContext*, JS::Handle<JSObject*>, JS::Handle<jsid>, JS::MutableHandle<JS::Value>)。这里传入的对象是正在进行属性查找的对象(可能是实际 DOM 对象的 Xray),jsid 是属性名称。属性应具有的值将返回到 MutableHandle<Value> 中,UndefinedValue() 表示该属性不存在。

如果使用此扩展属性,则底层 C++ 类还必须实现一个名为 GetOwnPropertyNames 的方法,其签名为 void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames, ErrorResult& aRv)。此方法将由 JS 引擎的枚举钩子调用,并且必须提供 DoResolve 可能解析的所有属性名称的超集。提供 DoResolve 实际上不会解析的名称是可以的。

[HeaderFile="path/to/headerfile.h"]

指示可以在哪里找到实现。类似于 Bindings.conf 中的 headerFile 注释。就像 Bindings.conf 中的 headerFile 一样,应该避免使用。

[JSImplementation="@mozilla.org/some-contractid;1"]

在接口上使用,以提供 实现接口的 JavaScript 组件 的 contractid。

[StoreInSlot]

用于标记可以通过 JIT 从 JS 对象中非常快速地获取的属性。此类属性将在为 DOM 对象创建 JS 包装器时立即调用其 getter,并且返回的值将直接存储在 JS 对象上。稍后获取属性将不会调用 C++ getter,而是使用缓存的值。如果属性返回的值需要更改,则 C++ 代码应调用相关绑定命名空间中的 ClearCachedFooValue 方法,其中 foo 是属性的名称。这将立即调用 C++ getter 并缓存其返回的值,因此它需要一个 JSContext 来进行操作。此扩展属性只能用于其 getter 为 [Pure][Constant] 且不为 [Throws][GetterThrows] 的属性。

因此,例如,给定此 IDL

interface MyInterface {
  [Pure, StoreInSlot] attribute long myAttribute;
};

MyInterface 的 C++ 实现将通过调用 mozilla::dom::MyInterface_Binding::ClearCachedMyAttributeValue(cx, this) 来清除缓存的值。此函数将在出错时返回 false,并且调用方负责处理由失败设置的任何 JSAPI 异常。

如果属性不是只读的,则设置它将自动清除缓存的值并再次获取它,然后再 setter 返回。

[Cached]

用于标记属性,当调用其 getter 时,将在 JS 对象上缓存返回值。这可用于实现其值为序列或字典的属性(否则每次都会返回一个新对象,因此在 Web IDL 中不允许)。

[StoreInSlot] 不同,这不会导致在 JS 包装器创建时急切地调用 getter;缓存是延迟的。[Cached] 属性必须为 [Pure][Constant],因为否则不调用 C++ getter 将是可观察的,但允许具有抛出 getter。可以通过调用相关绑定命名空间中的 ClearCachedFooValue 方法来清除其缓存值,其中 foo 是属性的名称。与 [StoreInSlot] 属性不同,这样做不会立即调用 getter,因此它不需要 JSContext

因此,例如,给定此 IDL

interface MyInterface {
  [Pure, Cached] attribute long myAttribute;
};

MyInterface 的 C++ 实现将通过调用 mozilla::dom::MyInterface_Binding::ClearCachedMyAttributeValue(this) 来清除缓存的值。JS 实现的 Web IDL 可以通过调用 this.__DOM_IMPL__._clearCachedMyAttributeValue() 来清除缓存的值。

如果属性不是只读的,则设置它将自动清除缓存的值。

[Frozen]

用于标记属性,当调用其 getter 时,将在返回之前对返回值调用 Object.freeze。此扩展属性仅允许用于返回序列、字典和 MozMap 的属性,并且对应于返回冻结的 Array(对于序列情况)或 Object(对于其他两种情况)。

[BinaryName]

[BinaryName] 可在方法或属性上指定,以更改将用于方法或属性的 C++ 函数名称。它采用单个字符串参数,即您希望方法或属性具有的名称,而不是它实际具有的名称。

例如,给定此 IDL

interface InterfaceWithRenamedThings {
  [BinaryName="renamedMethod"]
  undefined someMethod();
  [BinaryName="renamedAttribute"]
  attribute long someAttribute;
};

相应的 C++ 将是

class InterfaceWithRenamedThings
{
public:
  void RenamedMethod();
  int32_t RenamedAttribute();
  void SetRenamedAttribute(int32_t);
};

[Deprecated="tag"]

在弃用接口或方法时,[Deprecated] 注释会导致 Web IDL 编译器插入生成弃用警告的代码。此注释可以添加到接口方法或接口中。将其添加到接口会导致在第一次构造对象或调用对象上的任何静态方法时发出警告。

有效弃用标记的完整列表在 nsDeprecatedOperationList.h 中维护。每个新标记都需要定义一个本地化字符串,其中包含要显示的弃用消息。

[CrossOriginReadable]

用于标记属性,当读取时,不会测试相同来源约束:它可以从具有不同来源的上下文中读取。

[CrossOriginWrite]

用于标记属性,当写入时,不会测试相同来源约束:它可以从具有不同来源的上下文中写入。

[CrossOriginCallable]

用于标记方法,当调用时,不会测试相同来源约束:它可以从具有不同来源的上下文中调用。

[SecureContext]

我们使用一些特定于 Gecko 的细节实现了 标准扩展属性

  • 系统主体被视为安全。

  • 扩展程序探测非安全 DOM 对象将看到用 [SecureContext] 标记的 API。

  • XPConnect 沙箱看不到 [SecureContext] API,除非它们是用 isSecureContext: true 创建的。

[NeedsSubjectPrincipal][GetterNeedsSubjectPrincipal][SetterNeedsSubjectPrincipal]

用于标记需要知道主体主体的方法或属性。此主体将作为参数传递。参数将为 nsIPrincipal&,因为主体主体始终可用。

[NeedsSubjectPrincipal] 应用于方法和属性;对于属性,这意味着 getter 和 setter 都需要主体主体。[GetterNeedsSubjectPrincipal] 仅应用于属性。[SetterNeedsSubjectPrincipal] 仅应用于非只读属性。

这些属性还可以使用 [{Getter,Setter,}NeedsSubjectPrincipal=NonSystem] 约束为非系统主体。这会将参数类型更改为 nsIPrincipal*,并在使用系统主体调用时传递 nullptr

[NeedsCallerType]

用于标记需要知道调用方类型(在 mozilla::dom::CallerType 意义上)的方法或属性。这可以安全地用于在 worker 中公开的 API;在那里,它将指示所涉及的 worker 是否为 ChromeWorker。目前,唯一可能的调用方类型是 System(表示系统主体调用方)和 NonSystem

[GenerateInit]

当在字典上设置时,它将向生成的 C++ 类添加两个 Init 方法,其签名如下

bool Init(BindingCallContext& cx, JS::Handle<JS::Value> val, const char* sourceDescription="Value", bool passedToJSImpl=false);
bool Init(JSContext* cx_, JS::Handle<JS::Value> val, const char* sourceDescription="Value", bool passedToJSImpl=false);

这些方法将通过遵循 WebIDL 的 JavaScript 类型映射val 初始化字典。

[GenerateInitFromJSON]

当在字典上设置时,它将向生成的 C++ 类添加一个 Init 方法,其签名如下

bool Init(const nsAString& aJSON);

此扩展属性仅当字典的所有成员类型都可以在 JSON 中表示时才会生效(它们是字符串类型、不是无限制浮点数/双精度数的原始类型、void 类型或包含这些类型的序列、联合、字典或记录)。

预期此方法将以 JSON 字符串作为输入调用。JSON 字符串将被解析为 JavaScript 值,然后字典将通过遵循 WebIDL 的 JavaScript 类型映射 使用此值进行初始化。

注意:由于其实现方式的副作用,它还将添加 [GenerateInit] 扩展属性将添加的两个 Init 方法。

[GenerateToJSON]

当在字典上设置时,它将向生成的 C++ 类添加一个 ToJSON 方法,其签名如下

bool ToJSON(nsAString& aJSON);

此方法将通过以下步骤生成字典成员值的 JSON 表示形式,存储在 aJSON 中:首先根据 WebIDL 的 JavaScript 类型映射 将字典转换为 JavaScript 对象,然后将该对象转换为 JSON 字符串。

类型上的限制与 [GenerateInitFromJSON] 中的相同。

注意:由于实现方式的原因,它还会添加 ToObjectInternal 方法,该方法将由 [GenerateConversionToJS] 扩展属性添加。

[GenerateConversionToJS]

当在字典上设置时,它将在生成的 C++ 类中添加一个 ToObjectInternal 方法,其签名如下:

bool ToObjectInternal(JSContext* cx, JS::MutableHandle<JS::Value> rval);

此方法将根据 WebIDL 的 JavaScript 类型映射 创建一个 JavaScript 对象。

[GenerateEqualityOperator]

当在字典上设置时,它将在生成的 C++ 类中添加一个相等运算符。

这仅允许在字典上使用,这些字典的成员(自身或继承的)只有字符串、基本类型或枚举类型。

[Unsorted]

当在字典上设置时,字典的成员将不会按字典序排序(这是 WebIDL 指定的)。

这应该只用于不公开给 Web 的内部 API!

[ReflectedHTMLAttributeReturningFrozenArray]

用于将 HTML 反映的 IDL 属性标记为具有 FrozenArray<T>? 类型,其中 TElement 或从 Element 继承的接口。这应该只用于实现此类反射 IDL 属性的算法。

当调用此属性的 getter 时,它将在 JS 对象上缓存返回值的 JS 反映。C++ getter 在结果参数之前将传递一个额外的 bool* 参数。如果此参数不是 null,则实现应该将此参数指向的 bool 设置为 attr-associated elementscached attr-associated elements 是否相等。如果它不是 null,并且 getter 将其指向的值设置为 true,则将返回缓存的 JS 值,并且将忽略来自 C++ getter 的结果值(因此无需设置结果值)。如果它是 null,或者 getter 将其指向的值设置为 false,则缓存的值将设置为来自 C++ getter 的结果值的 JS 反映,然后返回该 JS 值。

请注意,这不会导致 JIT 直接从槽中获取缓存的值(如 [StoreInSlot][Cached] 所做的那样)。setter 也不会清除槽中的缓存值。

例如,此 IDL

interface Element {
  [Frozen, ReflectedHTMLAttributeReturningFrozenArray]
  attribute sequence<Element>? reflectedHTMLAttribute;
};

需要在 Element 中进行以下声明

class Element {
  // …
  void GetReflectedHTMLAttribute(
    bool* aUseCachedValue, Nullable<nsTArray<RefPtr<Element>>>& aResult);
  void SetReflectedHTMLAttribute(
    const Nullable<Sequence<OwningNonNull<Element>>>& aValue);
};

辅助对象

绑定的 C++ 端使用许多辅助对象。

Nullable<T>

Nullable<> 是在 Nullable.h 中声明并在 mozilla/dom/Nullable.h 中导出的一个结构体,用于表示没有自然方式表示 null 的类型的可空值。

Nullable<T> 有一个 IsNull() getter,用于返回是否表示 null,以及一个 Value() getter,用于返回一个 const T&,并且可以在它不为 null 时用于获取值。

Nullable<T> 有一个 SetNull() setter,用于将其设置为表示 null,以及两个可用于将其设置为值的 setter:void SetValue(T)(用于将其设置为给定值)和 T& SetValue() 用于直接修改底层的 T&

Optional<T>

Optional<> 是在 BindingDeclarations.h 中声明并在 mozilla/dom/BindingDeclarations.h 中导出的一个结构体,用于表示可选参数和字典成员,但仅限于那些没有默认值的成员。

Optional<T> 有一个 WasPassed() getter,用于在值可用时返回 true。在这种情况下,可以使用 Value() getter 获取值的 const T&

NonNull<T>

NonNull<T> 是在 BindingUtils.h 中声明并在 mozilla/dom/BindingUtils.h 中导出的一个结构体,用于表示非 null 的 C++ 对象。它有一个转换为 T& 的转换运算符。

OwningNonNull<T>

OwningNonNull<T> 是在 OwningNonNull.h 中声明并在 mozilla/OwningNonNull.h 中导出的一个结构体,用于表示非 null 的 C++ 对象,并持有对它们的强引用。它有一个转换为 T& 的转换运算符。

类型化数组、数组缓冲区、数组缓冲区视图

TypedArray.h 导出到 mozilla/dom/TypedArray.h,并公开与各种类型化数组类型相对应的结构体,以及 ArrayBufferArrayBufferView,所有这些都在 mozilla::dom 命名空间中。每个结构体都有一个 Data() 方法,用于返回指向相关类型的指针(uint8_t 用于 ArrayBufferArrayBufferView)以及一个 Length() 方法,用于返回以 *Data() 为单位的长度。例如,Int32Array 有一个返回 int32_t*Data() 和一个返回数组中 32 位整数个数的 Length()

Sequence<T>

Sequence<> 是在 BindingDeclarations.h 中声明并在 mozilla/dom/BindingDeclarations.h 中导出的一个类型,用于表示序列参数。它是一种类型化数组,但确切的类型对使用者是透明的。这允许绑定代码更改确切的定义(例如,使用不同大小的自动数组等),而无需更新所有被调用者。

CallbackFunction

CallbackFunction 是在 CallbackFunction.h 中声明并在 mozilla/dom/CallbackFunction.h 中导出的一个类型,用作所有生成的回调函数表示形式的公共基类。此类继承自 nsISupports,使用者必须确保对其进行循环收集,因为它使 JS 对象保持活动状态。

CallbackInterface

CallbackInterface 是在 CallbackInterface.h 中声明并在 mozilla/dom/CallbackInterface.h 中导出的一个类型,用作所有生成的回调接口表示形式的公共基类。此类继承自 nsISupports,使用者必须确保对其进行循环收集,因为它使 JS 对象保持活动状态。

DOMString

DOMString 是在 BindingDeclarations.h 中声明并在 mozilla/dom/BindingDeclarations.h 中导出的一个类,用于 Web IDL DOMString 返回值。它有一个转换为 nsString& 的转换运算符,以便它可以传递给采用该类型或 nsAString& 的方法,但关心性能的被调用者,有一个 StringBuffer 可用,并承诺至少在绑定代码从堆栈中退出之前保留 StringBuffer,也可以直接为它们的字符串返回值采用 DOMString,并使用 StringBuffer 及其长度调用其 SetStringBuffer 方法。这允许绑定代码在许多情况下避免额外的字符串缓冲区的引用计数,并且即使它最终不得不为 StringBuffer 添加引用,它也可以采用更快的代码路径。

GlobalObject

GlobalObject 是在 BindingDeclarations.h 中声明并在 mozilla/dom/BindingDeclarations.h 中导出的一个类,用于表示静态属性和操作(包括构造函数)的全局对象。它有一个 Get() 方法,用于返回全局对象的 JSObject*,以及一个 GetAsSupports() 方法,用于在主线程上返回全局对象的 nsISupports*(如果可用)。它还有一个 Context() 方法,用于返回调用发生所在的 JSContext*。需要注意的是:JSContext 的隔间可能与全局对象的隔间不匹配!

Date

Date 是在 BindingDeclarations.h 中声明并在 mozilla/dom/BindingDeclarations.h 中导出的一个类,用于表示 Web IDL 日期。它有一个 TimeStamp() 方法,返回一个双精度数,表示自纪元以来的毫秒数,以及 SetTimeStamp() 方法,可用于用双精度时间戳或 JS Date 对象对其进行初始化。它还有一个 ToDateObject() 方法,可用于创建一个新的 JS Date

ErrorResult

ErrorResult 是在 ErrorResult.h 中声明并在 mozilla/ErrorResult.h 中导出的一个类,用于表示 Web IDL 绑定中的异常。它具有以下方法:

  • Throw:允许抛出 nsresultnsresult 必须是失败代码。

  • ThrowTypeError:允许抛出一个带有给定错误消息的TypeError。允许的TypeError列表及其对应的消息位于dom/bindings/Errors.msg

  • ThrowJSException:允许抛出一个已存在的JS异常值。但是,在抛出任何此类异常之前(即使没有抛出异常),都必须调用MightThrowJSException()方法。

  • Failed:检查在此ErrorResult上是否已抛出异常。

  • ErrorCode:返回一个表示此ErrorResult状态(可能不完整)的失败nsresult

  • operator=:接收一个nsresult,如果结果是错误代码,则其行为类似于Throw,否则类似于无操作(除非已经抛出异常,在这种情况下它会进行断言)。这仅应用于具有到处都是nsresult的遗留代码;我们希望在某个时候摆脱这个运算符。

事件

简单的Event接口可以通过将接口文件添加到相应dom/webidl/moz.build文件中的GENERATED_EVENTS_WEBIDL_FILES来自动生成。您还可以使用一个简单的生成的C++文件对来构建更复杂的事件(例如,具有方法的事件)。

事件处理程序属性

许多接口定义了事件处理程序属性,例如

attribute EventHandler onthingchange;

如果您需要为接口实现事件处理程序属性,则在定义(头文件)中,可以使用方便的“IMPL_EVENT_HANDLER”宏

IMPL_EVENT_HANDLER(onthingchange);

“onthingchange”需要添加到StaticAtoms.py文件中

Atom("onthingchange", "onthingchange")

然后,触发事件的实际实现(.cpp)将如下所示

nsresult
MyInterface::DispatchThingChangeEvent()
{
  NS_NAMED_LITERAL_STRING(type, "thingchange");
  EventInit init;
  init.mBubbles = false;
  init.mCancelable = false;
  RefPtr<Event> event = Event::Constructor(this, type, init);
  event->SetTrusted(true);
  ErrorResult rv;
  DispatchEvent(*event, rv);
  return rv.StealNSResult();  // Assuming the caller cares about the return code.
}

Bindings.conf详细信息

请写下。特别是,需要至少描述concreteprefableaddExternalInterface的使用。

如何获取传递给给定方法的JSContext

在某些罕见情况下,您可能需要将JSContext*参数传递给一个不会以其他方式获取此类参数的C++方法。要了解如何实现此目的,请在dom/bindings/Bindings.conf中搜索implicitJSContext

使用Javascript实现Web IDL

警告

使用Javascript实现Web IDL已弃用。新的接口应始终在C++中实现!

可以在Gecko中使用JavaScript实现Web IDL接口,但是,**这仅限于在Web Worker中未公开的接口**。当绑定发生时,会创建两个对象

  • 内容公开对象:公开到网页的内容。

  • 实现对象:作为chrome特权脚本运行。这允许实现对象拥有内容公开对象所不具备的各种API。

由于存在两种类型的对象,因此您必须小心创建的对象类型。

创建JS实现的Web IDL对象

要创建JS实现的Web IDL对象,必须创建chrome端实现对象和内容端页面公开对象。有三种方法可以做到这一点。

使用Web IDL构造函数

如果接口具有构造函数,则可以通过从相关内容窗口获取该构造函数并调用它来创建内容端对象。例如

var contentObject = new contentWin.RTCPeerConnection();

返回的对象将是内容端对象的Xray包装器。以这种方式创建对象将自动使用其contractID创建chrome端对象。

此方法仅限于公开给网页的构造函数签名。对象的任何其他配置都需要在接口上的方法中完成。

由于涉及createInstance开销,因此以这种方式创建许多对象可能会很慢。

使用_create方法

可以通过在接口上调用静态_create方法为给定的chrome端对象创建内容端对象。此方法接受两个参数:要在其中创建对象的content窗口和要使用的chrome端对象。例如

var contentObject = RTCPeerConnection._create(contentWin, new
MyPeerConnectionImpl());

但是,如果您位于JS组件中,则可能只能通过某些窗口对象访问正确的接口对象。在这种情况下,代码将更类似于

var contentObject = contentWin.RTCPeerConnection._create(contentWin,
new MyPeerConnectionImpl());

以这种方式创建对象不会调用其__init方法或init方法。

通过从JS实现的Web IDL方法返回chrome端对象

如果JS实现的Web IDL方法声明为返回JS实现的接口,则从该方法返回的非Web IDL对象将被视为JS实现的WebIdL对象的chrome端部分,并且内容端部分将自动创建。

以这种方式创建对象不会调用其__init方法或init方法。

在JavaScript中实现Web IDL对象

要在JavaScript中实现Web IDL接口,首先添加一个Web IDL文件,就像您对C++实现的接口所做的那样。为了支持在JS中实现,您必须在接口上添加一个扩展属性JSImplementation="CONTRACT_ID_STRING",其中CONTRACT_ID_STRING是JS实现的XPCOM组件合同ID——注意“;1”只是Mozilla用于版本控制API的约定。这是一个示例

[JSImplementation="@mozilla.org/my-number;1"]
interface MyNumber {
  constructor(optional long firstNumber);
  attribute long value;
  readonly attribute long otherValue;
  undefined doNothing();
};

接下来,创建一个实现此接口的XPCOM组件。使用与您在Web IDL文件中指定的相同的contract ID。类ID无关紧要,除了它应该是一个新生成的ID。对于QueryInterface,您只需要实现nsISupports,而不是任何与Web IDL接口对应的内容。您用于XPCOM组件的名称应与接口的名称不同,以避免混淆错误消息。

Web IDL属性在JS对象或其原型链上实现为属性,而Web IDL方法在对象或原型上实现为方法。请注意,作为参数传递给您的接口的任何其他实例都是对象的完整面向Web的版本,而不是JS实现,因此您目前无法访问任何私有数据。

Web IDL构造函数调用将首先创建您的对象。如果XPCOM组件实现了nsIDOMGlobalPropertyInitializer,则对象的init方法将使用单个参数调用:构造函数来自的内容窗口。这允许JS实现知道它与哪个内容窗口相关联。init方法不应返回任何内容。在此之后,将创建内容端对象。然后,如果有任何构造函数参数,将调用对象的__init方法,并将构造函数参数作为其参数。

静态成员

JS实现的Web IDL不支持静态属性和方法(请参阅bug 863952)。但是,通过bug 1172785中的更改,您可以使用StaticClassOverride注释将静态方法路由到另一个对象上的C++实现。此注释包含包含命名方法实现的类的完整、命名空间限定名称。该类的include必须在其名称的基础上找到目录。

[JSImplementation="@mozilla.org/dom/foo;1"]
interface Foo {
  [StaticClassOverride="mozilla::dom::OtherClass"]
  static Promise<undefined> doSomething();
};

而不是调用JS实现上的方法;调用Foo.doSomething()将导致调用mozilla::dom::OtherClass::DoSomething()

检查权限或首选项

对于JS实现的Web IDL,init方法只能返回undefined。如果返回任何其他值,例如null,则绑定代码将断言或崩溃。换句话说,它的行为就像它具有“undefined”返回类型一样。首选项或权限检查应通过向Web IDL接口添加扩展属性来实现。这样做的好处是,如果检查失败,构造函数或对象根本不会显示。

对于首选项检查,添加扩展属性Pref="myPref.enabled",其中myPref.enabled是要检查的首选项。SettingsLock就是一个例子。

对于权限或其他类型的检查,添加扩展属性Func="MyPermissionChecker",其中MyPermissionChecker是在C++中实现的函数,如果应启用接口,则返回true。此函数可以执行任何需要的检查。PushManager就是一个例子。

示例

这是一个上述接口的JS实现示例。invisibleValue字段将无法访问Web内容,但可供doNothing()方法使用。

function MyNumberInner() {
  this.value = 111;
  this.invisibleValue = 12345;
}

MyNumberInner.prototype = {
  classDescription: "Get my number XPCOM Component",
  contractID: "@mozilla.org/my-number;1",
  QueryInterface: ChromeUtils.generateQI([]),
  doNothing: function() {},
  get otherValue() { return this.invisibleValue - 4; },
  __init: function(firstNumber) {
    if (arguments.length > 0) {
      this.value = firstNumber;
    }
  }
}

最后,添加一个组件和一个契约以及您需要实现XPCOM组件的任何其他清单内容。

绑定提供的保证

在JavaScript中实现Web IDL接口时,绑定实现将提供某些保证。例如,字符串或数字参数实际上将是原始字符串或数字。字典仅包含它们声明具有的属性,并且它们将具有正确的类型。接口参数实际上将是实现了该接口的对象。

绑定**不会**保证的大多数内容与objectany参数有关。它们将获得跨区段包装器,使从chrome代码触碰它们不会成为直接的安全漏洞,但在其他方面,如果页面试图恶意操作,它们可能会产生非常令人惊讶的行为。如果可能,请尽量避免使用这些类型。

从实现访问内容对象

如果Web IDL接口的JS实现需要访问内容对象,则它作为chrome实现对象上的名为__DOM_IMPL__的属性可用。此属性仅在创建内容端对象后才会出现。因此它在__init中可用,但在init中不可用。

确定调用Web IDL API的调用者的主体

这可以通过调用Component.utils.getWebIDLCallerPrincipal()来完成。

从JS实现的API抛出异常

JS实现的API可能抛出异常有两个原因。第一个原因是发生了一些不可预见的情况,第二个原因是规范要求抛出异常。

当由于不可预见的情况抛出异常时,异常将报告到控制台,并且将向调用内容脚本抛出一个经过清理的NS_ERROR_UNEXPECTED异常,以及调用您API的内容代码的文件/行。这将避免向内容代码公开chrome URI和其他实现细节。

当由于规范要求抛出异常而抛出异常时,您需要从与您的Web IDL对象关联的窗口(传递给您的init方法的窗口)创建异常。然后,绑定代码将重新抛出该异常到网页。有关此工作原理的示例

if (!isValid(passedInObject)) {
  throw new this.contentWindow.TypeError("Object is invalid");
}

if (!isValid(passedInObject)) {
  throw new this.contentWindow.DOMException("Object is invalid", "InvalidStateError");
}

取决于在这种情况下规范要求抛出的确切异常。

在某些情况下,您可能需要执行操作,其异常消息您只想传播到内容调用方。这可以通过以下方式完成

try {
  someOperationThatCanThrow();
} catch (e) {
  throw new this.contentWindow.Error(e.message);
}

从C++中实现的接口继承

可以使在JavaScript中实现的接口从在C++中实现的接口继承。为此,只需让一个接口继承自另一个接口,绑定代码将自动生成一个从父接口的实现继承的C++对象。实现父接口的类将需要一个接受nsPIDOMWindow*的构造函数(尽管它不必对该参数执行任何操作)。

如果实现父接口的类是抽象的,并且您希望使用特定的具体类作为要继承的实现,则需要在Bindings.conf中父接口的描述符中添加defaultImpl注释。注释的值是要用作JS实现的后代的父级的C++类;如果未指定defaultImpl,则将使用nativeType

例如,考虑我们希望在JavaScript中实现的此接口

[JSImplementation="some-contract"]
interface MyEventTarget : EventTarget {
  attribute EventHandler onmyevent;
  undefined dispatchTheEvent(); // Sends a "myevent" event to this EventTarget
}

实现将如下所示,忽略大部分XPCOM样板代码

function MyEventTargetImpl() {
}
MyEventTargetImpl.prototype = {
  // QI to nsIDOMGlobalPropertyInitializer so we get init() called on us.
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),

  init: function(contentWindow) {
    this.contentWindow = contentWindow;
  },

  get onmyevent() {
    return this.__DOM_IMPL__.getEventHandler("onmyevent");
  },

  set onmyevent(handler) {
    this.__DOM_IMPL__.setEventHandler("onmyevent", handler);
  },

  dispatchTheEvent: function() {
    var event = new this.contentWindow.Event("myevent");
    this.__DOM_IMPL__.dispatchEvent(event);
  },
};

该实现将自动支持在EventTarget上公开的API(例如,addEventListener)。调用dispatchTheEvent方法将导致分派一个事件,内容脚本可以通过它添加的监听器看到该事件。

请注意,在这种情况下,Chrome实现依赖于EventTarget上的一些[ChromeOnly]方法,这些方法是专门添加的,以便能够轻松地实现事件处理程序。其他情况可以根据需要执行类似的操作。