Skip to content

Instantly share code, notes, and snippets.

@caridy
Forked from rauschma/about.md
Last active April 7, 2022 19:07
Show Gist options
  • Save caridy/faccbf953adec95848e38cc8f56e8845 to your computer and use it in GitHub Desktop.
Save caridy/faccbf953adec95848e38cc8f56e8845 to your computer and use it in GitHub Desktop.
Transferring objects between a ShadowRealm and an incubator realm

Transferring objects between a ShadowRealm and an incubator realm

Material:

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Membrane</title>
</head>
<body>
<script type="module">
import {createShadowEval} from './membrane-lib.js';
const sr = new ShadowRealm();
const shadowEval = await createShadowEval(sr);
const obj = shadowEval(`
({
brand: 'Ford',
owner: {
first: 'Jane',
last: 'Doe',
},
})
`);
console.log(obj.brand); // 'Ford'
console.log(obj.owner.first); // 'Jane'
const fn1 = shadowEval(`(function fn1(o) { return o; })`);
const fn2 = shadowEval(`(function fn2(o) { return typeof o; })`);
const o = {};
console.log(fn1(o) === o); // true - identity round trip
console.log(fn2(o) === 'object'); // true - type preservation
</script>
</body>
</html>
const pointers = new WeakMap();
let localRef;
function isObject(value) {
return (
(value !== null && typeof value === 'object')
|| typeof value === 'function'
);
}
function getTargetType(value) {
const pointer = pointers.get(value);
if (pointer) {
pointer(); // type is not needed because a pointer already exists for this value
return 0;
}
if (Array.isArray(value)) {
return 2; // Array
} else if (typeof value === 'function') {
return 'prototype' in value ? 3 : 4; // Arrow Function or Function
}
return 1; // Object
}
/**
* We can’t transfer a (non-callable) object across realms.
* But we can transfer a function that applies proxy-trapped operations
* to the object.
*/
export function wrapLocalValue(value) {
if (isObject(value)) {
let pointer = pointers.get(value);
if (!pointer) {
pointer = (trapName, ...args) => {
switch (trapName) {
case 1: // initialization
return getTargetType(value);
case 2: // linkage
pointers.set(value, args[0]);
return () => (localRef = value) && 0; // returning a pointer for when the value comes back.
default:
const wrappedArgs = args.map(arg => wrapForeignValue(arg));
const result = Reflect[trapName](value, ...wrappedArgs);
return wrapLocalValue(result);
}
};
}
return pointer;
} else {
return value;
}
}
/**
* To use a wrapped object from another realm in the current realm,
* we create a Proxy that feeds the operations it traps to the function.
*/
export function wrapForeignValue(value) {
if (!isObject(value)) {
return value;
}
localRef = undefined;
const targetType = value(1);
if (targetType === 0) {
return localRef;
}
// All handler methods follow the same pattern.
// To avoid repetitive code, we create it via a loop.
const handler = {};
for (const trapName of Object.getOwnPropertyNames(Reflect)) {
handler[trapName] = (_target, ...args) => {
const wrappedArgs = args.map(arg => wrapLocalValue(arg));
const result = value(trapName, ...wrappedArgs);
return wrapForeignValue(result);
};
}
const target = targetType === 2 ? [] : (targetType === 3 ? function () {} : (targetType === 4 ? () => {} : {}));
const proxy = new Proxy(target, handler);
pointers.set(proxy, value(2, () => (localRef = proxy) && 0));
return proxy;
}
export async function createShadowEval(shadowRealm) {
const _getLocallyWrappedEval = await shadowRealm.importValue(import.meta.url, '_getLocallyWrappedEval');
return wrapForeignValue(_getLocallyWrappedEval());
}
export async function createShadowImport(shadowRealm) {
const _getLocallyWrappedImport = await shadowRealm.importValue(import.meta.url, '_getLocallyWrappedImport');
return wrapForeignValue(_getLocallyWrappedImport());
}
export function _getLocallyWrappedEval() {
return wrapLocalValue(globalThis.eval);
}
export function _getLocallyWrappedImport() {
return wrapLocalValue((moduleSpecifier) => import(moduleSpecifier));
}
@caridy
Copy link
Author

caridy commented Apr 7, 2022

@rauschma here is the diff:

diff --git a/membrane-lib.js b/membrane-lib.js
index e8e7660..7bd2c6e 100644
--- a/membrane-lib.js
+++ b/membrane-lib.js
@@ -1,3 +1,6 @@
+const pointers = new WeakMap();
+let localRef;
+
 function isObject(value) {
   return (
     (value !== null && typeof value === 'object')
@@ -5,6 +8,20 @@ function isObject(value) {
   );
 }

+function getTargetType(value) {
+  const pointer = pointers.get(value);
+  if (pointer) {
+    pointer(); // type is not needed because a pointer already exists for this value
+    return 0;
+  }
+  if (Array.isArray(value)) {
+    return 2; // Array
+  } else if (typeof value === 'function') {
+    return 'prototype' in value ? 3 : 4; // Arrow Function or Function
+  }
+  return 1; // Object
+}
+
 /**
  * We can’t transfer a (non-callable) object across realms.
  * But we can transfer a function that applies proxy-trapped operations
@@ -12,11 +29,23 @@ function isObject(value) {
  */
 export function wrapLocalValue(value) {
   if (isObject(value)) {
-    return (trapName, ...args) => {
-      const wrappedArgs = args.map(arg => wrapForeignValue(arg));
-      const result = Reflect[trapName](value, ...wrappedArgs);
-      return wrapLocalValue(result);
-    };
+    let pointer = pointers.get(value);
+    if (!pointer) {
+      pointer = (trapName, ...args) => {
+        switch (trapName) {
+          case 1: // initialization
+            return getTargetType(value);
+          case 2: // linkage
+            pointers.set(value, args[0]);
+            return () => (localRef = value) && 0; // returning a pointer for when the value comes back.
+          default:
+            const wrappedArgs = args.map(arg => wrapForeignValue(arg));
+            const result = Reflect[trapName](value, ...wrappedArgs);
+            return wrapLocalValue(result);
+        }
+      };
+    }
+    return pointer;
   } else {
     return value;
   }
@@ -30,7 +59,11 @@ export function wrapForeignValue(value) {
   if (!isObject(value)) {
     return value;
   }
-
+  localRef = undefined;
+  const targetType = value(1);
+  if (targetType === 0) {
+    return localRef;
+  }
   // All handler methods follow the same pattern.
   // To avoid repetitive code, we create it via a loop.
   const handler = {};
@@ -41,9 +74,10 @@ export function wrapForeignValue(value) {
       return wrapForeignValue(result);
     };
   }
-
-  const target = function () {};
-  return new Proxy(target, handler);
+  const target = targetType === 2 ? [] : (targetType === 3 ? function () {} : (targetType === 4 ? () => {} : {}));
+  const proxy = new Proxy(target, handler);
+  pointers.set(proxy, value(2, () => (localRef = proxy) && 0));
+  return proxy;
 }

 export async function createShadowEval(shadowRealm) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment