Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks
It’s gonna be tough to keep it together on this one. Okay. I got this. I am a professional technical writer. Straight face; all-business. Ahem: if you’ve been following the ongoing work at TC39 (the standards body responsible for maintaining and developing the standards that inform JavaScript) you may have encountered some of their recent work on ShadowRealms— snrk. Sorry! Sorry, I’m good! Just, whew — what a name, "ShadowRealms." Okay, hang on, let me start at the beginning. Maybe that will help.
It’s exceptionally likely you’ve seen JavaScript described as "single-threaded" at some point — that’s usually pretty high up on the list of JavaScript fundamentals, alongside "case sensitive," "whitespace insensitive," and "bad at math." That is correct, in the strict "computer science" sense, but it still gets my hackles up a little whenever I see it.
I mean, accurate in that JavaScript isn’t multi-threaded, for sure. A script is always executed in a very linear way — top to bottom, left to right, one execution context after another, winding up the call stack and then back down again. It’s just that you eventually come to learn about something like Web Workers, which — not to put too fine a point on this — allow you to execute JavaScript code in another thread. That’s where I think "JavaScript is single-threaded" becomes a less helpful framing, because even though JavaScript isn’t a multi-threaded language, a JavaScript application can make use of multiple threads.
It’s a better framing — and every bit as technically accurate — to say that a JavaScript realm is single-threaded. A realm refers to the environment where code is executed: a browser tab is a realm, and within that realm is the single thread where JavaScript is executed — the main thread . A Web Worker is a realm with a worker thread . JavaScript running in a cross-origin iframe is running in that iframe realm’s main thread. We can’t, for example, offload the execution of a single function to another thread — JavaScript is itself single-threaded, as a language. But a JavaScript application can span multiple realms and make use of multiple execution threads, and each of those realms can communicate with other realms in specific ways.
Each JavaScript realm has its own global environment. In a browser tab, the global object is the Window interface. The same is true in a non-same-origin iframe within that browser tab — the global object is the Window “owned" by that iframe:
( () => {<br>console.log( window.globalThis );<br>// Result: Window {}
console.log( theIframe.contentWindow.globalThis );<br>// Result: Window {}<br>})();
These aren’t the same global object:
( () => {<br>console.log( window.globalThis === theIframe.contentWindow.globalThis );<br>// Result: false<br>})();
The outer page and the inner iframe are two separate realms, both single-threaded, each with their own global objects and their own intrinsic objects:
(() => {<br>console.log( window.Array );<br>/* Result (expanded):<br>function Array()<br>from: function from()<br>fromAsync: function fromAsync()<br>isArray: function isArray()<br>length: 1<br>name: "Array"<br>of: function of()<br>prototype: Array []<br>Symbol(Symbol.species): undefined<br>: function ()<br>*/
console.log( theIframe.contentWindow.Array );<br>/* Result (expanded):<br>function Array()<br>from: function from()<br>fromAsync: function fromAsync()<br>isArray: function isArray()<br>length: 1<br>name: "Array"<br>of: function of()<br>prototype: Array []<br>Symbol(Symbol.species): undefined<br>: function ()<br>*/
console.log( window.Array === theIframe.contentWindow.Array );<br>// Result: false
})();
So, as you might expect, any global properties defined in the context of one realm will be unavailable to another:
function globalFunction() {};
console.log( window.globalFunction );<br>// Result: function globalFunction()
console.log( theIframe.contentWindow.globalFunction );<br>// Result: undefined
"Unavailable" — or, depending on how you look at it, unable to interfere with the the global object of another realm. If you’ve been JavaScripting for a while, you know that no matter how meticulous we are about managing scope, the global environment can get pretty messy despite our best efforts. Some of that is on us, sure — a stray variable binding happens to the best of us — but a lot of that clutter is a result of the early design decisions that went into the language itself, like the function declaration in the previous example. When you consider the staggering amount of JavaScript we don’t control that can get piled onto the average project — from frameworks to third-party helper libraries to polyfills to user analytics to advertisements — there’s potential for collisions, to say the least.
Given the global scope pollution that has haunted the language since time immemorial (the 90s), it isn’t hard to imagine the use cases for offloading code to a realm that can act as a sandbox for the execution of JavaScript we don’t want to impact, or be...