ses
Version:
Hardened JavaScript for Fearless Cooperation
201 lines (195 loc) • 6.85 kB
JavaScript
// Compartments need a mechanism to link a module from one compartment
// to another.
// The procedure is to use `compartment.module(specifier)` to obtain the module
// exports namespace from one compartment, possibly before importing or even
// merely loading module, and threading it into the module map of another
// compartment.
// For this to be possible, it is necessary to model the module exports
// namespace as a proxy that will treat all access to those exported properties
// as reference errors until the properties of the actual module are known.
// This provides the mechanism for modeling the public exports proxy
// and eventually connecting it to to the proxied exports.
import { makeAlias } from './module-load.js';
import {
Proxy,
TypeError,
create,
freeze,
mapGet,
mapHas,
mapSet,
ownKeys,
reflectGet,
reflectGetOwnPropertyDescriptor,
reflectHas,
reflectIsExtensible,
reflectPreventExtensions,
toStringTagSymbol,
weakmapSet,
} from './commons.js';
import { assert } from './error/assert.js';
const { quote: q } = assert;
// `deferExports` creates a module's exports proxy, proxied exports, and
// activator.
// A `Compartment` can create a module for any module specifier, regardless of
// whether it is loadable or executable, and use that object as a token that
// can be fed into another compartment's module map.
// Only after the specified module has been analyzed is it possible for the
// module namespace proxy to behave properly, so it throws exceptions until
// after the compartment has begun executing the module.
// The module instance must freeze the proxied exports and activate the exports
// proxy before executing the module.
//
// The module exports proxy's behavior differs from the ECMAScript 262
// specification for "module namespace exotic objects" only in that according
// to the specification value property descriptors have a non-writable "value"
// and this implementation models all properties with accessors.
//
// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects
//
export const deferExports = () => {
let active = false;
const exportsTarget = create(null, {
// Make this appear like an ESM module namespace object.
[toStringTagSymbol]: {
value: 'Module',
writable: false,
enumerable: false,
configurable: false,
},
});
return freeze({
activate() {
active = true;
},
exportsTarget,
exportsProxy: new Proxy(exportsTarget, {
get(_target, name, receiver) {
if (!active) {
throw TypeError(
`Cannot get property ${q(
name,
)} of module exports namespace, the module has not yet begun to execute`,
);
}
return reflectGet(exportsTarget, name, receiver);
},
set(_target, name, _value) {
throw TypeError(
`Cannot set property ${q(name)} of module exports namespace`,
);
},
has(_target, name) {
if (!active) {
throw TypeError(
`Cannot check property ${q(
name,
)}, the module has not yet begun to execute`,
);
}
return reflectHas(exportsTarget, name);
},
deleteProperty(_target, name) {
throw TypeError(
`Cannot delete property ${q(name)}s of module exports namespace`,
);
},
ownKeys(_target) {
if (!active) {
throw TypeError(
'Cannot enumerate keys, the module has not yet begun to execute',
);
}
return ownKeys(exportsTarget);
},
getOwnPropertyDescriptor(_target, name) {
if (!active) {
throw TypeError(
`Cannot get own property descriptor ${q(
name,
)}, the module has not yet begun to execute`,
);
}
return reflectGetOwnPropertyDescriptor(exportsTarget, name);
},
preventExtensions(_target) {
if (!active) {
throw TypeError(
'Cannot prevent extensions of module exports namespace, the module has not yet begun to execute',
);
}
return reflectPreventExtensions(exportsTarget);
},
isExtensible() {
if (!active) {
throw TypeError(
'Cannot check extensibility of module exports namespace, the module has not yet begun to execute',
);
}
return reflectIsExtensible(exportsTarget);
},
getPrototypeOf(_target) {
return null;
},
setPrototypeOf(_target, _proto) {
throw TypeError('Cannot set prototype of module exports namespace');
},
defineProperty(_target, name, _descriptor) {
throw TypeError(
`Cannot define property ${q(name)} of module exports namespace`,
);
},
apply(_target, _thisArg, _args) {
throw TypeError(
'Cannot call module exports namespace, it is not a function',
);
},
construct(_target, _args) {
throw TypeError(
'Cannot construct module exports namespace, it is not a constructor',
);
},
}),
});
};
/**
* @typedef {object} DeferredExports
* @property {Record<string, any>} exportsTarget - The object to which a
* module's exports will be added.
* @property {Record<string, any>} exportsProxy - A proxy over the `exportsTarget`,
* used to expose its "exports" to other compartments.
* @property {() => void} activate - Activate the `exportsProxy` such that it can
* be used as a module namespace object.
*/
/**
* Memoizes the creation of a deferred module exports namespace proxy for any
* arbitrary full specifier in a compartment. It also records the compartment
* and specifier affiliated with that module exports namespace proxy so it
* can be used as an alias into another compartment when threaded through
* a compartment's `moduleMap` argument.
*
* @param {*} compartment - The compartment to retrieve deferred exports from.
* @param {*} compartmentPrivateFields - The private fields of the compartment.
* @param {*} moduleAliases - The module aliases of the compartment.
* @param {string} specifier - The module specifier to retrieve deferred exports for.
* @returns {DeferredExports} - The deferred exports for the module specifier of
* the compartment.
*/
export const getDeferredExports = (
compartment,
compartmentPrivateFields,
moduleAliases,
specifier,
) => {
const { deferredExports } = compartmentPrivateFields;
if (!mapHas(deferredExports, specifier)) {
const deferred = deferExports();
weakmapSet(
moduleAliases,
deferred.exportsProxy,
makeAlias(compartment, specifier),
);
mapSet(deferredExports, specifier, deferred);
}
return mapGet(deferredExports, specifier);
};