Skip to content

Commit

Permalink
blog: WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ArhanChaudhary committed Aug 17, 2024
1 parent 42268e0 commit 268071c
Showing 1 changed file with 12 additions and 10 deletions.
22 changes: 12 additions & 10 deletions src/content/blog/Implementing === in JavaScript from Scratch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ switch (obj1) {
new Set().add(obj1).has(obj2) && console.log("same");
```

We will implement this basic operation on a far lower level. But what does that mean, and how else could JavaScript differentiate two objects that hold the same data? [Surface level research](https://stackoverflow.com/questions/1997661/unique-object-identifier-in-javascript) yields nothing. Let's take a deep dive.
We will implement this basic operation on a far lower level. But what does that mean, and how else could JavaScript differentiate two objects that hold the same data? [Surface level research](https://stackoverflow.com/questions/1997661/unique-object-identifier-in-javascript) yields nothing, so we're on our own. Let's take a deep dive.

A few weeks ago I was looking into the Chromium DevTools heap profiler for a separate [blog](../true-private-state-in-javascript-a-chromium-rabbit-hole/) post of mine. I keenly noticed that the heap profiler distinguishes JavaScript objects by memory location. I might be able to utilize that to my advantage, thus revealing a glimmer of light at the end of the tunnel.

Expand All @@ -54,35 +54,35 @@ await Main.MainImpl.sendOverProtocol('Emulation.setDeviceMetricsOverride', {
const data = await Main.MainImpl.sendOverProtocol("Page.captureScreenshot");
```

This requires explicit access to DevTools and some setup, meaning it wouldn't plainly work on a website. The alternative option was the `chrome.debugger` API within a Chrome extension. Although this would be a bit harder to work with, it was actually programmatic and eventually the option I chose.
This requires explicit access to DevTools and some setup, meaning it wouldn't work plainly on a website. The alternative option is the `chrome.debugger` API within a Chrome extension. Although this would be a bit harder to work with, it was actually programmatic and eventually the option I chose.

I hacked up a Chrome extension but was surprised when my testing errored with the following message.

```json
{"code":-32601,"message":"'HeapProfiler.enable' wasn't found"}
```

What's going on here? As it turns out, [internal documentation](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/public/devtools_protocol/#security-considerations) reveals that specific Protocol Domains are restricted for security reasons.
What's going on here? As it turns out, [internal documentation](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/public/devtools_protocol/#security-considerations) reveals that specific protocol domains are restricted for security reasons.

> However, since the protocol is also exposed to chrome extensions through chrome.debugger API, the backend implements additional access control in some of the methods to prevent extensios form accessing file system or otherwise escaping the sandbox. These restrictions are not extended to other types of clients.
[Here](https://chromium.googlesource.com/v8/v8/+/c42e620355453fc0510b06e089ca7d92598bd54f%5E%21/) is the v8 commit that adds these restrictions, dating May 2022 and Chromium version v103. You guessed what's going to happen next. The solution to our dilemma, of course, is time travel.
[Here](https://chromium.googlesource.com/v8/v8/+/c42e620355453fc0510b06e089ca7d92598bd54f%5E%21/) is the v8 commit that adds these restrictions, dating back to May 2022 and Chromium version v103. You guessed what's happening next. The solution to our dilemma, of course, is time travel.

[Archives](https://www.slimjet.com/chrome/google-chrome-old-version.php) of older versions of Chrome exist online. However, I'm wary of downloading unknown files from the Internet. Instead, I installed an official Chromium build from source following their [guide](https://www.chromium.org/getting-involved/download-chromium/#downloading-old-builds-of-chrome-chromium). Note that you may have to run `/usr/bin/xattr -cr /Applications/Chromium.app` on an M1 Mac to fix broken metadata preventing Chromium from launching.
Google doesn't distribute legacy versions of Chrome. We'll have to be a bit more hands-on. [Archives](https://www.slimjet.com/chrome/google-chrome-old-version.php) of older versions of Chrome exist online. However, I'm wary of downloading unknown files from the Internet. Instead, I installed an official legacy Chromium build from source following their [guide](https://www.chromium.org/getting-involved/download-chromium/#downloading-old-builds-of-chrome-chromium). Note that you may have to run `/usr/bin/xattr -cr /Applications/Chromium.app` on an M1 Mac to fix broken metadata preventing Chromium from launching.

My extension didn't initially work because global content scripts were only [supported](https://chromium.googlesource.com/chromium/src/+/8f07eaff87947a2e93214de2695de8052119180b) in Chromium v111, so I had to resort to an [older](https://stackoverflow.com/questions/9602022/chrome-extension-retrieving-global-variable-from-webpage/9636008#9636008) content script hack to get it to work.

That was a lot, let's take a small step back. To reiterate, the goal is to implement a [spec-compliant](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal) strict equality operation in JavaScript from scratch. We've found a version of Chromium that enables programmatic access to the DevTools heap profiler. Let's build the bridge between the CDP and JavaScript object memory addresses.

A good place to start is the [HeapProfiler.takeHeapSnapshot](https://chromedevtools.github.io/devtools-protocol/tot/HeapProfiler/#method-takeHeapSnapshot) method. This will profile the *entire* page, and is obviously cumbersome, but whatever. Next, the [Runtime.evaluate](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate) method creates a unique object identifier. The caveat is that this only evaluates the global scope, coercing some quite bizarre JavaScript.
A good place to start is the [HeapProfiler.takeHeapSnapshot](https://chromedevtools.github.io/devtools-protocol/tot/HeapProfiler/#method-takeHeapSnapshot) method. This will profile the *entire* page, and is obviously cumbersome, but whatever. Next, the [Runtime.evaluate](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate) method creates a unique object identifier from the evaluation result of a stringified JavaScript expression. The caveat is that this only evaluates the global scope, coercing us into some quite bizarre JavaScript.

`strictEquality.js`
```js
async function strictEquality(obj1, obj2) {
window.__obj1 = obj1;
window.__obj2 = obj2;
// Communicate with the extension content script
// and perform the strict equality
// and perform the strict equality (will be explained later)
document.dispatchEvent(new CustomEvent("strictEquality"));
let e = await new Promise((resolve) =>
document.addEventListener("strictEqualityResponse", resolve, { once: true })
Expand All @@ -97,7 +97,7 @@ async function strictEquality(obj1, obj2) {
}
```

Ugh. It's async not because of I/O, but because of the event loop. It's also non-reentrant and must be prefixed with `await`. These types of unavoidable stateful functions are typically only present within C, not JavaScript!
Ugh. It's async not because of I/O, but because of the event loop. It's also non-reentrant, or it must always be `await`ed. These types of unavoidable stateful functions are typically only present within C, not JavaScript!

Fine. Finally, we can pass that identifier to [HeapProfiler.getHeapObjectId](https://chromedevtools.github.io/devtools-protocol/tot/HeapProfiler/#method-getHeapObjectId) and earn our heap profiler memory address value.

Expand Down Expand Up @@ -159,7 +159,7 @@ window.__obj1 = 42;
}
```

While in theory this would be a simple equality operation, I wanted to stay true to my word and avoid its use entirely. Rather, we need to implement the [isStrictlyEqual](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal) subroutine at the high level like so.
While this would be a simple equality operation, I wanted to stay true to my word and avoid its use entirely. Rather, we need to implement the [isStrictlyEqual](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal) subroutine at the high level like so.

`background.js`
```js
Expand Down Expand Up @@ -222,6 +222,8 @@ function stringEquality(str1, str2) {
}
```

And... there we go. The full implementation is provided in my [GitHub repository](https://github.com/ArhanChaudhary/strictequality-js). I'm not sure what you should take out of this blog post. There's no point in trying to sugarcoat it. This reimplementation of strict equality in JavaScript is at rock bottom, hanging by a thread. It's async, (really) slow, outdated, and displays the most annoying "This extension is debugging this browser" banner. Did I also mention that because content scripts don't work globally it doesn't work on document load? You'll have to have fun awaiting the `strictEqualityLoaded` event before usage.
And... there we go. The full implementation is provided in a [GitHub repository](https://github.com/ArhanChaudhary/strictequality-js). I'm not sure what you should take out of this blog post. There's no point in trying to sugarcoat it.

This reimplementation of strict equality in JavaScript is at rock bottom, hanging by a thread. It's async, (really) slow, outdated, and displays the most annoying "This extension is debugging this browser" banner. Did I also mention that because content scripts don't work globally it doesn't work on page load? You'll have to have fun awaiting the `strictEqualityLoaded` event before usage.

Hey, at least it was really cool. Until next time!

0 comments on commit 268071c

Please sign in to comment.