forked from mirrors/gecko-dev
Also cleaned up a few other loose ends on webextensions api docs. MozReview-Commit-ID: FnyqmM7NjqE --HG-- extra : rebase_source : 6039a70c72790c14d8872e38e77e9596b7dac3f8
298 lines
10 KiB
ReStructuredText
298 lines
10 KiB
ReStructuredText
Implementing an event
|
|
=====================
|
|
Like a function, an event requires a definition in the schema and
|
|
an implementation in Javascript inside an instance of ExtensionAPI.
|
|
|
|
Declaring an event in the API schema
|
|
------------------------------------
|
|
The definition for a simple event looks like this:
|
|
|
|
.. code-block:: json
|
|
|
|
[
|
|
{
|
|
"namespace": "myapi",
|
|
"events": [
|
|
{
|
|
"name": "onSomething",
|
|
"type": "function",
|
|
"description": "Description of the event",
|
|
"parameters": [
|
|
{
|
|
"name": "param1",
|
|
"description": "Description of the first callback parameter",
|
|
"type": "number"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
This fragment defines an event that is used from an extension with
|
|
code such as:
|
|
|
|
.. code-block:: js
|
|
|
|
browser.myapi.onSomething.addListener(param1 => {
|
|
console.log(`Something happened: ${param1}`);
|
|
});
|
|
|
|
Note that the schema syntax looks similar to that for a function,
|
|
but for an event, the ``parameters`` property specifies the arguments
|
|
that will be passed to a listener.
|
|
|
|
Implementing an event
|
|
---------------------
|
|
Just like with functions, defining an event in the schema causes
|
|
wrappers to be automatically created and exposed to an extensions'
|
|
appropriate Javascript contexts.
|
|
An event appears to an extension as an object with three standard
|
|
function properties: ``addListener()``, ``removeListener()``,
|
|
and ``hasListener()``.
|
|
Also like functions, if an API defines an event but does not implement
|
|
it in a child process, the wrapper in the child process effectively
|
|
proxies these calls to the implementation in the main process.
|
|
|
|
A helper class called
|
|
`EventManager <reference.html#eventmanager-class>`_ makes implementing
|
|
events relatively simple. A simple event implementation looks like:
|
|
|
|
.. code-block:: js
|
|
|
|
this.myapi = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
return {
|
|
myapi: {
|
|
onSomething: new EventManager(context, "myapi.onSomething", fire => {
|
|
const callback = value => {
|
|
fire.async(value);
|
|
};
|
|
RegisterSomeInternalCallback(callback);
|
|
return () => {
|
|
UnregisterInternalCallback(callback);
|
|
};
|
|
}).api()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
The ``EventManager`` class is usually just used directly as in this example.
|
|
The first argument to the constructor is an ``ExtensionContext`` instance,
|
|
typically just the object passed to the API's ``getAPI()`` function.
|
|
The second argument is a name, it is used only for debugging.
|
|
The third argument is the important piece, it is a function that is called
|
|
the first time a listener is added for this event.
|
|
This function is passed an object (``fire`` in the example) that is used to
|
|
invoke the extension's listener whenever the event occurs. The ``fire``
|
|
object has several different methods for invoking listeners, but for
|
|
events implemented in the main process, the only valid method is
|
|
``async()`` which executes the listener asynchronously.
|
|
|
|
The event setup function (the function passed to the ``EventManager``
|
|
constructor) must return a cleanup function,
|
|
which will be called when the listener is removed either explicitly
|
|
by the extension by calling ``removeListener()`` or implicitly when
|
|
the extension Javascript context from which the listener was added is destroyed.
|
|
|
|
In this example, ``RegisterSomeInternalCallback()`` and
|
|
``UnregisterInternalCallback()`` represent methods for listening for
|
|
some internal browser event from chrome privileged code. This is
|
|
typically something like adding an observer using ``Services.obs`` or
|
|
attaching a listener to an ``EventEmitter``.
|
|
|
|
After constructing an instance of ``EventManager``, its ``api()`` method
|
|
returns an object with with ``addListener()``, ``removeListener()``, and
|
|
``hasListener()`` methods. This is the standard extension event interface,
|
|
this object is suitable for returning from the extension's
|
|
``getAPI()`` method as in the example above.
|
|
|
|
Handling extra arguments to addListener()
|
|
-----------------------------------------
|
|
The standard ``addListener()`` method for events may accept optional
|
|
addition parameters to allow extra information to be passed when registering
|
|
an event listener. One common application of this parameter is for filtering,
|
|
so that extensions that only care about a small subset of the instances of
|
|
some event can avoid the overhead of receiving the ones they don't care about.
|
|
|
|
Extra parameters to ``addListener()`` are defined in the schema with the
|
|
the ``extraParameters`` property. For example:
|
|
|
|
.. code-block:: json
|
|
|
|
[
|
|
{
|
|
"namespace": "myapi",
|
|
"events": [
|
|
{
|
|
"name": "onSomething",
|
|
"type": "function",
|
|
"description": "Description of the event",
|
|
"parameters": [
|
|
{
|
|
"name": "param1",
|
|
"description": "Description of the first callback parameter",
|
|
"type": "number"
|
|
}
|
|
],
|
|
"extraParameters": [
|
|
{
|
|
"name": "minValue",
|
|
"description": "Only call the listener for values of param1 at least as large as this value.",
|
|
"type": "number"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
Extra parameters defined in this way are passed to the event setup
|
|
function (the last parameter to the ``EventManager`` constructor.
|
|
For example, extending our example above:
|
|
|
|
.. code-block:: js
|
|
|
|
this.myapi = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
return {
|
|
myapi: {
|
|
onSomething: new EventManager(context, "myapi.onSomething", (fire, minValue) => {
|
|
const callback = value => {
|
|
if (value >= minValue)
|
|
fire.async(value);
|
|
}
|
|
};
|
|
RegisterSomeInternalCallback(callback);
|
|
return () => {
|
|
UnregisterInternalCallback(callback);
|
|
};
|
|
}).api()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Handling listener return values
|
|
-------------------------------
|
|
Some event APIs allow extensions to affect event handling in some way
|
|
by returning values from event listeners that are processed by the API.
|
|
This can be defined in the schema with the ``returns`` property:
|
|
|
|
.. code-block:: json
|
|
|
|
[
|
|
{
|
|
"namespace": "myapi",
|
|
"events": [
|
|
{
|
|
"name": "onSomething",
|
|
"type": "function",
|
|
"description": "Description of the event",
|
|
"parameters": [
|
|
{
|
|
"name": "param1",
|
|
"description": "Description of the first callback parameter",
|
|
"type": "number"
|
|
}
|
|
],
|
|
"returns": {
|
|
"type": "string",
|
|
"description": "Description of how the listener return value is processed."
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
And the implementation of the event uses the return value from ``fire.async()``
|
|
which is a Promise that resolves to the listener's return value:
|
|
|
|
.. code-block:: js
|
|
|
|
this.myapi = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
return {
|
|
myapi: {
|
|
onSomething: new EventManager(context, "myapi.onSomething", fire => {
|
|
const callback = async (value) => {
|
|
let rv = await fire.async(value);
|
|
log(`The onSomething listener returned the string ${rv}`);
|
|
};
|
|
RegisterSomeInternalCallback(callback);
|
|
return () => {
|
|
UnregisterInternalCallback(callback);
|
|
};
|
|
}).api()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Note that the schema ``returns`` definition is optional and serves only
|
|
for documentation. That is, ``fire.async()`` always returns a Promise
|
|
that resolves to the listener return value, the implementation of an
|
|
event can just ignore this Promise if it doesn't care about the return value.
|
|
|
|
Implementing an event in the child process
|
|
------------------------------------------
|
|
The reasons for implementing events in the child process are similar to
|
|
the reasons for implementing functions in the child process:
|
|
|
|
- Listeners for the event return a value that the API implementation must
|
|
act on synchronously.
|
|
|
|
- Either ``addListener()`` or the listener function has one or more
|
|
parameters of a type that cannot be sent between processes.
|
|
|
|
- The implementation of the event interacts with code that is only
|
|
accessible from a child process.
|
|
|
|
- The event can be implemented substantially more efficiently in a
|
|
child process.
|
|
|
|
The process for implementing an event in the child process is the same
|
|
as for functions -- simply implement the event in an ExtensionAPI subclass
|
|
that is loaded in a child process. And just as a function in a child
|
|
process can call a function in the main process with
|
|
`callParentAsyncFunction()`, events in a child process may subscribe to
|
|
events implemented in the main process with a similar `getParentEvent()`.
|
|
For example, the automatically generated event proxy in a child process
|
|
could be written explicitly as:
|
|
|
|
.. code-block:: js
|
|
|
|
this.myapi = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
return {
|
|
myapi: {
|
|
onSomething: new EventManager(context, "myapi.onSomething", fire => {
|
|
const listener = (value) => {
|
|
fire.async(value);
|
|
};
|
|
|
|
let parentEvent = context.childManager.getParentEvent("myapi.onSomething");
|
|
parent.addListener(listener);
|
|
return () => {
|
|
parent.removeListener(listener);
|
|
};
|
|
}).api()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Events implemented in a child process have some additional methods available
|
|
to dispatch listeners:
|
|
|
|
- ``fire.sync()`` This runs the listener synchronously and returns the
|
|
value returned by the listener
|
|
|
|
- ``fire.raw()`` This runs the listener synchronously without cloning
|
|
the listener arguments into the extension's Javascript compartment.
|
|
This is used as a performance optimization, it should not be used
|
|
unless you have a detailed understanding of Javascript compartments
|
|
and cross-compartment wrappers.
|
|
|
|
|