1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
Implementing a function
=======================
Implementing an API function requires at least two different pieces:
a definition for the function in the schema, and Javascript code that
actually implements the function.
Declaring a function in the API schema
--------------------------------------
An API schema definition for a simple function looks like this:
.. code-block:: json
[
{
"namespace": "myapi",
"functions": [
{
"name": "add",
"type": "function",
"description": "Adds two numbers together.",
"async": true,
"parameters": [
{
"name": "x",
"type": "number",
"description": "The first number to add."
},
{
"name": "y",
"type": "number",
"description": "The second number to add."
}
]
}
]
}
]
The ``type`` and ``description`` properties were described above.
The ``name`` property is the name of the function as it appears in
the given namespace. That is, the fragment above creates a function
callable from an extension as ``browser.myapi.add()``.
The ``parameters`` property describes the parameters the function takes.
Parameters are specified as an array of Javascript types, where each
parameter is a constrained Javascript value as described
in the previous section.
Each parameter may also contain additional properties ``optional``
and ``default``. If ``optional`` is present it must be a boolean
(and parameters are not optional by default so this property is typically
only added when it has the value ``true``).
The ``default`` property is only meaningful for optional parameters,
it specifies the value that should be used for an optional parameter
if the function is called without that parameter.
An optional parameter without an explicit ``default`` property will
receive a default value of ``null``.
Although it is legal to create optional parameters at any position
(i.e., optional parameters can come before required parameters), doing so
leads to difficult to use functions and API designers are encouraged to
use object-valued parameters with optional named properties instead,
or if optional parameters must be used, to use them sparingly and put
them at the end of the parameter list.
.. XXX should we describe allowAmbiguousArguments?
The boolean-valued ``async`` property specifies whether a function
is asynchronous.
For asynchronous functions,
the WebExtensions framework takes care of automatically generating a
`Promise <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>`_ and then resolving the Promise when the function
implementation completes (or rejecting the Promise if the implementation
throws an Error).
Since extensions can run in a child process, any API function that is
implemented (either partially or completely) in the parent process must
be asynchronous.
When a function is declared in the API schema, a wrapper for the function
is automatically created and injected into appropriate extension Javascript
contexts. This wrapper automatically validates arguments passed to the
function against the formal parameters declared in the schema and immediately
throws an Error if invalid arguments are passed.
It also processes optional arguments and inserts default values as needed.
As a result, API implementations generally do not need to write much
boilerplate code to validate and interpret arguments.
Implementing a function in the main process
-------------------------------------------
If an asynchronous function is not implemented in the child process,
the wrapper generated from the schema automatically marshalls the
function arguments, sends the request to the parent process,
and calls the implementation there.
When that function completes, the return value is sent back to the child process
and the Promise for the function call is resolved with that value.
Based on this, an implementation of the function we wrote the schema
for above looks like this:
.. code-block:: js
this.myapi = class extends ExtensionAPI {
getAPI(context) {
return {
myapi: {
add(x, y) { return x+y; }
}
}
}
}
The implementations of API functions are contained in a subclass of the
`ExtensionAPI <reference.html#extensionapi-class>`_ class.
Each subclass of ExtensionAPI must implement the ``getAPI()`` method
which returns an object with a structure that mirrors the structure of
functions and events that the API exposes.
The ``context`` object passed to ``getAPI()`` is an instance of
`BaseContext <reference.html#basecontext-class>`_,
which contains a number of useful properties and methods.
If an API function implementation returns a Promise, its result will
be sent back to the child process when the Promise is settled.
Any other return type will be sent directly back to the child process.
A function implementation may also raise an Error. But by default,
an Error thrown from inside an API implementation function is not
exposed to the extension code that called the function -- it is
converted into generic errors with the message "An unexpected error occurred".
To throw a specific error to extensions, use the ``ExtensionError`` class:
.. code-block:: js
this.myapi = class extends ExtensionAPI {
getAPI(context) {
return {
myapi: {
doSomething() {
if (cantDoSomething) {
throw new ExtensionError("Cannot call doSomething at this time");
}
return something();
}
}
}
}
}
The purpose of this step is to avoid bugs in API implementations from
exposing details about the implementation to extensions. When an Error
that is not an instance of ExtensionError is thrown, the original error
is logged to the
`Browser Console <https://developer.mozilla.org/en-US/docs/Tools/Browser_Console>`_,
which can be useful while developing a new API.
Implementing a function in a child process
------------------------------------------
Most functions are implemented in the main process, but there are
occasionally reasons to implement a function in a child process, such as:
- The function has one or more parameters of a type that cannot be automatically
sent to the main process using the structured clone algorithm.
- The function implementation interacts with some part of the browser
internals that is only accessible from a child process.
- The function can be implemented substantially more efficiently in
a child process.
To implement a function in a child process, simply include an ExtensionAPI
subclass that is loaded in the appropriate context
(e.g, ``addon_child``, ``content_child``, etc.) as described in
the section on :ref:`basics`.
Code inside an ExtensionAPI subclass in a child process may call the
implementation of a function in the parent process using a method from
the API context as follows:
.. code-block:: js
this.myapi = class extends ExtensionAPI {
getAPI(context) {
return {
myapi: {
async doSomething(arg) {
let result = await context.childManager.callParentAsyncFunction("anothernamespace.functionname", [arg]);
/* do something with result */
return ...;
}
}
}
}
}
As you might expect, ``callParentAsyncFunction()`` calls the given function
in the main process with the given arguments, and returns a Promise
that resolves with the result of the function.
This is the same mechanism that is used by the automatically generated
function wrappers for asynchronous functions that do not have a
provided implementation in a child process.
It is possible to define the same function in both the main process
and a child process and have the implementation in the child process
call the function with the same name in the parent process.
This is a common pattern when the implementation of a particular function
requires some code in both the main process and child process.
|