diff options
Diffstat (limited to 'toolkit/components/extensions/docs')
-rw-r--r-- | toolkit/components/extensions/docs/background.rst | 134 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/basics.rst | 208 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/events.rst | 314 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/functions.rst | 201 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/incognito.rst | 78 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/index.rst | 32 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/lifecycle.rst | 60 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/manifest.rst | 68 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/other.rst | 140 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/reference.rst | 35 | ||||
-rw-r--r-- | toolkit/components/extensions/docs/schema.rst | 145 |
11 files changed, 1415 insertions, 0 deletions
diff --git a/toolkit/components/extensions/docs/background.rst b/toolkit/components/extensions/docs/background.rst new file mode 100644 index 0000000000..71ce2a59d0 --- /dev/null +++ b/toolkit/components/extensions/docs/background.rst @@ -0,0 +1,134 @@ +Background +========== + +WebExtensions run in a sandboxed environment much like regular web content. +The purpose of extensions is to enhance the browser in a way that +regular content cannot -- WebExtensions APIs bridge this gap by exposing +browser features to extensions in a way preserves safety, reliability, +and performance. +The implementation of a WebExtension API runs with +`chrome privileges <https://developer.mozilla.org/en-US/docs/Security/Firefox_Security_Basics_For_Developers>`_. +Browser internals are accessed using +`XPCOM <https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM>`_ +or `ChromeOnly WebIDL features <https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings#ChromeOnly>`_. + +The rest of this documentation covers how API implementations interact +with the implementation of WebExtensions. +To expose some browser feature to WebExtensions, the first step is +to design the API. Some high-level principles for API design +are documented on the Mozilla wiki: + +- `Vision for WebExtensions <https://wiki.mozilla.org/WebExtensions/Vision>`_ +- `API Policies <https://wiki.mozilla.org/WebExtensions/policy>`_ +- `Process for creating new APIs <https://wiki.mozilla.org/WebExtensions/NewAPIs>`_ + +Javascript APIs +--------------- +Many WebExtension APIs are accessed directly from extensions through +Javascript. Functions are the most common type of object to expose, +though some extensions expose properties of primitive Javascript types +(e.g., constants). +Regardless of the exact method by which something is exposed, +there are a few important considerations when designing part of an API +that is accessible from Javascript: + +- **Namespace**: + Everything provided to extensions is exposed as part of a global object + called ``browser``. For compatibility with Google Chrome, many of these + features are also exposed on a global object called ``chrome``. + Functions and other objects are not exposed directly as properties on + ``browser``, they are organized into *namespaces*, which appear as + properties on ``browser``. For example, the + `tabs API <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs>`_ + uses a namespace called ``tabs``, so all its functions and other + properties appear on the object ``browser.tabs``. + For a new API that provides features via Javascript, the usual practice + is to create a new namespace with a concise but descriptive name. + +- **Environments**: + There are several different types of Javascript environments in which + extension code can execute: extension pages, content scripts, proxy + scripts, and devtools pages. + Extension pages include the background page, popups, and content pages + accessed via |getURL|_. + When creating a new Javascript feature the designer must choose + in which of these environments the feature will be available. + Most Javascript features are available in extension pages, + other environments have limited sets of API features available. + +.. |getURL| replace:: ``browser.runtime.getURL()`` +.. _getURL: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/getURL + +- **Permissions**: + Many Javascript features are only present for extensions that + include an appropriate permission in the manifest. + The guidelines for when an API feature requires a permission are + described in (*citation needed*). + +The specific types of features that can be exposed via Javascript are: + +- **Functions**: + A function callable from Javascript is perhaps the most commonly + used feature in WebExtension APIs. + New API functions are asynchronous, returning a + `Promise <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>`_. Even functions that do not return a result + use Promises so that errors can be indicated asynchronously + via a rejected Promise as opposed to a synchronously thrown Error. + This is due to the fact that extensions run in a child process and + many API functions require communication with the main process. + If an API function that needs to communicate in this way returned a + synchronous result, then all Javascript execution in the child + process would need to be paused until a response from the main process + was received. Even if a function could be implemented synchronously + within a child process, the standard practice is to make it + asynchronous so as not to constrain the implementation of the underlying + browser feature and make it impossible to move functionality out of the + child process. + Another consequence of functions using inter-process communication is + that the parameters to a function and its return value must all be + simple data types that can be sent between processes using the + `structured clone algorithm <https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm>`_. + +- **Events**: + Events complement functions (which allow an extension to call into + an API) by allowing an event within the browser to invoke a callback + in the extension. + Any time an API requires an extension to pass a callback function that + gets invoked some arbitrary number of times, that API method should be + defined as an event. + +Manifest Keys +------------- +In addition to providing functionality via Javascript, WebExtension APIs +can also take actions based on the contents of particular properties +in an extension's manifest (or even just the presence of a particular +property). +Manifest entries are used for features in which an extension specifies +some static information that is used when an extension is installed or +when it starts up (i.e., before it has the chance to run any code to use +a Javascript API). +An API may handle a manifest key and implement Javscript functionality, +see the +`browser action <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browserAction>`_ +API for an example. + +Other Considerations +-------------------- +In addition to the guidelines outlined above, +there are some other considerations when designing and implementing +a WebExtension API: + +- **Cleanup**: A badly written WebExtension should not be able to permanently + leak any resources. In particular, any action from an extension that + causes a resource to be allocated within the browser should be + automatically cleaned up when the extension is disabled or uninstalled. + This is described in more detail in the section on :ref:`lifecycle`. + +- **Performance**: A new WebExtension API should not add any new overhead + to the browser when the API is not used. That is, the implementation + of the API should not be loaded at all unless it is actively used by + an extension. In addition, initialization should be delayed when + possible -- extensions ared started relatively early in the browser + startup process so any unnecessary work done during extension startup + contributes directly to sluggish browser startup. + diff --git a/toolkit/components/extensions/docs/basics.rst b/toolkit/components/extensions/docs/basics.rst new file mode 100644 index 0000000000..a1f78b5c65 --- /dev/null +++ b/toolkit/components/extensions/docs/basics.rst @@ -0,0 +1,208 @@ +.. _basics: + +API Implementation Basics +========================= +This page describes some of the pieces involved when creating +WebExtension APIs. Detailed documentation about how these pieces work +together to build specific features is in the next section. + +The API Schema +-------------- +As described previously, a WebExtension runs in a sandboxed environment +but the implementation of a WebExtension API runs with full chrome +privileges. API implementations do not directly interact with +extensions' Javascript environments, that is handled by the WebExtensions +framework. Each API includes a schema that describes all the functions, +events, and other properties that the API might inject into an +extension's Javascript environment. +Among other things, the schema specifies the namespace into which +an API should be injected, what permissions (if any) are required to +use it, and in which contexts (e.g., extension pages, content scripts, etc) +it should be available. The WebExtensions framework reads this schema +and takes care of injecting the right objects into each extension +Javascript environment. + +API schemas are written in JSON and are based on +`JSON Schema <http://json-schema.org/>`_ with some extensions to describe +API functions and events. +The next section describes the format of the schema in detail. + +The ExtensionAPI class +---------------------- +Every WebExtension API is represented by an instance of the Javascript +`ExtensionAPI <reference.html#extensionapi-class>`_ class. +An instance of its API class is created every time an extension that has +access to the API is enabled. Instances of this class contain the +implementations of functions and events that are exposed to extensions, +and they also contain code for handling manifest keys as well as other +part of the extension lifecycle (e.g., updates, uninstalls, etc.) +The details of this class are covered in a subsequent section, for now the +important point is that this class contains all the actual code that +backs a particular WebExtension API. + +Built-in APIs versus Experiments +-------------------------------- +A WebExtension API can be built directly into the browser or it can be +contained in a special type of extension called a "WebExtension Experiment". +The API schema and the ExtensionAPI class are written in the same way +regardless of how the API will be delivered, the rest of this section +explains how to package a new API using these methods. + +Adding a built-in API +--------------------- +Built-in WebExtension APIs are loaded lazily. That is, the schema and +accompanying code are not actually loaded and interpreted until an +extension that uses the API is activated. +To actually register the API with the WebExtensions framework, an entry +must be added to the list of WebExtensions modules in one of the following +files: + +- ``toolkit/components/extensions/ext-toolkit.json`` +- ``browser/components/extensions/ext-browser.json`` +- ``mobile/android/components/extensions/ext-android.json`` + +Here is a sample fragment for a new API: + +.. code-block:: js + + "myapi": { + "schema": "chrome://extensions/content/schemas/myapi.json", + "url": "chrome://extensions/content/ext-myapi.js", + "paths": [ + ["myapi"], + ["anothernamespace", "subproperty"] + ], + "scopes": ["addon_parent"], + "permissions": ["myapi"], + "manifest": ["myapi_key"], + "events": ["update", "uninstall"] + } + +The ``schema`` and ``url`` properties are simply URLs for the API schema +and the code implementing the API. The ``chrome:`` URLs in the example above +are typically created by adding entries to ``jar.mn`` in the mozilla-central +directory where the API implementation is kept. The standard locations for +API implementations are: + +- ``toolkit/components/extensions``: This is where APIs that work in both + the desktop and mobile versions of Firefox (as well as potentially any + other applications built on Gecko) should go +- ``browser/components/extensions``: APIs that are only supported on + Firefox for the desktop. +- ``mobile/android/components/extensions``: APIs that are only supported + on Firefox for Android. + +Within the appropriate extensions directory, the convention is that the +API schema is in a file called ``schemas/name.json`` (where *name* is +the name of the API, typically the same as its namespace if it has +Javascript visible features). The code for the ExtensionAPI class is put +in a file called ``ext-name.js``. If the API has code that runs in a +child process, that is conventionally put in a file called ``ext-c-name.js``. + +The remaining properties specify when an API should be loaded. +The ``paths``, ``scopes``, and ``permissions`` properties together +cause an API to be loaded when Javascript code in an extension references +something beneath the ``browser`` global object that is part of the API. +The ``paths`` property is an array of paths where each individual path is +also an array of property names. In the example above, the sample API will +be loaded if an extension references either ``browser.myapi`` or +``browser.anothernamespace.subproperty``. + +A reference to a property beneath ``browser`` only causes the API to be +loaded if it occurs within a scope listed in the ``scopes`` property. +A scope corresponds to the combination of a Javascript environment +(e.g., extension pages, content scripts, etc) and the process in which the +API code should run (which is either the main/parent process, or a +content/child process). +Valid ``scopes`` are: + +- ``"addon_parent"``, ``"addon_child``: Extension pages + +- ``"content_parent"``, ``"content_child``: Content scripts + +- ``"devtools_parent"``, ``"devtools_child"``: Devtools pages + +The distinction between the ``_parent`` and ``_child`` scopes will be +explained in further detail in following sections. + +A reference to a property only causes the API to be loaded if the +extension referencing the property also has all the permissions listed +in the ``permissions`` property. + +A WebExtension API that is controlled by a manifest key can also be loaded +when an extension that includes the relevant manifest key is activated. +This is specified by the ``manifest`` property, which lists any manifest keys +that should cause the API to be loaded. + +Finally, APIs can be loaded based on other events in the WebExtension +lifecycle. These are listed in the ``events`` property and described in +more detail in :ref:`lifecycle`. + +WebExtensions Experiments +------------------------- +A new API may also be implemented within an extension. An API implemented +this way is called a WebExtension Experiment. Experiments can be useful +when actively developing a new API, as they do not require building +Firefox locally. Note that extensions that include experiments cannot be +signed by addons.mozilla.org. They may be installed temporarily via +``about:debugging`` or, on browser that support it (current Nightly and +Developer Edition), by setting the preference +``xpinstall.signatures.required`` to ``false``. You may also set the +preference ``extensions.experiments.enabled`` to ``true`` to install the +addon normally and test across restart. + +Experimental APIs have a few limitations compared with built-in APIs: + +- Experimental APIs can (currently) only be exposed to extension pages, + not to devtools pages or to content scripts. +- Experimental APIs cannot handle manifest keys (since the extension manifest + needs to be parsed and validated before experimental APIs are loaded). +- Experimental APIs cannot use the static ``"update"`` and ``"uninstall"`` + lifecycle events (since in general those may occur when an affected + extension is not active or installed). + +Experimental APIs are declared in the ``experiment_apis`` property in a +WebExtension's ``manifest.json`` file. For example: + +.. code-block:: js + + { + "manifest_version": 2, + "name": "Extension containing an experimental API", + "experiment_apis": { + "apiname": { + "schema": "schema.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["myapi"]], + "script": "implementation.js" + }, + + "child": { + "scopes": ["addon_child"], + "paths": [["myapi"]], + "script": "child-implementation.js" + } + } + } + } + +This is essentially the same information required for built-in APIs, +just organized differently. The ``schema`` property is a relative path +to a file inside the extension containing the API schema. The actual +implementation details for the parent process and for child processes +are defined in the ``parent`` and ``child`` properties of the API +definition respectively. Inside these sections, the ``scope`` and ``paths`` +properties have the same meaning as those properties in the definition +of a built-in API (though see the note above about limitations; the +only currently valid values for ``scope`` are ``"addon_parent"`` and +``"addon_child"``). The ``script`` property is a relative path to a file +inside the extension containing the implementation of the API. + +The extension that includes an experiment defined in this way automatically +gets access to the experimental API. An extension may also use an +experimental API implemented in a different extension by including the +string ``experiments.name`` in the ``permissions``` property in its +``manifest.json`` file. In this case, the string name must be replace by +the name of the API from the extension that defined it (e.g., ``apiname`` +in the example above. diff --git a/toolkit/components/extensions/docs/events.rst b/toolkit/components/extensions/docs/events.rst new file mode 100644 index 0000000000..8fd01976cd --- /dev/null +++ b/toolkit/components/extensions/docs/events.rst @@ -0,0 +1,314 @@ +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, + name: "myapi.onSomething", + register: 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, + name: "myapi.onSomething", + register: (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, + name: "myapi.onSomething", + register: 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, + name: "myapi.onSomething", + register: 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. + + 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. diff --git a/toolkit/components/extensions/docs/incognito.rst b/toolkit/components/extensions/docs/incognito.rst new file mode 100644 index 0000000000..7d94795888 --- /dev/null +++ b/toolkit/components/extensions/docs/incognito.rst @@ -0,0 +1,78 @@ +.. _incognito: + +Incognito Implementation +======================== + +This page provides a high level overview of how incognito works in +Firefox, primarily to help in understanding how to test the feature. + +The Implementation +------------------ + +The incognito value in manifest.json supports ``spanning`` and ``not_allowed``. +The other value, ``split``, may be supported in the future. The default +value is ``spanning``, however, by default access to private windows is +not allowed. The user must turn on support, per extension, in ``about:addons``. + +Internally this is handled as a hidden extension permission called +``internal:privateBrowsingAllowed``. This permission is reset when the +extension is disabled or uninstalled. The permission is accessible in +several ways: + +- extension.privateBrowsingAllowed +- context.privateBrowsingAllowed (see BaseContext) +- WebExtensionPolicy.privateBrowsingAllowed +- WebExtensionPolicy.canAccessWindow(DOMWindow) + +Testing +------- + +The goal of testing is to ensure that data from a private browsing session +is not accessible to an extension without permission. + +In Firefox 67, the feature will initially be disabled, however the +intention is to enable the feature on in 67. The pref controlling this +is ``extensions.allowPrivateBrowsingByDefault``. When this pref is +``true``, all extensions have access to private browsing and the manifest +value ``not_allowed`` will produce an error. To enable incognito.not_allowed +for tests you must flip the pref to false. + +Testing EventManager events +--------------------------- + +This is typically most easily handled by running a test with an extension +that has permission, using ``incognitoOverride: spanning`` in the call to +ExtensionTestUtils.loadExtension. You can then use a second extension +without permission to try and catch any events that would typically be passed. + +If the events can happen without calls produced by an extension, you can +also use BrowserTestUtils to open a private window, and use a non-permissioned +extension to run tests against it. + +There are two utility functions in head.js, getIncognitoWindow and +startIncognitoMonitorExtension, which are useful for some basic testing. + +Example: `browser_ext_windows_events.js <https://searchfox.org/mozilla-central/rev/78cd247b5d7a08832f87d786541d3e2204842e8e/browser/components/extensions/test/browser/browser_ext_windows_events.js>`_ + +Testing API Calls +----------------- + +This is easily done using an extension without permission. If you need +an ID of a window or tab, use getIncognitoWindow. In most cases, the +API call should throw an exception when the window is not accessible. +There are some cases where API calls explicitly do not throw. + +Example: `browser_ext_windows_incognito.js <https://searchfox.org/mozilla-central/rev/78cd247b5d7a08832f87d786541d3e2204842e8e/browser/components/extensions/test/browser/browser_ext_windows_incognito.js>`_ + +Privateness of window vs. tab +----------------------------- + +Android does not currently support private windows. When a tab is available, +the test should prefer tab over window. + +- PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser) +- PrivateBrowsingUtils.isContentWindowPrivate(widnow) + +When WebExtensionPolicy is handy to use, you can directly check window access: + +- policy.canAccessWindow(window) diff --git a/toolkit/components/extensions/docs/index.rst b/toolkit/components/extensions/docs/index.rst new file mode 100644 index 0000000000..f75eb5914e --- /dev/null +++ b/toolkit/components/extensions/docs/index.rst @@ -0,0 +1,32 @@ +WebExtensions API Development +============================= + +This documentation covers the implementation of WebExtensions inside Firefox. +Documentation about existing WebExtension APIs and how to use them +to develop WebExtensions is available +`on MDN <https://developer.mozilla.org/en-US/Add-ons/WebExtensions>`_. + +To use this documentation, you should already be familiar with +WebExtensions, including +`the anatomy of a WebExtension <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Anatomy_of_a_WebExtension>`_ +and `permissions <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/permissions>`_. +You should also be familiar with concepts from +`Firefox development <https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide>`_ +including `e10s <https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox>`_ +in particular. + +.. toctree:: + :caption: WebExtension API Developers Guide + :maxdepth: 2 + + background + basics + schema + functions + events + manifest + lifecycle + incognito + other + reference + diff --git a/toolkit/components/extensions/docs/lifecycle.rst b/toolkit/components/extensions/docs/lifecycle.rst new file mode 100644 index 0000000000..2384bada8d --- /dev/null +++ b/toolkit/components/extensions/docs/lifecycle.rst @@ -0,0 +1,60 @@ +.. _lifecycle: + +Managing the Extension Lifecycle +================================ +The techniques described in previous pages allow a WebExtension API to +be loaded and instantiated only when an extension that uses the API is +activated. +But there are a few other events in the extension lifecycle that an API +may need to respond to. + +Extension Shutdown +------------------ +APIs that allocate any resources (e.g., adding elements to the browser's +user interface, setting up internal event listeners, etc.) must free +these resources when the extension for which they are allocated is +shut down. An API does this by using the ``callOnClose()`` +method on an `Extension <reference.html#extension-class>`_ object. + +Extension Uninstall and Update +------------------------------ +In addition to resources allocated within an individual browser session, +some APIs make durable changes such as setting preferences or storing +data in the user's profile. +These changes are typically not reverted when an extension is shut down, +but when the extension is completely uninstalled (or stops using the API). +To handle this, extensions can be notified when an extension is uninstalled +or updated. Extension updates are a subtle case -- consider an API that +makes some durable change based on the presence of a manifest property. +If an extension uses the manifest key in one version and then is updated +to a new version that no longer uses the manifest key, +the ``onManifestEntry()`` method for the API is no longer called, +but an API can examine the new manifest after an update to detect that +the key has been removed. + +Handling lifecycle events +------------------------- + +To be notified of update and uninstall events, an extension lists these +events in the API manifest: + +.. code-block:: js + + "myapi": { + "schema": "...", + "url": "...", + "events": ["update", "uninstall"] + } + +If these properties are present, the ``onUpdate()`` and ``onUninstall()`` +methods will be called for the relevant ``ExtensionAPI`` instances when +an extension that uses the API is updated or uninstalled. + +Note that these events can be triggered on extensions that are inactive. +For that reason, these events can only be handled by extension APIs that +are built into the browser. Or, in other words, these events cannot be +handled by APIs that are implemented in WebExtension experiments. If the +implementation of an API relies on these events for corectness, the API +must be built into the browser and not delievered via an experiment. + +.. Should we even document onStartup()? I think no... diff --git a/toolkit/components/extensions/docs/manifest.rst b/toolkit/components/extensions/docs/manifest.rst new file mode 100644 index 0000000000..194dc43a8d --- /dev/null +++ b/toolkit/components/extensions/docs/manifest.rst @@ -0,0 +1,68 @@ +Implementing a manifest property +================================ +Like functions and events, implementing a new manifest key requires +writing a definition in the schema and extending the API's instance +of ``ExtensionAPI``. + +The contents of a WebExtension's ``manifest.json`` are validated using +a type called ``WebExtensionManifest`` defined in the namespace +``manifest``. +The first step when adding a new property is to extend the schema so +that manifests containing the new property pass validation. +This is done with the ``"$extend"`` property as follows: + +.. code-block:: js + + [ + "namespace": "manifest", + "types": [ + { + "$extend": "WebExtensionManifest", + "properties": { + "my_api_property": { + "type": "string", + "optional": true, + ... + } + } + } + ] + ] + +The next step is to inform the WebExtensions framework that this API +should be instantiated and notified when extensions that use the new +manifest key are loaded. +For built-in APIs, this is done with the ``manifest`` property +in the API manifest (e.g., ``ext-toolkit.json``). +Note that this property is an array so an extension can implement +multiple properties: + +.. code-block:: js + + "myapi": { + "schema": "...", + "url": "...", + "manifest": ["my_api_property"] + } + +The final step is to write code to handle the new manifest entry. +The WebExtensions framework processes an extension's manifest when the +extension starts up, this happens for existing extensions when a new +browser session starts up and it can happen in the middle of a session +when an extension is first installed or enabled, or when the extension +is updated. +The JSON fragment above causes the WebExtensions framework to load the +API implementation when it encounters a specific manifest key while +starting an extension, and then call its ``onManifestEntry()`` method +with the name of the property as an argument. +The value of the property is not passed, but the full manifest is +available through ``this.extension.manifest``: + +.. code-block:: js + + this.myapi = class extends ExtensionAPI { + onManifestEntry(name) { + let value = this.extension.manifest.my_api_property; + /* do something with value... */ + } + } diff --git a/toolkit/components/extensions/docs/other.rst b/toolkit/components/extensions/docs/other.rst new file mode 100644 index 0000000000..85a9b6db41 --- /dev/null +++ b/toolkit/components/extensions/docs/other.rst @@ -0,0 +1,140 @@ +Utilities for implementing APIs +=============================== + +This page covers some utility classes that are useful for +implementing WebExtension APIs: + +WindowManager +------------- +This class manages the mapping between the opaque window identifiers used +in the `browser.windows <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/windows>`__ API. +See the reference docs `here <reference.html#windowmanager-class>`__. + +TabManager +---------- +This class manages the mapping between the opaque tab identifiers used +in the `browser.tabs <https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs>`__ API. +See the reference docs `here <reference.html#tabmanager-class>`__. + +ExtensionSettingsStore +---------------------- +ExtensionSettingsStore (ESS) is used for storing changes to settings that are +requested by extensions, and for finding out what the current value +of a setting should be, based on the precedence chain or a specific selection +made (typically) by the user. + +When multiple extensions request to make a change to a particular +setting, the most recently installed extension will be given +precedence. + +It is also possible to select a specific extension (or no extension, which +infers user-set) to control a setting. This will typically only happen via +ExtensionPreferencesManager described below. When this happens, precedence +control is not used until either a new extension is installed, or the controlling +extension is disabled or uninstalled. If user-set is specifically chosen, +precedence order will only be returned to by installing a new extension that +takes control of the setting. + +ESS will manage what has control over a setting through any +extension state changes (ie. install, uninstall, enable, disable). + +Notifications: +^^^^^^^^^^^^^^ + +"extension-setting-changed": +**************************** + + When a setting changes an event is emitted via the apiManager. It contains + the following: + + * *action*: one of select, remove, enable, disable + + * *id*: the id of the extension for which the setting has changed, may be null + if the setting has returned to default or user set. + + * *type*: The type of setting altered. This is defined by the module using ESS. + If the setting is controlled through the ExtensionPreferencesManager below, + the value will be "prefs". + + * *key*: The name of the setting altered. + + * *item*: The new value, if any that has taken control of the setting. + + +ExtensionPreferencesManager +--------------------------- +ExtensionPreferencesManager (EPM) is used to manage what extensions may control a +setting that results in changing a preference. EPM adds additional logic on top +of ESS to help manage the preference values based on what is in control of a +setting. + +Defining a setting in an API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A preference setting is defined in an API module by calling EPM.addSetting. addSetting +allows the API to use callbacks that can handle setting preferences as needed. Since +the setting is defined at runtime, the API module must be loaded as necessary by EPM +to properly manage settings. + +In the api module definition (e.g. ext-toolkit.json), the api must use `"settings": true` +so the management code can discover which API modules to load in order to manage a +setting. See browserSettings[1] as an example. + +Settings that are exposed to the user in about:preferences also require special handling. +We typically show that an extension is in control of the preference, and prevent changes +to the setting. Some settings may allow the user to choose which extension (or none) has +control of the setting. + +Preferences behavior +^^^^^^^^^^^^^^^^^^^^ + +To actually set a setting, the module must call EPM.setSetting. This is typically done +via an extension API, such as browserSettings.settingName.set({ ...value data... }), though +it may be done at other times, such as during extension startup or install in a modules +onManifest handler. + +Preferences are not always changed when an extension uses an API that results in a call +to EPM.setSetting. When setSetting is called, the values are stored by ESS (above), and if +the extension currently has control, or the setting is controllable by the extension, then +the preferences would be updated. + +The preferences would also potentially be updated when installing, enabling, disabling or +uninstalling an extension, or by a user action in about:preferences (or other UI that +allows controlling the preferences). If all extensions that use a preference setting are +disabled or uninstalled, the prior user-set or default values would be returned to. + +An extension may watch for changes using the onChange api (e.g. browserSettings.settingName.onChange). + +[1] https://searchfox.org/mozilla-central/rev/04d8e7629354bab9e6a285183e763410860c5006/toolkit/components/extensions/ext-toolkit.json#19 + +Notifications: +^^^^^^^^^^^^^^ + +"extension-setting-changed:*name*": +*********************************** + + When a setting controlled by EPM changes an event is emitted via the apiManager. It contains + no other data. This is used primarily to implement the onChange API. + +ESS vs. EPM +----------- +An API may use ESS when it needs to allow an extension to store a setting value that +affects how Firefox works, but does not result in setting a preference. An example +is allowing an extension to change the newTab value in the newTab service. + +An API should use EPM when it needs to allow an extension to change a preference. + +Using ESS/EPM with experimental APIs +------------------------------------ + +Properly managing settings values depends on the ability to load any modules that +define a setting. Since experimental APIs are defined inside the extension, there +are situations where settings defined in experimental APIs may not be correctly +managed. The could result in a preference remaining set by the extension after +the extension is disabled or installed, especially when that state is updated during +safe mode. + +Extensions making use of settings in an experimental API should practice caution, +potentially unsetting the values when the extension is shutdown. Values used for +the setting could be stored in the extensions locale storage, and restored into +EPM when the extension is started again. diff --git a/toolkit/components/extensions/docs/reference.rst b/toolkit/components/extensions/docs/reference.rst new file mode 100644 index 0000000000..f88c0b872e --- /dev/null +++ b/toolkit/components/extensions/docs/reference.rst @@ -0,0 +1,35 @@ +WebExtensions Javascript Component Reference +============================================ +This page contains reference documentation for the individual classes +used to implement WebExtensions APIs. This documentation is generated +from jsdoc comments in the source code. + +ExtensionAPI class +------------------ +.. js:autoclass:: ExtensionAPI + :members: + +Extension class +--------------- +.. js:autoclass:: Extension + :members: + +EventManager class +------------------ +.. js:autoclass:: EventManager + :members: + +BaseContext class +----------------- +.. js:autoclass:: BaseContext + :members: + +WindowManager class +------------------- +.. js:autoclass:: WindowManagerBase + :members: + +TabManager class +---------------- +.. js:autoclass:: TabManagerBase + :members: diff --git a/toolkit/components/extensions/docs/schema.rst b/toolkit/components/extensions/docs/schema.rst new file mode 100644 index 0000000000..b55e918588 --- /dev/null +++ b/toolkit/components/extensions/docs/schema.rst @@ -0,0 +1,145 @@ +API Schemas +=========== +Anything that a WebExtension API exposes to extensions via Javascript +is described by the API's schema. The format of API schemas uses some +of the same syntax as `JSON Schema <http://json-schema.org/>`_. +JSON Schema provides a way to specify constraints on JSON documents and +the same method is used by WebExtensions to specify constraints on, +for example, parameters passed to an API function. But the syntax for +describing functions, namespaces, etc. is all ad hoc. This section +describes that syntax. + +An individual API schema consists of structured descriptions of +items in one or more *namespaces* using a structure like this: + +.. code-block:: js + + [ + { + "namespace": "namespace1", + // declarations for namespace 1... + }, + { + "namespace": "namespace2", + // declarations for namespace 2... + }, + // other namespaces... + ] + +Most of the namespaces correspond to objects available to extensions +Javascript code under the ``browser`` global. For example, entries in the +namespace ``example`` are accessible to extension Javascript code as +properties on ``browser.example``. +The namespace ``"manifest"`` is handled specially, it describes the +structure of WebExtension manifests (i.e., ``manifest.json`` files). +Manifest schemas are explained in detail below. + +Declarations within a namespace look like: + +.. code-block:: js + + { + "namespace": "namespace1", + "types": [ + { /* type definition */ }, + ... + ], + "properties": { + "NAME": { /* property definition */ }, + ... + }, + "functions": [ + { /* function definition */ }, + ... + ], + "events": [ + { /* event definition */ }, + ... + ] + } + +The four types of objects that can be defined inside a namespace are: + +- **types**: A type is a re-usable schema fragment. A common use of types + is to define in one place an object with a particular set of typed fields + that is used in multiple places in an API. + +- **properties**: A property is a fixed Javascript value available to + extensions via Javascript. Note that the format for defining + properties in a schema is different from the format for types, functions, + and events. The next subsection describes creating properties in detail. + +- **functions** and **events**: + These entries create functions and events respectively, which are + usable from Javascript by extensions. Details on how to implement + them are later in this section. + +Implementing a fixed Javascript property +---------------------------------------- +A static property is made available to extensions via Javascript +entirely from the schema, using a fragment like this one: + +.. code-block:: js + + [ + "namespace": "myapi", + "properties": { + "SOME_PROPERTY": { + "value": 24, + "description": "Description of my property here." + } + } + ] + +If a WebExtension API with this fragment in its schema is loaded for +a particular extension context, that extension will be able to access +``browser.myapi.SOME_PROPERTY`` and read the fixed value 24. +The contents of ``value`` can be any JSON serializable object. + +Schema Items +------------ +Most definitions of individual items in a schema have a common format: + +.. code-block:: js + + { + "type": "SOME TYPE", + /* type-specific parameters... */ + } + +Type-specific parameters will be described in subsequent sections, +but there are some optional properties that can appear in many +different types of items in an API schema: + +- ``description``: This string-valued property serves as documentation + for anybody reading or editing the schema. + +- ``permissions``: This property is an array of strings. + If present, the item in which this property appears is only made + available to extensions that have all the permissions listed in the array. + +- ``unsupported``: This property must be a boolean. + If it is true, the item in which it appears is ignored. + By using this property, a schema can define how a particular API + is intended to work, before it is implemented. + +- ``deprecated``: This property must be a boolean. If it is true, + any uses of the item in which it appears will cause a warning to + be logged to the browser console, to indicate to extension authors + that they are using a feature that is deprecated or otherwise + not fully supported. + + +Describing constrained values +----------------------------- +There are many places where API schemas specify constraints on the type +and possibly contents of some JSON value (e.g., the manifest property +``name`` must be a string) or Javascript value (e.g., the first argument +to ``browser.tabs.get()`` must be a non-negative integer). +These items are defined using `JSON Schema <http://json-schema.org/>`_. +Specifically, these items are specified by using one of the following +values for the ``type`` property: ``boolean``, ``integer``, ``number``, +``string``, ``array``, ``object``, or ``any``. +Refer to the documentation and examples at the +`JSON Schema site <http://json-schema.org/>`_ for details on how these +items are defined in a schema. |