| # Extension API Functions |
| |
| [TOC] |
| |
| ## Summary |
| |
| Extension APIs are implemented in terms of functions, events, types, and |
| properties. For many APIs, extension functions provide the majority of the |
| functionality and complexity. This document describes the typical extension API |
| function control flow and best practices for implementing new API functions. |
| |
| ## Extension Function Flow |
| |
| Most extension functions are asynchronous, with the majority of the work |
| happening in the browser process. Extensions will provide a callback to the |
| function to be invoked when the function is complete. The control flow for most |
| extension functions is as follows: |
| * JavaScript in the renderer process (typically, though not always, an extension |
| context in an extension's process) calls an API (e.g., |
| `chrome.tabs.create()`). |
| * Renderer bindings validate the call (see also the |
| [bindings documentation](/extensions/renderer/bindings.md), and forward the |
| arguments to the browser if the call is valid. |
| * The extension function behavior is implemented in the browser process, and |
| performs the necessary work (e.g., creating a new tab). |
| * The function replies to the renderer with the result (or error) from running |
| the function. The renderer bindings return the result (or error) to the |
| calling extension. |
| |
| Some functions deviate from this flow - the most common case is with custom |
| bindings code or renderer-side behavior. The |
| [bindings documentation](/extensions/renderer/bindings.md)) describes some |
| situations in which this happens. |
| |
| ## Extension Function Implementation |
| |
| Most extension functions are implemented in the browser process. Each |
| extension function has a separate class that implements the behavior for that |
| API function. A single function instance is created for each invocation of the |
| function, and is created with information about the call (including arguments, |
| caller, etc). |
| |
| ### Doing Stuff |
| An extension API function performs the necessary behavior by overriding the |
| `ExtensionFunction::Run()` method. `ExtensionFunction::Run()` returns a |
| `ResponseAction`, which indicates what should happen next. This can either be a |
| result to return to the caller (if the function finishes synchronously), an |
| error (if something went wrong), or a signal to respond later (if the function |
| will finish asynchronously). If a function will finish asynchronously, it must |
| call `ExtensionFunction::Respond()` to deliver the result to the caller. |
| |
| ### Function Lifetime |
| Extension functions are reference-counted in order to make asynchronous work |
| easy. However, they are not automatically collected when the calling renderer, |
| or even the associated profile, is shut down. Extension function |
| implementations should properly handle these cases if they can potentially |
| outlive objects they depend on, such as the renderer or profile. |
| |
| ### Function Registration |
| Extension functions are registered by their name, which is used to route a |
| request from the renderer to the appropriate function implementation. They |
| also use a unique enumeration, which is used in various histograms. |
| |
| ### Example Implementation |
| Below is an example implementation of a simple Extension API function, |
| `gizmo.frobulate`. An extension would call this API by calling |
| `chrome.gizmo.frobulate()`. |
| |
| #### gizmo\_api.idl |
| The first step is to define the function in the API schema. See the |
| [schema documentation](/chrome/common/extensions/api/schemas.md) for more |
| information. |
| |
| ``` |
| namespace gizmo { |
| callback FrobulateCallback = void(DOMString result); |
| |
| interface functions { |
| // Tells the system to frobulate. |
| // |cycles|: The number of cycles for which to frobulate. |
| // |callback|: The callback to invoke when the frobulation is done; the |
| // result contains the outcome of the frobulation. |
| static void frobulate(long cycles, |
| optional FrobulateCallback callback); |
| }; |
| }; |
| ``` |
| |
| #### gizmo\_api.h |
| Next, we define the function in C++, starting with the header file. |
| |
| ``` |
| GizmoFrobulateFunction : public UIThreadExtensionFunction { |
| public: |
| // This declares the extension function and initiates the mapping between the |
| // string name to the C++ class as well as the histogram value. |
| DECLARE_EXTENSION_FUNCTION("gizmo.frobulate", GIZMO_FROBULATE); |
| |
| GizmoFrobulateFunction(); |
| |
| private: |
| ~GizmoFrobulateFunction() override; |
| |
| // ExtensionFunction: |
| ResponseAction Run() override; |
| |
| DISALLOW_COPY_AND_ASSIGN(GizmoFrobulateFunction); |
| }; |
| ``` |
| |
| #### gizmo\_api.cc |
| Finally, the meat of the implementation is in the .cc file. |
| |
| ``` |
| GizmoFrobulateFunction:: GizmoFrobulateFunction() = default; |
| GizmoFrobulateFunction::~ GizmoFrobulateFunction() = default; |
| |
| ExtensionFunction::ResponseAction GizmoFrobulateFunction::Run() { |
| // We can create a typed struct of the arguments from the generated code. |
| std::unique_ptr<api::gizmo::Frobulate::Params> params( |
| api::gizmo::Frobulate::Params::Create(*args_)); |
| |
| // EXTENSION_FUNCTION_VALIDATE() is used to assert things that should only |
| // ever be true, and should have already been enforced. This equates to a |
| // DCHECK in debug builds and a BadMessage() (which kills the calling |
| // renderer) in release builds. We use it here because we know that the |
| // C++ type conversion should always succeed, since it should have been |
| // validated by the renderer bindings. |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| int max_cycles = GetMaxCyclesFromPrefs(); |
| |
| if (params->cycles > max_cycles) { |
| // The bindings can only validate parameters in certain (static) ways, so |
| // runtime constraints (such as a user value for the maximum number of |
| // frobulation cycles) must be validated by hand. |
| // Returned error values are exposed to the extension on the |
| // chrome.runtime.lastError property in the callback. |
| return RespondNow( |
| Error(StringPrintf("Cannot frobulate for more than %d cycles.", |
| max_cycles))); |
| } |
| |
| std::string frobulate_result = GetFrobulator()->Frobulate(params->cycles); |
| // The frobulation succeeded, so return the result to the extension. Note that |
| // even though the function finished synchronously in C++, the extension still |
| // sees this as an asynchronous function, because the IPC between the |
| // renderer and the browser is asynchronous. |
| return RespondNow( |
| OneArgument(std::make_unique<base::Value>(std::move(frobulate_result)))); |
| } |
| ``` |
| |
| If the `gizmo.frobulate()` function implementation had needed to respond |
| asynchronously from the browser process, it is fairly straightforward to |
| implement. |
| |
| ``` |
| ExtensionFunction::ResponseAction GizmoFrobulateFunction::Run() { |
| std::unique_ptr<api::gizmo::Frobulate::Params> params( |
| api::gizmo::Frobulate::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| int max_cycles = GetMaxCyclesFromPrefs(); |
| if (params->cycles > max_cycles) { |
| return RespondNow( |
| Error(StringPrintf("Cannot frobulate for more than %d cycles.", |
| max_cycles))); |
| } |
| |
| GetFrobulator()->Frobulate( |
| params->cycles, |
| // Note that |this| is refcounted, so binding automatically adds a |
| // reference. |
| base::Bind(&GizmoFrobulateFunction::OnFrobulated, this)); |
| |
| // Note: Since we are returning RespondLater() here, it is required that |
| // Frobulate() did not call the callback synchronously (in which case, |
| // Respond() would have already been called). |
| return RespondLater(); |
| } |
| |
| void GizmoFrobulateFunction::OnFrobulated(const std::string& frobulate_result) { |
| Respond(OneArgument(std::make_unique<base::Value>(frobulate_result))); |
| } |
| ``` |
| |
| ## Do's and Don't's |
| |
| ### Do |
| * Consider edge cases. The bindings should properly validate that the |
| parameters passed to the extension function match those specified in the |
| schema, but will not validate beyond that. Consider whether there are any |
| restrictions that need to be validated prior to using the input from |
| extensions. For instance, checking that a tabId provided by an extension |
| corresponds to an existing tab. |
| |
| * Account for all possible callers. Extension APIs can be called from different |
| contexts - some can be called from WebUI, others from normal web pages, others |
| from content scripts. The ExtensionFunction::extension() variable thus can |
| be null in certain cases, and should only be assumed to be valid if the |
| API is restricted to being called from an extension context. |
| |
| * Certain functions can be throttled, in case they are too expensive. Consider |
| if this is appropriate for the function. |
| |
| ### Don't |
| * Return both a value and an error. If something fails, only an error should be |
| returned, which will set the value of chrome.runtime.lastError. Only return a |
| value if the call succeeded. |
| |
| * Fail without setting an error. Errors are important for developers to |
| diagnose what went wrong. Be descriptive enough to be helpful, but concise. |
| For instance, an error might be "No tab found with ID '42'.". |
| |
| * Use deprecated variants. The ExtensionFunction implementation has been |
| around for a long time, and has undergone various iterations. There are a |
| number of deprecated aspects. Please don't use them. These include |
| ChromeAsyncExtensionFunction(), GetCurrentBrowser(), and others. |
| |
| * Use the EXTENSION\_FUNCTION\_VALIDATE() if there is any valid way the |
| condition could be false. For instance, it is valid to use it to assert that |
| conversions that should have been checked by the renderer succeed, since |
| otherwise the API should never have been triggered. However, it should not be |
| used to validate assertions that are enforced elsewhere, such as validating |
| that a tab ID is valid. |