Implementing a new extension API

Proposal



Defining the Interface

All extension APIs should start restricted to the dev channel. Do this by adding an entry to chrome/common/extensions/api/_permission_features.json.

Extension APIs can be defined in either IDL or JSON Schema. IDL is much more concise, but doesn't include all of the features supported by JSON Schema (e.g. "choices", "additionalProperties", ...).

Either way, add your schema definition to chrome/common/extensions/api/api.gyp, which generates a bunch of boilerplate for you in <build_dir>/gen/chrome/common/extensions/api/*: models for your API, and the glue to hook into your implementation.

Adding documentation

Adding documentation is very simple:
  1. Write a summary for your API and put it in chrome/common/extensions/docs/templates/intros/myapi.html. 
  2. Create the publicly accessible template(s) in chrome/common/extensions/docs/templates/public/{extension,apps}/myapi.html. Whichever of extensions and/or apps you add an HTML file in depends on which platform can access your API.
    • Each will look something like: {{+partials.standard_extensions_api api:apis.myapi intro:intros.myapi}} (or apps).
C++ implementation

The actual C++ implementation should live in chrome/browser/extensions/api/myapi/* (as mentioned above, the magic glue is generated for you).

Functions

Extension APIs are implemented as subclasses of SyncExtensionFunction or AsyncExtensionFunction from chrome/browser/extensions/extension_function.h. Note that SyncExtensionFunction just means it's synchronous relative to the browser UI thread; from the extension's perspective, it still receives results asynchronously via a callback.
  • Use DECLARE_EXTENSION_FUNCTION_NAME to declare the JavaScript identifier for the function.
  • Use EXTENSION_FUNCTION_VALIDATE to validate input arguments, which are placed in args_. Failing this check kills the renderer, so the idiom for this is catching bugs in the renderer (for example: schema validation errors).
  • Use SetError() to return an error message; in this case, you'll want to return false from RunImpl().
  • Use SetResult() to “return” single values via a callback (to return multiple values, set result_ directly).
  • Add your implementation files to chrome/chrome_browser_extensions.gypi.
Model generation 

Include chrome/common/extensions/api/myapi_api.h in your implementation to use the generated model. Let's say we have the following IDL (or equivalent JSON schema):

namespace myapi {
  dictionary BazOptions {
    // Describes what the id argument means.
    long id;
    // Describes what the s argument means.
    DOMString s;
  };

  dictionary BazResult {
    long x;
    long y;
  };

  callback BazCallback = void (BazResult result);

  interface Functions {
    // An interesting comment describing what the baz operation is.
    // Note that this function can take multiple input arguments, including things like
    // long and DOMString, but they have been elided for simplicity.
    static void doBaz(BazOptions options, BazCallback callback);
  };
};

A simple C++ implementation might look like this:

class BazExtensionFunction : public AsyncExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION_NAME("myapi.doBaz");

 private:
  virtual bool RunImpl() OVERRIDE {
    // Args are passed in via the args_ member as a base::ListValue.
    // Use the convenience member of the glue class to easily parse it.
    scoped_ptr<api::myapi::DoBaz::Params> params(
        api::myapi::DoBaz::Params::Create(*args_));
    EXTENSION_FUNCTION_VALIDATE(params.get());
   
    api::myapi::BazResult result;
    result.x = params->options.id;
    base::StringToInt(params->options.s, &result.y);

    // Use the convenience member of the glue class to easily serialize
    // to base::Value. ExtensionFunction owns the resulting base::Value.
    SetResult(api::myapi::DoBaz::Results::Create(result));
    // Not needed if you're a SyncExtensionFunction 
    // since it's set on the return value of RunImpl().
    SendResponse(true /* success */);
    return true;
  }
};

ExtensionFunction is refcounted and instantiated once per call to that extension function, so use base::Bind(this) to ensure it's kept alive (or use AddRef...Release if that's not possible for some reason).

Events

Use ExtensionEventRouter to dispatch events to extensions. Prefer the versions that allow you to pass in base::Value rather than a JSON serialized format. Event names should be defined in chrome/browser/extensions/event_names.h.

As with extension functions, it generates some C++ glue classes. Let's say we have the following IDL (or equivalent JSON Schema):
namespace myapi {
  dictionary Foo {
     // This comment should describe what the id parameter is for.
     long id;
     // This comment should describe what the bar parameter is for.
     DOMString bar;
  };

  interface Events {
    // Fired when something interesting has happened.
    // |foo|: The details of the interesting event.
    static void onInterestingEvent(Foo foo);
  };
};

To use the generated glue in C++:

api::myapi::Foo foo;
foo.id = 5;
foo.bar = "hello world";
ExtensionSystem::Get(profile)->event_router()->DispatchEventToExtension(
    extension_id,
    event_names::kOnInterestingEvent,
    *
api::myapi::OnInterestingEvent::Create(foo),
    profile,
    GURL());

Permissions

By default, extension APIs should require a permission named the same as the API namespace. 

New permissions are added in APIPermissionInfo::RegisterAllPermissions in  chrome/common/extensions/permissions/api_permission.cc

If your API is safe enough, you can allow extensions to use it by default by adding it to kNonPermissionModuleNames in chrome/common/extensions/permissions/permission_set.cc.

Advanced Extension Functionality

Custom Bindings

Custom JS bindings go in chrome/renderer/resources/extensions/*.js.

These are necessary for doing anything special: synchronous API functions, functions bound to types, anything renderer-side. (If it's a common enough operation, please send us patches to do it automatically as part of the bindings generation :).

New Manifest Sections

If your API requires a new manifest section:
  1. Add the section to chrome/common/extensions/api/_manifest_features.json.
  2. Parse the section in chrome/common/extensions/extension.cc:Extension::LoadExtensionFeatures().
  3. Add a test for parsing manifests using the new section at chrome/common/extensions/manifest_tests/extension_manifests_<section_name>_unittest.cc.

Testing Your Implementation

Make sure it has tests. Like all of Chrome, we prefer unit tests to integration tests.
  • There is a relatively new mini framework for unit tests in chrome/browser/extensions/extension_function_test_utils.h. Hopefully it meets your needs.
  • If not, there is the older API tests for integration tests.
Iterating
  1. Create an example extension that uses your API and add it to chrome/common/extensions/docs/examples
  2. Ask someone else (preferably someone in chrome-devrel@ or chrome-extensions-team@) to make a second example extension
  3. Iterate based on how those examples went
  4. Make sure that your API is functional on all of Chrome's platforms that have access to the web store and that all tests are enabled on all platforms
  5. Announce the dev channel API to the community by sending mail to chromium-extensions@chromium.org and a blog post if appropriate
  6. Encourage community feedback (ask chrome-extensions-team@ for ideas here)
  7. Iterate on the API based on community feedback
  8. In general, an API should be on dev channel for at least one full release cycle before moving on to the next phase

Going to Stable

Follow the Going Live Phase instructions.


Comments