UNPKG

ses

Version:

Hardened JavaScript for Fearless Cooperation

201 lines (195 loc) 6.85 kB
// 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); };