Web IDL: Defining Web APIs and Implementing JavaScript Bindings

Cameron McCormack · @heycam

Web IDL Defining Web APIs and Implementing JavaScript Bindings

In the beginning…

…there was the DOM.

  • Access to HTML, XML and other tree-based markup languages.
  • DOM Level 1 was published in 1998.
  • First use of OMG IDL to describe interfaces that DOM objects implement.

What does it look like?

        interface Node {
          const unsigned short ELEMENT_NODE   = 1;
          const unsigned short ATTRIBUTE_NODE = 2;
          ...
          readonly attribute DOMString nodeName;
                   attribute DOMString nodeValue;
          ...
          Node appendChild(in Node newChild) raises(DOMException);
          ...
        };
      

What does it look like?

        interface Node {
          const unsigned short ELEMENT_NODE   = 1;
          const unsigned short ATTRIBUTE_NODE = 2;
          ...
          readonly attribute DOMString nodeName;
                   attribute DOMString nodeValue;
          ...
          Node appendChild(in Node newChild) raises(DOMException);
          ...
        };
      

What does it look like?

        interface Node {
          const unsigned short ELEMENT_NODE   = 1;  // constants
          const unsigned short ATTRIBUTE_NODE = 2;
          ...
          readonly attribute DOMString nodeName;
                   attribute DOMString nodeValue;
          ...
          Node appendChild(in Node newChild) raises(DOMException);
          ...
        };
      

What does it look like?

        interface Node {
          const unsigned short ELEMENT_NODE   = 1;
          const unsigned short ATTRIBUTE_NODE = 2;
          ...
          readonly attribute DOMString nodeName;  // attributes
                   attribute DOMString nodeValue;
          ...
          Node appendChild(in Node newChild) raises(DOMException);
          ...
        };
      

What does it look like?

        interface Node {
          const unsigned short ELEMENT_NODE   = 1;
          const unsigned short ATTRIBUTE_NODE = 2;
          ...
          readonly attribute DOMString nodeName;
                   attribute DOMString nodeValue;
          ...
          Node appendChild(in Node newChild) raises(DOMException);
          ...  // operations
        };
      

Defines a set of C-like types — float, bool, unsigned long, …

Language independence

  • DOM designed with two target languages in mind: Java and JS.
  • OMG defines IDL bindings for various specific languages…
    …but not for JavaScript!
  • So DOM has language binding appendices for Java and JavaScript.
    (DOM Levels 1, 2 and 3 target ECMAScript 3rd Edition, although the 5th Edition is current.)

Unfortunately…

  • The JS language binding appendix is not very precise.
  • Precision — and accuracy — is required for interoperability!

A quick ECMAScript 5th Ed. primer

  • JS has a few types:
    • Undefined – undefined
    • Boolean – false, true
    • Number – 0, -2.5, Infinity, NaN
    • String – "", "hello"
    • Object – { }, { "key": "value" }, [1, 2, 3], function() { }, /^regexp?$/

A quick ECMAScript 5th Ed. primer

  • JS objects have properties, each with a String name and either a value (data property) or a getter/setter pair (accessor property).
  • Properties have attributes: Writable, Enumerable, Configurable
  • JS objects can also have a prototype object for simple inheritance:
    array_instance → Array.prototype → Object.prototype → null
    • array_instance = [10, 20, 30]: 0, 1, 2, length
    • Array.prototype: push, pop, concat, …
    • Object.prototype: toString, …
Prototype Object Node
The Node class has the following constants:
Node.ELEMENT_NODE
This constant is of type Number and its value is 1.
  • Both ECMAScript 3rd Ed. and 5th Ed. have neither classes nor constants. What JS constructs do IDL constants correspond to? Non-writable data properties?
  • Shouldn’t the ELEMENT_NODE property be on both Node and Node.prototype?
Object Node
The Node object has the following properties:
nodeValue
This property is of type String, can raise a DOMException object on setting and can raise a DOMException object on retrieval.
  • What does it mean for a property to have a type?
  • What if we assign something other than a String?
              anElement.nodeValue = 0x10;
              anElement.nodeValue = ({ "toString": function() { return "a"; } });
            
  • Is it a data property or an accessor property? Observable with Object.getOwnPropertyDescriptor().
Object Node
The Node object has the following methods:
appendChild(newChild)
This method returns a Node object.
The newChild parameter is a Node object. […]
  • Does the property live on a Node instance or its prototype?
  • Can you pass ({ "nodeType": Node.ELEMENT_NODE }) to it?
  • What about: nodesubclass_instance → NodeSubclass → Node → Object
              function NodeSubclass() { }
              NodeSubclass.prototype.__proto__ = Node.prototype;
              document.documentElement.appendChild(new NodeSubclass());
            

Unanswered questions

  • All these questions (and many more) are unanswered by the original DOM specification.
  • All Web specifications up until 2008 were written like this.
  • Ten years’ worth of unspecified behaviour!
  • But we should be able to answer these questions consistently for all specifications at once.

Enter: Web IDL

  • http://www.w3.org/TR/WebIDL/
  • Originally named “Language Bindings for DOM Specifications”.
  • Two goals:
    1. Resolve unspecified behaviour from the use of OMG IDL.
    2. Allow more JavaScript-appropriate APIs to be defined, while still supporting language independence.

What behaviour does Web IDL define?

  • Type mappings:  IDL unsigned short ↔ JS Number
  • Type conversions:
                anElement.nodeValue = 0x10;
                anElement.nodeValue = ({ "toString": function() { return "a"; } });
              
    Any JS value is converted to an IDL DOMString value by effectively passing it to the global String() function.
                anElement.nodeValue = "16";
                anElement.nodeValue = "a";
              

What behaviour does Web IDL define?

  • Type conversions:
                var nodeLike = ({ "nodeType": Node.ELEMENT_NODE });
                document.documentElement.appendChild(nodeLike)
                 
                function NodeSubclass() { }
                NodeSubclass.prototype.__proto__ = Node.prototype;
                document.documentElement.appendChild(new NodeSubclass());
              
    Author created JS objects will never be considered to implement an interface such as Node. A TypeError exception will be thrown.

What behaviour does Web IDL define?

  • Properties:
    • Constants: non-writable, non-configurable, enumerable data property, on both Node and Node.prototype.
    • Attributes: configurable, enumerable accessor property on the prototype.
    • Operations: configurable, enumerable data property (with a Function value) on the prototype.
  • Not magical, helps author understanding, allows monkeypatching.

What behaviour does Web IDL define?

  • Many other things:
    • What happens when you pass too many/few arguments.
    • What happens when you grab a Function corresponding to an IDL operation and apply it to some other type of object.
    • How interface inheritance corresponds to a prototype chain.
    • How DOM objects are stringified.

New JavaScripty features

  • The base OMG IDL language is extended to support defining behaviours that JS supports.
  • API ergonomics tailored to JS first.

Indexed and named properties

  • The NodeList interface exposes array-like properties:
                var kids = document.documentElement.childNodes;
                alert(kids);  // [object NodeList]
                alert(kids[0]);  // [object HTMLHeadElement]
                alert(kids[1]);  // [object HTMLBodyElement]
              
  • Keywords to implement these indexed properties:
                interface NodeList {
                  getter Node? item(unsigned long index);
                  readonly attribute unsigned long length;
                };
              

Constructors

  • The [Constructor] extended attribute declares that you can use new on the interface:
                [Constructor]
                interface Document : Node {
                  readonly attribute Element? documentElement;
                  ...
                };
              
    Then:
                var doc = new Document();
              

Dictionaries

  • Dictionary types are used for named-argument objects:
                [Constructor(DOMString type, optional EventInit eventInitDict)]
                interface Event {
                  void initEvent(DOMString type, boolean bubbles, boolean cancelable);
                  ...
                };
                dictionary EventInit {
                  boolean bubbles;
                  boolean cancelable;
                };
              

Dictionaries

  • Instead of:
                var event = document.createEvent("Event");
                event.initEvent("click", true, false);
              
    you can write:
                var event1 = new Event("click", { bubbles: true, cancelable: false });
                var event2 = new Event("click", { bubbles: true });
                var event3 = new Event("click");
              

Enumerations

  • String constant enumerations:
                enum CanvasDrawingStylesFillRules { "nonzero", "evenodd" };
                interface CanvasRenderingContext2D {
                  attribute CanvasDrawingStylesFillRules fillRule;
                  ...
                };
              
    Then:
                var ctx = document.querySelector("canvas").getContext("2d");
                ctx.fillRule = "evenodd";
                ctx.fillRule = "something";  // throws TypeError
              

Generating JS bindings for Firefox

  1. Can copy/paste IDL directly from specifications.
  2. Easier to write C++ classes implementing interfaces.
  3. Generated code is simpler and faster.

Performance of new bindings

  • 100× faster than standard old bindings (plain XPConnect).
  • 4–5× faster than quickstubbed old bindings (use of properties with JS Functions that know what IDL member they correspond to and what type of object they're on).
  • 2.5–3× faster than custom quickstubs (additional hand written C++ to perform argument type conversion and dispatch).
  • Fast behaviour by default.
  • On a 2.7 GHz machine: ~1 us → 8–9 ns.
Firefox 18 Firefox 21
DOM Attributes 695.10runs/s ±1.13% 814.70runs/s ±0.52% 15~19%
DOM Modification 353.90runs/s ±1.86% 365.06runs/s ±2.66% ~same
DOM Query 18301.34runs/s ±1.27% 19598.90runs/s ±1.75% 4~10%
DOM Traversal 504.14runs/s ±0.51% 671.90runs/s ±0.30% 32~34%
http://dromaeo.com/?id=188932,188933

Thank You

Cameron McCormack, Mozilla · @heycam

Shower, by Vadim Makeev, Opera Software
Mozilla styling, by Chris Heilmann, Mozilla
Title page photo, by Flickr user Kasaa, http://www.flickr.com/photos/kasaa/3194418445