Realm API

Realm API 提供沙箱功能(sandbox),允许隔离代码,防止那些被隔离的代码拿到全局对象。

以前,经常使用<iframe>作为沙箱。

  1. const globalOne = window;
  2. let iframe = document.createElement('iframe');
  3. document.body.appendChild(iframe);
  4. const globalTwo = iframe.contentWindow;

上面代码中,<iframe>的全局对象是独立的(iframe.contentWindow)。Realm API 可以取代这个功能。

  1. const globalOne = window;
  2. const globalTwo = new Realm().global;

上面代码中,Realm API单独提供了一个全局对象new Realm().global

Realm API 提供一个Realm()构造函数,用来生成一个 Realm 对象。该对象的global属性指向一个新的顶层对象,这个顶层对象跟原始的顶层对象类似。

  1. const globalOne = window;
  2. const globalTwo = new Realm().global;
  3. globalOne.evaluate('1 + 2') // 3
  4. globalTwo.evaluate('1 + 2') // 3

上面代码中,Realm 生成的顶层对象的evaluate()方法,可以运行代码。

下面的代码可以证明,Realm 顶层对象与原始顶层对象是两个对象。

  1. let a1 = globalOne.evaluate('[1,2,3]');
  2. let a2 = globalTwo.evaluate('[1,2,3]');
  3. a1.prototype === a2.prototype; // false
  4. a1 instanceof globalTwo.Array; // false
  5. a2 instanceof globalOne.Array; // false

上面代码中,Realm 沙箱里面的数组的原型对象,跟原始环境里面的数组是不一样的。

Realm 沙箱里面只能运行 ECMAScript 语法提供的 API,不能运行宿主环境提供的 API。

  1. globalTwo.evaluate('console.log(1)')
  2. // throw an error: console is undefined

上面代码中,Realm 沙箱里面没有console对象,导致报错。因为console不是语法标准,是宿主环境提供的。

如果要解决这个问题,可以使用下面的代码。

  1. globalTwo.console = globalOne.console;

Realm()构造函数可以接受一个参数对象,该参数对象的intrinsics属性可以指定 Realm 沙箱继承原始顶层对象的方法。

  1. const r1 = new Realm();
  2. r1.global === this;
  3. r1.global.JSON === JSON; // false
  4. const r2 = new Realm({ intrinsics: 'inherit' });
  5. r2.global === this; // false
  6. r2.global.JSON === JSON; // true

上面代码中,正常情况下,沙箱的JSON方法不同于原始的JSON对象。但是,Realm()构造函数接受{ intrinsics: 'inherit' }作为参数以后,就会继承原始顶层对象的方法。

用户可以自己定义Realm的子类,用来定制自己的沙箱。

  1. class FakeWindow extends Realm {
  2. init() {
  3. super.init();
  4. let global = this.global;
  5. global.document = new FakeDocument(...);
  6. global.alert = new Proxy(fakeAlert, { ... });
  7. // ...
  8. }
  9. }

上面代码中,FakeWindow模拟了一个假的顶层对象window