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.
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.
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.
Not all APIs are available to all contexts; we restrict which capabilities are exposed based on multiple factors.
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.
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.
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:
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).
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.
An API implementation may use one or more of these 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 are dispatched when the associated action occurs.
There are three types of events.
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.
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.
There are a number of differences between the Extensions Bindings System and Blink Bindings.
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 differs significantly between WebIDL and Extension APIs.
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 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.