XPCOM在Gecko 2.0中的更改
原文:https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_2.0
引入于Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1
Gecho 2中有几个更改,影响了XPCOM组件的兼容性。本文对它们作出详细介绍,并提供对更新代码的一些建议。
不再有冻结接口(frozen interfaces)
从现在开始,所有的接口如有改变,恕不另行通知。文档将被更新,移除那些被标记为“冻结”和“未冻结”的接口的引用。
组件注册
XPCOM组件注册的方式在Gecko 2中改变。在Gecko 2之前,在组件注册期间,会加载和调用所有的二进制和js组件文件,要求它们自我注册。如果你使用XPCOMUtils.jsm,有一些被隐藏,因而无法发现,但它还是存在。
从Gecko 2开始,组件使用manifest文件进行注册,和chrome相似。事实上,使用相同的chrome manifest文件注册组件。
现有的所有XPCOM组件都需要被更新以支持该注册方式。不过,这比较简单,实际上可以同时支持两种注册方式以保持向后兼容。
组件manifest
现在,所有组件的注册都通过manifest文件进行。对于扩展,它和当前用来注册chrome的chrome.manifest相同。
XPT文件
任何XPT文件的路径都必需在manifest中显式地用interfaces指令列出:
interfaces components/mycomponent.xpt
js组件
js组件的注册信息不再位于组件自身;而被移动到manifest中。仅当XPCOM组件管理器需要创建组件时才会加载js组件。
chrome.manifest:
# The {classID} here must match the classID in mycomponent.js
component {e6b866e3-41b2-4f05-a4d2-3d4bde0f7ef8} components/mycomponent.js
contract @foobar/mycomponent;1 {e6b866e3-41b2-4f05-a4d2-3d4bde0f7ef8}
category profile-after-change MyComponent @foobar/mycomponent;1
js代码必须导出一个NSGetFactory()函数,而不再是原先的NSGetModule()函数;NSGetFactory()接受一个类ID(CID)作为参数。
例如,js代码:
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function myComponent() {
}
myComponent.prototype = {
// 必须和chrome.manifest中的内容相匹配
classID: Components.ID("{e6b866e3-41b2-4f05-a4d2-3d4bde0f7ef8}"),
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMyComponent]),
/* 此处实现nsIMyComponent */
...
};
// XPCOM使用下面的这行创建组件。
// 每个组件原型必须拥有一个.classID。我们将使用classID创建组件。
const NSGetFactory = XPCOMUtils.generateNSGetFactory([myComponent]);
组件可实现向后和Gecko 1.9.2的兼容,这通过动态检测XPCOMUtils.jsm导出哪个符号、并导出对应的函数来完成:
/**
* XPCOMUtils.generateNSGetFactory 引入于 Mozilla 2 (Firefox 4, SeaMonkey 2.1)。
* XPCOMUtils.generateNSGetModule 引入于 Mozilla 1.9 (Firefox 3.0)。
*/
if (XPCOMUtils.generateNSGetFactory)
var NSGetFactory = XPCOMUtils.generateNSGetFactory([myComponent]);
else
var NSGetModule = XPCOMUtils.generateNSGetModule([myComponent]);
二进制组件
二进制组件必需在manifest中显式地用binary-component指令列出:
binary-component components/mycomponent.dll
组件中的C++代码必须更改:二进制组件不再导出一个NSGetModule()函数,而是导出一个NSModule数据符号,它指向一个mozilla::Module结构。关于mozilla::Module结构的更多信息,参考Module.h。实现动态module的最新示例,参考nsSampleModule.cpp。
注意,nsIGenericFactory.h已被移除。nsIGenericFactory.h的引用应当被替换为mozilla/ModuleUtils.h。
使用额外的宏NS_IMPL_MOZILLA192_NSGETMODULE,二进制组件可以兼容Mozilla 1.9.2和2.0,详细信息可参考nsSampleModule.cpp。
注意:对FF4以上的主发行版,二进制XPCOM组件必须重新编译。使用js-ctypes后,你将可以更加容易地过日子。
还要注意,使用二进制组件的扩展现在在安装manifest中必须使用unpack属性。
平台指令
过去在特定平台子目录下进行查找的component/chrome系统(如Windows上的platform/WINNT_x86-msvc/chrome.manifest)不再被支持。使用 OS 和 ABI chrome注册指令来获得相同的效果:
binary-component components/windows/mycomponent.dll ABI=WINNT_x86-msvc binary-component components/mac/mycomponent.dylib ABI=Darwin_x86-gcc3 binary-component components/mac/mycomponent64.dylib ABI=Darwin_x86_64-gcc3 binary-component components/linux/mycomponent.so ABI=Linux_x86-gcc3
这也意味着平台偏好也再可行。如果你需要根据平台调整默认偏好,可以在首次运行时检查所在的平台然后更改偏好。
分类注册
Gecko 2之前,扩展可以在启动期间监听 xpcom-startup 和 app-startup notifications,并执行一些动作。但现在不再如此。现在,扩展能够接收的最早的启动通知是profile-after-change(建议总是兼通它)。这是因为它是发生于配置文件夹(所以还包含偏好和其它服务)生效之后的最早的通知之一。
需要更改的内容
如果你的扩展监听了xpcom-startup 或 app-startup,那么你需要更新代码为监听profile-after-change。
通常,扩展监听app-startup是因为,在过去,你需要用它来执行加载,以便能够注册对profile-after-change的监听。不过,在Gecko 1.9.1中,就不再如此;你现在可以使用分类管理器注册profile-after-change。详细信息参考接收启动通知。
为添加一个分类实体,你必须在chrome.manifest中插入下面这行:
category profile-after-change MyComponent @foobar/mycomponent;1
重要:以前,分类实体的id拥有前缀"service,",如果组件被实现为一个服务的话。前一到chrome.manifest后,该前缀废弃。
分类名称变更
XPCOM分类管理器用于注册特定的全局帮助对象。因为chrome.manifest是空格分隔的格式,因此,带有空格的分类名称无法被注册。于是,下面的分类被更改:
Old name | New name |
---|---|
JavaScript global constructor | JavaScript-global-constructor |
JavaScript global constructor prototype alias | JavaScript-global-constructor-prototype-alias |
JavaScript global property | JavaScript-global-property |
JavaScript global privileged property | JavaScript-global-privileged-property |
JavaScript global static nameset | JavaScript-global-static-nameset |
JavaScript global dynamic nameset | JavaScript-global-dynamic-nameset |
JavaScript DOM class | JavaScript-DOM-class |
JavaScript DOM interface | JavaScript-DOM-interface |
XSLT extension functions | XSLT-extension-functions |
缘由
以前,无论何时,只要Gecko检查到应用程序版本变更,或者是一或多个扩展被添加、移除、开启或禁用,都需要在其启动过程中抛弃所有已存在的组件注册,然后重启应用程序(我们称之“扩展管理器重启”)。这是必需的,为确保不再有效的任何组件都被正确地处理、并注册所有一切、加载所需的任何新组件。
在理论上,这对用户不可见,但它是一个费时费力的过程,因为每个组件都需要在重启过程中被加载和执行,然后被卸载,然后再被重新加载。
除此之外,当前正在进行让FF多线程工作,内容的处理要么需要根据每个进程注册组件,要么以某种方式和chrome进程共享一个组件缓存。
组件注册模型的改变让这种叫作扩展管理器重启的玩意成为过去式。我们不再依赖于启动时的隐性组件缓存,而是从manifest文件读取应用程序的组件注册,然后加载组件。这可以加载足够的XPCOM,并运行;然后我们可以运行扩展管理器,执行必需的安装、反安装或更新任何已安装的扩展。
Electrolysis content processes can simply read the component registrations during startup.
XPCNativeWrapper的变更
无法从manifest禁止XPCNativeWrapper
在manifest中指定xpcnativewrappers=no(即,XPCNativeWrapper自动化)不再被支持。它一直被拟定为一个短期的解决方法,在扩展的作者更改代码以使用XPCNativeWrappers时,仍然允许扩展继续工作。
如果插件依赖于附加到内容对象的XBL绑定(比如,调用函数的能力或获取和设置XBL绑定创建的属性),你将需要使用XPCNativeWrapper属性wrappedJSObject来访问包装对象。
如果你需要调用Web中定义的函数或访问Web中的属性,你也需要如此做。举个例子,可能的一种的情况是,你编写了一个扩展,为Web邮件服务添加了一个删除按钮,而你需要调用服务定义的window.delete()函数。
另一方面,如果你仅仅访问DOM方法或属性,那么你无需使用xpcnativewrappers=no,应当从manifest中移除它。
XPCNativeWrapper杂项的变更
对PCNativeWrapper的"expando"属性使用delete操作符不再抛出一个安全异常。
XPCOMUtils.jsm的变更
代码模块XPCOMUtils.jsm已被更新,允许指定你想要注册组件于其中的应用程序的ID。
获取XPCOM服务
现在可以使用Get函数获取若干常用的XPCOM服务(如:nsCOMPtr<nsIIOService> ioService = mozilla::services::GetIOService()),参考mozilla::services namespace。这让从C++代码中访问这些服务更加容易。