diff --git a/explainer.md b/explainer.md
index b3cd06e..7aa2199 100644
--- a/explainer.md
+++ b/explainer.md
@@ -1,590 +1,633 @@
-# ShadowRealms Explainer
-
-
-* [Introduction](#Introduction)
-* [API (TypeScript Format)](#APITypeScriptFormat)
-* [Motivations](#Motivations)
-* [Clarifications](#Clarifications)
-* [Why not separate processes?](#Whynotseparateprocesses)
-* [Security](#Security)
-* [Use Cases](#UseCases)
- * [Third Party Scripts](#ThirdPartyScripts)
- * [Code Testing](#CodeTesting)
- * [Running tests in a ShadowRealm](#RunningtestsinaRealm)
- * [Test FWs + Tooling to run tests in a shadowRealm](#TestFWsToolingtoruntestsinarealm)
- * [Codebase segmentation](#Codebasesegmentation)
- * [Template libraries](#Templatelibraries)
- * [DOM Virtualization](#DOMVirtualization)
- * [DOM Virtualization: AMP WorkerDOM Challenge](#DOMVirtualization:AMPWorkerDOMChallenge)
- * [JSDOM + vm Modules](#JSDOMvmModules)
- * [Virtualized Environment](#VirtualizedEnvironment)
- * [DOM mocking](#DOMmocking)
-* [Modules](#Modules)
-* [Integrity](#Integrity)
-* [Status Quo](#StatusQuo)
-* [Iframes](#Iframes)
-* [FAQ](#FAQ)
-
-
-
-
-## Introduction
-
-The ShadowRealm proposal provides a new mechanism to execute JavaScript code within the context of a new global object and set of JavaScript built-ins.
-
-The API enables control over the execution of different programs within a Realm, providing a proper mechanism for virtualization. This is not possible in the Web Platform today and the proposed API is aimed to a seamless solution for all JS environments.
-
-There are various examples where the ShadowRealm API can be well applied to:
-
- * Web-based IDEs or any kind of 3rd party code execution using same origin evaluation policies.
- * DOM Virtualization (e.g.: Google AMP)
- * Test frameworks and reporters (in-browser tests, but also in node using `vm`).
- * testing/mocking (e.g.: jsdom)
- * Server side rendering (to avoid collision and data leakage)
- * in-browser code editors
- * in-browser transpilation
-
-This document expands a list of some of these [use cases with examples](#UseCases).
-
-As detailed in the [Security](#Security) section, the ShadowRealm API is not a full spectrum mechanism against security issues when evaluating code. As such, it makes it a bad choice for some code execution use cases (e.g., spreadsheet functions blocking the main UI thread).
-
-## API (TypeScript Format)
-
-This is the ShadowRealm API description in TypeScript format:
-
-```ts
-declare class ShadowRealm {
- constructor();
- importValue(specifier: string, bindingName: string): Promise;
- evaluate(sourceText: string): PrimitiveValueOrCallable;
-}
-```
+# ShadowRealm Explainer
-The proposed specification defines:
+## What is this?
-- The [`constructor`](https://tc39.es/proposal-shadowrealm/#sec-shadowrealm).
-- The [`ShadowRealm#importValue()`](https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue) method, equivalent to the `import()` expression, but capturing a primitive or callable values.
-- The [`get ShadowRealm#evaluate`](https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.evaluate) method promotes an indirect eval in the shadowRealm but only allows the return of primitive or callable values.
-- A new wrapped function exotic object with a custom `[[Call]]` internal that has a shared identity of a connected function from another realm associated to it. This identity is not exposed and there is no way to trace back to connected functions cross-realms in user-land.
+ShadowRealm is a mechanism that lets you **execute JavaScript code synchronously, in a fresh, isolated environment**.
-### Quick API Usage Example
+"Fresh" means an execution environment with a pristine global object, without any globally visible modifications that might have been made β it's as if you were executing code in a completely new browser tab or interpreter process ("realm"). "Isolated" means that any modifications you make inside the ShadowRealm environment aren't visible to code outside of it. "Synchronous" means you don't have to `await` the result.
```javascript
-const red = new ShadowRealm();
-
-// realms can import modules that will execute within its own environment.
-// When the module is resolved, it captured the binding value, or creates a new
-// wrapped function that is connected to the callable binding.
-const redAdd = await red.importValue('./inside-code.js', 'add');
-
-// redAdd is a wrapped function exotic object that chains its call to the
-// respective imported binding.
-let result = redAdd(2, 3);
-
-console.assert(result === 5); // yields true
+globalThis.someValue = 2;
-// The evaluate method can provide quick code evaluation within the constructed
-// shadowRealm without requiring any module loading, while it still requires CSP
-// relaxing.
-globalThis.someValue = 1;
-red.evaluate('globalThis.someValue = 2'); // Affects only the ShadowRealm's global
-console.assert(globalThis.someValue === 1);
+const realm = new ShadowRealm();
-// The wrapped functions can also wrap other functions the other way around.
-const setUniqueValue =
- await red.importValue('./inside-code.js', 'setUniqueValue');
+console.log(globalThis.someValue);
+ // logs "2", as you might expect
+console.log(realm.evaluate(`globalThis.someValue`));
+ // logs "undefined": the global modification is not
+ // visible inside the fresh environment
-/* setUniqueValue = (cb) => (cb(globalThis.someValue) * 2); */
+realm.evaluate(`globalThis.someValue = 3;`);
-result = setUniqueValue((x) => x ** 3);
-
-console.assert(result === 16); // yields true
+console.log(globalThis.someValue);
+ // logs "2": the ShadowRealm's environment is isolated,
+ // so the global modification does not affect the
+ // environment outside of it
+console.log(realm.evaluate(`globalThis.someValue`));
+ // logs "3", as you might expect
```
-## Motivations
-
-It's quite common for applications to contain programs from multiple sources, whether from different teams, vendors, package managers, etc., or just programs with different set of requirements from the environment.
-
-These programs must currently contend for the global shared resources, specifically, the shared global object, and the side effect of executing those programs are often hard to observe, causing conflicts between the different programs, and potentially affecting the integrity of the app itself.
-
-Attempting to solve these problems with existing DOM APIs will require implementing an asynchronous communication protocol, which is often a deal-breaker for many use cases. It usually just adds complexity for cases where a same-process Realm is sufficient. It's also very important that values can be immediately shared. Other communications require data to be serialized before it's sent back and forth.
-
-__The primary goal of this proposal is to provide a proper mechanism to control the execution of a program, providing a new global object, a new set of intrinsics, no access to objects cross-realms, a separate module graph and synchronous communication between both realms__.
-
-In addition to the motivations given above, another commonly-cited motivation is virtualization and portability. Some of the functionalities of the VM module in Node can also be standardized, providing the infrastructure for virtualization of JavaScript programs in all environments.
-
-Finally, a distinct but related problem this proposal could solve is the current inability to completely virtualize the environment where the program should be executed. With this proposal, we are taking a giant step toward that missing feature of the language.
-
-A ShadowRealm-like API is an often-requested feature from developers, directly or indirectly. It was an original part of the ES6 spec, but it didn't make to the initial cut. This proposal attempts to resolve prior objections and get to a solution that all implementers can agree upon.
-
-## Non-goals
-
-This proposal does not aim to provide host hooks, or any other mechanism to control or prevent IO operations from within the ShadowRealm instance.
-
-The ShadowRealm proposal does not aim to provide availability protection as it is designed to share the same thread to allow synchronous communication between Realms.
-
-It does not provide full protection for confidentiality, as such, a ShadowRealm instance initially provides access to APIs that can be used to infer information and sense the timing from the environment in various ways.
+To prevent information leakage, only **primitive values** and **callable objects** can pass in and out of a ShadowRealm. Non-callable objects are forbidden. Although callables are also objects, when they pass through the ShadowRealm boundary they are wrapped in another object that allows calling them but hides their properties.
-### How does a ShadowRealm operate?
-
-A ShadowRealm executes code with the same JavaScript heap as the surrounding context where the ShadowRealm instance is created. Code runs synchronously in the same thread. Note: The surrounding context is often referenced as the _incubator realm_ within this proposal.
-
-Same-origin iframes also create a new global object which is synchronously accessible. A ShadowRealm differs from same-origin iframes by omitting Web APIs such as the DOM, and async config for code injected through dynamic imports. Problems related to identity discontinuity exist in iframes but are not a possibility in a ShadowRealm as object values are not transferred cross-realms in user land. The only connection exists internally through wrapped functions.
-
-Sites like Salesforce.com make extensive use of same-origin iframes to create such global objects. Our experience with same-origin iframes motivated us to steer this proposal forward, which has the following advantages:
-
-- Frameworks would be able to better craft the available API within the global object of the ShadowRealm, aiming for what is necessary to evaluate the program.
-- Tailoring up [the exposed set of APIs into the code](#VirtualizedEnvironment) within the ShadowRealm provides a better developer experience for a less expensive work compared to tailoring down a full set of exposed APIs - e.g. iframes - that includes handling presence of `[LegacyUnforgeable]` attributes like `window.top`.
-- We hope the resources used for a ShadowRealm will be somewhat lighter weight (both in terms of memory and CPU) for the browser when compared to an iframe, especially when frameworks rely on several Realms in the same application.
-- A ShadowRealm is not accessible by traversing the DOM of the incubator realm. This will be an ideal and/or better approach compared to attaching iframes elements and their contentWindow to the DOM. [Detaching iframes](#Iframes) would also add new set of problems.
-- A newly created shadowRealm does not have immediate access to any object from the incubator realm - and vice-versa - and won't have access to `window.top` as iframes would.
+```javascript
+const realm = new ShadowRealm();
+
+console.log(realm.evaluate(`42`));
+ // logs "42", because 42 is a primitive
+realm.evaluate(`{}`);
+ // throws an exception; you can't access the ShadowRealm's
+ // object from the original realm, even an empty object
+
+const logValueFromOuterRealm = realm.evaluate(`
+function logValueFromOuterRealm(value) {
+ console.log(value);
+}`);
+// logValueFromOuterRealm is callable, so returning it as
+// the result of `evaluate` to the outer realm gives us a
+// callable object
+
+logValueFromOuterRealm('I like cats');
+ // logs "I like cats", because strings are primitives
+logValueFromOuterRealm({});
+ // throws an exception; you can't access the outer realm's
+ // object from inside the ShadowRealm, even an empty object,
+ // even as a function argument
+
+realm.evaluate(`logValueFromOuterRealm.sideChannel = true;`);
+
+realm.evaluate(`console.log(logValueFromOuterRealm.sideChannel);`);
+ // logs "true", because logValueFromOuterRealm now has an
+ // extra property.
+console.log(logValueFromOuterRealm.sideChannel);
+ // logs "undefined": the extra property can't be observed
+ // or accessed in the outer realm, even though it's present
+ // in the ShadowRealm
+```
-The ShadowRealm API is complementary to stronger isolation APIs such as Workers and cross-origin iframes. The API is useful for contexts where synchronous execution is an essential requirement, e.g., emulating the DOM for integration with third-party code. A ShadowRealm instance can avoid often-prohibitive serialization overhead by using a common heap as the surrounding context.
+And that's the basic idea of ShadowRealm!
-The ShadowRealm API does __not__ introduce a new evaluation mechanism. The code evaluation is subject to the [same restrictions of the incubator realm via CSP](#Evaluation), or any other restriction in Node.
+## Why would I want this?
-JavaScript modules are associated with a global object and set of built-ins. Each ShadowRealm instance contains its own separate module graph which runs in the context of that ShadowRealm, so that a full JavaScript development experience is available.
+Web applications grow bigger and more customizable all the time.
-## Clarifications
+Applications that allow themselves to be customized via plugins always have to deal with the problem of badly-behaved plugins that reach into places where they're not supposed to and break other code. In JavaScript most built-in stuff is overwritable, so it's a common problem. When application writers have a way of segmenting off and isolating code they don't control, they can deliver a more stable experience to users.
-### Terminology
+Applications such as JavaScript programming and teaching environments have this same need, but more directly. Users' code should not be able to bring down the application, but also things defined in the application code should not be accessible from users' code.
-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, where they can be pulled out from their parent DOM tree.
+Finally, some applications have use for a simulated environment: for example, the [JSDOM](https://github.com/jsdom/jsdom) library builds an environment with a fake DOM, for doing HTML document manipulation on the server. JSDOM is specific to Node.js. With ShadowRealm you can build the same thing in a cross-platform way. This allows more secure scraping or processing such as that done in [Firefox Reader View](https://github.com/mozilla/readability).
-This proposal is limited to the semantics specified by ECMA-262 with no extra requirements from the web counterparts.
+There are currently other ways to accomplish approximately the same thing as ShadowRealm, but they are either not synchronous, or not isolated, or not cross-platform. ShadowRealm allows developers to implement this kind of thing with just less hassle and potential for mistakes. That's good for users, who then face fewer security bugs.
-### The ShadowRealm's Global Object
+See the example "Isolating dependencies from one another" in the use cases section, which compares ShadowRealm with other means of accomplishing the same thing.
-Each ShadowRealm's [Global Object](https://tc39.es/ecma262/#sec-ordinary-object) is an [Ordinary Object](https://tc39.es/ecma262/#sec-ordinary-object). It does not require exotic internals or new primitives.
+## What _isn't_ ShadowRealm for?
-Instances of ShadowRealm Objects and their Global Objects have their lifeline to their incubator Realm, they are not _detachable_ from it. Instead, they work as a group, sharing the settings of their incubator Realm. In other words, they act as encapsulation boundaries, they are analogous to a closure or a private field.
+ShadowRealm is sometimes called a "sandbox", but people might have conflicting expectations of the term "sandbox". ShadowRealm gives you **integrity protection**, in that you have complete control over what objects exist in the ShadowRealm, and you have complete control over how code in the ShadowRealm is allowed to affect the outer realm.
-
+However, it does not give you **availability protection**. ShadowRealms share a process and a thread with the outer realm. That's why synchronous communication is possible. But that also means it's possible to freeze up the outer realm or use all of its memory. It's as simple as this:
+```javascript
+realm.evaluate(`while (true) {}`);
+```
-### Evaluation
+ShadowRealm also does not give you **confidentiality protection**. In other words, it won't protect you from timing attacks such as Spectre, and it's possible to deduce fingerprinting information from inside a ShadowRealm. For example, `Intl.supportedValuesOf('timeZone')` gives you fingerprinting information about what version of the time zone database is used by the JavaScript engine.
-Any code evaluation mechanism in this API is subject to the existing [Content-Security-Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy).
+You _can_ use ShadowRealm as a building block for an environment that _does_ protect confidentiality, by deleting APIs such as `Intl.supportedValuesOf` immediately after creating the ShadowRealm, but that's at your own risk.
-If the CSP directive from a page disallows `unsafe-eval`, it prevents synchronous evaluation in the ShadowRealm, i.e.: `ShadowRealm#evaluate`.
+Here's a table showing which tool offers which security protections. "π" means it can be done, but is not by default.
-The CSP of a page can also set directives like the `default-src` to prevent a ShadowRealm from using `ShadowRealm#importValue()`.
+| **Tool** π Protects π | **Integrity** | **Availability** | **Confidentiality** |
+| --- | --- | --- | --- |
+| `node:vm` module | β
| β | π |
+| iframe | β | β | β |
+| cross-origin iframe | β
| π | π |
+| Worker | β
| π | π |
+| **ShadowRealm** | β
| β | π |
-### Module Graph
+## Proposed API
-Each instance of a ShadowRealm must have its own Module Graph.
+The API surface is tiny. Just three functions! The API is a low-level one, intended to be used as a building block for more complex functionality.
+### Constructor
```javascript
-const shadowRealm = new ShadowRealm();
-
-// imports code that executes within its own environment.
-const doSomething = await shadowRealm.importValue('./file.js', 'redDoSomething');
-
-// This call chains to the shadowRealm's redDoSomething
-doSomething();
+realm = new ShadowRealm();
```
+Creates a fresh ShadowRealm object. It's always separate from any other realms, even other ShadowRealms that have already been created.
-### Compartments
-
-This proposal does not define any virtualization mechanism for host behavior. Therefore, it distinguishes itself from the current existing [Compartments](https://github.com/tc39/proposal-compartments) proposal.
-
-A new [Compartment](https://github.com/tc39/proposal-compartments) provides a new Realm constructor. A Realm object from a Compartment is subject to the Compartment's host virtualization mechanism.
-
+### evaluate
```javascript
-const compartment = new Compartment(options);
-const VirtualizedRealm = compartment.globalThis.ShadowRealm;
-const shadowRealm = new VirtualizedRealm();
-const doSomething = await shadowRealm.importValue('./file.js', 'redDoSomething');
+result = realm.evaluate(code);
```
+Executes `code`, a string containing a JavaScript expression, inside the ShadowRealm, and returns the result if it is a primitive. If the result is a callable object, returns a wrapper object that allows calling, but hides any properties.
-The Compartments proposal offers a more complex API that offers tailoring over aspects beyond the global APIs but with modifications to internal structure such as module graph. The ShadowRealm API just offers immediate access to what is already specified in ECMAScript as it's already structured to distinguish different references from realms.
-
-### Why not separate processes?
-
-Creating a Realm that runs in a separate process is another alternative, while allowing users to define and create their own protocol of communication between these processes.
-
-This alternative was discarded for two main reasons:
-
-1. There are existing mechanism to achieve this today in both browsers, and nodejs. E.g.: cross-origin iframes, workers, etc. They seem to be good enough when asynchronous communication is sufficient to implement the feature.
-2. Asynchronous communication is a deal-breaker for many use-cases, specifically when security is __not__ an issue, and sometimes it just added complexity for cases where a same-process Realm is sufficient.
+If the result is a non-callable object, throws a TypeError. If executing `code` causes an exception, it throws a fresh TypeError so as not to expose the ShadowRealm's exception object. See the [errors explainer](./errors.md) for more information.
-E.g. Google AMP run in a cross-origin iframe, and just want more control about what code they executed in that cross-origin application.
-
-There are some identified challenges explained within the current use cases for a ShadowRealm such as the [WorkerDOM Virtualization challenge for Google AMP](#DOMVirtualization) and the current use of [JSDOM and Node VM modules](#JSDOMvmModules) that would be better placed using an interoperable ShadowRealm API as presented by this proposal.
-
-## Security
-
-It is useful to look at this from the lenses of [the taxonomy of security essay](https://agoric.com/blog/all/taxonomy-of-security-issues/), which formalizes a framework to explain security and modularity issues in various systems.
-
-Based on the essay linked above, we can say that the ShadowRealm proposal provides a very limited protection:
-
-> β
integrity βοΈ availability β οΈ confidentiality
-
-### β
Integrity
-
-This proposal can be a good complement to integrity mechanisms by providing ways to evaluate code across different object graphs (different global objects) while maintaining the integrity of both realms. The integrity guarantee of the ShadowRealm API only extends to code that might inadvertently step on each other's feet (e.g. writing to the same global variable).
-
-A concrete example of this is the Google's AMP current mechanism:
-
-* Google News App creates multiples sub-apps that can be presented to the user.
-* Each sub-app runs in a cross-origin iframe (communicating with the main app via post-message).
-* Each vendor (one per app) can attempt to enhance their sub-app that display their content by executing their code in a ShadowRealm that provide access to a well defined set of APIs to preserve the integrity of the sub-app.
-
-### βοΈ Availability Protection
-
-A ShadowRealm shares the same process with its incubator Realm. While direct cross-realm object access is prevented via the callable boundary, the ShadowRealm API was design to share a heap and thus a process. This is what allows the synchronous communication between the incubator realm and the ShadowRealm instance. This means all those resources are shared, preventing the ShadowRealm or the incubator realm from providing any guarantees in terms of liveness or progress. In other words, code running in a ShadowRealm can produce resource exhaustion, or excessive allocation of memory that can prevent the incubator realm from proceeding.
-
-A concrete example of this is plugin system to implement heavy matrix computations:
-
-* Each plugin can receive that, and carry on computation task.
-* The task to compute can be implemented in an asynchronous manner due to the nature of the computation.
-* The main UI thread will remain block during the heavy computation even though the result is expected to be produced asynchronously.
-
-
-### β οΈ Confidentiality Protection
-
-Confidentiality, also known as Information Hiding or Secrecy, cannot be fully guaranteed by the ShadowRealm API. On the Web, two good examples of confidentiality violations are fingerprinting, and privacy violations.
-
-To provide Confidentiality, no one can infer information they are not supposed to know. The most pernicious threats to confidentiality are side channels like Meltdown and [Spectre](https://leaky.page/), where code running inside a ShadowRealm can infer another realmβs secrets from timing differences. The APIs available in a ShadowRealm may also be used to infer information about the environment of the user, which is commonly known as fingerprinting.
-
-The ShadowRealm API can however be used as a building block towards providing confidentiality protections, for example when combined with inescapable mechanisms that prevent the measurement of duration, and remove fingerprinting surfaces.
-
-## Use Cases
+### importValue
+```javascript
+exportValue = await realm.importValue(moduleSpecifier, exportName);
+```
-These are some of the key use cases where The ShadowRealm API becomes very useful and important:
+Imports `moduleSpecifier` into the ShadowRealm environment (as if `await import(moduleSpecifier)` were executed) and gets an export named `exportName` from that module. Just as in `evaluate()`, if the export is a primitive or callable value, it is returned or wrapped, respectively, and otherwise a TypeError is thrown.
-- Third Party Scripts
-- Code Testing
-- Codebase segmentation
-- Template libraries
-- DOM Virtualization
+Each ShadowRealm has its own module graph, meaning that imports are not shared between realms.
-### Third Party Scripts
+## Use cases
-We acknowledge that applications need a quick and simple execution of code. There are cases where **many** scripts are executed for the same application. There isn't a need for a new host or agent. This is also not aiming to defend against malicious code or xss injections. Our focus is on multi-libraries and building blocks from different authors that can conflict with each other.
+### Example: Isolating dependencies from one another
-The ShadowRealm API provides integrity preserving semantics - including built-ins - of root and incubator Realms, setting specific boundaries for the Environment Records.
+Say you have a large codebase with dependencies that conflict with each other. Maybe they require incompatible versions of a common dependency, or maybe one dependency [modifies a built-in prototype in a way that breaks another dependency](https://developer.chrome.com/blog/smooshgate). Maybe your product is an app that may be customized by each customer with plugins that they write; of course you can't guarantee the code quality of these plugins, and the main codebase needs to be robust against that. Or maybe you're just part of a large organization and you want to limit the damage that miscommunications between departments can wreak in production.
-Third Party Scripts can be executed in a non-blocking asynchronous evaluation through the `ShadowRealm#importValue()`.
+As a developer of such a codebase, you can use ShadowRealm to segment off the potentially badly-behaved dependencies. The end user of this codebase benefits by having a more stable experience while still being able to load whatever weird plugins they want.
-There is no need for immediate access to the application globals - e.g. `window`, `document`. This comes as a convenience for the application that can provide - or not - values and API in different ways. This also creates several opportunities for customization with the ShadowRealm Globals and prevent collision with other global values and other third party scripts.
+As a contrived example, let's consider a fictitious dependency, `sketchy-product.js`, that calculates the result of multiplying numbers together:
```javascript
-const shadowRealm = new ShadowRealm();
-
-// pluginFramework and pluginScript become available in the ShadowRealm
-const [ init, ready ] = await Promise.all([
- shadowRealm.importValue('./pluginFramework.js', 'init'),
- shadowRealm.importValue('./pluginScript.js', 'ready'),
-]);
+// My version is leeter than built-in reduce???
+Array.prototype.reduce = function (callback, accumulator = 1) {
+ for (let index = 0; index < this.length; index++) {
+ accumulator = callback(accumulator, this[index]);
+ }
+ return accumulator;
+}
-// The Plugin Script will execute within the ShadowRealm
-init(ready);
+// Calculate the product of the numbers passed as arguments
+function product(...multiplicands) {
+ return multiplicands.reduce((a, b) => a * b);
+}
+export default product;
```
-### Code Testing
-
-While multi-threading is useful for testing, the layering enabled from the ShadowRealm API is also great. Test frameworks can use a ShadowRealm to inject code and also control the order of the injections if necessary.
-
-Testing code can run autonomously within the boundaries set from the ShadowRealm object without immediately conflicting with other tests.
+(What's sketchy about it? `product()` gives perfectly fine results, but it overwrites the global `Array.prototype.reduce` with a broken version! This will break most other code that calls `reduce` without the second argument, including the common idiom `reduce((a, b) => a + b)` to calculate a sum. Bonus if you spotted that it also doesn't call the callback with the index and array as 3rd and 4th argument.)
-#### Running tests in a ShadowRealm
+You need to use this dependency in your codebase, but it breaks large portions of the rest of your code. Here's how to isolate it with ShadowRealm:
```javascript
-import { test } from 'testFramework';
-const shadowRealm = new ShadowRealm();
+const realm = new ShadowRealm();
+const safeProduct = await realm.importValue('./sketchy-product.js', 'default');
-const [ runTests, getReportString, suite ] = await Promise.all([
- shadowRealm.importValue('testFramework', 'runTests'),
- shadowRealm.importValue('testFramework', 'getReportString'),
- shadowRealm.importValue('./my-tests.js', 'suite'),
-]);
+// Does it still work?
+console.assert(safeProduct(1, 2, 3, 4) === 24);
-// start tests execution
-runTests(suite);
-
-// request a tap formatted string of the test results when they are ready
-getReportString('tap', res => console.log(res));
+// Is it really safe?
+console.assert([1, 2, 3, 4].reduce((a, b) => a + b) === 10);
+// phew!
```
-### Codebase segmentation
-
-A big codebase tend to evolve slowly and soon becomes legacy code. Old code vs new code is a constant struggle for developers.
-
-Modifying code to resolve a conflict (e.g.: global variables) is non-trivial, specially in big codebases.
-
-The ShadowRealm API can provide a _lightweight_ mechanism to preserve the integrity of the intrinsics. Therefore, it could isolate libraries or logical pieces of the codebase per ShadowRealm.
-
-### DOM Virtualization
-
-We still want things to interact with the DOM without spending any excessive amount of resources.
-
-It is important for applications to emulate the DOM as best as possible. Requiring authors to change their code to run in our virtualized environment is difficult. Specially if they are using third party libraries.
+Let's compare some of the alternatives that exist today, without ShadowRealm. First off, in Node.js you could use the VM module.
```javascript
-const shadowRealm = new ShadowRealm();
-
-const initVirtualDocument = await shadowRealm.importValue('virtual-document', 'init');
-await shadowRealm.importValue('./publisher-amin.js', 'symbolId');
-
-init();
-```
-
-#### DOM Virtualization: AMP WorkerDOM Challenge
-
-Problem: `Element.getBoundingClientRect()` doesn't work over async comm channels (i.e. [worker-dom](https://github.com/ampproject/worker-dom)).
-
-
+// Run with --experimental-vm-modules
+import assert from 'node:assert';
+import fs from 'node:fs/promises';
+import vm from 'node:vm';
+
+const sourceText = await fs.readFile('sketchy-product.js', { encoding: 'utf-8' });
+
+const context = vm.createContext({});
+const isolatedModule = new vm.SourceTextModule(sourceText, { context });
+await isolatedModule.link(() => {
+ // This callback can be empty because sketchy-product doesn't
+ // have any other dependencies that need to be linked
+});
+await isolatedModule.evaluate();
-The communication is also limited by serialization aspects of [transferable objects](https://html.spec.whatwg.org/multipage/structured-data.html#transferable-objects), e.g.: functions or Proxy objects are not _transferable_.
+const safeProduct = isolatedModule.namespace.default;
-#### JSDOM + vm Modules
+// Does it still work?
+assert.equal(safeProduct(1, 2, 3, 4), 24);
-JSDOM [relies on VM](https://github.com/jsdom/jsdom/blob/0b1f84f499a0b23fad054228b34412869f940765/lib/jsdom/living/nodes/HTMLScriptElement-impl.js#L221-L248) functionality to emulate the __HTMLScriptElement__ and maintains a [shim of the vm module](https://github.com/jsdom/jsdom/blob/bfe7de63d6b1841053d572a915b2ff06bd4357b9/lib/jsdom/vm-shim.js) when it is bundled to run in a webpage where it doesnβt have access to the Node's __vm__ module.
+// Is it really safe?
+assert.equal([1, 2, 3, 4].reduce((a, b) => a + b), 10);
+// phew!
+```
-The ShadowRealm API provides a single API for this virtualization in both browsers and NodeJS.
+Node.js's VM module is just as good as ShadowRealm for this use case, and in some ways is a more powerful API that allows influencing more parts of the module import process. In other ways, it is less safe because you have to avoid passing objects into the realm that might leak references to the main realm's global object.
-### Virtualized Environment
+Unfortunately, `node:vm` is specific to Node.js (although Deno provides it as well), and still experimental. Blink provides an Isolate API which is similar but runs in another thread, but it is not available in JS userspace.
-The usage of different realms allow customized access to the global environment. To start, The global object could be immediately frozen.
+In a browser, you could use an iframe. Here's one way to do that:
```javascript
-const shadowRealm = new ShadowRealm();
-
-shadowRealm.evaluate('Object.freeze(globalThis), 0');
+const iframe = document.createElement('iframe');
-// or without CSP relaxing:
+// We must attach the iframe to the active browsing context,
+// or import() will be blocked
+const body = document.getElementsByTagName('body')[0];
+body.append(iframe);
-const freezeRealmGlobal = await shadowRealm.importValue('./inside-code.js', 'reflectFreezeRealmGlobal');
+const realm = iframe.contentWindow;
+const imported = await realm.eval(`import('./sketchy-product.js')`);
+const safeProduct = imported.default;
+iframe.remove();
+// It's OK to detach the iframe now that we have imported
+// the function, because there are no further imports.
-/**
- * inside-code.js
- *
- * export function reflectFreezeRealmGlobal() {
- * try {
- * Object.freeze(globalThis);
- * catch {
- * return false;
- * }
- * return true;
- * }
- **/
+// Does it still work?
+console.assert(safeProduct(1, 2, 3, 4) === 24);
-freezeRealmGlobal();
+// Is it really safe?
+console.assert([1, 2, 3, 4].reduce((a, b) => a + b) === 10);
+// phew!
```
-In web browsers, this is currently not possible. The way to get manage new ShadowRealms would be through iframes, but they also share a window proxy object.
+This example imports the module into a separate realm just like ShadowRealm. But that realm isn't isolated like a ShadowRealm is. Unlike ShadowRealm where nothing is accessible between realms by default, with iframes _everything_ is accessible by default. In the above example we can still just reach into the iframe realm and manipulate its objects: in fact that's what we're doing with `realm.eval`, we're literally grabbing the iframe realm's global `eval` function and executing it.
+Suppose `sketchy-product.js` shipped a new version that popped up a message:
```javascript
-const iframe = document.createElement('iframe');
-document.body.appendChild(iframe);
-const rGlobal = iframe.contentWindow; // same as iframe.contentWindow.globalThis
-
-Object.freeze(rGlobal); // TypeError, cannot freeze window proxy
+alert('If you liked this multiplication, please follow my SoundCloud');
```
+This would just pop up an intrusive message in the application! We'd have to guard against this by clearing all of the potentially dangerous stuff out of the iframe realm, _before_ loading the module, with things like `realm.alert = () => {}`. But some dangerous properties like [`window.top`](https://developer.mozilla.org/en-US/docs/Web/API/Window/top) can't be overwritten or deleted.
-The same iframe approach won't also have a direct access to import modules dynamically. The usage of `shadowRealm.importValue` is possible instead of roughly using eval functions or setting _script type module_ in the iframe, if available.
+To solve this problem, you can lock down an iframe using the `sandbox` attribute. This allows you to remove certain permissions from the iframe realm. You can treat it as cross-origin (i.e., originating from a different website and therefore severely limited in permissions.)
-#### DOM mocking
-
-The ShadowRealm API allows a much smarter approach for DOM mocking, where the globalThis can be setup in userland:
+Here's an example using a cross-origin iframe. It's built on the previous example, but is much more complicated.
```javascript
-const shadowRealm = new ShadowRealm();
-
-const installFakeDOM = await shadowRealm.importValue('./fakedom.js', 'default');
-
-// Custom properties can be added to the ShadowRealm
-installFakeDOM();
-```
-
-This code allows a customized set of properties to each new ShadowRealm - e.g. `document` - and avoid issues on handling immutable accessors/properties from the Window proxy. e.g.: `window.top`, `window.location`, etc..
-
-This explainer document speculates a `installFakeDOM` API to set up a proper frame emulation. We understand there might be many ways to explore how to emulate frames with plenty of room for improvement, as seen in [some previous discussions](https://github.com/tc39/proposal-shadowrealm/issues/268#issuecomment-674338593), as in the following pseudo-code:
+const iframe = document.createElement('iframe');
+iframe.setAttribute('sandbox', 'allow-scripts');
+iframe.setAttribute('style', 'visibility: hidden;');
+const permissions = [
+ 'camera', 'display-capture', 'fullscreen', 'gamepad', 'geolocation',
+ // ...etc. (All permissions, even those not yet supported, should be
+ // listed here, as the default is to allow all.)
+];
+iframe.setAttribute('allow',
+ permissions.map((feature) => `${feature} 'none'`).join('; '));
+
+const iframeHandshake = new Promise((resolve, reject) => {
+ window.addEventListener('message', ({ origin, source, data }) => {
+ try {
+ if (origin !== 'null') throw new Error(`unexpected origin ${origin}`);
+ if (source !== iframe.contentWindow) throw new Error('wrong source');
+ if (data !== 'handshake') throw new Error('unexpected handshake message');
+ resolve();
+ } catch (error) {
+ reject(error);
+ }
+ }, { once: true });
+});
-```javascript
-export default function() {
- const someRealmIntrinsicsNeededForWrappers = extractIntrinsicsFromGlobal(customGlobalThis);
- Object.defineProperties(globalThis, {
- document: createFakeDocumentDescriptor(someRealmIntrinsicsNeededForWrappers),
- Element: createFakeElementDescriptor(someRealmIntrinsicsNeededForWrappers),
- Node: ...
- ... // all necessary DOM related globals should be defined here
+// We must attach the iframe to the active browsing context, or it
+// won't load
+const body = document.getElementsByTagName('body')[0];
+body.append(iframe);
+
+// Load the isolated realm in the iframe and import the module we
+// want to isolate
+iframe.srcdoc = `
+
+
+
+
+
+
+
+`;
+await iframeHandshake;
+const realm = iframe.contentWindow;
+
+function safeProduct(...multiplicands) {
+ return new Promise((resolve, reject) => {
+ globalThis.addEventListener('message', ({ origin, source, data }) => {
+ try {
+ if (origin !== 'null') throw new Error(`unexpected origin ${origin}`);
+ if (source !== realm) throw new Error('wrong source');
+ const { operation, result, error } = data;
+ console.assert(operation === 'product');
+ resolve(result);
+ } catch (error) {
+ reject(error);
+ }
+ }, { once: true });
+
+ realm.postMessage({
+ operation: 'product',
+ operands: multiplicands,
+ }, { targetOrigin: '*' });
});
}
-function createFakeDocumentDescriptor(someIntrinsics) {
- return {
- enumerable: true,
- configurable: false,
- get: new someIntrinsics.Proxy(document, createHandlerWithDistortionsForDocument(someIntrinsics));
- };
-}
+// Does it still work?
+console.assert(await safeProduct(1, 2, 3, 4) === 24);
-function extractIntrinsicsFromGlobal(customGlobalThis) {
- return {
- Proxy,
- ObjectPrototype: Object.prototype,
- create: Object.create,
- ... // whatever you need to facilitate the creation of proper identities for the fake DOM
- };
-}
+// Is it really safe?
+console.assert([1, 2, 3, 4].reduce((a, b) => a + b) === 10);
+// phew!
```
-## Errors
-
-Errors originated from a ShadowRealm are subject to stack censoring. Similarly, those errors must be copied when crossing the callable boundary, while doing so, the host must produce a TypeError, and may provide a message and stack properties without violating the stack censoring principle. For more details about errors, refer to the [errors explainer](./errors.md).
-
-## Modules
-
-In principle, the ShadowRealm proposal does not provide the controls for the module graphs. Every new ShadowRealm initializes its own module graph, while any invocation to `ShadowRealm.prototype.importValue()` method, or by using `import()` when evaluating code inside the shadowRealm through wrapped functions, will populate this module graph. This is analogous to same-domain iframes, and VM in nodejs.
-
-However, the [Compartments](https://github.com/tc39/proposal-compartments) proposal plans to provide the low level hooks to control the module graph per ShadowRealm. This is one of the intersection semantics between the two proposals.
+Synchronously executing the iframe realm's `eval` function, as we did in the original iframe example, is no longer allowed. A cross-origin iframe realm can only communicate asynchronously, via `postMessage`. So now a simple synchronous operation has become asynchronous, which is a strong drawback! You have to use `await` to call the function, and it means that everything using it has to become asynchronous as well.
+It is also, as you can see, much more involved to set up asynchronous communication between the main realm and an iframe realm. The above example can't be run from a local file, it requires an HTTP server that serves `sketchy-product.js` with the `Access-Control-Allow-Origin: null` header. Additionally, it's easy to make a mistake that results in the setup being insecure, such as forgetting to check the message source.
-### Example: Virtualized Contexts
-
-Importing modules allow us to run asynchronous executions with set boundaries for access to global environment contexts.
-
-- `main.js`:
+None of the above solutions are available both in server runtimes and in the browser. ShadowRealm makes it possible to do this in a portable way. However, for an almost-cross-platform solution without ShadowRealm, you could use a Worker. Here's an example.
+**main.js:**
```javascript
-globalThis.blueValue = "a global value";
-
-const r = new ShadowRealm();
-
-r.importValue("./sandbox.js", "test").then(test => {
-
- // globals in the incubator shadowRealm are not leaked to the constructed shadowRealm
- test("blueValue"); // undefined
- test("redValue"); // 42
+const worker = new Worker('adaptor.js', {
+ type: 'module',
+ name: 'sketchy dependency adaptor',
});
-```
-- `sandbox.js`:
+function safeProduct(...multiplicands) {
+ return new Promise((resolve, reject) => {
+ worker.addEventListener('message', (event) => {
+ const { operation, result, error } = event.data;
+ console.assert(operation === 'product');
+ error ? reject(error) : resolve(result);
+ }, { once: true });
+
+ worker.postMessage({
+ operation: 'product',
+ operands: multiplicands,
+ });
+ });
+}
-```javascript
-// blueValue is not available as a global name here
+// Does it still work?
+console.assert(await safeProduct(1, 2, 3, 4) === 24);
-// Names here are not leaked to the incubator shadowRealm
-globalThis.redValue = 42;
+// Is it really safe?
+console.assert([1, 2, 3, 4].reduce((a, b) => a + b) === 10);
+// phew!
-export function test(property) {
- return globalThis[property];
-}
+worker.terminate();
```
-## Example: iframe vs ShadowRealm
-
-If you're using anonymous iframe today to "evaluate" javascript code in a different realm, you can replace it with a new ShadowRealm, as a more performant option, without identity discontinuity, e.g.:
-
+**adaptor.js:**
```javascript
-const globalOne = window;
-let iframe = document.createElement('iframe');
-document.body.appendChild(iframe);
-const iframeArray = iframe.contentWindow.Array;
+import product from './sketchy-product.js';
+
+addEventListener('message', (event) => {
+ try {
+ const { operation, operands } = event.data;
+ console.assert(operation === 'product');
+ const result = product(...operands);
+ postMessage({ operation, result });
+ } catch (error) {
+ postMessage({ operation: 'product', error });
+ }
+});
+```
-console.assert(iframeArray !== Array);
+Some server runtimes, such as Deno and Bun, also support this. Node.js [does not](https://github.com/nodejs/node/issues/43583). This simple example could be changed to use Node.js's `worker_threads` module with minimal adaptation (replacing `addEventListener` with `on`/`once`, etc.) but that can [quickly get more complicated](https://github.com/developit/web-worker).
-const list = iframeArray('a', 'b', 'c');
+However, Workers still have an obvious drawback: like cross-origin iframes, they only allow asynchronous communication. It's not possible to communicate synchronously with a Worker, because the Worker is running in another thread. Also like cross-origin iframes, Workers bring a lot more overhead with them.
-list instanceof Array; // false
-[] instanceof iframeArray; // false
-Array.isArray(list); // true
-```
+Here's a table summarizing the advantages and disadvantages of each tool for isolating your dependencies:
-This code is **not** possible with the ShadowRealm API! Non-primitive values are not transfered cross-realms using the ShadowRealm API.
+| **Tool** π | **Cross-platform** | **Synchronous** | **Isolated** | **Convenient** |
+| --- | --- | --- | --- | --- |
+| `node:vm` module | β | β
| β
| β
|
+| iframe | β | β
| β | β
|
+| cross-origin iframe | β | β | β
| β |
+| Worker | π almost | β | β
| β
|
+| **ShadowRealm** | β
| β
| β
| β
|
-## Example: Node's vm objects vs ShadowRealms
+In a real-world example, things would be more complicated. Probably the badly-behaved dependency would need to deal with objects and not just numbers, so you'd need to design an interface between it and the rest of your code. You'd pass the interface's capabilities in to the ShadowRealm as callable functions.
-If you're using node's `vm` module today to "evaluate" javascript code in a different realm, you can replace some of its usage with a new ShadowRealm, e.g.:
+If the dependency needed to pass objects back and forth, you'd need to use a [membrane](https://github.com/ajvincent/es-membrane) [library](https://github.com/salesforce/near-membrane) to set up communication proxy objects on either side of the boundary. When we talk about using ShadowRealm as a building block for higher-level functionality, a membrane library is an example of higher-level functionality that can be built with ShadowRealm.
-```javascript
-const vm = require('vm');
-const script = new vm.Script(`
-function add(a, b) {
- return a + b;
-}
+### Example: Online code editor
-const x = add(1, 2);
-`);
-script.runInContext(new vm.createContext());
-```
+Here's another example: an online code editor, built using React, that is powered by ShadowRealm.
-will become:
+In this code editor, you can't do `alert()` or access `window.top` or overwrite builtins or anything else nefarious, the way you could if using `eval()` instead of ShadowRealm, and that's by default!
```javascript
-const shadowRealm = new ShadowRealm();
-
-const result = shadowRealm.evaluate(`
-function add(a, b) {
- return a + b;
+import React, { useState } from "react";
+
+export function App() {
+ const [code, setCode] = useState("");
+ const [result, setResult] = useState("");
+ const [logs, setLogs] = useState([]);
+
+ const runCode = () => {
+ // Create a new realm each time. Otherwise, the results
+ // from previous runs could influence this run. (That
+ // may be what you want, in which case you could reuse
+ // the same realm.)
+ const realm = new ShadowRealm();
+
+ // Setup a fake console.log() in the ShadowRealm that
+ // sends its results back to the main realm. Objects
+ // can't pass the boundary, so we convert the result to
+ // a string. This produces results like [object Object],
+ // so in a more sophisticated code editor you'd probably
+ // want to send some kind of serialized JSON object that
+ // the console could use to do rich formatting on the
+ // value.
+ const pendingLogs = [];
+ realm.evaluate(`
+ (appendLog) => {
+ globalThis.console = {
+ log(...args) {
+ args.forEach((arg) => appendLog(String(arg)));
+ },
+ };
+ }
+ `)((message) => pendingLogs.push(message));
+
+ // Execute the user's code inside the ShadowRealm. Same
+ // as the console window, we convert the result into a
+ // string.
+ try {
+ const val = realm.evaluate(`
+ (code) => {
+ try {
+ return String(eval(code));
+ } catch (error) {
+ return 'β οΈ ' + String(error);
+ }
+ }
+ `)(code);
+ setResult(val);
+ } catch (error) {
+ // Errors from the user's code are handled above. If
+ // we catch an error here, that's probably something
+ // wrong with our realm setup, so show it differently.
+ setResult('π΄ ' + error.toString());
+ }
+ setLogs(logs.concat(pendingLogs));
+ };
+
+ return (
+
+ );
}
-
-const x = add(1, 2);
-x;
-`);
```
-__Note__: these two are rough equivalents in functionality only. The `vm` API still has some extended capabilities and ergonomics, even if it fundamentally allow code execution in a different realm.
-
-## Status Quo
+### Example: Testing environment with controlled clock
-The current status quo is 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.
+Here's an example of ShadowRealm being used to create a controlled environment within which to run code. It maintains a [mock](https://en.wikipedia.org/wiki/Mock_object) system clock outside of the ShadowRealm, and ensures that APIs that access the system clock from inside the ShadowRealm (like `Date.now()`) use the mock clock instead.
-## Iframes
+(In reality, this example doesn't fix _everything_ to use the mock clock; you'd have to replace the `Date` constructor as well, `setInterval`/`clearInterval`, `AbortSignal.timeout()`, and `performance`, and you'd have to handle the same edge cases as the real APIs. This is all omitted for brevity.)
-Developers can technically already create a new ShadowRealm by creating a new same-domain iframe, but there are a few impediments to using this as a reliable mechanism:
-
-* the global object of the iframe is a window proxy, which implements a bizarre behavior, including its unforgeable proto chain.
-* 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.
-* The global `top` reference cannot be redefined 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 being a restriction to dynamic `import()` calls.
-* Exposure of cross-realms objects with identity discontinuity.
-
-### Detachable
-
-For clarifications, the term detachable means an iframe pulled out from the DOM tree:
+ShadowRealm is ideal for testing. Besides allowing you to set up the environment to work exactly as you want, it's also helpful that each ShadowRealm instance has its own module graph. That allows you to make sure that stateful dependencies are reset in between tests, for example.
```javascript
-var iframe = document.createElement("iframe");
-
- // attaching the iframe to the DOM tree
-document.body.appendChild(iframe);
-
-var iframeWindow = iframe.contentWindow;
-
-// Get accessor that returns the topmost window.
-iframeWindow.top; // Cannot be properly redefined/virtualized: { get: top(), set: undefined, enumerable: true, configurable: false }
-
-// **detaching** the iframe
-document.body.removeChild(iframe);
-
-// get accessor still exists, now returns null
-iframeWindow.top;
+class ClockControlledRealm extends ShadowRealm {
+ // Temporal.Instant representing the system time under our control
+ #systemTime;
+ // Monotonic clock consisting of elapsed Temporal.Duration
+ #elapsedMonotonic = new Temporal.Duration();
+ #pendingTimeouts = [];
+ #nextID = 0;
+
+ constructor(initialSystemTime = new Temporal.Instant(0n)) {
+ super();
+ this.#systemTime = Temporal.Instant.from(initialSystemTime);
+
+ // Set up the means of querying the system time. (Note, there are others
+ // as well not covered here, such as the Date constructor. This is just
+ // to illustrate how it would work.)
+ this.evaluate(`
+ (systemEpochNs) => {
+ Temporal.Now.instant = () => new Temporal.Instant(systemEpochNs());
+ Date.now = () => Number(systemEpochNs() / 1_000_000n);
+ }
+ `)(() => this.#systemTime.epochNanoseconds);
+
+ // Set up the timers. (Likewise, this is an illustration of how it would
+ // work and doesn't cover setInterval, AbortSignal.timeout, etc.)
+ this.evaluate(`
+ (set, clear) => {
+ globalThis.setTimeout = (f, delay = 0, ...args) => {
+ const callable = f.bind(globalThis, ...args);
+ return set(delay, callable);
+ };
+ globalThis.clearTimeout = clear;
+ }
+ `)((delayMs, callable) => {
+ // enqueue a timeout
+ const triggerTime = this.#elapsedMonotonic.add({ milliseconds: delayMs });
+ const id = this.#nextID++;
+ const index = this.#pendingTimeouts.findLastIndex((entry) =>
+ Temporal.Duration.compare(entry.triggerTime, triggerTime) <= 0);
+ this.#pendingTimeouts.splice(index + 1, 0, { id, triggerTime, callable });
+ return id;
+ }, (id) => {
+ // clear timeout with given ID
+ const index = this.#pendingTimeouts.findIndex((entry) => entry.id === id);
+ this.#pendingTimeouts.splice(index, 1);
+ });
+ }
+
+ setClock(newSystemTime) {
+ // Set the system clock to the new time. The monotonic clock doesn't change;
+ // timeouts will continue to execute after the requested time has elapsed.
+ this.#systemTime = Temporal.Instant.from(newSystemTime);
+ }
+
+ timePasses(duration) {
+ duration = Temporal.Duration.from(duration);
+
+ // Advance the system clock by the requested amout
+ this.#systemTime = this.#systemTime.add(duration);
+
+ // Also advance the monotonic clock by the requested amount. Balance the
+ // result up to hours
+ this.#elapsedMonotonic = this.#elapsedMonotonic
+ .add(duration)
+ .round({ largestUnit: 'hours' });
+
+ // Execute any pending timeouts that would have happened in the meantime
+ while (this.#pendingTimeouts[0] &&
+ Temporal.Duration.compare(
+ this.#pendingTimeouts[0].triggerTime,
+ this.#elapsedMonotonic
+ ) <= 0) {
+ const { callable } = this.#pendingTimeouts.shift();
+ callable();
+ }
+ }
+}
```
-## FAQ
-
-### So does the ShadowRealm API only have the ECMAScript APIs available?
-
-It creates a new copy of the built-ins from ECMAScript. Additionally, the host can add other APIs. We have open discussions about additional [HTML properties](https://github.com/tc39/proposal-shadowrealm/issues/284) or [some intrinsics subset](https://github.com/tc39/proposal-shadowrealm/issues/288).
-
-### Can I use the ShadowRealm API to run code securely?
-
-It depends on what kind of security protections are required. See the [Security](#Security) section for details.
-
-### Most libraries won't work unless they add dependencies manually
-
-> Doesn't this mean that most libraries won't work unless to add its dependencies manually. Like we could see people using this even to isolate WebAssembly code, thought that requires you adding the methods needed for that.
-
-Absolutely, this is equivalent to what happens to [Node VM](https://nodejs.org/api/vm.html) today as a low level API prior art. As a developer you need to setup the environment to execute code.
-
-Ideally the ShadowRealms would arrive a clean state, allowing tailoring for what is necessary to be added. This contrasts with the tailoring over unforgeables. e.g. `window.top`, `window.location`, etc
-
-Considering all the trade offs, the clean state seems the best option, in our opinion. It allows tailoring for multiple purposes and comprehends more use cases.
-
-### Exploration ahead
-
-There is more to explore ahead for the ShadowRealm proposal, but not yet for this current proposal. The current API is good enough to enable synchronous execution of code and membranes implementation, even if setup might require async import for code injection.
+You can run code, such as a test suite, inside this realm, and advance the time as needed. This is a feature in some test harnesses such as [Jasmine](https://jasmine.github.io/tutorials/async#using-the-mock-clock-to-avoid-writing-asynchronous-tests). It would otherwise need to be implemented by overwriting the main realm's clock.
+
+## Security implications
+
+Any code evaluation mechanism in this API is subject to the existing [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP). If the CSP directive from a page disallows `unsafe-eval`, it prevents synchronous evaluation in the ShadowRealm. That is, `evaluate()` won't work.
+
+On the other hand, `importValue()` doesn't require `unsafe-eval` β it's equivalent to a dynamic `import()` call. However, the CSP of a page can set directives like `default-src` to prevent a ShadowRealm from loading resources using `importValue()`.
+
+Without ShadowRealm, if you wanted to provide the same functionality as ShadowRealm in a cross-platform way, it'd require using `eval` and therefore you'd have to allow `unsafe-eval` in your website's CSP. With ShadowRealm, the main use case of isolating dependencies from each other becomes possible using only `importValue()` and therefore does not require a CSP with `unsafe-eval`.
+
+## Decision Record
+
+- ShadowRealm (then called [Realm](https://gist.github.com/dherman/7568885)) was part of the original ES6 spec, but didn't make the cut.
+- ShadowRealms execute code in the same thread and process, because there are already mechanisms to do this across threads and processes (e.g., Workers); and not all use cases require asynchronous communication.
+- ShadowRealm environments include all the built-ins defined in the ECMAScript specification, but hosts are allowed to add additional built-ins. Browser hosts will add built-ins with [an `[Exposed=*]` annotation in WebIDL](https://www.w3.org/TR/design-principles/#expose-everywhere). This is because developers shouldn't need to be aware that common "JavaScript-y" APIs like `TextEncoder` are technically not part of JavaScript from a standards perspective. (History: [#284](https://github.com/tc39/proposal-shadowrealm/issues/284), [#288](https://github.com/tc39/proposal-shadowrealm/issues/288), [#393](https://github.com/tc39/proposal-shadowrealm/issues/393).)
+- More past discussions can be found in the [proposal-shadowrealm issue tracker](https://github.com/tc39/proposal-shadowrealm/issues) and in TC39 plenary meeting notes:
+ - [February 2025](https://github.com/tc39/notes/blob/main/meetings/2025-02/february-18.md#shadowrealm-status-update)
+ - [December 2024](https://github.com/tc39/notes/blob/main/meetings/2024-12/december-02.md#shadowrealm-for-stage-3)
+ - [June 2024](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2024-06/june-12.md#shadowrealm-update)
+ - [February 2024](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2024-02/feb-7.md#shadowrealms-update)
+ - [November 2023](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2023-11/november-27.md#shadowrealm-stage-2-update)
+ - [September 2023](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2023-09/september-27.md#shadowrealm-implementer-feedback-and-demotion-to-stage-2)
+ - [November 2022](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2022-11/dec-01.md#shadowrealm)
+ - [September 2022](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2022-09/sep-13.md#shadowrealm-update)
+ - [June 2022](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2022-06/jun-06.md#shadowrealm-implementation-status-and-normate-updates)
+ - [March 2022](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2022-03/mar-29.md#shadowrealms-updates)
+ - [December 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-12/dec-14.md#shadowrealms-updates-and-potential-normative-changes)
+ - [August 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-08/aug-31.md#realms-renaming-bikeshedding-thread)
+ - [July 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-07/july-13.md#realms-for-stage-3) ([+ continuation](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-07/july-15.md#realms-for-stage-3-continued))
+ - [May 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-05/may-26.md#realms)
+ - [April 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-04/apr-21.md#isolated-realms-update)
+ - [January 2021](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2021-01/jan-26.md#realms-update)
+ - [November 2020](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2020-11/nov-17.md#realms-for-stage-3)
+ - [June 2020](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2020-06/june-4.md#realms-stage-2-update)
+ - [February 2020](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2020-02/february-5.md#update-on-realms)
+ - [July 2018](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2018-07/july-24.md#report-on-realms-shim-security-review)
+ - [May 2018](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2018-05/may-23.md#realms)
+ - [March 2018](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2018-03/mar-20.md#10ia-update-on-frozen-realms-in-light-of-meltdown-and-spectre)
+ - [March 2017](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2017-03/mar-23.md#10iic-realms-update)
+ - [January 2017](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2017-01/jan-26.md#13iid-seeking-stage-1-for-realms)
+ - [March 2016](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2016-03/march-30.md#draft-proposed-frozen-realm-api)
+ - [May 2015](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2015-05/may-29.md#fresh-realms-breakout)
+ - [June 2014](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2014-06/jun-4.md#47-removal-of-realms-api-from-es6-postponement-to-es7)
+ - [January 2014](https://github.com/tc39/notes/blob/21ff7b482a627bf86ea0981eac60ceb5924ed1f1/meetings/2014-01/jan-29.md#security-review-for-loadersrealms)