Extension API Functions

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, 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) 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 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.