summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/docs/functions.rst
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/extensions/docs/functions.rst
parentInitial commit. (diff)
downloadthunderbird-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.rst201
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.