leobalter / shadowrealms-polyfill

MIT License
58 stars 0 forks source link

import wrapped function from Red Realm #8

Closed leobalter closed 3 years ago

leobalter commented 3 years ago

we want to avoid string evaluation.

We could expand the exploration of module import to return a wrapped function from the realm

Raw Idea:

const r = new Realm;

const wrappedRedFn = await r.importWrapped('./specifier.js', 'optionalBindingNameOrDefault');

Questions

leobalter commented 3 years ago

I've been trying to expand this API design. This is very hard to write for the polyfill as I won't be able to run import in detached iframes.

I'm not trying to answer any of the open questions yet (see top post).

In order to avoid string evaluation, we can reverse the usage of wrapped callback function to resolve injected modules into wrapped functions.

const red = new Realm();

const wrappedRedFn = await red.importWrapped('./specifier.js', 'injectedFunction');

This would be a rough equivalent of:

const red = new Realm();
let wrappedRedFn;

{
    const __redFunction = new red.AsyncFunction('spec', 'name', `
        const ns = await import(spec);

        if ({}.hasOwnProperty.call(ns, name)) {
            return ns[name];
        }

        throw new TypeError('Binding name not available');
    `);

    wrappedRedFn = __redFunction('./specifier', 'injectedFunction').then(
            fn => Realm.[WRAP_FUNCTION_INTERNAL](fn)
        ).catch(err => throw new TypeError(err.message));
}

This asserts the received wrappedRedFn is a Blue Function. When called, it triggers a call to the Red Function.

assert(wrappedRedFn instanceof Function);
assert.sameValue(Object.getPrototypeOf(wrappedRedFn), Function.prototype);

The injected module namespace and function is not leaked within the Red Realm, but can observe things only from the Red Realm.

// specifier.js:
export default function() {
    /* ignored */
}

export function injectedFunction(x) {
    return globalThis.someValue + x;
};
const red = new Realm();

const wrappedRedFn = await red.importWrapped('./specifier.js', 'injectedFunction');

red.eval('globalThis.someValue = "Hello, "');

globalThis.someValue = 'Olá, ';

wrappedRedFn('World!'); // yields to 'Hello, World!'
littledan commented 3 years ago

This API makes sense to me. Do you have any more thoughts on what Realm.[WRAP_FUNCTION_INTERNAL] would do?