WebIDL Extended Attribute 大解密

作者:
瀏覽:268

Gecko 的 New DOM bindings 文章中,Kan-Ru 曾經介紹過 Mozilla 開始改用 WebIDL Binding 的歷史小故事,也在文中手把手的教大家如何透過 WebIDL expose API 給 application。本篇將進一步介紹 WebIDL 中的 Extended Attribute 與其在目前 code base 中的使用情形,讓大家對於常用的 Extended Attribute 有基本的認識,進而在制定 API 的時候能夠很快的將設計理念對應到 WebIDL 檔案上。

首先讓我們舉個簡單例子來介紹到底什麼是 Extended Attribute,假設我們今天要提供一個 interface 給 application 去 開關藍芽以及拿到目前已經配對的裝置的 address,我們的 WebIDL 大致上會像:

interface BluetoothAdapter {
  readonly attribute sequence pairedDevices;

  Promise enable();
  Promise disable();
}

但在實際使用上,我們其實需要更加詳盡的描述我們的 API,這就是 Extended Attribute 出馬的時候了!就讓我們來改進上面這個例子來感受一下 Extended Attribute 到底是什麼跟它的威力吧!

上面的例子我們沒有描述到的有:

  1. application 需要有什麼樣的權限才能使用這個藍芽相關的 API
  2. sequence type 的 attribute 是否有 Cache
  3. enable/disable 的 method 會不會每次都 create 一個新的物件
  4. enable/disable 會不會 throw exception

而根據你的設計還可能再加上更多細節,在這邊就不一一舉例了,讓我們立馬根據以上這些來改進一下吧!

[CheckPermissions="bluetooth"]
interface BluetoothAdapter {
  [Cached, Pure]
  readonly attribute sequence pairedDevices;

  [NewObject, Throws]
  Promise enable();
  [NewObject, Throws]
  Promise disable();
}

登登,一個加上滿滿的 Extended Attribute 的值得一戰 (review?) 的 WebIDL 就這樣神奇的誕生了呢!事實上除了 WebIDL 描述不清會讓使用 API 的人滿腹疑惑無處問以外,這些東西也與我們的 WebIDL Bindings 和 WebIDL parser 的實作息息相關。例如若我們沒有指明 pairedDevices 這個 sequence type 的 attribute 是否有 [Cached] 且是否是 [Pure] 或 [Constant] 兩者之一的話,在一開始 parse WebIDL 的時候就會產生錯誤。[2] [3] 更重要的是,如果你在 WebIDL 裡沒有說清楚講明白的話,是一定會收到來自 DOM Peer 的 review- 的!(這血淚的連結就不在這邊附上了)

喂喂喂,等一下,這些 Extended Attribute 怎麼蹦出來的?是什麼意思?什麼時機要用?詳情請見 W3C WebIDL spec, MDN上的 WebIDL Bindings, WebIDL parser 的 source code, Bugzilla 等等。看完以後相信你一定可以對 Mozilla 所有的 WebIDL Extended Attribute 瞭若指掌,了然於心,信手拈來就是一個活跳跳的 WebIDL file!什麼?這麼多不知道從哪個 Extend Attribute 開始看起?看這篇文章就對了!俗話說數字會說話,就讓我們繼續看下去。

目前 dom/webidl 下有 610 個 WebIDL file,共使用了 44 種,總計 3484 個Extended Attribute 的敘述,詳細統計圖表如下:

WebIDL Extended Attribute 大解密

這前 12 名 你是否都清楚呢?以下就讓我們來認識一下它們吧,畢竟擒賊要先擒王阿!

1. [Throws] (Attribute, Method) (Mozilla only)

允許 C++ callee 能夠 Throw,binding generator 將會在產生 binding 的 code 的時候加上 exception handling。

以之前例子中的 enable method 為例,我們可以在產生出來的 objdir-gecko/dom/bindings/BluetoothAdapterBinding.cpp 看到 exception handling 的程式碼片段:

static bool
enable(JSContext* cx, JS::Handle obj, mozilla::dom::bluetooth::BluetoothAdapter* self, const JSJitMethodCallArgs& args)
{
  ErrorResult rv;
  nsRefPtr result;
  result = self->Enable(rv);
  rv.WouldReportJSException();
  if (rv.Failed()) {
    return ThrowMethodFailedWithDetails(cx, rv, "BluetoothAdapter", "enable");
  }
  ...
}

2. [SetterThrows] (non-readonly attribute) (Mozilla only)

概念與 [Throw] 相同,但 [Throws] 使用在 attribute 上面的時候是同時適用於 Getter and Setter,即 binding gererator 產生出來的 |get_$attributeName| 和 |set_$attributeName| 兩個函式皆會做 exception handling。而 [SetterThrows] 則僅能用於 non-readonly attribute,產生出來的 code 只有 |set_$attributeName| 會做 exception handling。

3. [Pure] (Attribute, Method) (Mozilla only)

代表此 attribute/method 會一直 return 相同的值直到有 DOM setter 或 non-[Pure] DOM method 被執行。

4. [Pref] (Attribute, Method, Constant on an interface, Interface) (Mozilla only)

格式為 [Pref="preferenceName"],且此 preference 的值必須為 boolean。

代表此 Attribute/Method/Constant/Interface 只有在此 preference 的值是 true 的時候才會被 expose。

舉 B2G 中的 Telephony 這個 interface 為例:

// dom/webidl/Telephony.webidl
[Pref="dom.telephony.enabled"]
interface Telephony : EventTarget {
  // Telephony APIs
}

// source/modules/libpref/src/init/all.js
// Telephony API
#ifdef MOZ_B2G_RIL
pref("dom.telephony.enabled", true);
#else
pref("dom.telephony.enabled", false);
#endif

由以上的例子可以看出 Telephony 的 API 是由 MOZ_B2G_RIL 這個 build flag 去決定是否要將 dom.telephony.enabled 這個 preference 設起來,再透過 [Pref="dom.telephony.enabled"] 來去控制是否要 expose Telephony 相關的 API。

5. [NewObject] (Method) (W3C)

代表此 Method 每次都會 create 一個新的 object 並 return 此 object 的 reference。

6. [Constructor] (Interface) (W3C)

Application 在使用這個 interface 的時候,能夠使用所定義的 Constructor 去得到實作這個 interface 的 object。
舉例說明當我們有下面這份WebIDL:

// WebIDL
[Constructor,
 Constructor(float radius)]
interface Circle {
  attribute float radius;
}

interface Square {
  attribute float length;
}

// Application
var x = new Circle();
var y = new Circle(1.5);
var z = new Square(); //Will throw a TypeError exception.

那麼在 application 就可以像上面的例子去利用 WebIDL 中定義的 Constructor去建立一個實作這個 interface 的 object。 (當然,我們對應的 C++ 的 class 也必須去實作這個 constructor。) 反之,沒有定義的話就會產生 TypeError。

7. [ChromeOnly] (Method, Attribute, Constant on an interface, Interface) (Mozilla only)

代表只會 expose 給 Chrome Window。(不 Expose 給 Web Content)

8. [Constant] (Attribute) (Mozilla only)

取這個 attribute 時永遠會拿到相同的值。

9. [NoInterfaceObject] (Interface) (W3C)

對 application 而言這個 interface 並不存在。

舉例來說:

// WebIDL
[NoInterfaceObject]
interface MyInterface {
  void doSomething();
}

// Application
typeof MyInterface;        // undefined
MyInterface.doSomething(); // exception, MyInterface is undefined

10. [Func] (Method, Attribute, Constant on an interface, Interface) (Mozilla only)

使用方式為 [Func="functionName"],代表此 Method/Attribute/Constant/Interface 只有在這個 static function 的 return 值為 true 的時候才會被 expose。

11. [CheckPermissions] (Method, Attribute, Constant on an interface, Interface) (Mozilla only)

[CheckPermissions="permission1 permission2"],當 application 至少擁有其中一個 permission 的時候,就會 expose 這個 Method/Attribute/Constant/Interface。

12. [Cached] (Attribute) (Mozilla only)

必須與 [Pure] 或 [Constant] 一起使用。除此之外,對於 type 是 dictionary 或是 sequence 的 attribute,一定要是 [Cached] 或是 [StoreInSlot]

當 application 想要取得此 attribute 的值的時候,會直接拿到 cache 住的值。Gecko 可以透過呼叫 ClearCachedMyAttributeValue 去將 cache 住的值清成 undefined,則 application 下次使用這個 attribute 的 getter 的時候就會呼叫到 C++ class 中實作的 getter function,而非直接 return cache 值。

介紹完了常用的 TOP 12 WebIDL Extended Attribute,希望大家對這些 Extended Attribute 都有更深的認識了,如何去應用相信已經難不倒各位了吧!

[1] Gecko 的 New DOM bindings

[2] dxr.mozilla.org/mozilla-central/source/dom/bindings/parser/WebIDL.py#2876

[3] dxr.mozilla.org/mozilla-central/source/dom/bindings/parser/WebIDL.py#2923

[4] http://www.w3.org/TR/WebIDL/

[5] https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings