forked from mirrors/gecko-dev
		
	 8f6d72ad28
			
		
	
	
		8f6d72ad28
		
	
	
	
	
		
			
			Also cleaned up a few other loose ends on webextensions api docs. MozReview-Commit-ID: FnyqmM7NjqE --HG-- extra : rebase_source : 6039a70c72790c14d8872e38e77e9596b7dac3f8
		
			
				
	
	
		
			201 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			201 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| 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.
 |