javascriptnode.jsecmascript-6v8

How does the JavaScript engine handle object identity and prototype inheritance between ShadowRealms and the main execution context?


I’m experimenting with the ECMAScript proposal for ShadowRealm and ran into some deeply unintuitive behavior around object identity, function bindings, and prototype inheritance across realms.

Consider this example (run in an environment that supports ShadowRealm, like recent Chrome or Node.js with flags):

const realm = new ShadowRealm();

const fn = realm.evaluate(`
  globalThis.shared = { x: 1 };
  function getShared() { return shared; }
  getShared;
`);

const objFromRealm = fn();

console.log(objFromRealm); // [object Object]
console.log(Object.getPrototypeOf(objFromRealm) === Object.prototype); // false
console.log(objFromRealm instanceof Object); // false
console.log(typeof objFromRealm); // 'object'

Questions:

I tried accessing an object created inside a ShadowRealm from the main execution context. I expected it to behave like a normal object — for example, instanceof Object should return true, and the prototype should match the main realm’s Object.prototype. Instead, I found that objects from the ShadowRealm are not recognized as instances of Object in the main realm, and their prototype chains are completely separate. This behavior seems to break conventional expectations about object identity and typeof, so I’m trying to understand what’s really happening behind the scenes and whether this is by design or a side effect of compartment isolation.


Solution

  • When you’re working with ShadowRealm, you’re essentially spinning up a completely isolated execution environment, kind of like a JS multiverse where each realm has its own version of the standard built-ins (like Object, Array, etc.). That means the Object constructor and its prototype in the ShadowRealm is not the same as the one in your main realm they may look alike, but under the hood, they’re entirely separate references.

    So when you do something like:

    objFromRealm instanceof Object // false

    …it’s false because you’re asking, “Is this object from the ShadowRealm an instance of my realm’s Object?” And the answer is no it’s an instance of their Object. This is why Object.getPrototypeOf(objFromRealm) === Object.prototype fails too. Different realm, different prototype chain.

    But then typeof objFromRealm === 'object' still returns 'object', and that’s by spec typeof is deliberately shallow and doesn’t care about realm boundaries. It just says, “Yeah, this is an object-shaped thing,” without checking what dimension it came from.

    As for how engines handle this, yeah, most of them do some form of wrapping or proxying when objects cross realm boundaries this is necessary to maintain memory safety and prevent prototype pollution. Think of it like a one-way mirror: you can see the object, interact with it in limited ways, but you’re not really holding the original you’re holding a safe wrapper around it.

    So yes, this absolutely leads to identity confusion and instanceof inconsistencies. It’s by design to prevent one realm from messing with another's internals. You can't monkey-patch their Object.prototype, and you can't leak stuff back into your realm accidentally which is a good thing from a security/sandboxing standpoint, but definitely counterintuitive if you’re expecting vanilla JS behavior.

    Bottom line: ShadowRealm is hard isolation, and JS identity rules don’t transcend realms. If you're passing objects between realms, don't rely on instance of prototype equality, or identity checks they’ll lie to you. Treat inter-realm values like remote data: you can use them, but don’t trust their internals unless you wrap or sanitize them explicitly.