Convert xpcom/docs/*.rst to MyST-flavored Markdown. Update stringguide.xpidl :ref: targets to the explicit (stringguide-xpidl)= MyST anchor. Differential Revision: https://phabricator.services.mozilla.com/D304220
284 lines
10 KiB
Markdown
284 lines
10 KiB
Markdown
(writing-xpcom-interface)=
|
|
|
|
# Tutorial for Writing a New XPCOM Interface
|
|
|
|
## High Level Overview
|
|
|
|
In order to write code that works in native code (C++, Rust), and JavaScript contexts, it's necessary to have a mechanism to do so. For chrome privileged contexts, this is the XPCOM Interface Class.
|
|
|
|
This mechanism starts with an {ref}`XPIDL` file to define the shape of the interface. In the {searchfox}`build system <xpcom/idl-parser/xpidl>`, this file is processed, and {searchfox}`Rust <__GENERATED__/dist/xpcrs/rt>` and {searchfox}`C++ <__GENERATED__/dist/include>` code is automatically generated.
|
|
|
|
Next, the interface's methods and attributes must be implemented. This can be done through either a JSM module, or through a C++ interface class. Once these steps are done, the new files must be added to the appropriate {code}`moz.build` files to ensure the build system knows how to find them and process them.
|
|
|
|
Often these XPCOM components are wired into the {code}`Services` JavaScript object to allow for ergonomic access to the interface. For example, open the [Browser Console] and type {code}`Services.` to interactively access these components.
|
|
|
|
From C++, components can be accessed via {code}`mozilla::components::ComponentName::Create()` using the {code}`name` option in the {code}`components.conf`.
|
|
|
|
While {code}`Services` and {code}`mozilla::components` are the preferred means of accessing components, many are accessed through the historical (and somewhat arcane) {code}`createInstance` mechanism. New usage of these mechanisms should be avoided if possible.
|
|
|
|
```javascript
|
|
let component = Cc["@mozilla.org/component-name;1"].createInstance(
|
|
Ci.nsIComponentName
|
|
);
|
|
```
|
|
|
|
```c++
|
|
nsCOMPtr<nsIComponentName> component = do_CreateInstance(
|
|
"@mozilla.org/component-name;1");
|
|
```
|
|
|
|
## Writing an XPIDL
|
|
|
|
First decide on a name. Conventionally the interfaces are prefixed with {code}`nsI` (historically Netscape) or {code}`mozI` as they are defined in the global namespace. While the interface is global, the implementation of an interface can be defined in a namespace with no prefix. Historically many component implementations still use the {code}`ns` prefixes (notice that the {code}`I` was dropped), but this convention is no longer needed.
|
|
|
|
This tutorial assumes the component is located at {code}`path/to` with the name {code}`ComponentName`. The interface name will be {code}`nsIComponentName`, while the implementation will be {code}`mozilla::ComponentName`.
|
|
|
|
To start, create an {ref}`XPIDL` file:
|
|
|
|
```bash
|
|
touch path/to/nsIComponentName.idl
|
|
```
|
|
|
|
And hook it up to the {code}`path/to/moz.build`
|
|
|
|
```python
|
|
XPIDL_SOURCES += [
|
|
"nsIComponentName.idl",
|
|
]
|
|
XPIDL_MODULE = "nsIComponentName"
|
|
|
|
FINAL_LIBRARY = "xul"
|
|
```
|
|
|
|
Next write the initial {code}`.idl` file: {code}`path/to/nsIComponentName.idl`
|
|
|
|
(contract-ids)=
|
|
|
|
```c++
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// This is the base include which defines nsISupports. This class defines
|
|
// the QueryInterface method.
|
|
#include "nsISupports.idl"
|
|
|
|
// `scriptable` designates that this object will be used with JavaScript
|
|
// `uuid` The example below uses a UUID with all Xs. Replace the Xs with
|
|
// your own UUID generated with `mach gen-uuid`, `uuidgen`, or
|
|
// https://mozilla.pettay.fi/uuidgen.html
|
|
|
|
/**
|
|
* Make sure to document your interface.
|
|
*/
|
|
[scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)]
|
|
interface nsIComponentName : nsISupports {
|
|
|
|
// Fill out your definition here. This example attribute only returns a bool.
|
|
|
|
/**
|
|
* Make sure to document your attributes.
|
|
*/
|
|
readonly attribute boolean isAlive;
|
|
};
|
|
```
|
|
|
|
This definition only includes one attribute, {code}`isAlive`, which will demonstrate that we've done our work correctly at the end. For a more comprehensive guide for this syntax, see the {ref}`XPIDL` docs.
|
|
|
|
Once {code}`./mach build` is run, the XPIDL parser will read this file, and give any warnings if the syntax is wrong. It will then auto-generate the C++ (or Rust) code for us. For this example the generated {code}`nsIComponentName` class will be located in:
|
|
|
|
{code}`{obj-directory}/dist/include/nsIComponentName.h`
|
|
|
|
It might be useful to check out what was automatically generated here, or see the existing {searchfox}`generated C++ header files on SearchFox <__GENERATED__/dist/>`.
|
|
|
|
## Writing the C++ implementation
|
|
|
|
Now we have a definition for an interface, but no implementation. The interface could be backed by a JavaScript implementation using a JSM, but for this example we'll use a C++ implementation.
|
|
|
|
Add the C++ sources to {code}`path/to/moz.build`
|
|
|
|
```python
|
|
EXPORTS.mozilla += [
|
|
"ComponentName.h",
|
|
]
|
|
|
|
UNIFIED_SOURCES += [
|
|
"ComponentName.cpp",
|
|
]
|
|
```
|
|
|
|
Now write the header: {code}`path/to/ComponentName.h`
|
|
|
|
```c++
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#ifndef mozilla_nsComponentName_h__
|
|
#define mozilla_nsComponentName_h__
|
|
|
|
// This will pull in the header auto-generated by the .idl file:
|
|
// {obj-directory}/dist/include/nsIComponentName.h
|
|
#include "nsIComponentName.h"
|
|
|
|
// The implementation can be namespaced, while the XPCOM interface is globally namespaced.
|
|
namespace mozilla {
|
|
|
|
// Notice how the class name does not need to be prefixed, as it is defined in the
|
|
// `mozilla` namespace.
|
|
class ComponentName final : public nsIComponentName {
|
|
// This first macro includes the necessary information to use the base nsISupports.
|
|
// This includes the QueryInterface method.
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// This second macro includes the declarations for the attributes. There is
|
|
// no need to duplicate these declarations.
|
|
//
|
|
// In our case it includes a declaration for the isAlive attribute:
|
|
// GetIsAlive(bool *aIsAlive)
|
|
NS_DECL_NSICOMPONENTNAME
|
|
|
|
public:
|
|
ComponentName() = default;
|
|
|
|
private:
|
|
// A private destructor must be declared.
|
|
~ComponentName() = default;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|
|
```
|
|
|
|
Now write the definitions: {code}`path/to/ComponentName.cpp`
|
|
|
|
```c++
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ComponentName.h"
|
|
|
|
namespace mozilla {
|
|
|
|
// Use the macro to inject all of the definitions for nsISupports.
|
|
NS_IMPL_ISUPPORTS(ComponentName, nsIComponentName)
|
|
|
|
// This is the actual implementation of the `isAlive` attribute. Note that the
|
|
// method name is somewhat different than the attribute. We specified "read-only"
|
|
// in the attribute, so only a getter, not a setter was defined for us. Here
|
|
// the name was adjusted to be `GetIsAlive`.
|
|
//
|
|
// Another common detail of implementing an XPIDL interface is that the return values
|
|
// are passed as out parameters. The methods are treated as fallible, and the return
|
|
// value is an `nsresult`. See the XPIDL documentation for the full nitty gritty
|
|
// details.
|
|
//
|
|
// A common way to know the exact function signature for a method implementation is
|
|
// to copy and paste from existing examples, or inspecting the generated file
|
|
// directly: {obj-directory}/dist/include/nsIComponentName.h
|
|
NS_IMETHODIMP
|
|
ComponentName::GetIsAlive(bool* aIsAlive) {
|
|
*aIsAlive = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace: mozilla
|
|
```
|
|
|
|
## Registering the component
|
|
|
|
At this point, the component should be correctly written, but it's not registered with the component system. In order to this, we'll need to create or modify the {code}`components.conf`.
|
|
|
|
```bash
|
|
touch path/to/components.conf
|
|
```
|
|
|
|
Now update the {code}`moz.build` to point to it.
|
|
|
|
```python
|
|
XPCOM_MANIFESTS += [
|
|
"components.conf",
|
|
]
|
|
```
|
|
|
|
It is probably worth reading over {ref}`defining-xpcom-components`, but the following config will be sufficient to hook up our component to the {code}`Services` object.
|
|
Services should also be added to `tools/lint/eslint/eslint-plugin-mozilla/lib/services.json`.
|
|
The easiest way to do that is to copy from `<objdir>/xpcom/components/services.json`.
|
|
|
|
```python
|
|
Classes = [
|
|
{
|
|
# This CID is the ID for component entries, and needs a separate UUID from
|
|
# the .idl file. Replace the Xs with a uuid from `mach gen-uuid`,
|
|
# `uuidgen`, or https://mozilla.pettay.fi/uuidgen.html
|
|
'cid': '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}',
|
|
'interfaces': ['nsIComponentName'],
|
|
|
|
# A contract ID is a human-readable identifier for an _implementation_ of
|
|
# an XPCOM interface.
|
|
#
|
|
# "@mozilla.org/process/environment;1"
|
|
# ^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^ ^
|
|
# | | | |
|
|
# | | | The version number, usually just 1.
|
|
# | | Component name
|
|
# | Module
|
|
# Domain
|
|
#
|
|
# This design goes back to a time when XPCOM was intended to be a generalized
|
|
# solution for the Gecko Runtime Environment (GRE). At this point most (if
|
|
# not all) of mozilla-central has an @mozilla domain.
|
|
'contract_ids': ['@mozilla.org/component-name;1'],
|
|
|
|
# This is the name of the C++ type that implements the interface.
|
|
'type': 'mozilla::ComponentName',
|
|
|
|
# The header file to pull in for the implementation of the interface.
|
|
'headers': ['path/to/ComponentName.h'],
|
|
|
|
# In order to hook up this interface to the `Services` object, we can
|
|
# provide the "js_name" parameter. This is an ergonomic way to access
|
|
# the component.
|
|
'js_name': 'componentName',
|
|
},
|
|
]
|
|
```
|
|
|
|
At this point the full {code}`moz.build` file should look like:
|
|
|
|
```python
|
|
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
# vim: set filetype=python:
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
XPIDL_SOURCES += [
|
|
"nsIComponentName.idl",
|
|
]
|
|
|
|
XPCOM_MANIFESTS += [
|
|
"components.conf",
|
|
]
|
|
|
|
EXPORTS.mozilla += [
|
|
"ComponentName.h",
|
|
]
|
|
|
|
UNIFIED_SOURCES += [
|
|
"ComponentName.cpp",
|
|
]
|
|
|
|
FINAL_LIBRARY = "xul"
|
|
```
|
|
|
|
This completes the implementation of a basic XPCOM Interface using C++. The component should be available via the [Browser Console] or other chrome contexts.
|
|
|
|
```javascript
|
|
console.log(Services.componentName.isAlive);
|
|
> true
|
|
```
|
|
|
|
[browser console]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Console
|