Skip to content

Commit 975b100

Browse files
committed
1 parent 2c0a75a commit 975b100

File tree

3 files changed

+188
-73
lines changed

3 files changed

+188
-73
lines changed

assets/amp-workerdom-challenge.png

48.2 KB
Loading

assets/detachable-realms.png

38 KB
Loading

explainer.md

+188-73
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
# Realms Explainer
22

3-
## Introduction
3+
<!-- vscode-markdown-toc -->
4+
* [Introduction](#Introduction)
5+
* [API (TypeScript Format)](#APITypeScriptFormat)
6+
* [Motivations](#Motivations)
7+
* [Clarifications](#Clarifications)
8+
* [Terminology](#Terminology)
9+
* [The Realm's Global Object](#TheRealmsGlobalObject)
10+
* [Evaluation](#Evaluation)
11+
* [Module graph](#Modulegraph)
12+
* [Compartments](#Compartments)
13+
* [Use Cases](#UseCases)
14+
* [Third Party Scripts](#ThirdPartyScripts)
15+
* [Code Testing](#CodeTesting)
16+
* [Running tests in a Realm](#RunningtestsinaRealm)
17+
* [Test FWs + Tooling to run tests in a realm](#TestFWsToolingtoruntestsinarealm)
18+
* [Codebase segmentation](#Codebasesegmentation)
19+
* [Template libraries](#Templatelibraries)
20+
* [DOM Virtualization](#DOMVirtualization)
21+
* [DOM Virtualization: AMP WorkerDOM Challenge](#DOMVirtualization:AMPWorkerDOMChallenge)
22+
* [More Examples](#MoreExamples)
23+
* [Modules](#Modules)
24+
* [General Goals and Values for Realms](#GeneralGoalsandValuesforRealms)
25+
* [Integrity](#Integrity)
26+
* [Security vs Integrity](#SecurityvsIntegrity)
27+
* [Virtualized Environment](#VirtualizedEnvironment)
28+
* [DOM mocking](#DOMmocking)
29+
* [ Alternatives](#Alternatives)
30+
* [Status Quo](#StatusQuo)
31+
* [Iframes](#Iframes)
32+
* [Why not separate processes?](#Whynotseparateprocesses)
33+
34+
<!-- vscode-markdown-toc-config
35+
numbering=false
36+
autoSave=true
37+
/vscode-markdown-toc-config -->
38+
<!-- /vscode-markdown-toc -->
39+
40+
## <a name='Introduction'></a>Introduction
441

542
Realms are a distinct global environment, with its own global object containing its own intrinsics and built-ins (standard objects that are not bound to global variables, like the initial value of Object.prototype).
643

@@ -10,7 +47,7 @@ The Realms API does not create - or rely on - a new executing thread. New realms
1047

1148
Any code executed within this realm may introduce changes to global variables or built-ins, but limited to the realm global Record.
1249

13-
## API (TypeScript Format)
50+
## <a name='APITypeScriptFormat'></a>API (TypeScript Format)
1451

1552
```ts
1653
declare class Realm {
@@ -20,15 +57,7 @@ declare class Realm {
2057
}
2158
```
2259

23-
### Intuitions
24-
25-
* sandbox
26-
* virtualization / testing
27-
* iframe without DOM
28-
* principled version of Node's `'vm'` module
29-
* sync Worker
30-
31-
## Motivations
60+
## <a name='Motivations'></a>Motivations
3261

3362
Why do developers need realms?
3463

@@ -37,8 +66,9 @@ It's quite common for an applications to contain programs from multiple sources,
3766
Various examples where Realms can be used to avoid this:
3867

3968
* Web-based IDEs or any kind of 3rd party code execution uses same origin evaluation.
40-
* Test frameworks (in-browser tests, but also in node using `vm`).
41-
* testing/mocking (e.g., jsdom)
69+
* DOM Virtualization (e.g.: AMP)
70+
* Test frameworks and reporters(in-browser tests, but also in node using `vm`).
71+
* testing/mocking (e.g.: jsdom)
4272
* Most plugin mechanism for the web (e.g., spreadsheet functions).
4373
* Sandboxing (e.g.: Oasis Project)
4474
* Server side rendering (to avoid collision and data leakage)
@@ -47,42 +77,158 @@ Various examples where Realms can be used to avoid this:
4777

4878
Note: the majority of the examples above will require synchronous operations to be supported, which makes it almost impossible to use Workers & co., or any other isolation mechanism in browsers and nodejs today.
4979

50-
## Overview
80+
## <a name='Clarifications'></a>Clarifications
81+
82+
### <a name='Terminology'></a>Terminology
83+
84+
In the Web Platform, both `Realm` and `Global Object` are usually associated to Window, Worker, and Worklets semantics. They are also associated to their detachable nature.
85+
86+
This proposal is limited to the semantics specified by ECMA-262 with no extra requirements from the web counterparts.
87+
88+
### <a name='TheRealmsGlobalObject'></a>The Realm's Global Object
89+
90+
- The Realm's [Global Object](https://tc39.es/ecma262/#sec-ordinary-object) is an [Ordinary Object](https://tc39.es/ecma262/#sec-ordinary-object).
91+
- A Realm Object (and its global object) are not detachable
92+
- A Realm Object has a lifeline to its incubator Realm
93+
94+
![](assets/detachable-realms.png)
95+
96+
### <a name='Evaluation'></a>Evaluation
97+
98+
The Realm API does not introduce a new way to evaluate code, it reuses the existing evaluation mechanisms:
99+
100+
- If you disable CSP `unsafe-eval`, you cannot evaluate code synchronously.
101+
- If you disable CSP `default-src`, you cannot use `Realm.prototype.import()`.
102+
103+
### <a name='Modulegraph'></a>Module graph
104+
105+
Every Realm object has its own module graph.
106+
107+
```js
108+
const realm = new Realm();
109+
110+
// imports code that executes within its own environment.
111+
const { doSomething } = await realm.import('./file.js');
112+
113+
doSomething();
114+
```
115+
116+
### <a name='Compartments'></a>Compartments
117+
118+
- A new [compartment](https://github.com/tc39/proposal-compartments) provides a new Realm constructor
119+
- A realm object created from a compartment is subject to the compartment's virtualization mechanism
120+
121+
```js
122+
const compartment = new Compartment(options);
123+
const VirtualizedRealm = compartment.globalThis.Realm;
124+
const realm = new VirtualizedRealm();
125+
const { doSomething } = await realm.import('./file.js');
126+
```
127+
128+
## <a name='UseCases'></a>Use Cases
51129

52-
## API
130+
- Third Party Scripts
131+
- Code Testing
132+
- Codebase segmentation
133+
- Template libraries
134+
- DOM Virtualization
53135

54-
A walk-through of the Realm API with examples and explanations:
136+
### <a name='ThirdPartyScripts'></a>Third Party Scripts
55137

56-
## Examples
138+
- We want a quick and simple execution of third party scripts.
139+
- Not one, not two, not three, but many scripts.
140+
- We don't need a new host/agent, but just integrity of the expected Intrinsics.
141+
- Non-blocking async loading and execution via `realm.import`.
142+
- No immediate access to application globals (e.g. `window`) is also convenient.
143+
- No immediate access to unforgeables (e.g. `window.top`, `window.location`) is great.
57144

58145
```js
59-
// this is the root realm
60-
var x = 39;
146+
import { api } from 'pluginFramework';
61147
const realm = new Realm();
62148

63-
// globals from the root/parent realm are not leaked to the nested realms
64-
realm.globalThis.x; // undefined
149+
realm.globalThis.api = api;
150+
151+
await realm.import('./plugin1.js');
152+
```
65153

66-
// evaluation occurs within the nested realm
67-
realm.globalThis.eval("var x = 42");
154+
### <a name='CodeTesting'></a>Code Testing
68155

69-
// global names can be regularly observed in the realm's globalThis
70-
realm.globalThis.x; // 42
156+
While threading is good for testing, the layering from Realms is also great.
71157

72-
// global values are not leaked to the parent realms
73-
x; // 39
158+
Test frameworks can use Realms to inject code and also control the order the injections if necessary.
74159

75-
// built-ins configurability are not different
76-
delete realm.globalThis.Math;
160+
#### <a name='RunningtestsinaRealm'></a>Running tests in a Realm
77161

78-
// realms can dynamic import module that will execute within it's own
79-
// environment. Imports can be observed from the parent realm.
80-
realm.import('./file.js').then(ns => ns.execCustomCode());
162+
```js
163+
import { test } from 'testFramework';
164+
const realm = new Realm();
165+
166+
realm.globalThis.test = test;
167+
await realm.import('./main-spec.js');
168+
169+
test.report();
81170
```
82171

172+
#### <a name='TestFWsToolingtoruntestsinarealm'></a>Test FWs + Tooling to run tests in a realm
173+
174+
```js
175+
const realm = new Realm();
176+
const [ framework, { tap } ] = await Promise.all([
177+
realm.import('testFramework'),
178+
realm.import('reporters')
179+
]);
180+
181+
framework.use(tap);
182+
await realm.import('./main-spec.js');
183+
```
184+
185+
### <a name='Codebasesegmentation'></a>Codebase segmentation
186+
187+
- A big codebase tend to evolve slowly.
188+
- Old code vs new code is a constant struggle.
189+
- Modifying code to resolve a conflict (e.g.: global variables) is non-trivial.
190+
- With a lightweight mechanism to preserve the integrity of the intrinsics you could isolate libraries, or logical pieces of the codebase.
191+
192+
### <a name='Templatelibraries'></a>Template libraries
193+
194+
Code generation should not be subject to pollution (global, prototype, etc.). E.g.: Lodash's `_.template()` uses `Function(...)` to create a compiled template function, instead it could use a Realm to avoid leaking global variables. The same Realm could be reused multiple times.
195+
196+
```js
197+
var compiled = _.template(
198+
'<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'
199+
);
200+
201+
compiled({ users: ['user1', 'user2'] });
202+
```
203+
204+
### <a name='DOMVirtualization'></a>DOM Virtualization
205+
206+
- We still want things to still interact with the DOM
207+
- We don't want to spend any excessive amount of resources
208+
- We want to emulate the DOM as best as possible
209+
- Requiring other libraries to change to meet our requirements is difficult
210+
211+
```js
212+
import virtualDocument from 'virtual-document';
213+
214+
const realm = new Realm();
215+
216+
realm.globalThis.document = virtualDocument;
217+
218+
await realm.import('./publisher-amin.js');
219+
```
220+
221+
#### <a name='DOMVirtualization:AMPWorkerDOMChallenge'></a>DOM Virtualization: AMP WorkerDOM Challenge
222+
223+
Problem: Element.getBoundingClientRect() doesn't work over async comm channels (i.e. [worker-dom](https://github.com/ampproject/worker-dom)).
224+
225+
![AMP WorkerDOM Challenge diagram](assets/amp-workerdom-challenge.png)
226+
227+
## <a name='MoreExamples'></a>More Examples
228+
83229
See some other examples [here](EXAMPLES.md).
84230

85-
### Modules
231+
## <a name='Modules'></a>Modules
86232

87233
In principle, the Realm proposal does not provide the controls for the module graphs. Every new Realm initializes its own module graph, while any invocation to `Realm.prototype.import()` method, or by using `import()` when evaluating code inside the realm, will populate this module graph. This is analogous to same-domain iframes, and VM in nodejs.
88234

@@ -100,25 +246,23 @@ There are some precedents on how to solve the identity discontinuity issues by u
100246

101247
There is one important thing to keep in mind when it comes to sharing module graphs. The ESM linkage is not asynchronous. This dictates that in order to share modules between realms, those realms should share the same process, otherwise the bindings between those modules cannot work according to the language. This is another reason to support our claim that Realms should be running within the same process.
102248

103-
## Use Cases
249+
## <a name='GeneralGoalsandValuesforRealms'></a>General Goals and Values for Realms
104250

105-
### Integrity
251+
### <a name='Integrity'></a>Integrity
106252

107253
We believe that realms can be a good complement to integrity mechanisms by providing ways to evaluate code who access different object graphs (different global objects) while maintaining the integrity of the outer realm. A concrete example of this is the Google's AMP current mechanism:
108254

109255
* Google News App creates multiples sub-apps that can be presented to the user.
110256
* Each sub-app runs in a cross-domain iframe (communicating with the main app via post-message).
111257
* Each vendor (one per app) can attempt to enhance their sub-app that display their content by executing their code in a realm that provide access to a well defined set of APIs to preserve the integrity of the sub-app.
112258

113-
_TODO: cc @jridgewell to see if this is accurate._
114-
115259
There are many examples like this for the web: Google Sheets, Figma's plugins, or Salesforce's Locker Service for Web Components.
116260

117-
#### Security vs Integrity
261+
#### <a name='SecurityvsIntegrity'></a>Security vs Integrity
118262

119263
There are also other more exotic cases in which measuring of time ("security") is not a concern, especially in IOT where many devices might not have process boundaries at all. Or examples where security is not a concern, e.g.: test runners like jest (from facebook) that relies on nodejs, JSDOM and VM contexts to execute individual tests while sharing a segment of the object graph to achieve the desired performance budget. No doubts that this type of tools are extremely popular these days, e.g.: JSDOM has 10M installs per week according to NPM's registry.
120264

121-
### Virtualized Environment
265+
### <a name='VirtualizedEnvironment'></a>Virtualized Environment
122266

123267
The usage of different realms allow customized access to the global environment. To start, The global object could be immediately frozen.
124268

@@ -147,7 +291,7 @@ realm.import('./file.js');
147291
iframe.contentWindow.eval("import('./file.js')");
148292
```
149293

150-
#### DOM mocking
294+
#### <a name='DOMmocking'></a>DOM mocking
151295

152296
The Realms API allows a much smarter approach for DOM mocking, where the globalThis can be setup in userland. This also takes advantage of the Realm constructor being subclassable:
153297

@@ -166,50 +310,21 @@ class FakeWindow extends Realm {
166310

167311
This code allows a customized set of properties to each new Realm and avoid issues on handling immutable accessors/properties from the Window proxy. e.g.: `window.top`, `window.location`, etc..
168312

169-
## Testing
170-
171-
The Realms API is very useful for testing purposes. It can provide a limited context that can observe code reliance:
172-
173-
```js
174-
import Tester from 'myTestFramework';
175-
176-
class TestRealm extends Realm {
177-
constructor(...args) {
178-
super(...args);
179-
const globalThis = this.globalThis;
180-
181-
// Loads the globalThis with the Tester API
182-
Object.assign(globalThis, new Tester());
183-
184-
Object.freeze(globalThis);
185-
}
186-
187-
async exec(testFile) {
188-
// Assuming testFile matches a valid loader specifier
189-
return await this.import(testFile);
190-
}
191-
}
192-
193-
const myTests = new TestRealm();
194-
195-
myTests.exec('./hanoi-tower-spec.js');
196-
```
197-
198-
## Alternatives
313+
## <a name='Alternatives'></a> Alternatives
199314

200-
### Status Quo
315+
### <a name='StatusQuo'></a>Status Quo
201316

202317
Using VM module in nodejs, and same-domain iframes in browsers. Although, VM modules in node is a very good approximation to this proposal, iframes are problematic.
203318

204-
### Iframes
319+
### <a name='Iframes'></a>Iframes
205320

206321
Developers can technically already create a new Realm by creating new same-domain iframe, but there are few impediments to use this as a reliable mechanism:
207322

208323
* the global object of the iframe is a window proxy, which implements a bizarre behavior, including its unforgeable proto chain.
209324
* there are multiple ~~unforgeable~~ unvirtualizable objects due to the DOM semantics, this makes it almost impossible to eliminate certain capabilities while downgrading the window to a brand new global without DOM.
210325
* global `top` reference is ~~unforgeable~~ not virtualizable and leaks a reference to another global object. The only way to null out this behavior is to detach the iframe, which imposes other problems, the more relevant is dynamic `import()` calls, __which cannot work in detached realms__.
211326

212-
### Why not separate processes?
327+
### <a name='Whynotseparateprocesses'></a>Why not separate processes?
213328

214329
This is another alternative, creating a Realm that runs in a separate process, while allowing users to define and create their own protocol of communication between these processes. This alternative was discarded for two main reasons:
215330

0 commit comments

Comments
 (0)