定义 XPCOM 组件

本文档说明了如何编写 components.conf 文件。有关 idl 格式的文档,请参阅 XPIDL。有关编写新 XPCOM 接口的教程,请参阅 编写新 XPCOM 接口的教程

原生 XPCOM 组件在构建时注册,并编译成静态数据结构,这使得它们能够以较少的运行时开销进行访问。每个希望注册组件的模块都必须提供一个清单,描述它实现的每个组件、其类型以及如何构建它。

清单文件是 Python 数据文件,在 moz.build 文件中的 XPCOM_MANIFESTS 文件列表中注册。

XPCOM_MANIFESTS += [
  'components.conf',
]

这些文件可以定义以下任何特殊变量

# Optional: A function to be called once, the first time any component
# listed in this manifest is instantiated.
InitFunc = 'nsInitFooModule'
# Optional: A function to be called at shutdown if any component listed in
# this manifest has been instantiated.
UnloadFunc = 'nsUnloadFooModule'

# Optional: A processing priority, to determine how early or late the
# manifest is processed. Defaults to 50. In practice, this mainly affects
# the order in which unload functions are called at shutdown, with higher
# priority numbers being called later.
Priority = 10

# Optional: A list of header files to include before calling init or
# unload functions, or any legacy constructor functions.
#
# Any header path beginning with a `/` is loaded relative to the root of
# the source tree, and must not rely on any local includes.
#
# Any relative header path must be exported.
Headers = [
    '/foo/nsFooModule.h',
    'nsFoo.h',
]

# A list of component classes provided by this module.
Classes = [
    {
        # ...
    },
    # ...
]

# A list of category registrations
Categories = {
    'category': {
        'name': 'value',
        'other-name': ('value', ProcessSelector.MAIN_PROCESS_ONLY),
        # ...
    },
    # ...
}

类定义可以具有以下属性

name (可选)

如果存在,此组件将在 mozilla/Components.h 中的 mozilla::components 命名空间中生成一个具有给定名称的条目,这使得可以轻松访问其 CID、服务和实例构造函数(例如) components::Foo::CID()components::Foo::Service()components::Foo::Create()

cid

包含此组件 CID 的 UUID 字符串,格式为 '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'

contract_ids (可选)

要为此类注册的契约 ID 列表。

categories (可选)

要为此组件的契约 ID 注册的类别条目的字典。字典中的每个键都是类别的名称。每个值都是包含单个条目的字符串,或条目列表。每个条目都是字符串名称,或格式为 {'name': 'value', 'backgroundtasks': BackgroundTasksSelector.ALL_TASKS} 的字典。默认情况下,类别条目注册为 **无后台任务**:它们具有 'backgroundtasks': BackgroundTasksSelector.NO_TASKS

type (可选,默认为 ``nsISupports``)

实现此组件的类的完全限定类型。默认为 nsISupports,但如果指定了 init_method 属性,或者既未提供 constructor 也未提供 legacy_constructor 属性,则 **必须** 提供。

headers (可选)

要包含的标题列表,以便调用此组件的构造函数,格式与全局 Headers 属性相同。

init_method (可选)

在返回新创建的此类实例之前要调用的方法的名称。该方法不能带任何参数,并且必须返回 nsresult。如果返回失败,则该失败将传播到 getServicecreateInstance 调用方。

constructor (可选)

要调用的构造函数的完全限定名称,以便创建此类的实例。此函数必须在 headers 属性中列出的标题之一中声明,不能带任何参数,并且必须返回 already_AddRefed<iface>,其中 ifacetype 属性中提供的接口。

此属性与 legacy_constructor 不兼容。

esModule (可选)

如果提供,则必须是包含组件的 JavaScript 实现的 JavaScript 模块 的 URL。 constructor 属性必须包含导出函数的名称,该函数可以被构造以创建组件的新实例。

jsm (已弃用,可选)

不要使用。改用 esModule

legacy_constructor (可选)

此属性已弃用,不应在新代码中使用。

要调用的构造函数的完全限定名称,以便创建此类的实例。此函数必须在 headers 属性中列出的标题之一中声明,并且必须具有签名 nsresult(const nsID& aIID, void** aResult),并且行为等效于 nsIFactory::CreateInstance

此属性与 constructor 不兼容。

singleton (可选,默认为 ``False``)

如果为 true,则此组件的构造函数预计每次调用都会返回相同的单例,并且不会为其生成 mozilla::components::<name>::Create() 方法。

overridable (可选,默认为 ``False``)

如果为 true,则此组件的契约 ID 预计会被某些测试覆盖,因此其 mozilla::components::<name>::Service() 获取器将每次调用都通过契约 ID 查找它。因此,此组件必须在其 contract_ids 数组中提供至少一个契约 ID。

如果为 false,则 Service() 获取器将始终基于其静态数据检索服务,并且无法被覆盖。

注意:启用此选项会降低性能,并且不应在可以避免的情况下或获取器被任何热代码使用时启用。

external (可选,如果提供了任何 headers,则默认为 ``False``,否则为 True)

如果为 true,则此组件的 type 的构造函数必须在另一个翻译单元中定义,使用 NS_IMPL_COMPONENT_FACTORY(type)。构造函数必须返回 already_AddRefed<nsISupports>,并将用于构造此类型的实例。

此选项仅应在无法轻松包含定义组件具体类型的标题而无需本地包含的情况下使用。

注意:外部构造函数可能不会指定 init_method,因为生成的代码将没有调用它所需的必要类型信息。此选项也与 constructorlegacy_constructor 不兼容。

processes (可选,默认为 ``ProcessSelector.ANY_PROCESS``)

一个可选的说明符,限制此组件可以在哪些类型的进程中加载。这必须是 ProcessSelector 的属性,其名称与 Module::ProcessSelector 枚举中的值之一相同。

条件编译

此清单可以运行任何适当的 Python 代码,以根据构建配置自定义 Classes 数组的值。为了简化此过程,以下全局变量可用

defined

如果给定的构建配置设置已定义且为 true,则返回 true 的函数。

buildconfig

buildconfig python 模块,其 substs 属性包含所有可用构建替换的字典。

组件构造函数

有几种方法可以定义组件构造函数,它们主要取决于使用它们的代码有多旧

类构造函数

定义组件的最简单方法是包含定义具体类型的标头,并让组件管理器调用该类的构造函数

'type': 'mozilla::foo::Foo',
'headers': ['mozilla/Foo.h'],

这通常是定义非单例构造函数的首选方法,但对于依赖本地包含进行定义的类来说可能不切实际。

单例构造函数

单例类通常预期提供自己的构造函数,该函数在第一次调用时缓存单例实例,并在后续调用中返回相同的实例。这需要在包含的标头中声明构造函数,并在单独的源文件中实现它

'type': 'mozilla::foo::Foo',
'headers': ['mozilla/Foo.h'],
'constructor': 'mozilla::Foo::GetSingleton',

Foo.h

class Foo final : public nsISupports {
 public:
  static already_AddRefed<Foo> GetSingleton();
};

Foo.cpp

already_AddRefed<Foo> Foo::GetSingleton() {
  // ...
}

外部构造函数

对于其标头不容易包含的类型,可以使用不完整类型的模板特化来定义构造函数

'type': 'mozilla::foo::Foo',
'external: True,'

Foo.cpp

NS_IMPL_COMPONENT_FACTORY(Foo) {
  return do_AddRef(new Foo()).downcast<nsISupports>();
}

旧版构造函数

在新代码中不应使用这些构造函数,并留作读者的练习。

注册类别

需要使用与其契约 ID 相同的值定义类别条目的类可以使用以下方法

'contract_ids': ['@mozilla.org/foo;1'],
'categories': {
    'content-policy': 'm-foo',
    'Gecko-Content-Viewers': ['image/jpeg', 'image/png'],
},

这将定义以下每个类别条目

  • "content-policy" "m-foo", "@mozilla.org/foo;1"

  • "Gecko-Content-Viewers" "image/jpeg" "@mozilla.org/foo;1"

  • "Gecko-Content-Viewers" "image/png" "@mozilla.org/foo;1"

某些类别条目没有契约 ID 作为值。这些条目可以通过添加到全局 Categories 字典来指定

Categories = {
    'update-timer': {
        'nsUpdateService': '@mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,43200,86400',
    }
}

可以通过使用元组作为值来限制这些条目在每个进程中的使用

Categories = {
    '@mozilla.org/streamconv;1': {
        '?from=gzip&to=uncompressed': ('', ProcessSelector.ALLOW_IN_SOCKET_PROCESS),
    }
}