@masknet/compartment
Advanced tools
@@ -69,8 +69,5 @@ class ModuleSource { | ||
/** @internal */ const opaqueProxy = /*#__PURE__*/ getOpaqueProxy(); | ||
/** @internal */ function internalError() { | ||
throw new TypeError('Internal error.'); | ||
/** @internal */ function assertFailed(message) { | ||
throw new TypeError('Assertion failed.' + (message ? ' ' : '') + message); | ||
} | ||
/** @internal */ function assertFailed() { | ||
throw new TypeError('Assertion failed.'); | ||
} | ||
/** @internal */ function unreachable(val) { | ||
@@ -256,20 +253,23 @@ throw new TypeError('Unreachable'); | ||
let imports; | ||
/** @internal */ let setGlobalThis; | ||
/** @internal */ let setParentGlobalThis; | ||
/** @internal */ let setParentImportHook; | ||
/** @internal */ let setParentImportMetaHook; | ||
class Module { | ||
// The constructor is equivalent to ParseModule in SourceTextModuleRecord | ||
// https://tc39.es/ecma262/#sec-parsemodule | ||
constructor(moduleSource, referral, // it actually NOT an optional argument when it is the top-level Module. | ||
importHook = defaultImportHook, importMeta){ | ||
constructor(moduleSource, handler){ | ||
if (typeof moduleSource !== 'object') throw new TypeError('moduleSource must be an object'); | ||
if (typeof referral === 'object' && referral !== null) throw new TypeError('referral must be a primitive'); | ||
if (typeof importHook !== 'function') throw new TypeError('importHook must be a function'); | ||
let assignedImportMeta; | ||
if (importMeta === undefined) assignedImportMeta = null; | ||
else if (typeof importMeta !== 'object') throw new TypeError('importMeta must be an object'); | ||
else assignedImportMeta = importMeta; | ||
// impossible to create a ModuleSource instance | ||
if (moduleSource instanceof ModuleSource) internalError(); | ||
if (moduleSource instanceof ModuleSource) assertFailed('ModuleSource instance cannot be created'); | ||
const module = normalizeVirtualModuleRecord(moduleSource); | ||
this.#Source = moduleSource; | ||
this.#Referral = referral; | ||
if (handler === null) throw new TypeError('handler must not be null'); | ||
let importHook; | ||
let importMetaHook; | ||
if (typeof handler === 'object') { | ||
importHook = handler.importHook; | ||
if (typeof importHook !== 'function' && importHook !== undefined) throw new TypeError('importHook must be a function'); | ||
importMetaHook = handler.importMetaHook; | ||
if (typeof importMetaHook !== 'function' && importMetaHook !== undefined) throw new TypeError('importMetaHook must be a function'); | ||
} | ||
this.#VirtualModuleSource = moduleSource; | ||
this.#Execute = module.execute; | ||
@@ -279,4 +279,5 @@ this.#NeedsImport = module.needsImport; | ||
this.#HasTLA = !!module.isAsync; | ||
this.#AssignedImportMeta = assignedImportMeta; | ||
this.#ImportHook = importHook; | ||
this.#ImportMetaHook = importMetaHook; | ||
this.#HandlerValue = handler; | ||
const { importEntries , indirectExportEntries , localExportEntries , requestedModules , starExportEntries } = normalizeBindingsToSpecRecord(module.bindings); | ||
@@ -290,3 +291,3 @@ this.#ImportEntries = importEntries; | ||
get source() { | ||
return this.#Source; | ||
return this.#VirtualModuleSource; | ||
} | ||
@@ -299,4 +300,3 @@ //#region ModuleRecord fields https://tc39.es/ecma262/#table-module-record-fields | ||
// *this value* when calling #Execute. | ||
#Referral; | ||
#Source; | ||
#VirtualModuleSource; | ||
#Execute; | ||
@@ -307,5 +307,7 @@ #NeedsImportMeta; | ||
#ImportHook; | ||
#ImportHookCache = new Map(); | ||
#AssignedImportMeta; | ||
#ImportMetaHook; | ||
#HandlerValue; | ||
/** the global environment this module binds to */ #GlobalThis = globalThis; | ||
#ParentImportHook = defaultImportHook; | ||
#ParentImportMetaHook; | ||
/** imported module cache */ #ImportEntries; | ||
@@ -328,2 +330,3 @@ #LocalExportEntries; | ||
const module = this; | ||
if (!(module.#Status !== 0)) assertFailed(); | ||
if (exportStarSet.includes(module)) return []; | ||
@@ -343,4 +346,2 @@ exportStarSet.push(module); | ||
const requestedModule = Module.#GetImportedModule(module, e2.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!requestedModule) assertFailed(); | ||
const starNames = requestedModule.#GetExportedNames(exportStarSet); | ||
@@ -358,2 +359,3 @@ for (const n of starNames){ | ||
const module1 = this; | ||
if (!(module1.#Status !== 0)) assertFailed(); | ||
for (const r of resolveSet){ | ||
@@ -383,4 +385,2 @@ if (module1 === r.module && exportName === r.exportName) { | ||
const importedModule = Module.#GetImportedModule(module1, e4.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule) assertFailed(); | ||
if (e4.ImportName === all) { | ||
@@ -407,4 +407,2 @@ // Assert: module does not provide the direct binding for this export. | ||
const importedModule1 = Module.#GetImportedModule(module1, e5.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule1) assertFailed(); | ||
let resolution = importedModule1.#ResolveExport(exportName, resolveSet); | ||
@@ -430,5 +428,4 @@ if (resolution === ambiguous) return ambiguous; | ||
const state = { | ||
Action: 'graph-loading', | ||
IsLoading: true, | ||
PendingModules: 1, | ||
PendingModulesCount: 1, | ||
Visited: [], | ||
@@ -446,18 +443,16 @@ PromiseCapability: pc, | ||
const requestedModulesCount = module3.#RequestedModules.length; | ||
state1.PendingModules = state1.PendingModules + requestedModulesCount; | ||
state1.PendingModulesCount = state1.PendingModulesCount + requestedModulesCount; | ||
for (const required of module3.#RequestedModules){ | ||
if (state1.IsLoading) { | ||
const record = module3.#LoadedModules.get(required); | ||
if (record) { | ||
Module.#InnerModuleLoading(state1, record); | ||
} else { | ||
Module.#HostLoadImportedModule(module3, required, state1.HostDefined, state1); | ||
} | ||
const record = module3.#LoadedModules.get(required); | ||
if (record) { | ||
Module.#InnerModuleLoading(state1, record); | ||
} else { | ||
Module.#LoadImportedModule(module3, required, state1.HostDefined, state1); | ||
} | ||
if (!state1.IsLoading) return; | ||
} | ||
} | ||
if (!state1.IsLoading) return; | ||
if (!(state1.PendingModules >= 1)) assertFailed(); | ||
state1.PendingModules = state1.PendingModules - 1; | ||
if (state1.PendingModules === 0) { | ||
if (!(state1.PendingModulesCount >= 1)) assertFailed(); | ||
state1.PendingModulesCount = state1.PendingModulesCount - 1; | ||
if (state1.PendingModulesCount === 0) { | ||
state1.IsLoading = false; | ||
@@ -516,3 +511,2 @@ for (const loaded of state1.Visited){ | ||
const importedModule2 = Module.#GetImportedModule(module4, i.ModuleRequest); | ||
if (!importedModule2) assertFailed(); | ||
// import * as ns from '..' | ||
@@ -572,3 +566,3 @@ if (i.ImportName === namespace) { | ||
// Note: export property should not be enumerable? | ||
// but it will crash Chrome devtools.See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
// but it will crash Chrome devtools. See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
enumerable: true | ||
@@ -588,17 +582,41 @@ }; | ||
/** All call to ExecuteModule must use Task.run to keep the call stack continue */ #ExecuteModule(promise) { | ||
const execute = this.#Execute; | ||
if (!execute) return; | ||
this.#Execute = undefined; | ||
// prepare context | ||
this.#ContextObject.globalThis = this.#GlobalThis; | ||
if (this.#NeedsImportMeta) { | ||
this.#ContextObject.importMeta = Object.assign({}, this.#AssignedImportMeta); | ||
const importMeta = { | ||
__proto__: null | ||
}; | ||
if (this.#ImportMetaHook) Reflect.apply(this.#ImportMetaHook, this.#HandlerValue, [ | ||
importMeta | ||
]); | ||
else if (this.#ParentImportMetaHook) Reflect.apply(this.#ParentImportMetaHook, undefined, [ | ||
importMeta | ||
]); | ||
this.#ContextObject.importMeta = importMeta; | ||
} | ||
if (this.#NeedsImport) { | ||
this.#ContextObject.import = async (specifier, options)=>{ | ||
specifier = String(specifier); | ||
const status = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: PromiseCapability(), | ||
HostDefined: createTask(`import("${specifier}")`) | ||
}; | ||
Module.#HostLoadImportedModule(this, specifier, status.HostDefined, status); | ||
return status.PromiseCapability.Promise; | ||
const referrer = this; | ||
const promiseCapability = PromiseCapability(); | ||
let hasModuleInternalSlot = false; | ||
try { | ||
specifier.#HandlerValue; | ||
hasModuleInternalSlot = true; | ||
} catch {} | ||
if (hasModuleInternalSlot) { | ||
const hostDefined = createTask(`import(<module block>)`); | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(specifier), hostDefined); | ||
} else { | ||
specifier = String(specifier); | ||
const hostDefined1 = createTask(`import("${specifier}")`); | ||
if (referrer.#LoadedModules.has(specifier)) { | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(referrer.#LoadedModules.get(specifier)), hostDefined1); | ||
} else { | ||
Module.#LoadImportedModule(referrer, specifier, hostDefined1, promiseCapability); | ||
} | ||
} | ||
return promiseCapability.Promise; | ||
}; | ||
@@ -609,19 +627,15 @@ } | ||
if (!this.#HasTLA) { | ||
if (!!promise) assertFailed(); | ||
if (this.#Execute) { | ||
Reflect.apply(this.#Execute, this.#Source, [ | ||
env1, | ||
this.#ContextObject | ||
]); | ||
} | ||
if (promise) assertFailed(); | ||
const result = Reflect.apply(execute, this.#VirtualModuleSource, [ | ||
env1, | ||
this.#ContextObject | ||
]); | ||
if (result) throw new TypeError('Due to specification limitations, in order to support Async Modules (modules that use Top Level Await or a Virtual Module that has an execute() function that returns a Promise), the Virtual Module record must be marked with `isAsync: true`. The `isAsync` property is non-standard, and it is being tracked in https://github.com/tc39/proposal-compartments/issues/84.'); | ||
} else { | ||
if (!promise) assertFailed(); | ||
if (this.#Execute) { | ||
Promise.resolve(Reflect.apply(this.#Execute, this.#Source, [ | ||
env1, | ||
this.#ContextObject | ||
])).then(promise.Resolve, promise.Reject); | ||
} | ||
Promise.resolve(Reflect.apply(execute, this.#VirtualModuleSource, [ | ||
env1, | ||
this.#ContextObject | ||
])).then(promise.Resolve, promise.Reject); | ||
} | ||
this.#Execute = undefined; | ||
} | ||
@@ -719,3 +733,2 @@ // https://tc39.es/ecma262/#sec-moduledeclarationlinking | ||
const requiredModule = this.#GetImportedModule(module7, required1); | ||
if (!requiredModule) assertFailed(); | ||
index = this.#InnerModuleLinking(requiredModule, stack2, index); | ||
@@ -769,3 +782,2 @@ if (![ | ||
let requiredModule2 = this.#GetImportedModule(module8, required2); | ||
if (!requiredModule2) assertFailed(); | ||
index1 = this.#InnerModuleEvaluation(requiredModule2, stack3, index1, HostDefined2); | ||
@@ -918,3 +930,3 @@ if (![ | ||
if (module12.#Namespace) return module12.#Namespace; | ||
if (!(module12.#Status !== 1)) assertFailed(); | ||
if (!(module12.#Status !== 0 && module12.#Status !== 1)) assertFailed(); | ||
const exportedNames1 = module12.#GetExportedNames(); | ||
@@ -975,55 +987,37 @@ const namespaceObject2 = { | ||
static #GetImportedModule(module13, spec) { | ||
return module13.#LoadedModules.get(spec); | ||
const record1 = module13.#LoadedModules.get(spec); | ||
if (!record1) assertFailed(); | ||
return record1; | ||
} | ||
static #HostLoadImportedModule(referrer, specifier, hostDefined, payload) { | ||
let promiseCapability = referrer.#ImportHookCache.get(specifier); | ||
function onFulfilled(module) { | ||
promiseCapability?.Resolve(module); | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, NormalCompletion(module)); | ||
} | ||
function onRejected(reason) { | ||
promiseCapability?.Reject(reason); | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(reason)); | ||
} | ||
if (promiseCapability) { | ||
if (promiseCapability.Status.Type === 'Fulfilled') { | ||
onFulfilled(promiseCapability.Status.Value); | ||
return; | ||
} else if (promiseCapability.Status.Type === 'Pending') { | ||
promiseCapability.Promise.then(onFulfilled, onRejected); | ||
return; | ||
} | ||
// if error, fallthorugh | ||
} | ||
promiseCapability = PromiseCapability(); | ||
referrer.#ImportHookCache.set(specifier, promiseCapability); | ||
static #LoadImportedModule(referrer, specifier, hostDefined, payload) { | ||
try { | ||
const result = referrer.#ImportHook(specifier, referrer.#Referral); | ||
if (result === null) throw new SyntaxError(`Failed to load module ${specifier}.`); | ||
try { | ||
const module14 = result; | ||
module14.#Referral; | ||
onFulfilled(module14); | ||
return; | ||
} catch {} | ||
// treat it as a Promise | ||
Promise.resolve(result).then((result)=>{ | ||
if (result === null) onRejected(new SyntaxError(`Failed to load module ${specifier}.`)); | ||
const importHookResult = referrer.#ImportHook ? Reflect.apply(referrer.#ImportHook, referrer.#HandlerValue, [ | ||
specifier | ||
]) : Reflect.apply(referrer.#ParentImportHook, undefined, [ | ||
specifier | ||
]); | ||
const importHookPromise = Promise.resolve(importHookResult); | ||
const onFulfilled = (result)=>{ | ||
let completion; | ||
try { | ||
const module = result; | ||
module.#Referral; | ||
onFulfilled(module); | ||
result.#HandlerValue; | ||
completion = NormalCompletion(result); | ||
} catch (error) { | ||
onRejected(new TypeError('importHook must return an instance of Module')); | ||
completion = ThrowCompletion(new TypeError('importHook must return a Module instance')); | ||
} | ||
}); | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, completion, hostDefined); | ||
}; | ||
const onRejected = (error)=>{ | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined); | ||
}; | ||
importHookPromise.then(onFulfilled, onRejected); | ||
} catch (error) { | ||
onRejected(error); | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined); | ||
} | ||
} | ||
static #FinishLoadingImportedModule(referrer1, specifier1, state3, result1) { | ||
static #FinishLoadingImportedModule(referrer1, specifier1, payload1, result1, hostDefined1) { | ||
if (result1.Type === 'normal') { | ||
const record1 = referrer1.#LoadedModules.get(specifier1); | ||
if (record1) { | ||
if (!(record1 === result1.Value)) assertFailed(); | ||
const record2 = referrer1.#LoadedModules.get(specifier1); | ||
if (record2) { | ||
if (!(record2 === result1.Value)) assertFailed(); | ||
} else { | ||
@@ -1033,34 +1027,29 @@ referrer1.#LoadedModules.set(specifier1, result1.Value); | ||
} | ||
if (state3.Action === 'graph-loading') { | ||
Module.#ContinueModuleLoading(state3, result1); | ||
if ('Visited' in payload1) { | ||
Module.#ContinueModuleLoading(payload1, result1); | ||
} else { | ||
Module.#ContinueDynamicImport(state3, result1); | ||
Module.#ContinueDynamicImport(payload1, result1, hostDefined1); | ||
} | ||
} | ||
static #ContinueDynamicImport(state4, moduleCompletion1) { | ||
const promiseCapability1 = state4.PromiseCapability; | ||
static #ContinueDynamicImport(promiseCapability, moduleCompletion1, hostDefined2) { | ||
if (moduleCompletion1.Type === 'throw') { | ||
promiseCapability1.Reject(moduleCompletion1.Value); | ||
promiseCapability.Reject(moduleCompletion1.Value); | ||
return; | ||
} | ||
const module15 = moduleCompletion1.Value; | ||
const loadPromise = module15.#LoadRequestedModules(state4.HostDefined); | ||
const module14 = moduleCompletion1.Value; | ||
const loadPromise = module14.#LoadRequestedModules(hostDefined2); | ||
function onRejected1(reason) { | ||
promiseCapability1.Reject(reason); | ||
promiseCapability.Reject(reason); | ||
} | ||
function linkAndEvaluate() { | ||
try { | ||
module15.#Link(); | ||
const evaluatePromise = module15.#Evaluate(state4.HostDefined); | ||
module14.#Link(); | ||
const evaluatePromise = module14.#Evaluate(hostDefined2); | ||
function onFulfilled() { | ||
try { | ||
const namespace = Module.#GetModuleNamespace(module15); | ||
promiseCapability1.Resolve(namespace); | ||
} catch (error) { | ||
promiseCapability1.Reject(error); | ||
} | ||
const namespace = Module.#GetModuleNamespace(module14); | ||
promiseCapability.Resolve(namespace); | ||
} | ||
evaluatePromise.then(onFulfilled, onRejected1); | ||
} catch (error) { | ||
promiseCapability1.Reject(error); | ||
promiseCapability.Reject(error); | ||
} | ||
@@ -1074,14 +1063,9 @@ } | ||
const promiseCapability = PromiseCapability(); | ||
let HostDefinedName = 'Module<...>'; | ||
if (typeof module.#Referral === 'symbol') HostDefinedName = `Module<@${module.#Referral.description}>`; | ||
else if (typeof module.#Referral === 'string') HostDefinedName = `"${module.#Referral}"`; | ||
const state = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: promiseCapability, | ||
HostDefined: createTask(`import(${HostDefinedName})`) | ||
}; | ||
Module.#ContinueDynamicImport(state, NormalCompletion(module)); | ||
const hostDefined = createTask(`import(<module block>)`); | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(module), hostDefined); | ||
return promiseCapability.Promise; | ||
}; | ||
setGlobalThis = (module, global)=>module.#GlobalThis = global; | ||
setParentGlobalThis = (module, global)=>module.#GlobalThis = global; | ||
setParentImportHook = (module, hook)=>module.#ParentImportHook = hook; | ||
setParentImportMetaHook = (module, hook)=>module.#ParentImportMetaHook = hook; | ||
} | ||
@@ -1152,15 +1136,4 @@ } | ||
getOwnPropertyDescriptor: ()=>undefined, | ||
defineProperty () { | ||
// TODO: | ||
internalError(); | ||
}, | ||
deleteProperty () { | ||
return false; | ||
}, | ||
has () { | ||
return false; | ||
}, | ||
ownKeys () { | ||
return []; | ||
}, | ||
defineProperty: ()=>false, | ||
deleteProperty: ()=>false, | ||
isExtensible: ()=>false, | ||
@@ -1173,39 +1146,59 @@ preventExtensions: ()=>true, | ||
class Evaluators { | ||
constructor(options){ | ||
const { globalThis: globalThis1 = realGlobalThis , importHook =defaultImportHook , importMeta =null } = options; | ||
if (typeof globalThis1 !== 'object') throw new TypeError('globalThis must be an object'); | ||
if (typeof importHook !== 'function') throw new TypeError('importHook must be a function'); | ||
if (typeof importMeta !== 'object') throw new TypeError('importMeta must be an object'); | ||
get globalThis() { | ||
return this.#AssignGlobalThis; | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval; | ||
Function = Function; | ||
// implementation | ||
constructor(handler){ | ||
const { globalThis: globalThis1 , importHook , importMeta } = handler; | ||
this.#Handler = handler; | ||
if (typeof globalThis1 !== 'object' && globalThis1 !== undefined) throw new TypeError('globalThis must be an object'); | ||
if (typeof importHook !== 'function' && importHook !== undefined) throw new TypeError('importHook must be a function'); | ||
if (typeof importMeta !== 'object' && importMeta !== undefined && importMeta !== null) throw new TypeError('importMeta must be an object'); | ||
const parent = this; | ||
class Evaluators extends TopEvaluators { | ||
constructor(options){ | ||
const { globalThis: globalThis1 = parent.#globalThis , importHook =parent.#importHook , importMeta =parent.#importMeta ?? null } = options; | ||
super({ | ||
globalThis: globalThis1, | ||
importHook, | ||
importMeta | ||
}); | ||
super(options); | ||
this.#ParentEvaluator = parent; | ||
} | ||
} | ||
let Module$1 = class Module extends Module { | ||
constructor(moduleSource, referral, importHook, importMeta){ | ||
super(moduleSource, referral, importHook ?? parent.#importHook, importMeta ?? parent.#importMeta); | ||
setGlobalThis(this, parent.#globalThis); | ||
constructor(moduleSource, handler){ | ||
super(moduleSource, handler); | ||
setParentGlobalThis(this, parent.#CalculatedGlobalThis ??= parent.#GetGlobalThis()); | ||
setParentImportHook(this, parent.#CalculatedImportHook ??= parent.#GetImportHook()); | ||
setParentImportMetaHook(this, (meta)=>Object.assign(meta, parent.#CalculatedImportMeta ??= parent.#GetImportMeta())); | ||
} | ||
}; | ||
this.#importHook = importHook; | ||
this.#importMeta = importMeta ?? undefined; | ||
this.#globalThis = globalThis1; | ||
this.#AssignedImportHook = importHook; | ||
this.#AssignedImportMeta = importMeta; | ||
this.#AssignGlobalThis = globalThis1; | ||
this.Module = Module$1; | ||
this.Evaluators = Evaluators; | ||
} | ||
get globalThis() { | ||
return this.#globalThis; | ||
#ParentEvaluator; | ||
#AssignGlobalThis; | ||
#AssignedImportHook; | ||
#AssignedImportMeta; | ||
#CalculatedGlobalThis; | ||
#CalculatedImportHook; | ||
#CalculatedImportMeta; | ||
#Handler; | ||
#GetGlobalThis() { | ||
if (this.#AssignGlobalThis) return this.#AssignGlobalThis; | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetGlobalThis(); | ||
return realGlobalThis; | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval; | ||
Function = Function; | ||
#globalThis; | ||
#importHook; | ||
#importMeta; | ||
#GetImportHook() { | ||
if (this.#AssignedImportHook) return this.#AssignedImportHook.bind(this.#Handler); | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetImportHook(); | ||
return defaultImportHook; | ||
} | ||
#GetImportMeta() { | ||
if (this.#AssignedImportMeta) return this.#AssignedImportMeta; | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetImportMeta(); | ||
return null; | ||
} | ||
} | ||
@@ -1215,3 +1208,3 @@ const TopEvaluators = Evaluators; | ||
/** @internal */ function defaultImportHook() { | ||
throw new TypeError(`This evaluator does not have any import resolution.`); | ||
throw new TypeError(`This evaluator does not have any import resolution strategy.`); | ||
} | ||
@@ -1218,0 +1211,0 @@ |
import { Module } from './Module.js'; | ||
import type { ImportHook } from './types.js'; | ||
export interface EvaluatorsOptions { | ||
globalThis?: object; | ||
importHook?: ImportHook; | ||
importMeta?: object | null; | ||
globalThis?: object | undefined; | ||
importHook?: ImportHook | undefined; | ||
importMeta?: object | null | undefined; | ||
} | ||
export declare class Evaluators { | ||
#private; | ||
constructor(options: EvaluatorsOptions); | ||
Module: typeof Module; | ||
Evaluators: typeof Evaluators; | ||
get globalThis(): object; | ||
get globalThis(): object | undefined; | ||
eval: typeof eval; | ||
Function: FunctionConstructor; | ||
constructor(handler: EvaluatorsOptions); | ||
} | ||
//# sourceMappingURL=Evaluators.d.ts.map |
@@ -1,10 +0,20 @@ | ||
import { Module, Module as TopModule, setGlobalThis } from './Module.js'; | ||
import { Module, Module as TopModule, setParentGlobalThis, setParentImportHook, setParentImportMetaHook, } from './Module.js'; | ||
export class Evaluators { | ||
constructor(options) { | ||
const { globalThis = realGlobalThis, importHook = defaultImportHook, importMeta = null } = options; | ||
if (typeof globalThis !== 'object') | ||
Module; | ||
Evaluators; | ||
get globalThis() { | ||
return this.#AssignGlobalThis; | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval; | ||
Function = Function; | ||
// implementation | ||
constructor(handler) { | ||
const { globalThis, importHook, importMeta } = handler; | ||
this.#Handler = handler; | ||
if (typeof globalThis !== 'object' && globalThis !== undefined) | ||
throw new TypeError('globalThis must be an object'); | ||
if (typeof importHook !== 'function') | ||
if (typeof importHook !== 'function' && importHook !== undefined) | ||
throw new TypeError('importHook must be a function'); | ||
if (typeof importMeta !== 'object') | ||
if (typeof importMeta !== 'object' && importMeta !== undefined && importMeta !== null) | ||
throw new TypeError('importMeta must be an object'); | ||
@@ -14,29 +24,49 @@ const parent = this; | ||
constructor(options) { | ||
const { globalThis = parent.#globalThis, importHook = parent.#importHook, importMeta = parent.#importMeta ?? null, } = options; | ||
super({ globalThis, importHook, importMeta }); | ||
super(options); | ||
this.#ParentEvaluator = parent; | ||
} | ||
} | ||
class Module extends TopModule { | ||
constructor(moduleSource, referral, importHook, importMeta) { | ||
super(moduleSource, referral, importHook ?? parent.#importHook, importMeta ?? parent.#importMeta); | ||
setGlobalThis(this, parent.#globalThis); | ||
constructor(moduleSource, handler) { | ||
super(moduleSource, handler); | ||
setParentGlobalThis(this, (parent.#CalculatedGlobalThis ??= parent.#GetGlobalThis())); | ||
setParentImportHook(this, (parent.#CalculatedImportHook ??= parent.#GetImportHook())); | ||
setParentImportMetaHook(this, (meta) => Object.assign(meta, (parent.#CalculatedImportMeta ??= parent.#GetImportMeta()))); | ||
} | ||
} | ||
this.#importHook = importHook; | ||
this.#importMeta = importMeta ?? undefined; | ||
this.#globalThis = globalThis; | ||
this.#AssignedImportHook = importHook; | ||
this.#AssignedImportMeta = importMeta; | ||
this.#AssignGlobalThis = globalThis; | ||
this.Module = Module; | ||
this.Evaluators = Evaluators; | ||
} | ||
Module; | ||
Evaluators; | ||
get globalThis() { | ||
return this.#globalThis; | ||
#ParentEvaluator; | ||
#AssignGlobalThis; | ||
#AssignedImportHook; | ||
#AssignedImportMeta; | ||
#CalculatedGlobalThis; | ||
#CalculatedImportHook; | ||
#CalculatedImportMeta; | ||
#Handler; | ||
#GetGlobalThis() { | ||
if (this.#AssignGlobalThis) | ||
return this.#AssignGlobalThis; | ||
if (this.#ParentEvaluator) | ||
return this.#ParentEvaluator.#GetGlobalThis(); | ||
return realGlobalThis; | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval; | ||
Function = Function; | ||
#globalThis; | ||
#importHook; | ||
#importMeta; | ||
#GetImportHook() { | ||
if (this.#AssignedImportHook) | ||
return this.#AssignedImportHook.bind(this.#Handler); | ||
if (this.#ParentEvaluator) | ||
return this.#ParentEvaluator.#GetImportHook(); | ||
return defaultImportHook; | ||
} | ||
#GetImportMeta() { | ||
if (this.#AssignedImportMeta) | ||
return this.#AssignedImportMeta; | ||
if (this.#ParentEvaluator) | ||
return this.#ParentEvaluator.#GetImportMeta(); | ||
return null; | ||
} | ||
} | ||
@@ -47,4 +77,4 @@ const TopEvaluators = Evaluators; | ||
export function defaultImportHook() { | ||
throw new TypeError(`This evaluator does not have any import resolution.`); | ||
throw new TypeError(`This evaluator does not have any import resolution strategy.`); | ||
} | ||
//# sourceMappingURL=Evaluators.js.map |
@@ -1,2 +0,2 @@ | ||
export type { Binding, ImportBinding, ImportAllBinding, ExportBinding, ExportAllBinding, VirtualModuleRecord, VirtualModuleRecordExecuteContext, ModuleNamespace, ImportHook, Referral, StaticModuleRecordInstance, } from './types.js'; | ||
export type { Binding, ImportBinding, ImportAllBinding, ExportBinding, ExportAllBinding, VirtualModuleRecord, VirtualModuleRecordExecuteContext, ModuleNamespace, ImportHook, ImportMetaHook, ModuleHandler, } from './types.js'; | ||
export { ModuleSource } from './ModuleSource.js'; | ||
@@ -3,0 +3,0 @@ export { Evaluators } from './Evaluators.js'; |
import { ModuleSource } from './ModuleSource.js'; | ||
import type { ImportHook, Referral, VirtualModuleRecord } from './types.js'; | ||
export declare let imports: <T extends object = any>(specifier: Module<T>, options?: ImportCallOptions) => Promise<T>; | ||
export declare class Module<T extends object = any> { | ||
import type { ModuleHandler, ModuleNamespace, VirtualModuleRecord } from './types.js'; | ||
export declare let imports: <T extends ModuleNamespace = any>(specifier: Module<T>, options?: ImportCallOptions) => Promise<T>; | ||
export declare class Module<T extends ModuleNamespace = any> { | ||
#private; | ||
constructor(moduleSource: ModuleSource<T> | VirtualModuleRecord, referral: Referral, importHook?: ImportHook, importMeta?: object); | ||
constructor(moduleSource: ModuleSource<T> | VirtualModuleRecord, handler: ModuleHandler); | ||
get source(): ModuleSource | VirtualModuleRecord | null; | ||
} | ||
//# sourceMappingURL=Module.d.ts.map |
import { ModuleSource } from './ModuleSource.js'; | ||
import { all, ambiguous, empty, namespace, NormalCompletion, PromiseCapability, ThrowCompletion, } from './utils/spec.js'; | ||
import { normalizeBindingsToSpecRecord, normalizeVirtualModuleRecord } from './utils/normalize.js'; | ||
import { assertFailed, internalError, opaqueProxy } from './utils/assert.js'; | ||
import { assertFailed, opaqueProxy } from './utils/assert.js'; | ||
import { defaultImportHook } from './Evaluators.js'; | ||
@@ -9,28 +9,30 @@ import { createTask } from './utils/async-task.js'; | ||
/** @internal */ | ||
export let setGlobalThis; | ||
export let setParentGlobalThis; | ||
/** @internal */ | ||
export let setParentImportHook; | ||
/** @internal */ | ||
export let setParentImportMetaHook; | ||
export class Module { | ||
// The constructor is equivalent to ParseModule in SourceTextModuleRecord | ||
// https://tc39.es/ecma262/#sec-parsemodule | ||
constructor(moduleSource, referral, | ||
// it actually NOT an optional argument when it is the top-level Module. | ||
importHook = defaultImportHook, importMeta) { | ||
constructor(moduleSource, handler) { | ||
if (typeof moduleSource !== 'object') | ||
throw new TypeError('moduleSource must be an object'); | ||
if (typeof referral === 'object' && referral !== null) | ||
throw new TypeError('referral must be a primitive'); | ||
if (typeof importHook !== 'function') | ||
throw new TypeError('importHook must be a function'); | ||
let assignedImportMeta; | ||
if (importMeta === undefined) | ||
assignedImportMeta = null; | ||
else if (typeof importMeta !== 'object') | ||
throw new TypeError('importMeta must be an object'); | ||
else | ||
assignedImportMeta = importMeta; | ||
// impossible to create a ModuleSource instance | ||
if (moduleSource instanceof ModuleSource) | ||
internalError(); | ||
assertFailed('ModuleSource instance cannot be created'); | ||
const module = normalizeVirtualModuleRecord(moduleSource); | ||
this.#Source = moduleSource; | ||
this.#Referral = referral; | ||
if (handler === null) | ||
throw new TypeError('handler must not be null'); | ||
let importHook; | ||
let importMetaHook; | ||
if (typeof handler === 'object') { | ||
importHook = handler.importHook; | ||
if (typeof importHook !== 'function' && importHook !== undefined) | ||
throw new TypeError('importHook must be a function'); | ||
importMetaHook = handler.importMetaHook; | ||
if (typeof importMetaHook !== 'function' && importMetaHook !== undefined) | ||
throw new TypeError('importMetaHook must be a function'); | ||
} | ||
this.#VirtualModuleSource = moduleSource; | ||
this.#Execute = module.execute; | ||
@@ -40,4 +42,5 @@ this.#NeedsImport = module.needsImport; | ||
this.#HasTLA = !!module.isAsync; | ||
this.#AssignedImportMeta = assignedImportMeta; | ||
this.#ImportHook = importHook; | ||
this.#ImportMetaHook = importMetaHook; | ||
this.#HandlerValue = handler; | ||
const { importEntries, indirectExportEntries, localExportEntries, requestedModules, starExportEntries } = normalizeBindingsToSpecRecord(module.bindings); | ||
@@ -51,3 +54,3 @@ this.#ImportEntries = importEntries; | ||
get source() { | ||
return this.#Source; | ||
return this.#VirtualModuleSource; | ||
} | ||
@@ -62,4 +65,3 @@ //#region ModuleRecord fields https://tc39.es/ecma262/#table-module-record-fields | ||
// *this value* when calling #Execute. | ||
#Referral; | ||
#Source; | ||
#VirtualModuleSource; | ||
#Execute; | ||
@@ -70,6 +72,8 @@ #NeedsImportMeta; | ||
#ImportHook; | ||
#ImportHookCache = new Map(); | ||
#AssignedImportMeta; | ||
#ImportMetaHook; | ||
#HandlerValue; | ||
/** the global environment this module binds to */ | ||
#GlobalThis = globalThis; | ||
#ParentImportHook = defaultImportHook; | ||
#ParentImportMetaHook; | ||
/** imported module cache */ | ||
@@ -96,2 +100,4 @@ #ImportEntries; | ||
const module = this; | ||
if (!(module.#Status !== ModuleStatus.new)) | ||
assertFailed(); | ||
if (exportStarSet.includes(module)) | ||
@@ -115,5 +121,2 @@ return []; | ||
const requestedModule = Module.#GetImportedModule(module, e.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!requestedModule) | ||
assertFailed(); | ||
const starNames = requestedModule.#GetExportedNames(exportStarSet); | ||
@@ -133,2 +136,4 @@ for (const n of starNames) { | ||
const module = this; | ||
if (!(module.#Status !== ModuleStatus.new)) | ||
assertFailed(); | ||
for (const r of resolveSet) { | ||
@@ -153,5 +158,2 @@ if (module === r.module && exportName === r.exportName) { | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule) | ||
assertFailed(); | ||
if (e.ImportName === all) { | ||
@@ -178,5 +180,2 @@ // Assert: module does not provide the direct binding for this export. | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest); | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule) | ||
assertFailed(); | ||
let resolution = importedModule.#ResolveExport(exportName, resolveSet); | ||
@@ -209,5 +208,4 @@ if (resolution === ambiguous) | ||
const state = { | ||
Action: 'graph-loading', | ||
IsLoading: true, | ||
PendingModules: 1, | ||
PendingModulesCount: 1, | ||
Visited: [], | ||
@@ -226,21 +224,19 @@ PromiseCapability: pc, | ||
const requestedModulesCount = module.#RequestedModules.length; | ||
state.PendingModules = state.PendingModules + requestedModulesCount; | ||
state.PendingModulesCount = state.PendingModulesCount + requestedModulesCount; | ||
for (const required of module.#RequestedModules) { | ||
if (state.IsLoading) { | ||
const record = module.#LoadedModules.get(required); | ||
if (record) { | ||
Module.#InnerModuleLoading(state, record); | ||
} | ||
else { | ||
Module.#HostLoadImportedModule(module, required, state.HostDefined, state); | ||
} | ||
const record = module.#LoadedModules.get(required); | ||
if (record) { | ||
Module.#InnerModuleLoading(state, record); | ||
} | ||
else { | ||
Module.#LoadImportedModule(module, required, state.HostDefined, state); | ||
} | ||
if (!state.IsLoading) | ||
return; | ||
} | ||
} | ||
if (!state.IsLoading) | ||
return; | ||
if (!(state.PendingModules >= 1)) | ||
if (!(state.PendingModulesCount >= 1)) | ||
assertFailed(); | ||
state.PendingModules = state.PendingModules - 1; | ||
if (state.PendingModules === 0) { | ||
state.PendingModulesCount = state.PendingModulesCount - 1; | ||
if (state.PendingModulesCount === 0) { | ||
state.IsLoading = false; | ||
@@ -301,4 +297,2 @@ for (const loaded of state.Visited) { | ||
const importedModule = Module.#GetImportedModule(module, i.ModuleRequest); | ||
if (!importedModule) | ||
assertFailed(); | ||
// import * as ns from '..' | ||
@@ -358,3 +352,3 @@ if (i.ImportName === namespace) { | ||
// Note: export property should not be enumerable? | ||
// but it will crash Chrome devtools.See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
// but it will crash Chrome devtools. See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
enumerable: true, | ||
@@ -375,17 +369,42 @@ }; | ||
#ExecuteModule(promise) { | ||
const execute = this.#Execute; | ||
if (!execute) | ||
return; | ||
this.#Execute = undefined; | ||
// prepare context | ||
this.#ContextObject.globalThis = this.#GlobalThis; | ||
if (this.#NeedsImportMeta) { | ||
this.#ContextObject.importMeta = Object.assign({}, this.#AssignedImportMeta); | ||
const importMeta = { __proto__: null }; | ||
if (this.#ImportMetaHook) | ||
Reflect.apply(this.#ImportMetaHook, this.#HandlerValue, [importMeta]); | ||
else if (this.#ParentImportMetaHook) | ||
Reflect.apply(this.#ParentImportMetaHook, undefined, [importMeta]); | ||
this.#ContextObject.importMeta = importMeta; | ||
} | ||
if (this.#NeedsImport) { | ||
this.#ContextObject.import = async (specifier, options) => { | ||
specifier = String(specifier); | ||
const status = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: PromiseCapability(), | ||
HostDefined: createTask(`import("${specifier}")`), | ||
}; | ||
Module.#HostLoadImportedModule(this, specifier, status.HostDefined, status); | ||
return status.PromiseCapability.Promise; | ||
const referrer = this; | ||
const promiseCapability = PromiseCapability(); | ||
let hasModuleInternalSlot = false; | ||
try { | ||
; | ||
specifier.#HandlerValue; | ||
hasModuleInternalSlot = true; | ||
} | ||
catch { } | ||
if (hasModuleInternalSlot) { | ||
const hostDefined = createTask(`import(<module block>)`); | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(specifier), hostDefined); | ||
} | ||
else { | ||
specifier = String(specifier); | ||
const hostDefined = createTask(`import("${specifier}")`); | ||
if (referrer.#LoadedModules.has(specifier)) { | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(referrer.#LoadedModules.get(specifier)), hostDefined); | ||
} | ||
else { | ||
Module.#LoadImportedModule(referrer, specifier, hostDefined, promiseCapability); | ||
} | ||
} | ||
return promiseCapability.Promise; | ||
}; | ||
@@ -397,7 +416,7 @@ } | ||
if (!this.#HasTLA) { | ||
if (!!promise) | ||
if (promise) | ||
assertFailed(); | ||
if (this.#Execute) { | ||
Reflect.apply(this.#Execute, this.#Source, [env, this.#ContextObject]); | ||
} | ||
const result = Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject]); | ||
if (result) | ||
throw new TypeError('Due to specification limitations, in order to support Async Modules (modules that use Top Level Await or a Virtual Module that has an execute() function that returns a Promise), the Virtual Module record must be marked with `isAsync: true`. The `isAsync` property is non-standard, and it is being tracked in https://github.com/tc39/proposal-compartments/issues/84.'); | ||
} | ||
@@ -407,7 +426,4 @@ else { | ||
assertFailed(); | ||
if (this.#Execute) { | ||
Promise.resolve(Reflect.apply(this.#Execute, this.#Source, [env, this.#ContextObject])).then(promise.Resolve, promise.Reject); | ||
} | ||
Promise.resolve(Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject])).then(promise.Resolve, promise.Reject); | ||
} | ||
this.#Execute = undefined; | ||
} | ||
@@ -503,4 +519,2 @@ // https://tc39.es/ecma262/#sec-moduledeclarationlinking | ||
const requiredModule = this.#GetImportedModule(module, required); | ||
if (!requiredModule) | ||
assertFailed(); | ||
index = this.#InnerModuleLinking(requiredModule, stack, index); | ||
@@ -561,4 +575,2 @@ if (![ | ||
let requiredModule = this.#GetImportedModule(module, required); | ||
if (!requiredModule) | ||
assertFailed(); | ||
index = this.#InnerModuleEvaluation(requiredModule, stack, index, HostDefined); | ||
@@ -739,3 +751,3 @@ if (![ModuleStatus.evaluating, ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(requiredModule.#Status)) | ||
return module.#Namespace; | ||
if (!(module.#Status !== ModuleStatus.unlinked)) | ||
if (!(module.#Status !== ModuleStatus.new && module.#Status !== ModuleStatus.unlinked)) | ||
assertFailed(); | ||
@@ -793,57 +805,35 @@ const exportedNames = module.#GetExportedNames(); | ||
static #GetImportedModule(module, spec) { | ||
return module.#LoadedModules.get(spec); | ||
const record = module.#LoadedModules.get(spec); | ||
if (!record) | ||
assertFailed(); | ||
return record; | ||
} | ||
static #HostLoadImportedModule(referrer, specifier, hostDefined, payload) { | ||
let promiseCapability = referrer.#ImportHookCache.get(specifier); | ||
function onFulfilled(module) { | ||
promiseCapability?.Resolve(module); | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, NormalCompletion(module)); | ||
} | ||
function onRejected(reason) { | ||
promiseCapability?.Reject(reason); | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(reason)); | ||
} | ||
if (promiseCapability) { | ||
if (promiseCapability.Status.Type === 'Fulfilled') { | ||
onFulfilled(promiseCapability.Status.Value); | ||
return; | ||
} | ||
else if (promiseCapability.Status.Type === 'Pending') { | ||
promiseCapability.Promise.then(onFulfilled, onRejected); | ||
return; | ||
} | ||
// if error, fallthorugh | ||
} | ||
promiseCapability = PromiseCapability(); | ||
referrer.#ImportHookCache.set(specifier, promiseCapability); | ||
static #LoadImportedModule(referrer, specifier, hostDefined, payload) { | ||
try { | ||
const result = referrer.#ImportHook(specifier, referrer.#Referral); | ||
if (result === null) | ||
throw new SyntaxError(`Failed to load module ${specifier}.`); | ||
try { | ||
const module = result; | ||
module.#Referral; | ||
onFulfilled(module); | ||
return; | ||
} | ||
catch { } | ||
// treat it as a Promise | ||
Promise.resolve(result).then((result) => { | ||
if (result === null) | ||
onRejected(new SyntaxError(`Failed to load module ${specifier}.`)); | ||
const importHookResult = referrer.#ImportHook | ||
? Reflect.apply(referrer.#ImportHook, referrer.#HandlerValue, [specifier]) | ||
: Reflect.apply(referrer.#ParentImportHook, undefined, [specifier]); | ||
const importHookPromise = Promise.resolve(importHookResult); | ||
const onFulfilled = (result) => { | ||
let completion; | ||
try { | ||
const module = result; | ||
module.#Referral; | ||
onFulfilled(module); | ||
; | ||
result.#HandlerValue; | ||
completion = NormalCompletion(result); | ||
} | ||
catch (error) { | ||
onRejected(new TypeError('importHook must return an instance of Module')); | ||
completion = ThrowCompletion(new TypeError('importHook must return a Module instance')); | ||
} | ||
}); | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, completion, hostDefined); | ||
}; | ||
const onRejected = (error) => { | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined); | ||
}; | ||
importHookPromise.then(onFulfilled, onRejected); | ||
} | ||
catch (error) { | ||
onRejected(error); | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined); | ||
} | ||
} | ||
static #FinishLoadingImportedModule(referrer, specifier, state, result) { | ||
static #FinishLoadingImportedModule(referrer, specifier, payload, result, hostDefined) { | ||
if (result.Type === 'normal') { | ||
@@ -859,11 +849,10 @@ const record = referrer.#LoadedModules.get(specifier); | ||
} | ||
if (state.Action === 'graph-loading') { | ||
Module.#ContinueModuleLoading(state, result); | ||
if ('Visited' in payload) { | ||
Module.#ContinueModuleLoading(payload, result); | ||
} | ||
else { | ||
Module.#ContinueDynamicImport(state, result); | ||
Module.#ContinueDynamicImport(payload, result, hostDefined); | ||
} | ||
} | ||
static #ContinueDynamicImport(state, moduleCompletion) { | ||
const promiseCapability = state.PromiseCapability; | ||
static #ContinueDynamicImport(promiseCapability, moduleCompletion, hostDefined) { | ||
if (moduleCompletion.Type === 'throw') { | ||
@@ -874,3 +863,3 @@ promiseCapability.Reject(moduleCompletion.Value); | ||
const module = moduleCompletion.Value; | ||
const loadPromise = module.#LoadRequestedModules(state.HostDefined); | ||
const loadPromise = module.#LoadRequestedModules(hostDefined); | ||
function onRejected(reason) { | ||
@@ -882,11 +871,6 @@ promiseCapability.Reject(reason); | ||
module.#Link(); | ||
const evaluatePromise = module.#Evaluate(state.HostDefined); | ||
const evaluatePromise = module.#Evaluate(hostDefined); | ||
function onFulfilled() { | ||
try { | ||
const namespace = Module.#GetModuleNamespace(module); | ||
promiseCapability.Resolve(namespace); | ||
} | ||
catch (error) { | ||
promiseCapability.Reject(error); | ||
} | ||
const namespace = Module.#GetModuleNamespace(module); | ||
promiseCapability.Resolve(namespace); | ||
} | ||
@@ -906,16 +890,9 @@ evaluatePromise.then(onFulfilled, onRejected); | ||
const promiseCapability = PromiseCapability(); | ||
let HostDefinedName = 'Module<...>'; | ||
if (typeof module.#Referral === 'symbol') | ||
HostDefinedName = `Module<@${module.#Referral.description}>`; | ||
else if (typeof module.#Referral === 'string') | ||
HostDefinedName = `"${module.#Referral}"`; | ||
const state = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: promiseCapability, | ||
HostDefined: createTask(`import(${HostDefinedName})`), | ||
}; | ||
Module.#ContinueDynamicImport(state, NormalCompletion(module)); | ||
const hostDefined = createTask(`import(<module block>)`); | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(module), hostDefined); | ||
return promiseCapability.Promise; | ||
}; | ||
setGlobalThis = (module, global) => (module.#GlobalThis = global); | ||
setParentGlobalThis = (module, global) => (module.#GlobalThis = global); | ||
setParentImportHook = (module, hook) => (module.#ParentImportHook = hook); | ||
setParentImportMetaHook = (module, hook) => (module.#ParentImportMetaHook = hook); | ||
} | ||
@@ -981,15 +958,4 @@ } | ||
getOwnPropertyDescriptor: () => undefined, | ||
defineProperty() { | ||
// TODO: | ||
internalError(); | ||
}, | ||
deleteProperty() { | ||
return false; | ||
}, | ||
has() { | ||
return false; | ||
}, | ||
ownKeys() { | ||
return []; | ||
}, | ||
defineProperty: () => false, | ||
deleteProperty: () => false, | ||
isExtensible: () => false, | ||
@@ -996,0 +962,0 @@ preventExtensions: () => true, |
@@ -36,4 +36,4 @@ import type { Module } from './Module.js'; | ||
export interface VirtualModuleRecord { | ||
bindings?: Array<Binding>; | ||
execute?(environment: any, context?: VirtualModuleRecordExecuteContext): void | Promise<void>; | ||
bindings?: Array<Binding> | undefined; | ||
execute?(environment: any, context: VirtualModuleRecordExecuteContext): void | Promise<void>; | ||
needsImportMeta?: boolean | undefined; | ||
@@ -46,10 +46,11 @@ needsImport?: boolean | undefined; | ||
importMeta?: object; | ||
import?(spec: string, options?: ImportCallOptions): Promise<ModuleNamespace>; | ||
import?<T extends ModuleNamespace = ModuleNamespace>(spec: string | Module<T>, options?: ImportCallOptions): Promise<T>; | ||
globalThis: typeof globalThis; | ||
} | ||
export interface StaticModuleRecordInstance { | ||
get bindings(): readonly Binding[]; | ||
export type ImportHook = (importSpecifier: string) => PromiseLike<Module | null> | Module | null; | ||
export type ImportMetaHook = (importMeta: object) => void; | ||
export interface ModuleHandler { | ||
importHook?: ImportHook | undefined; | ||
importMetaHook?: ImportMetaHook; | ||
} | ||
export type ImportHook = (importSpecifier: string, referrer: Referral) => PromiseLike<Module | null> | Module | null; | ||
export type Referral = symbol | string | number | bigint; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -9,10 +9,6 @@ function getOpaqueProxy() { | ||
/** @internal */ | ||
export function internalError() { | ||
throw new TypeError('Internal error.'); | ||
export function assertFailed(message) { | ||
throw new TypeError('Assertion failed.' + (message ? ' ' : '') + message); | ||
} | ||
/** @internal */ | ||
export function assertFailed() { | ||
throw new TypeError('Assertion failed.'); | ||
} | ||
/** @internal */ | ||
export function unreachable(val) { | ||
@@ -19,0 +15,0 @@ throw new TypeError('Unreachable'); |
{ | ||
"name": "@masknet/compartment", | ||
"version": "0.3.13", | ||
"version": "0.4.0", | ||
"type": "module", | ||
@@ -16,3 +16,3 @@ "main": "./dist/index.js", | ||
"devDependencies": { | ||
"@swc/core": "^1.3.9" | ||
"@swc/core": "^1.3.10" | ||
}, | ||
@@ -19,0 +19,0 @@ "files": [ |
# @masknet/compartment | ||
An eval-free implementation of [Compartment](https://github.com/tc39/proposal-compartments/pull/46/files). | ||
> WARNING: This package currently does not follow the [Semantic Versioning](https://semver.org/) because the original standard is still developing. The minor version might include breaking changes! | ||
This package should be run in the environment described below: | ||
This package implements a user-land [Virtual Module Source][layer-2] evaluator. | ||
- Satisfy the [security assumption](../../README.md#security-assumptions). | ||
- ES2021 syntax available. | ||
This package currently implements the following specs/API explainers: | ||
- [ECMA262 Normative PR: Layering: Add HostLoadImportedModule hook](https://github.com/tc39/ecma262/pull/2905/) | ||
- [Module Block proposal](https://tc39.es/proposal-js-module-blocks/) | ||
- [Compartment proposal: Layer 0: Module and ModuleSource constructor][layer-0] | ||
- [Compartment proposal: Layer 1: ModuleSource reflection][layer-1] | ||
- [Compartment proposal: Layer 2: Virtual Module Source][layer-2] | ||
- [Compartment proposal: Layer 3: Evaluator][layer-3] | ||
## Assumptions and runtime requirements | ||
1. The environment is already `lockdown()` by [ses][ses]. | ||
2. Dynamic code execution (`eval` and `Function`) is not possible. | ||
3. Code executed are either trusted or precompiled into a [Virtual Module Source][layer-2] by a compiler like [@masknet/static-module-record-swc](../static-module-record-swc/). | ||
4. ECMAScript 2022 syntax is available. | ||
## APIs | ||
TODOs before first release: | ||
### `ModuleSource` constructor | ||
- Add some tests | ||
- Document APIs and limitations | ||
Implements `ModuleSource` from [layer 0][layer-0] and [layer 1][layer-1] of the compartment proposal. | ||
This constructor always throws like it is in an environment that cannot use eval. | ||
```ts | ||
new ModuleSource() | ||
// EvalError: Refused to evaluate a string as JavaScript. | ||
``` | ||
### `Module` constructor | ||
Implements `Module` from [layer 0][layer-0] and [layer-2][layer-2] of the compartment proposal. | ||
```ts | ||
import { Module, imports, type VirtualModuleRecord } from '@masknet/compartment' | ||
const virtualModule: VirtualModuleRecord = { | ||
execute(environment, context) { | ||
console.log('module constructed!') | ||
}, | ||
} | ||
const module = new Module(virtualModule, import.meta.url, () => null) | ||
// ^referral ^importHook | ||
const moduleNamespace = await imports(module) | ||
``` | ||
### `imports` function | ||
This function is a user-land dynamic import that accepts `Module` instances. | ||
This function does not accept strings as dynamic import does. | ||
### `Evaluators` constructor | ||
This constructor implements `Evaluators` from [layer 3][layer-3] of the compartment proposal. | ||
```ts | ||
import { Evaluators, Module, imports, type VirtualModuleRecord } from '@masknet/compartment' | ||
const globalThis = { answer: 42 } | ||
const evaluators = new Evaluators({ globalThis }) | ||
const virtualModule: VirtualModuleRecord = { | ||
bindings: [{ export: 'x' }], | ||
execute(environment, { globalThis }) { | ||
environment.x = globalThis.answer // 42 | ||
}, | ||
} | ||
const module = new Evaluators.Module(virtualModule, import.meta.url, () => null) | ||
const moduleNamespace = await imports(module) | ||
moduleNamespace.x // 42 | ||
``` | ||
### `makeGlobalThis` function | ||
This function is a utility function that creates a new object that contains only items from the ECMAScript specification. | ||
Those items are from the **current realm**, therefore sharing them with the Evaluators without [lockdown()](ses) might bring serious problems. | ||
```ts | ||
import { makeGlobalThis } from '@masknet/compartment' | ||
const globalThis = makeGlobalThis() | ||
globalThis.Array // [Function: Array] | ||
``` | ||
[ses]: https://github.com/endojs/endo/tree/master/packages/ses | ||
[layer-0]: https://tc39.es/proposal-compartments/0-module-and-module-source.html | ||
[layer-1]: https://github.com/tc39/proposal-compartments/blob/master/1-static-analysis.md | ||
[layer-2]: https://github.com/tc39/proposal-compartments/blob/master/2-virtual-module-source.md | ||
[layer-3]: https://github.com/tc39/proposal-compartments/blob/master/3-evaluator.md |
@@ -1,43 +0,58 @@ | ||
import { Module, Module as TopModule, setGlobalThis } from './Module.js' | ||
import { | ||
Module, | ||
Module as TopModule, | ||
setParentGlobalThis, | ||
setParentImportHook, | ||
setParentImportMetaHook, | ||
} from './Module.js' | ||
import type { ModuleSource } from './ModuleSource.js' | ||
import type { ImportHook, Referral, VirtualModuleRecord } from './types.js' | ||
import type { ImportHook, ModuleHandler, VirtualModuleRecord } from './types.js' | ||
export interface EvaluatorsOptions { | ||
globalThis?: object | ||
importHook?: ImportHook | ||
importMeta?: object | null | ||
globalThis?: object | undefined | ||
importHook?: ImportHook | undefined | ||
importMeta?: object | null | undefined | ||
} | ||
export class Evaluators { | ||
constructor(options: EvaluatorsOptions) { | ||
const { globalThis = realGlobalThis, importHook = defaultImportHook, importMeta = null } = options | ||
Module: typeof Module | ||
Evaluators: typeof Evaluators | ||
get globalThis() { | ||
return this.#AssignGlobalThis | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval | ||
Function = Function | ||
if (typeof globalThis !== 'object') throw new TypeError('globalThis must be an object') | ||
if (typeof importHook !== 'function') throw new TypeError('importHook must be a function') | ||
if (typeof importMeta !== 'object') throw new TypeError('importMeta must be an object') | ||
// implementation | ||
constructor(handler: EvaluatorsOptions) { | ||
const { globalThis, importHook, importMeta } = handler | ||
this.#Handler = handler | ||
if (typeof globalThis !== 'object' && globalThis !== undefined) | ||
throw new TypeError('globalThis must be an object') | ||
if (typeof importHook !== 'function' && importHook !== undefined) | ||
throw new TypeError('importHook must be a function') | ||
if (typeof importMeta !== 'object' && importMeta !== undefined && importMeta !== null) | ||
throw new TypeError('importMeta must be an object') | ||
const parent = this | ||
class Evaluators extends TopEvaluators { | ||
constructor(options: EvaluatorsOptions) { | ||
const { | ||
globalThis = parent.#globalThis, | ||
importHook = parent.#importHook, | ||
importMeta = parent.#importMeta ?? null, | ||
} = options | ||
super({ globalThis, importHook, importMeta }) | ||
super(options) | ||
this.#ParentEvaluator = parent | ||
} | ||
} | ||
class Module extends TopModule { | ||
constructor( | ||
moduleSource: ModuleSource | VirtualModuleRecord, | ||
referral: Referral, | ||
importHook?: ImportHook, | ||
importMeta?: object, | ||
) { | ||
super(moduleSource, referral, importHook ?? parent.#importHook, importMeta ?? parent.#importMeta) | ||
setGlobalThis(this, parent.#globalThis) | ||
constructor(moduleSource: ModuleSource | VirtualModuleRecord, handler: ModuleHandler) { | ||
super(moduleSource, handler) | ||
setParentGlobalThis(this, (parent.#CalculatedGlobalThis ??= parent.#GetGlobalThis())) | ||
setParentImportHook(this, (parent.#CalculatedImportHook ??= parent.#GetImportHook())) | ||
setParentImportMetaHook(this, (meta) => | ||
Object.assign(meta, (parent.#CalculatedImportMeta ??= parent.#GetImportMeta())), | ||
) | ||
} | ||
} | ||
this.#importHook = importHook | ||
this.#importMeta = importMeta ?? undefined | ||
this.#globalThis = globalThis | ||
this.#AssignedImportHook = importHook | ||
this.#AssignedImportMeta = importMeta | ||
this.#AssignGlobalThis = globalThis | ||
@@ -47,13 +62,26 @@ this.Module = Module | ||
} | ||
Module: typeof Module | ||
Evaluators: typeof Evaluators | ||
get globalThis() { | ||
return this.#globalThis | ||
#ParentEvaluator: Evaluators | undefined | ||
#AssignGlobalThis: object | undefined | ||
#AssignedImportHook: ImportHook | undefined | ||
#AssignedImportMeta: object | undefined | null | ||
#CalculatedGlobalThis: object | undefined | ||
#CalculatedImportHook: ImportHook | undefined | ||
#CalculatedImportMeta: object | undefined | null | ||
#Handler: EvaluatorsOptions | ||
#GetGlobalThis(): object { | ||
if (this.#AssignGlobalThis) return this.#AssignGlobalThis | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetGlobalThis() | ||
return realGlobalThis | ||
} | ||
// We do not support `eval` and `Function`. | ||
eval = eval | ||
Function = Function | ||
#globalThis: object | ||
#importHook: ImportHook | ||
#importMeta: object | undefined | ||
#GetImportHook(): ImportHook { | ||
if (this.#AssignedImportHook) return this.#AssignedImportHook.bind(this.#Handler) | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetImportHook() | ||
return defaultImportHook | ||
} | ||
#GetImportMeta(): ImportMeta | null { | ||
if (this.#AssignedImportMeta) return this.#AssignedImportMeta | ||
if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetImportMeta() | ||
return null | ||
} | ||
} | ||
@@ -65,3 +93,3 @@ const TopEvaluators = Evaluators | ||
export function defaultImportHook(): never { | ||
throw new TypeError(`This evaluator does not have any import resolution.`) | ||
throw new TypeError(`This evaluator does not have any import resolution strategy.`) | ||
} |
@@ -11,4 +11,4 @@ export type { | ||
ImportHook, | ||
Referral, | ||
StaticModuleRecordInstance, | ||
ImportMetaHook, | ||
ModuleHandler, | ||
} from './types.js' | ||
@@ -15,0 +15,0 @@ export { ModuleSource } from './ModuleSource.js' |
import { ModuleSource } from './ModuleSource.js' | ||
import type { | ||
ImportHook, | ||
ImportMetaHook, | ||
ModuleHandler, | ||
ModuleNamespace, | ||
Referral, | ||
VirtualModuleRecord, | ||
@@ -22,35 +23,36 @@ VirtualModuleRecordExecuteContext, | ||
import { normalizeBindingsToSpecRecord, normalizeVirtualModuleRecord } from './utils/normalize.js' | ||
import { assertFailed, internalError, opaqueProxy } from './utils/assert.js' | ||
import { assertFailed, opaqueProxy } from './utils/assert.js' | ||
import { defaultImportHook } from './Evaluators.js' | ||
import { createTask, type Task } from './utils/async-task.js' | ||
export let imports: <T extends object = any>(specifier: Module<T>, options?: ImportCallOptions) => Promise<T> | ||
export let imports: <T extends ModuleNamespace = any>(specifier: Module<T>, options?: ImportCallOptions) => Promise<T> | ||
/** @internal */ | ||
export let setGlobalThis: (module: Module, global: object) => void | ||
export let setParentGlobalThis: (module: Module, global: object) => void | ||
/** @internal */ | ||
export let setParentImportHook: (module: Module, handler: ImportHook) => void | ||
/** @internal */ | ||
export let setParentImportMetaHook: (module: Module, handler: ImportMetaHook) => void | ||
export class Module<T extends object = any> { | ||
export class Module<T extends ModuleNamespace = any> { | ||
// The constructor is equivalent to ParseModule in SourceTextModuleRecord | ||
// https://tc39.es/ecma262/#sec-parsemodule | ||
constructor( | ||
moduleSource: ModuleSource<T> | VirtualModuleRecord, | ||
referral: Referral, | ||
// it actually NOT an optional argument when it is the top-level Module. | ||
importHook: ImportHook = defaultImportHook, | ||
importMeta?: object, | ||
) { | ||
constructor(moduleSource: ModuleSource<T> | VirtualModuleRecord, handler: ModuleHandler) { | ||
if (typeof moduleSource !== 'object') throw new TypeError('moduleSource must be an object') | ||
if (typeof referral === 'object' && referral !== null) throw new TypeError('referral must be a primitive') | ||
if (typeof importHook !== 'function') throw new TypeError('importHook must be a function') | ||
let assignedImportMeta: null | object | ||
if (importMeta === undefined) assignedImportMeta = null | ||
else if (typeof importMeta !== 'object') throw new TypeError('importMeta must be an object') | ||
else assignedImportMeta = importMeta | ||
// impossible to create a ModuleSource instance | ||
if (moduleSource instanceof ModuleSource) internalError() | ||
if (moduleSource instanceof ModuleSource) assertFailed('ModuleSource instance cannot be created') | ||
const module = normalizeVirtualModuleRecord(moduleSource) | ||
this.#Source = moduleSource | ||
this.#Referral = referral | ||
if (handler === null) throw new TypeError('handler must not be null') | ||
let importHook: ImportHook | undefined | ||
let importMetaHook: ImportMetaHook | undefined | ||
if (typeof handler === 'object') { | ||
importHook = handler.importHook | ||
if (typeof importHook !== 'function' && importHook !== undefined) | ||
throw new TypeError('importHook must be a function') | ||
importMetaHook = handler.importMetaHook | ||
if (typeof importMetaHook !== 'function' && importMetaHook !== undefined) | ||
throw new TypeError('importMetaHook must be a function') | ||
} | ||
this.#VirtualModuleSource = moduleSource | ||
this.#Execute = module.execute | ||
@@ -61,4 +63,5 @@ this.#NeedsImport = module.needsImport | ||
this.#AssignedImportMeta = assignedImportMeta | ||
this.#ImportHook = importHook | ||
this.#ImportMetaHook = importMetaHook | ||
this.#HandlerValue = handler | ||
@@ -74,3 +77,3 @@ const { importEntries, indirectExportEntries, localExportEntries, requestedModules, starExportEntries } = | ||
get source(): ModuleSource | VirtualModuleRecord | null { | ||
return this.#Source | ||
return this.#VirtualModuleSource | ||
} | ||
@@ -86,4 +89,3 @@ //#region ModuleRecord fields https://tc39.es/ecma262/#table-module-record-fields | ||
// *this value* when calling #Execute. | ||
#Referral: Referral | ||
#Source: VirtualModuleRecord | ||
#VirtualModuleSource: VirtualModuleRecord | ||
#Execute: VirtualModuleRecord['execute'] | ||
@@ -93,7 +95,9 @@ #NeedsImportMeta: boolean | undefined | ||
#ContextObject: VirtualModuleRecordExecuteContext | undefined | ||
#ImportHook: ImportHook | ||
#ImportHookCache = new Map<string, PromiseCapability<Module>>() | ||
#AssignedImportMeta: object | null | ||
#ImportHook: ImportHook | undefined | ||
#ImportMetaHook: ImportMetaHook | undefined | ||
#HandlerValue: ModuleHandler | ||
/** the global environment this module binds to */ | ||
#GlobalThis: object = globalThis | ||
#ParentImportHook: ImportHook = defaultImportHook | ||
#ParentImportMetaHook: ImportMetaHook | undefined | ||
/** imported module cache */ | ||
@@ -121,2 +125,3 @@ #ImportEntries: ModuleImportEntry[] | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
if (exportStarSet.includes(module)) return [] | ||
@@ -136,4 +141,2 @@ exportStarSet.push(module) | ||
const requestedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!requestedModule) assertFailed() | ||
const starNames = requestedModule.#GetExportedNames(exportStarSet) | ||
@@ -154,2 +157,3 @@ for (const n of starNames) { | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
for (const r of resolveSet) { | ||
@@ -173,4 +177,2 @@ if (module === r.module && exportName === r.exportName) { | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule) assertFailed() | ||
if (e.ImportName === all) { | ||
@@ -194,4 +196,2 @@ // Assert: module does not provide the direct binding for this export. | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
// TODO: https://github.com/tc39/ecma262/pull/2905/files#r973044508 | ||
if (!importedModule) assertFailed() | ||
let resolution = importedModule.#ResolveExport(exportName, resolveSet) | ||
@@ -224,6 +224,5 @@ if (resolution === ambiguous) return ambiguous | ||
const pc = PromiseCapability<void>() | ||
const state: ModuleLoadState_GraphLoading = { | ||
Action: 'graph-loading', | ||
const state: GraphLoadingState = { | ||
IsLoading: true, | ||
PendingModules: 1, | ||
PendingModulesCount: 1, | ||
Visited: [], | ||
@@ -236,3 +235,3 @@ PromiseCapability: pc, | ||
} | ||
static #InnerModuleLoading(state: ModuleLoadState_GraphLoading, module: Module) { | ||
static #InnerModuleLoading(state: GraphLoadingState, module: Module) { | ||
if (!state.IsLoading) assertFailed() | ||
@@ -242,18 +241,16 @@ if (module.#Status === ModuleStatus.new && !state.Visited.includes(module)) { | ||
const requestedModulesCount = module.#RequestedModules.length | ||
state.PendingModules = state.PendingModules + requestedModulesCount | ||
state.PendingModulesCount = state.PendingModulesCount + requestedModulesCount | ||
for (const required of module.#RequestedModules) { | ||
if (state.IsLoading) { | ||
const record = module.#LoadedModules.get(required) | ||
if (record) { | ||
Module.#InnerModuleLoading(state, record) | ||
} else { | ||
Module.#HostLoadImportedModule(module, required, state.HostDefined, state) | ||
} | ||
const record = module.#LoadedModules.get(required) | ||
if (record) { | ||
Module.#InnerModuleLoading(state, record) | ||
} else { | ||
Module.#LoadImportedModule(module, required, state.HostDefined, state) | ||
} | ||
if (!state.IsLoading) return | ||
} | ||
} | ||
if (!state.IsLoading) return | ||
if (!(state.PendingModules >= 1)) assertFailed() | ||
state.PendingModules = state.PendingModules - 1 | ||
if (state.PendingModules === 0) { | ||
if (!(state.PendingModulesCount >= 1)) assertFailed() | ||
state.PendingModulesCount = state.PendingModulesCount - 1 | ||
if (state.PendingModulesCount === 0) { | ||
state.IsLoading = false | ||
@@ -266,3 +263,3 @@ for (const loaded of state.Visited) { | ||
} | ||
static #ContinueModuleLoading(state: ModuleLoadState_GraphLoading, moduleCompletion: Completion<Module>) { | ||
static #ContinueModuleLoading(state: GraphLoadingState, moduleCompletion: Completion<Module>) { | ||
if (!state.IsLoading) return | ||
@@ -316,3 +313,2 @@ if (moduleCompletion.Type === 'normal') Module.#InnerModuleLoading(state, moduleCompletion.Value) | ||
const importedModule = Module.#GetImportedModule(module, i.ModuleRequest) | ||
if (!importedModule) assertFailed() | ||
// import * as ns from '..' | ||
@@ -370,3 +366,3 @@ if (i.ImportName === namespace) { | ||
// Note: export property should not be enumerable? | ||
// but it will crash Chrome devtools.See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
// but it will crash Chrome devtools. See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
enumerable: true, | ||
@@ -390,17 +386,45 @@ } | ||
#ExecuteModule(promise?: PromiseCapability<void>) { | ||
const execute = this.#Execute | ||
if (!execute) return | ||
this.#Execute = undefined | ||
// prepare context | ||
this.#ContextObject!.globalThis = this.#GlobalThis as any | ||
if (this.#NeedsImportMeta) { | ||
this.#ContextObject!.importMeta = Object.assign({}, this.#AssignedImportMeta) | ||
const importMeta = { __proto__: null } | ||
if (this.#ImportMetaHook) Reflect.apply(this.#ImportMetaHook, this.#HandlerValue, [importMeta]) | ||
else if (this.#ParentImportMetaHook) Reflect.apply(this.#ParentImportMetaHook, undefined, [importMeta]) | ||
this.#ContextObject!.importMeta = importMeta | ||
} | ||
if (this.#NeedsImport) { | ||
this.#ContextObject!.import = async (specifier: string, options?: ImportCallOptions) => { | ||
specifier = String(specifier) | ||
const status: ModuleLoadState_DynamicImport = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: PromiseCapability(), | ||
HostDefined: createTask(`import("${specifier}")`), | ||
this.#ContextObject!.import = async ( | ||
specifier: string | Module<ModuleNamespace>, | ||
options?: ImportCallOptions, | ||
) => { | ||
const referrer = this | ||
const promiseCapability = PromiseCapability<ModuleNamespace>() | ||
let hasModuleInternalSlot = false | ||
try { | ||
;(specifier as Module).#HandlerValue | ||
hasModuleInternalSlot = true | ||
} catch {} | ||
if (hasModuleInternalSlot) { | ||
const hostDefined = createTask(`import(<module block>)`) | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(specifier as Module), hostDefined) | ||
} else { | ||
specifier = String(specifier) | ||
const hostDefined = createTask(`import("${specifier}")`) | ||
if (referrer.#LoadedModules.has(specifier)) { | ||
Module.#ContinueDynamicImport( | ||
promiseCapability, | ||
NormalCompletion(referrer.#LoadedModules.get(specifier)!), | ||
hostDefined, | ||
) | ||
} else { | ||
Module.#LoadImportedModule(referrer, specifier, hostDefined, promiseCapability) | ||
} | ||
} | ||
Module.#HostLoadImportedModule(this, specifier, status.HostDefined, status) | ||
return status.PromiseCapability.Promise as any | ||
return promiseCapability.Promise as any | ||
} | ||
@@ -413,16 +437,15 @@ } | ||
if (!this.#HasTLA) { | ||
if (!!promise) assertFailed() | ||
if (this.#Execute) { | ||
Reflect.apply(this.#Execute, this.#Source, [env, this.#ContextObject]) | ||
} | ||
if (promise) assertFailed() | ||
const result = Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject]) | ||
if (result) | ||
throw new TypeError( | ||
'Due to specification limitations, in order to support Async Modules (modules that use Top Level Await or a Virtual Module that has an execute() function that returns a Promise), the Virtual Module record must be marked with `isAsync: true`. The `isAsync` property is non-standard, and it is being tracked in https://github.com/tc39/proposal-compartments/issues/84.', | ||
) | ||
} else { | ||
if (!promise) assertFailed() | ||
if (this.#Execute) { | ||
Promise.resolve(Reflect.apply(this.#Execute, this.#Source, [env, this.#ContextObject])).then( | ||
promise.Resolve, | ||
promise.Reject, | ||
) | ||
} | ||
Promise.resolve(Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject])).then( | ||
promise.Resolve, | ||
promise.Reject, | ||
) | ||
} | ||
this.#Execute = undefined! | ||
} | ||
@@ -511,3 +534,2 @@ // https://tc39.es/ecma262/#sec-moduledeclarationlinking | ||
const requiredModule = this.#GetImportedModule(module, required) | ||
if (!requiredModule) assertFailed() | ||
index = this.#InnerModuleLinking(requiredModule, stack, index) | ||
@@ -565,3 +587,2 @@ if ( | ||
let requiredModule = this.#GetImportedModule(module, required) | ||
if (!requiredModule) assertFailed() | ||
index = this.#InnerModuleEvaluation(requiredModule, stack, index, HostDefined) | ||
@@ -725,3 +746,3 @@ if ( | ||
if (module.#Namespace) return module.#Namespace | ||
if (!(module.#Status !== ModuleStatus.unlinked)) assertFailed() | ||
if (!(module.#Status !== ModuleStatus.new && module.#Status !== ModuleStatus.unlinked)) assertFailed() | ||
const exportedNames = module.#GetExportedNames() | ||
@@ -780,49 +801,33 @@ | ||
static #GetImportedModule(module: Module, spec: string) { | ||
return module.#LoadedModules.get(spec) | ||
const record = module.#LoadedModules.get(spec) | ||
if (!record) assertFailed() | ||
return record | ||
} | ||
static #HostLoadImportedModule(referrer: Module, specifier: string, hostDefined: Task, payload: ModuleLoadState) { | ||
let promiseCapability = referrer.#ImportHookCache.get(specifier) | ||
function onFulfilled(module: Module) { | ||
promiseCapability?.Resolve(module) | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, NormalCompletion(module)) | ||
} | ||
function onRejected(reason: unknown) { | ||
promiseCapability?.Reject(reason) | ||
Module.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(reason)) | ||
} | ||
if (promiseCapability) { | ||
if (promiseCapability.Status.Type === 'Fulfilled') { | ||
onFulfilled(promiseCapability.Status.Value) | ||
return | ||
} else if (promiseCapability.Status.Type === 'Pending') { | ||
promiseCapability.Promise.then(onFulfilled, onRejected) | ||
return | ||
} | ||
// if error, fallthorugh | ||
} | ||
promiseCapability = PromiseCapability() | ||
referrer.#ImportHookCache.set(specifier, promiseCapability) | ||
static #LoadImportedModule( | ||
referrer: Module, | ||
specifier: string, | ||
hostDefined: Task, | ||
payload: GraphLoadingState | PromiseCapability<ModuleNamespace>, | ||
) { | ||
try { | ||
const result = referrer.#ImportHook(specifier, referrer.#Referral) | ||
if (result === null) throw new SyntaxError(`Failed to load module ${specifier}.`) | ||
try { | ||
const module = result as Module | ||
module.#Referral | ||
onFulfilled(module) | ||
return | ||
} catch {} | ||
// treat it as a Promise | ||
Promise.resolve(result).then((result) => { | ||
if (result === null) onRejected(new SyntaxError(`Failed to load module ${specifier}.`)) | ||
const importHookResult = referrer.#ImportHook | ||
? Reflect.apply(referrer.#ImportHook, referrer.#HandlerValue, [specifier]) | ||
: Reflect.apply(referrer.#ParentImportHook, undefined, [specifier]) | ||
const importHookPromise = Promise.resolve(importHookResult) | ||
const onFulfilled = (result: any) => { | ||
let completion: Completion<Module> | ||
try { | ||
const module = result as Module | ||
module.#Referral | ||
onFulfilled(module) | ||
;(result as Module).#HandlerValue | ||
completion = NormalCompletion(result) | ||
} catch (error) { | ||
onRejected(new TypeError('importHook must return an instance of Module')) | ||
completion = ThrowCompletion(new TypeError('importHook must return a Module instance')) | ||
} | ||
}) | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, completion, hostDefined) | ||
} | ||
const onRejected = (error: any) => { | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined) | ||
} | ||
importHookPromise.then(onFulfilled, onRejected) | ||
} catch (error) { | ||
onRejected(error) | ||
this.#FinishLoadingImportedModule(referrer, specifier, payload, ThrowCompletion(error), hostDefined) | ||
} | ||
@@ -834,4 +839,5 @@ } | ||
specifier: string, | ||
state: ModuleLoadState, | ||
payload: GraphLoadingState | PromiseCapability<ModuleNamespace>, | ||
result: Completion<Module>, | ||
hostDefined: Task, | ||
) { | ||
@@ -846,11 +852,14 @@ if (result.Type === 'normal') { | ||
} | ||
if (state.Action === 'graph-loading') { | ||
Module.#ContinueModuleLoading(state, result) | ||
if ('Visited' in payload) { | ||
Module.#ContinueModuleLoading(payload, result) | ||
} else { | ||
Module.#ContinueDynamicImport(state, result) | ||
Module.#ContinueDynamicImport(payload, result, hostDefined) | ||
} | ||
} | ||
static #ContinueDynamicImport(state: ModuleLoadState_DynamicImport, moduleCompletion: Completion<Module>) { | ||
const promiseCapability = state.PromiseCapability | ||
static #ContinueDynamicImport( | ||
promiseCapability: PromiseCapability<ModuleNamespace>, | ||
moduleCompletion: Completion<Module>, | ||
hostDefined: Task, | ||
) { | ||
if (moduleCompletion.Type === 'throw') { | ||
@@ -861,3 +870,3 @@ promiseCapability.Reject(moduleCompletion.Value) | ||
const module = moduleCompletion.Value | ||
const loadPromise = module.#LoadRequestedModules(state.HostDefined) | ||
const loadPromise = module.#LoadRequestedModules(hostDefined) | ||
function onRejected(reason: unknown) { | ||
@@ -869,10 +878,6 @@ promiseCapability.Reject(reason) | ||
module.#Link() | ||
const evaluatePromise = module.#Evaluate(state.HostDefined) | ||
const evaluatePromise = module.#Evaluate(hostDefined) | ||
function onFulfilled() { | ||
try { | ||
const namespace = Module.#GetModuleNamespace(module) | ||
promiseCapability.Resolve(namespace) | ||
} catch (error) { | ||
promiseCapability.Reject(error) | ||
} | ||
const namespace = Module.#GetModuleNamespace(module) | ||
promiseCapability.Resolve(namespace) | ||
} | ||
@@ -892,15 +897,9 @@ evaluatePromise.then(onFulfilled, onRejected) | ||
let HostDefinedName = 'Module<...>' | ||
if (typeof module.#Referral === 'symbol') HostDefinedName = `Module<@${module.#Referral.description}>` | ||
else if (typeof module.#Referral === 'string') HostDefinedName = `"${module.#Referral}"` | ||
const state: ModuleLoadState_DynamicImport = { | ||
Action: 'dynamic-import', | ||
PromiseCapability: promiseCapability, | ||
HostDefined: createTask(`import(${HostDefinedName})`), | ||
} | ||
Module.#ContinueDynamicImport(state, NormalCompletion(module)) | ||
const hostDefined = createTask(`import(<module block>)`) | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(module), hostDefined) | ||
return promiseCapability.Promise as any | ||
} | ||
setGlobalThis = (module, global) => (module.#GlobalThis = global) | ||
setParentGlobalThis = (module, global) => (module.#GlobalThis = global) | ||
setParentImportHook = (module, hook) => (module.#ParentImportHook = hook) | ||
setParentImportMetaHook = (module, hook) => (module.#ParentImportMetaHook = hook) | ||
} | ||
@@ -913,16 +912,9 @@ } | ||
interface ModuleLoadState_GraphLoading { | ||
Action: 'graph-loading' | ||
interface GraphLoadingState { | ||
PromiseCapability: PromiseCapability<void> | ||
IsLoading: boolean | ||
PendingModules: number | ||
PendingModulesCount: number | ||
Visited: Module[] | ||
HostDefined: Task | ||
} | ||
interface ModuleLoadState_DynamicImport { | ||
Action: 'dynamic-import' | ||
PromiseCapability: PromiseCapability<ModuleNamespace> | ||
HostDefined: Task | ||
} | ||
type ModuleLoadState = ModuleLoadState_DynamicImport | ModuleLoadState_GraphLoading | ||
@@ -978,15 +970,4 @@ const enum ModuleStatus { | ||
getOwnPropertyDescriptor: () => undefined, | ||
defineProperty() { | ||
// TODO: | ||
internalError() | ||
}, | ||
deleteProperty() { | ||
return false | ||
}, | ||
has() { | ||
return false | ||
}, | ||
ownKeys() { | ||
return [] | ||
}, | ||
defineProperty: () => false, | ||
deleteProperty: () => false, | ||
isExtensible: () => false, | ||
@@ -993,0 +974,0 @@ preventExtensions: () => true, |
@@ -39,4 +39,4 @@ // https://github.com/tc39/proposal-compartments/blob/775024d93830ee6464363b4b373d9353425a0776/README.md | ||
export interface VirtualModuleRecord { | ||
bindings?: Array<Binding> | ||
execute?(environment: any, context?: VirtualModuleRecordExecuteContext): void | Promise<void> | ||
bindings?: Array<Binding> | undefined | ||
execute?(environment: any, context: VirtualModuleRecordExecuteContext): void | Promise<void> | ||
needsImportMeta?: boolean | undefined | ||
@@ -50,10 +50,14 @@ needsImport?: boolean | undefined | ||
importMeta?: object | ||
import?(spec: string, options?: ImportCallOptions): Promise<ModuleNamespace> | ||
import?<T extends ModuleNamespace = ModuleNamespace>( | ||
spec: string | Module<T>, | ||
options?: ImportCallOptions, | ||
): Promise<T> | ||
globalThis: typeof globalThis | ||
} | ||
export interface StaticModuleRecordInstance { | ||
get bindings(): readonly Binding[] | ||
export type ImportHook = (importSpecifier: string) => PromiseLike<Module | null> | Module | null | ||
export type ImportMetaHook = (importMeta: object) => void | ||
export interface ModuleHandler { | ||
importHook?: ImportHook | undefined | ||
importMetaHook?: ImportMetaHook | ||
} | ||
export type ImportHook = (importSpecifier: string, referrer: Referral) => PromiseLike<Module | null> | Module | null | ||
export type Referral = symbol | string | number | bigint |
@@ -10,12 +10,8 @@ function getOpaqueProxy() { | ||
/** @internal */ | ||
export function internalError(): never { | ||
throw new TypeError('Internal error.') | ||
export function assertFailed(message?: string): never { | ||
throw new TypeError('Assertion failed.' + (message ? ' ' : '') + message) | ||
} | ||
/** @internal */ | ||
export function assertFailed(): never { | ||
throw new TypeError('Assertion failed.') | ||
} | ||
/** @internal */ | ||
export function unreachable(val: never): never { | ||
throw new TypeError('Unreachable') | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
230244
3.27%93
481.25%4256
-0.09%