diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/extensions/docs/functions.rst | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/extensions/docs/functions.rst')
-rw-r--r-- | toolkit/components/extensions/docs/functions.rst | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/toolkit/components/extensions/docs/functions.rst b/toolkit/components/extensions/docs/functions.rst new file mode 100644 index 0000000000..f1727aceed --- /dev/null +++ b/toolkit/components/extensions/docs/functions.rst @@ -0,0 +1,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. |