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 `_ 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 `_ 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 `_, 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 `_, 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.