Extension Bindings

What Is It?

The Bindings System is responsible for creating the JS entry points for APIs. It creates the chrome object (if it does not exist) and adds the API objects (e.g. tabs) that should be accessible to the context.

Initialization

Bindings are initialized by creating an ObjectTemplate from an API specification and stamping out copies of this template. This means that once an API is instantiated once, further instantiations within that same process are significantly faster. The API itself is specified from a .json or .idl file in extensions/common/api or chrome/common/extensions/api.

This is slightly complicated because APIs may have features (such as specific methods or events) that are restricted in certain contexts, even if the rest of the API is available. As a result, after object instantiation, there’s a chance we may have to alter the object in order to remove these unavailable features.

API Features

A “feature” of an API is a property on the API object to expose some functionality. There are three main types of features exposed on APIs.

  • Functions: Functions are the main type of feature exposed on APIs. They allow callers to interact with the browser and trigger behavior.

  • Events: Most events are dispatched when something happens to inform an interested party of the instance. Callers subscribe to the events they are interested in, and are notified only for subscribed events. While most events do not influence behavior change in the browser, declarative events may.

  • Properties: Certain APIs have exposed properties that are accessed directly on the API object. These are frequently constants (including enum definitions), but are also sometimes properties relating to the state of the context.

Restriction

Not all APIs are available to all contexts; we restrict which capabilities are exposed based on multiple factors.

Scope

Features may be restricted at multiple scopes. The most common is at the API-scope - where none of the API will be made available if the requirements aren’t met. In this case, the chrome. property will simply be undefined. However, we also have the ability to restrict features on a more granular scope, such as at the method or event level. In this case, even though most of an API may be available, a certain function might not be; or, conversely, only a small subset of features may be available while the rest of the API is restricted.

Restricting Properties

Feature restrictions are based on a specific v8::Context. Different contexts within the same frame may have different API availabilities (this is significantly different than the web platform, where features are exposed at the frame-level). The bindings system takes into account context type, associated extensions, URL, and more when evaluating features; for more information, see the feature documentation.

Typical Function Flow

The typical flow for all API methods is the same. A JS entry point (the method on the API object) leads to a common native implementation. This implementation has the following steps:

  • Argument Parsing:

Passed arguments are parsed against an expected signature defined in the API specification. If the passed arguments match the signature, the arguments are normalized and converted to a serialized format (base::Value).

  • Request Dispatch: A request is dispatched with the parsed arguments and other information about the request (such as requesting context and user gesture status). If a callback is included in the arguments, it is stored (along with other information about the request) until the response is received.
  • Request Response: A response is provided asynchronously, indicating success or failure, along with any return values (to pass to a provided callback) or an error message. The pending request is removed.

Custom Function Hooks

Certain APIs need to deviate from this typical flow in order to customize behavior. We provide the following general custom hooks for APIs to modify the typical behavior.

  • updateArgumentsPreValidate: Allows an API implementation to modify passed arguments before the argument signature is validated. This can be useful in the case of undocumented (internal) parameters or properties, such as a generated ID.
  • updateArgumentsPostValidate: Allows an API implementation to modify passed arguments after the argument signature is validated, but before the request is handled. Note: this is usually bad practice, as any modification means that the arguments no longer match the expected signature. This can cause headaches when we attempt to deserialize these values.
  • handleRequest: Allows an API implementation to internally handle a request. This is useful when the request itself should not go through the normal flow, such as when the logic requires a greater level of involvement on the renderer, or is entirely handled without needing to message the browser.
  • customCallback: Allows an API implementation to add a callback that should be called with the result of an API function call before the caller’s callback is invoked. It is the responsibility of the custom callback to invoke the original callback, which is passed as an argument. This is useful when the return results should be mutated before returning to the caller (which can be necessary when the eventual result could be a renderer-specific concept, such as a DOMWindow).

An API implementation may use one or more of these hooks.

Registering Hooks

Custom Hooks can be registered through either native or JS bindings. In native bindings, APIs can subclass APIBindingHooksDelegate and register themselves with the bindings system. This typically happens during the bootstrapping of the renderer process. Native binding hooks are the preferred approach for new bindings.

We also expose hooks in JS through the APIBindingBridge object, which provides a registerCustomHook method to allow APIs to create hooks in JS. This style of custom hooks is not preferred and will be deprecated. These are bad because a) JS is much more susceptible to untrusted code and b) since these run on each object instantiation, the performance cost is significantly higher.

Events

Events are dispatched when the associated action occurs.

Types

There are three types of events.

  • Regular: These events are dispatched to the subscriber when something happens, and merely serve as a notification to allow the subscriber to react.
  • Declarative: Declarative events allow a subscriber to specify some action to be taken when an event occurs. For instance, the declarativeContent API allows a subscriber to indicate that an action should be shown whenever a certain URL pattern or CSS rule is matched. For these events, the subscriber is not notified when the event happens; rather, the browser immediately takes the specified action. By virtue of not notifying the subscriber, we help preserve the user’s privacy; if a subscriber says “do X when the user visits example.com”, it does not know whether the user visited example.com. (Note: subsequent actions, such as a user interacting with the action on a given page, can expose this.)
  • Imperative: A few events are designed to be dispatched and to return a response from the subscriber, indicating an action the browser should take. These are predominantly used in the webRequest API, where a subscriber can register events for navigations, receive notifications of those navigations, and return a result of whether the navigation should continue, cancel, or redirect. These events are generally discouraged for performance reasons, and declarative events are preferred.

Filters

Certain events also allow the registration of filters, which allow subscribers to only be notified of a subset of events. For example, the webNavigation and webRequest APIs allow filtering by URL pattern, so that uninteresting navigations are ignored.

Legacy JavaScript Implementations

The prior bindings system was implemented primarily in JavaScript, rather than utilizing native code. There were many reasons for this, but they include ease of coding and more limited interactions with Blink (WebKit at the time) and V8. Unfortunately, this led to numerous security vulnerabilities (because untrusted code can run in the same context) and performance issues (because bindings were set up per context, and could not be cached in any way).

While the native bindings system replaces the core functionality with a native implementation, individual APIs may still be implemented in JavaScript custom bindings, or hooks. These should eventually be replaced by native-only implementations.

Differences Between Web/Blink Bindings

There are a number of differences between the Extensions Bindings System and Blink Bindings.

Common Implementation to Optimize Binary Size

Most Extension APIs are implemented in the browser process after a common flow in the renderer. This allows us to optimize the renderer implementation for space and have the majority of APIs lead to a single entry point, which can match an API against an expected schema. This is contrary to Blink Bindings, which set up a distinct separate entry point for each API, and then individually parses the expected results.

The Blink implementation provides greater speed, but comes at a larger generated code cost, since each API has its own generated parsing and handling code. Since most Blink/open web APIs are implemented in the renderer, this cost is not as severe - each API would already require specialized code in the renderer.

Extension APIs, on the other hand, are predominantly implemented in the browser; this means we can optimize space by having a single parsing/handling point. This is also beneficial because many extension APIs are exposed on a more limited basis, where only a handful of contexts need access to them, and thus the binary size savings is more valuable, and the speed cost less harmful.

Signature Matching

Signature matching differs significantly between WebIDL and Extension APIs.

Optional Inner Parameters

Unlike OWP APIs, Extension APIs allow for optional inner parameters. For instance, if an API has the signature (integer, optional string, optional function), it may be invoked with (integer, function) - which would not be valid in the OWP. This also allows for inner parameters to be optional with subsequent required parameters, such as (integer, optional string, function) - again, something which would be disallowed on the OWP.

Unknown Properties

Unknown properties on objects are, by default, unallowed. That is, if a function accepts an object that has properties of foo and bar, passing {foo: <foo>, bar: <bar>, baz: <baz>} is invalid.