Blink‎ > ‎

Blink-in-JavaScript

Overview

Blink-in-JavaScript is a mechanism to enable Blink developers to implement DOM features in JavaScript (instead of C++). Currently developers have to write C++ whenever they implement DOM features in Blink. However, C++ causes a lot of security issues and is hard to maintain. If we can implement more things in JavaScript, Blink would become more secure and easier to maintain.

The goal of Blink-in-JS is improving maintainability, security and layering of the web architecture. The goal is NOT improving performance, power and memory. Thus Blink-in-JS is not intending to support performance-sensitive or memory-sensitive DOM features. Alternately the target features of Blink-in-JS are:
  • High-level DOM features that can be easily implemented on top of existing JavaScript APIs
  • DOM features that are unloved and should be factored out from C++
  • DOM features that are going to be deprecated
  • DOM features that are going to be implemented in C++ in the near future (i.e., Polyfil)
You can learn the design in this design document and this slide. If you are interested in the security model, you can also look at this document. This page focuses on how to use Blink-in-JS in the Blink code base using concrete examples.

Basics

The most common usage of Blink-in-JS is to implement DOM features defined in an IDL file in JavaScript. For example, consider to implement a <marquee> tag in Blink-in-JS.

In this case, what you need to do is just to:
  • Add [ImplementedInPrivateScript] IDL extended attributes to DOM attributes/methods in an IDL file.
  • Implement the DOM attributes/methods in JavaScript (This JavaScript file is called a private script.)
Specifically, you first need to add [ImplementedInPrivateScript] IDL extended attributes to DOM attributes/methods in HTMLMarqueeElement.idl.

interface HTMLMarqueeElement : HTMLElement {
  [ImplementedInPrivateScript] void start();
  [ImplementedInPrivateScript] void stop();
  [ImplementedInPrivateScript] attribute long loop;
  [ImplementedInPrivateScript] attribute long scrollAmount;
  ...;
};

Second, you need to implement the DOM attributes/methods in HTMLMarqueeElement.js.

installClass("HTMLMarqueeElement", function(HTMLMarqueeElementPrototype)) {

  HTMLMarqueeElementPrototype.start = function() {
    // Implement the start() method.
    // |this| object is equal to the wrapper of the <marquee> element.
  }

  Object.defineProperty(HTMLMarqueeElementPrototype, 'loop', {
      get: function() {
        // Implement the getter of the loop attribute.
        // |this| object is equal to the wrapper of the <marquee> element.
      },
      set: function(value) {
        // Implement the setter of the loop attribute.
        // |this| object is equal to the wrapper of the <marquee> element.
      },
  });

  ...; // Implement other DOM attributes/methods.

};

That's it. Then the IDL compiler auto-generates the binding code that connects user's script with the JavaScript functions defined in the private script.

The important points are as follows:
  • A private script runs in a dedicated isolated world. For security reasons, no JavaScript objects nor DOM wrappers are shared between the private script and user's script. This restriction is needed to prevent the private script from leaking confidential information to user's script.
  • To force the restriction, the type of arguments you can pass from user's script to the private script is limited to JavaScript primitive types (e.g., int, double, string, boolean etc) and DOM wrappers. Similarly, the type you can return from the private script back to user's script is limited to JavaScript primitive types and DOM wrappers. You cannot pass JavaScript functions, JavaScript objects, Promises etc.
  • |global| is a window object of the private script. You can use the |global| object as you like, but note that the |global| object is shared among all private scripts. For example, HTMLMarqueeElement.js and XSLT.js share the same |global| object. Thus you should not cache data specific to your private script onto the |global| object.
  • |HTMLMarqueeElementPrototype| is a prototype object of the private script. Your main work is to define DOM attributes/methods on the prototype object.
  • |this| object is equal to the wrapper of the <marquee> element in the isolated world for private scripts. Due to the security isolation, |this| object is a different wrapper from the wrapper of the <marquee> element in the main world. You can cache whatever you want onto the |this| object. It is guaranteed that the |this| object is not accessible from user's script.
  • By default, the IDL compiler assumes that you have HTMLMarqueeElement.h in Blink. If you don't want to have HTMLMarqueeElement.h, you need to add [NoImplHeader] IDL attribute on the interface.
  • |this| object has the following prototype chain: |this| --> HTMLMarqueeElementPrototype --> HTMLMarqueeElement.prototype --> HTMLElement.prototype --> Element.prototype --> Node.prototype --> ....
For more details, you can look at how HTMLMarqueeElement.js and PrivateScriptTest.js are implemented.

Details

By using the [ImplementedInPrivateScript] IDL extended attribute, you can implement DOM features exposed to the web in a private script. However, this is sometimes not sufficient to implement real-world DOM features in a private script. Sometimes you will need to invoke a private script from C++. Sometimes you will need to implement internal DOM attributes/methods that are only exposed to private scripts.

In general, Blink-in-JS supports the following four kinds of APIs.
  • [user's script & private script => C++]: This is a normal DOM attribute/method (where Blink-in-JS is not involved). The DOM attribute/method is implemented in C++, and the DOM attribute/method is exposed to both user's script and private scripts.
  • [user's script & private script => private script]: This is the most common usage of Blink-in-JS explained above. The DOM attribute/method is implemented in a private script, and the DOM attribute/method is exposed to both user's script and private scripts.
  • [private script => C++]: This is an "internal" DOM attribute/method for private scripts. The DOM attribute/method is implemented in C++, and the DOM attribute/method is exposed only to private scripts (not exposed to user's script).
  • [C++ => private script]: This is a way to invoke a private script from C++. The DOM attribute/method is implemented in a private script, and a C++ static function is provided to invoke the DOM attribute/method so that Blink can use it wherever it wants.
You can control the kind of each API by combining the [ImplementedInPrivateScript] IDL extended attribute and [OnlyExposedToPrivateScript] IDL attribute.
  • [user's script & private script => C++]: Use no IDL extended attributes.
  • [user's script & private script => private script]: Use [ImplementedInPrivateScript].
  • [private script => C++]: Use [OnlyExposedToPrivateScript].
  • [C++ => private script]: Use [ImplementedInPrivateScript, OnlyExposedToPrivateScript].
Here is an example:

interface XXX {
  void f1();  // Normal DOM method implemented in C++; exposed to user's script and private scripts.
  [ImplementedInPrivateScript] void f2();  // DOM method implemented in a private script; exposed to user's script and private scripts.
  [OnlyExposedToPrivateScript] void f3();  // DOM method implemented in C++; exposed only to private scripts.
  [ImplementedInPrivateScript, OnlyExposedToPrivateScript] void f4();  // DOM method implemented in a private script; V8XXX::PrivateScript::f4Method() is provided as a static method so that Blink can invoke the private script.
};

For more details, see test cases in PrivateScriptTest.idl.

DOM attributes/methods that have [OnlyExposedToPrivateScript] IDL attribute are "backdoors". Backdoors are strongly discouraged unless you are certain that the backdoors are APIs that are missing in the current web platform and should be exposed to the web in the future. Ideally Blink-in-JS should be implemented only by using existing web platform APIs. The only case where backdoors are allowed is a case where we think that the API is a missing part of the current web platform and should be exposed to the web in the future. (One of the goals of Blink-in-JS is to understand what APIs are missing in the web platform by trying to implement built-in contents of Blink in JavaScript.)


Contacts

If you have any questions or comments, feel to free to ask haraken@. I'm planning to factor out more things from C++ to Blink-in-JS to improve the hackability of the Blink core. Your contributions are super welcome!

Comments