For Developers‎ > ‎

Web IDL interfaces

Web interfaces – exposed as JavaScript objects – are generally specified in Web IDL (Interface Definition Language), a declarative language (sometimes written without the space as WebIDL). This is the language used in standard specifications, and Blink uses IDL files to specify the interface and generate JavaScript bindings (formally, C++ code that the V8 JavaScript virtual machine uses to call Blink itself). Web IDL in Blink is close to the standard, and the resulting bindings use standard conventions to call Blink code, but there are additional features to specify implementation details, primarily Blink IDL extended attributes.

To implement a new Web IDL interface in Blink:
The bulk of the work is the implementation, secondarily tests. The interface (IDL file) should require minimal work (ideally just copy-and-paste the spec), assuming nothing unusual is being done, and the build can be forgotten about once you've set it up. Details follow.

IDL

  • Find spec
  • Create a new file called Foo.idl in the same directory as you will implement the interface, generally Source/core/* or Source/modules/*
The initial IDL file should contain:
  • License header
  • Link to the spec
  • Link to tracking bug for implementing the interface
  • IDL "fragment" copied from the spec
See Blink IDL: Style for style guide.

IDL files contain two types of data:
  • Blink interface – behavior; ideally should agree with spec, but differences from spec should be reflected in the IDL itself
  • Blink implementation – internal-use data, like function names or implementation-specific flags, like memory management.
Note that if Blink behavior differs from the spec, the Blink IDL file should reflect Blink behavior. This makes interface differences visible, rather than hiding them in the C++ implementation or bindings generator.
Also as a rule, nop data should not be included: if Blink (bindings generator) ignores an IDL keyword or extended attribute, do not include it, as it suggests a difference in behavior when there is none. If this results in a difference from the spec, this is good, as it makes the difference in behavior visible.

Initially you likely want to comment out all attributes and operations, uncommenting them as you implement them.

Nulls and non-finite numbers

Two points to be careful of, and which are often incorrect in specs, particularly older specs, are nullability and non-finite values (infinities and NaN). These are both to ensure correct type checking. If these are incorrect in the spec – for example, a prose specifying behavior on non-finite values, but the IDL not reflecting this – please file a spec bug upstream, and link to it in the IDL file.

If null values are valid (for attributes, argument types, or method return values), the type MUST be marked with a ? to indicate nullable, as in attribute Foo? foo;
Note that for arguments (but not attributes or method return values), optional is preferred to nullable (see Re: removeEventListener with only one passed parameter...).

Similarly, IEEE floating point allows non-finite numbers (infinities and NaN); if these are valid, the floating point type – float or double – MUST be marked as unrestricted as in unrestricted float or unrestricted double – the bare float or double means finite floating point.

Union types

Many older specs use overloading when a union type argument would be clearer. Please match spec, but file a spec bug for these and link to it. For example:
// FIXME: should be void bar((long or Foo) foo); https://www.w3.org/Bugs/Public/show_bug.cgi?id=123
void bar(long foo);
void bar(Foo foo);

Also, beware that you can't have multiple nullable arguments in the distinguishing position in an overload, as these are not distinguishing (what does null resolve to?). This is best resolved by using a union type if possible; otherwise make sure to mark only one overload as having a nullable argument in that position.

Don't do this:
void zork(Foo? x);
void zork(Bar? x); // What does zork(null) resolve to?
Instead do this:
void zork(Foo? x);
void zork(Bar x);
...but preferably this:
void zork((Foo or Bar)? x);

Extended attributes

You will often need to add Blink-specific extended attributes to specify implementation details.
Please comment extended attributes – why do you need special behavior?

Bindings

C++

Bindings code assumes that a C++ class exists, with methods for each attribute or operation (with some exceptions). Attributes are implemented as properties, meaning that while in the JavaScript interface these are read and written as attributes, in C++ these are read and written by getter and setter methods.

For cases where an IDL attribute reflects a content attribute, you do not need to write boilerplate methods to call getAttribute() and setAttribute(). Instead, use the [Reflect] extended attribute, and these calls will automatically be generated inline in the bindings code, with optimizations in some cases. However, if you wish to access these attributes from C++ code (say in another class), not just from JavaScript, you will need to write a getter and/or a setter, as necessary.

Names

The class and methods have default names, which can be overridden by the [ImplementedAs] extended attribute; this is strongly discouraged, and method names should align with the spec unless there is very good reason for them to differ (this is sometimes necessary when there is a conflict, say when inheriting another interface).

Given an IDL file Foo.idl:
interface Foo {
    attribute long a;
    attribute DOMString cssText;
    void f();
    void f(long arg);
    void g(optional long arg);
};

...a minimal header file Foo.h illustrating this is:
class Foo {
public:
    int a();
    void setA(int);
    String cssText();
    void setCSSText(const String&);
    void f();
    void f(int);
    void g();
    void g(int);
    // Alternatively, can use default arguments:
    // void f(int arg=0);
};
  • IDL interfaces assume a class of the same name: class Foo.
  • IDL attributes call a getter of the same name, and setter with set prepended and capitalization fixed: a() and setA(). This correctly capitalizes acronyms, so the setter for cssText is setCSSText(). (If you need to add acronyms, these are set in v8_common.ACRONYMS.)
  • IDL operations call a C++ method of the same name: f().
  • Web IDL overloading and IDL optional arguments without default values map directly to C++ overloading (optional arguments without default values correspond to an overload either including or excluding the argument).
  • IDL optional arguments with default values map to C++ calls with these values filled in, and thus do not require C++ overloading.
    • C++ default values SHOULD NOT be used unless necessary (not yet supported by compiler).
    • However, currently IDL default values are only partly supported (Bug 258153), and thus C++ default values are used.
    • There are various complicated corner cases, like non-trailing optional arguments without defaults, like
      • foo(optional long x, optional long y = 0);

Type information ("wrapper")

Blink objects that are visible in JavaScript need type information, fundamentally because JavaScript is dynamically typed (so values have type), concretely because the bindings code uses type introspection for dynamic dispatch (function resolution of bindings functions): given a C++ object (representing the implementation of a JavaScript object), accessing it from V8 requires calling the correct C++ binding methods, which requires knowing its JavaScript type (i.e., the IDL interface type).
Blink does not use C++ run-time type information (RTTI), and thus the type information must be stored separately.

There are various ways this is done, most simply (for Blink developers) by the C++ class inheriting ScriptWrappable and calling ScriptWrappable::init on this in the constructor. Stylistically ScriptWrappable should be the last class, or at least after more interesting classes, and should be directly inherited by the class that calls ScriptWrappable::init (not indirectly from a more distant ancestor).

Note that classes that call ScriptWrappable::init are concrete classes, and will generally be marked FINAL, unless they need to be inherited (see below).

Concretely, this stores the type information in m_wrapperOrTypeInfo by calling setTypeInfo with the type info – the correct JavaScript type is determined by calling a function template that calls the corresponding binding code: the initializeScriptWrappableForInterface function in the bindings converts the C++ type to a JavaScript type, so C++ overload resolution computes the conversion. (This passes via some helper functions at global scope due to MSVC Bug 664619.) Thus you need to call ScriptWrappable::init with a C++ object that has bindings for its type; if you call it in a non-JS visible class, you'll get a symbol lookup error at run time.

Explicitly:

Foo.h:
#ifndef Foo_h
#define Foo_h

#include "bindings/v8/ScriptWrappable.h"

namespace WebCore {

class Foo FINAL : /* maybe others */ public ScriptWrappable {
    // ...
};

} // namespace WebCore

#endif Foo_h

Foo.cpp:
// ...

namespace WebCore {

Foo::Foo(/* ... */)
{
    ScriptWrappable::init(this);
}

// ...

} // namespace WebCore

In case of C++ inheritance, it's preferable to avoid calling ScriptWrappable::init in both a base class and a derived class, most simply because this creates overhead on a redundant write. In many cases this can be avoided by having an abstract base class that both concrete classes inherit. Stylistically, FIXME
However, in some cases – notably if both a base class and a derived class implement JS interface types (say, if there is IDL inheritance and the C++ inheritance is the same) – you will need to call ScriptWrappable::init both in the base class and the derived class.

Thus, to avoid this:
Foo.h:
class Foo FINAL : public Bar, public ScriptWrappable  { /* ... */ };
Foo.cpp:
Foo::Foo(/* ... */)
{
    ScriptWrappable::init(this);
}
Bar.h:
class Bar : public ScriptWrappable { /* ... */ };
Bar.cpp
Bar::Bar(/* ... */)
{
    ScriptWrappable::init(this);
}

...instead use an abstract base class, and have both concrete classes inherit ScriptWrappable directly:
Foo.h:
class Foo FINAL : public FooBarBase, public ScriptWrappable  { /* ... */ };
Foo.cpp:
Foo::Foo(/* ... */)
{
    ScriptWrappable::init(this);
}
Bar.h:
class Bar FINAL : public FooBarBase, public ScriptWrappable  { /* ... */ };
Bar.cpp:
Bar::Bar(/* ... */)
{
    ScriptWrappable::init(this);
}
FooBarBase.h:
class FooBarBase { /* ... */ };

History (ScriptWrappable)

Build

You need to list the .idl file and .h/.cpp files in the correct GYP variable so that they will be built (bindings generated, Blink code compiled), and then you need to regyp (re-run GYP) to regenerate the build files. Files are listed in a .gypi (GYP Include) file. For core files, this is Source/core/core.gypi.

There are 3 dichotomies in these .idl files, which affect where you list them in the build:
  • core vs. modules – which subtree they are in
  • main interface vs. dependency – partial interfaces and implemented interfaces do not have individual bindings (.h/.cpp) generated
  • testing or not – testing interfaces do not appear in the aggregate bindings
If you generate an IDL file at build time, there is a 4th dichotomy:
  • generated or not (static) – generated files need to be treated differently by the build system (passed as command line arguments, rather than listed in a gyp-generated file, since generated files are in the build directory, which is only known at build time, not gyp time).
For core interfaces, the IDL file MUST be listed in the core_idl_files variable (_files is a special suffix that tells GYP to treat these values as paths), or in the core_dependency_idl_files variable, if the IDL file is a partial interface or the target (right side of) an implements statement. This distinction is because partial interfaces and implemented interfaces do not have their own bindings generated, so these IDL files are not directly compiled.
Testing files MUST instead be listed in the core_testing_idl_files variable; there are currently no core testing dependency files.

The C++ files should be listed in the core_files variable or an appropriate core_*_files variable, depending on directory, or core_testing_files if a testing interface.

Modules files are analogous, and placed in Source/modules/modules.gypi. There are currently no modules testing interface files, but there are modules testing dependency files, which are listed in modules_dependency_idl_files and modules_testing_files.

Tests

Make sure to test:
  • default objects – create a new object and pass as argument or assign to an attribute
  • undefined/null – if passed to nullable arguments or set to nullable attributes, should not throw; if passed to non-nullable arguments or set to non-nullable attributes, should throw but not crash

Subtyping

There are three mechanisms for subtyping in IDL:
  • inheritance: interface A : B { ... };
  • implements statements: A implements B;
  • partial interface: partial interface A { ... };
The corresponding C++ implementations are as follows, here illustrated for attribute T foo;
  • inheritance: handled by JavaScript, but often have corresponding C++ inheritance; one of:
    • class A { ... };
    • class A : B { ... };
  • implements: C++ class must implement methods, either itself or via inheritance; one of:
    • class A { public: T foo(); void setFoo(...); ... };
    • class A : B { ... };
    • unless there is a layering violation (an interface in modules implemented in core), in which case put [TreatAsPartial] on the implemented interface definition and implement as static member functions, as in partial interface.
  • partial interface: implemented as static member functions in an unrelated class:
    • class B { static T foo(A& a); static void setFoo(A& a, ...); ... };
IDL files SHOULD agree with spec, and almost always MUST do so. It is not ok to change the kind of subtyping or move members between interfaces, and violations SHOULD or MUST be fixed:
  • Inheritance is visible to JavaScript (in the prototype chain), so it MUST be correct (it is NOT ok to have non-spec inheritance relationships).
  • The distinction between "member of (main) interface definition, member of implemented interface, member of partial interface" is not visible in JavaScript (these are all just properties of the prototype object), so while this SHOULD agree with spec (so Blink IDL agrees with IDL in the spec), this is not strictly required.
  • The distinction between "member of (child) interface" and "member of parent interface" is visible in JavaScript (as property on prototype object corresponding to (child) interface vs. property on prototype object corresponding to parent interface), and thus MUST be correct (it is NOT ok to move members between an interface and a parent if this disagrees with spec).

Technical details

While members of an interface definition, members of implemented interface, and members of partial interfaces are identical for JavaScript, partial interface members – and members of certain implemented interfaces, namely those with the [TreatAsPartial] extended attribute – are treated differently internally in Blink (see below).

Inheritance and implements are both interface inheritance. JavaScript has single inheritance, and IDL inheritance corresponds to JavaScript inheritance, while IDL implements provides multiple inheritance in IDL, which does not correspond to inheritance in JavaScript.

In both cases, by spec, members of the inherited or implemented interface must be implemented on the JavaScript object implementing the interface. Concretely, members of inherited interfaces are implemented as properties on the prototype object of the parent interface, while members of implemented interfaces are implemented as properties of the implementing interface.

In C++, members of an interface definition and members of implemented interfaces are implemented on the C++ object (referred to as the parameter or variable impl) implementing the JavaScript object. Specifically this is done in the Blink class corresponding to the IDL interface or a base class – the C++ hierarchy is invisible to both JavaScript and the bindings.

Implementation-wise, inheritance and implements differ in two ways:
  • Inheritance sets the prototype object (this is visible in JavaScript via getPrototypeOf); implements does not.
  • Bindings are not generated for inherited members (JavaScript dispatches these to the parent prototype), but are generated for implemented members.
For simplicity, in the wrapper (used by V8 to call Blink) the bindings just treat members of implemented interfaces and partial interfaces as if they were part of the main interface: there is no multiple inheritance in the bindings implementation.

If (IDL) interface A inherits from interface B, then usually (C++) class A inherits from class B, meaning that:
interface A : B { /* ... */ };
is usually implemented as:
class A : B { /* ... */ };
...or perhaps:
class A : C { /* ... */ };
class C : B { /* ... */ };

However, the bindings are agnostic about this, and simply set the prototype in the wrapper object to be the inherited interface (concretely, sets the parentClass attribute in the WrapperTypeInfo of the class's bindings). Dispatch is thus done in JavaScript.

"A implements B;"
should mean that members declared in (IDL) interface B
are members of (C++) classes implementing A.
impl.

Partial interfaces formally are type extension (external type extension, since specified in a separate place from the original definition), and in principle are simply part of the interface, just defined separately, as a convenience for spec authors. However in practice, members of partial interfaces are not assumed to be implemented on the C++ object (impl), and are not defined in the Blink class implementing the interface. Instead, they are implemented as static members of a separate class, which take impl as their first argument. This is done because in practice, partial interfaces are type extensions, which often only used in subtypes or are deactivated (via conditionals or as runtime enabled features), and we do not want to bloat the main Blink class to include these.

Further, in some cases we must use type extension (static methods) for implemented interfaces as well. This is due to componentization in Blink (see Browser Components), currently core versus modules. Code in core cannot inherit from code in modules, and thus if an interface in core implements an interface in modules, this must be implemented via type extension (static methods in modules). This is an exceptional case, and indicates that Blink's internal layering (componentization) disagrees with the layering implied by the IDL specs, and formally should be resolved by moving the relevant interface from modules to core. This is not always possible or desirable (for internal implementation reasons), and thus static methods can be specified via the [TreatAsPartial] extended attribute on the implemented interface.

Inheritance and code reuse

IDL has single inheritance, which maps directly to JavaScript inheritance (prototype chain). C++ has multiple inheritance, and the two hierarchies need not be related.

FIXME: There are issues if a C++ class inherits from another C++ class that implements an IDL interface, as .
downcasting

IDL has 3 mechanisms for combining interfaces:
  • (Single) inheritance
  • implements
  • partial interface

Examples

Sharing code with a legacy interface (unprefixing)

...

Changing inheritance → implements

Converting a parent to the target of an implements

See also

Other Blink interfaces, not standard Web IDL interfaces:
  • Public C++ API: C++ API used by C++ programs embedding Blink (not JavaScript), including the (C++) "web API"
  • Implementing a new extension API: Chrome extensions (JavaScript interfaces used by extensions), also use a dialect of Web IDL for describing interfaces

External links

For reference, documentation by other projects.
Comments