diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 760fb9c04d..e40ba110b8 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -5,6 +5,7 @@ #include "builtin/ModuleObject.h" +#include "builtin/Promise.h" #include "builtin/SelfHostingDefines.h" #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" @@ -1030,6 +1031,22 @@ ModuleObject::Evaluate(JSContext* cx, HandleModuleObject self) return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluate); } +/* static */ ModuleNamespaceObject* +ModuleObject::GetOrCreateModuleNamespace(JSContext* cx, HandleModuleObject self) +{ + FixedInvokeArgs<1> args(cx); + args[0].setObject(*self); + + RootedValue result(cx); + if (!CallSelfHostedFunction(cx, cx->names().GetModuleNamespace, UndefinedHandleValue, args, + &result)) + { + return nullptr; + } + + return &result.toObject().as(); +} + DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, NamespaceSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, status, StatusSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluationError, EvaluationErrorSlot) @@ -1519,3 +1536,88 @@ js::GetOrCreateModuleMetaObject(JSContext* cx, HandleObject moduleArg) return metaObject; } + +JSObject* +js::CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier) +{ + JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook; + if (!moduleResolveHook) { + JS_ReportErrorASCII(cx, "Module resolve hook not set"); + return nullptr; + } + + RootedObject result(cx, moduleResolveHook(cx, referencingPrivate, specifier)); + if (!result) { + return nullptr; + } + + if (!result->is()) { + JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object"); + return nullptr; + } + + return result; +} + +JSObject* +js::StartDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleValue specifierArg) +{ + RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx)); + if (!promiseConstructor) { + return nullptr; + } + + RootedObject promiseObject(cx, JS::NewPromiseObject(cx, nullptr)); + if (!promiseObject) { + return nullptr; + } + + Handle promise = promiseObject.as(); + + RootedString specifier(cx, ToString(cx, specifierArg)); + if (!specifier) { + if (!RejectPromiseWithPendingError(cx, promise)) + return nullptr; + return promise; + } + + JS::ModuleDynamicImportHook importHook = cx->runtime()->moduleDynamicImportHook; + MOZ_ASSERT(importHook); + if (!importHook(cx, referencingPrivate, specifier, promise)) { + if (!RejectPromiseWithPendingError(cx, promise)) + return nullptr; + return promise; + } + + return promise; +} + +bool +js::FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier, + HandleObject promiseArg) +{ + Handle promise = promiseArg.as(); + + if (cx->isExceptionPending()) { + return RejectPromiseWithPendingError(cx, promise); + } + + RootedObject result(cx, CallModuleResolveHook(cx, referencingPrivate, specifier)); + if (!result) { + return RejectPromiseWithPendingError(cx, promise); + } + + RootedModuleObject module(cx, &result->as()); + if (module->status() != MODULE_STATUS_EVALUATED) { + JS_ReportErrorASCII(cx, "Unevaluated or errored module returned by module resolve hook"); + return RejectPromiseWithPendingError(cx, promise); + } + + RootedObject ns(cx, ModuleObject::GetOrCreateModuleNamespace(cx, module)); + if (!ns) { + return RejectPromiseWithPendingError(cx, promise); + } + + RootedValue value(cx, ObjectValue(*ns)); + return PromiseObject::resolve(cx, promise, value); +} diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index dfd76ee847..aaeeb2faaa 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -288,6 +288,9 @@ class ModuleObject : public NativeObject static bool Instantiate(JSContext* cx, HandleModuleObject self); static bool Evaluate(JSContext* cx, HandleModuleObject self); + static ModuleNamespaceObject* GetOrCreateModuleNamespace(JSContext* cx, + HandleModuleObject self); + void setMetaObject(JSObject* obj); // For BytecodeEmitter. @@ -374,6 +377,16 @@ class MOZ_STACK_CLASS ModuleBuilder JSObject* GetOrCreateModuleMetaObject(JSContext* cx, HandleObject module); +JSObject* +CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier); + +JSObject* +StartDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleValue specifier); + +bool +FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier, + HandleObject promise); + } // namespace js template<> diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 0aaf68a00f..4da00a8671 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -3863,6 +3863,16 @@ OriginalPromiseThenBuiltin(JSContext* cx, HandleValue promiseVal, HandleValue on return true; } +MOZ_MUST_USE bool +js::RejectPromiseWithPendingError(JSContext* cx, Handle promise) +{ + // Not much we can do about uncatchable exceptions, just bail. + RootedValue exn(cx); + if (!GetAndClearException(cx, &exn)) + return false; + return PromiseObject::reject(cx, promise, exn); +} + static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction); diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 3d6dbe0e62..5e5c850d60 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -146,6 +146,9 @@ OriginalPromiseThen(JSContext* cx, Handle promise, MOZ_MUST_USE JSObject* PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value); +MOZ_MUST_USE bool +RejectPromiseWithPendingError(JSContext* cx, Handle promise); + MOZ_MUST_USE PromiseObject* CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 360c1b89a7..c19ea6cff8 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -9088,8 +9088,14 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; case PNK_CALL_IMPORT: - reportError(nullptr, JSMSG_NO_DYNAMIC_IMPORT); - return false; + if (!cx->asJSContext()->runtime()->moduleDynamicImportHook) { + reportError(nullptr, JSMSG_NO_DYNAMIC_IMPORT); + return false; + } + if (!emitTree(pn->as().right()) || !emit1(JSOP_DYNAMIC_IMPORT)) { + return false; + } + break; case PNK_SETTHIS: if (!emitSetThis(&pn->as())) diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 75aa618cfd..173ffcb8d7 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -595,7 +595,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) } BinaryNodeType newCallImport(Node importHolder, Node singleArg) { - return new_(PNK_CALL_IMPORT, JSOP_NOP, importHolder, singleArg); + return new_(PNK_CALL_IMPORT, JSOP_DYNAMIC_IMPORT, importHolder, singleArg); } UnaryNodeType newExprStatement(Node expr, uint32_t end) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 677337ac88..ed3361c33a 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -837,7 +837,6 @@ class NameResolver case PNK_CALL_IMPORT: { BinaryNode* node = &cur->as(); - MOZ_ASSERT(cur->isArity(PN_BINARY)); if (!resolve(node->right(), prefix)) return false; break; diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index f094a3b013..54d2bef108 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -541,6 +541,8 @@ IsTypeofKind(ParseNodeKind kind) * PNK_ARRAYPUSH unary pn_op: JSOP_ARRAYCOMP * pn_kid: array comprehension expression * PNK_NOP (NullaryNode) + * PNK_IMPORT_META (BinaryNode) + * PNK_CALL_IMPORT (BinaryNode) */ enum ParseNodeArity { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index ad38321bc0..5ed28e220c 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -10515,6 +10515,10 @@ Parser::importExpr(YieldHandling yieldHandling) MUST_MATCH_TOKEN_MOD(TOK_RP, TokenStream::Operand, JSMSG_PAREN_AFTER_ARGS); + if (!context->asJSContext()->runtime()->moduleDynamicImportHook && !abortIfSyntaxParser()) { + return null(); + } + return handler.newCallImport(importHolder, arg); } else { error(JSMSG_UNEXPECTED_TOKEN, TokenKindToDesc(next)); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index d027e09c25..521c252a16 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -4708,3 +4708,28 @@ BaselineCompiler::emit_JSOP_IMPORTMETA() frame.push(ObjectValue(*metaObject)); return true; } + +typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleValue, HandleValue); +static const VMFunction StartDynamicModuleImportInfo = + FunctionInfo(js::StartDynamicModuleImport, + "StartDynamicModuleImport"); + +bool +BaselineCompiler::emit_JSOP_DYNAMIC_IMPORT() +{ + RootedValue referencingPrivate(cx, FindScriptOrModulePrivateForScript(script)); + + // Put specifier value in R0. + frame.popRegsAndSync(1); + + prepareVMCall(); + pushArg(R0); + pushArg(referencingPrivate); + if (!callVM(StartDynamicModuleImportInfo)) { + return false; + } + + masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); + frame.push(R0); + return true; +} diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index d1acec2f01..24a97f20b6 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -241,7 +241,8 @@ namespace jit { _(JSOP_JUMPTARGET) \ _(JSOP_IS_CONSTRUCTING) \ _(JSOP_TRY_DESTRUCTURING_ITERCLOSE) \ - _(JSOP_IMPORTMETA) + _(JSOP_IMPORTMETA) \ + _(JSOP_DYNAMIC_IMPORT) class BaselineCompiler : public BaselineCompilerSpecific { diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 78d39bfc52..9aa97be31c 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2402,6 +2402,19 @@ CodeGenerator::visitNullarySharedStub(LNullarySharedStub* lir) } } +typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleValue, HandleValue); +static const VMFunction StartDynamicModuleImportInfo = + FunctionInfo(js::StartDynamicModuleImport, + "StartDynamicModuleImport"); + +void +CodeGenerator::visitDynamicImport(LDynamicImport* lir) +{ + pushArg(ToValue(lir, LDynamicImport::SpecifierIndex)); + pushArg(ToValue(lir, LDynamicImport::ReferencingPrivateIndex)); + callVM(StartDynamicModuleImportInfo, lir); +} + typedef JSObject* (*LambdaFn)(JSContext*, HandleFunction, HandleObject); static const VMFunction LambdaInfo = FunctionInfo(js::Lambda, "Lambda"); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index ff09222f17..5e4ddd606d 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -442,6 +442,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitRandom(LRandom* ins); void visitSignExtend(LSignExtend* ins); + void visitDynamicImport(LDynamicImport* lir); #ifdef DEBUG void emitDebugForceBailing(LInstruction* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index a7bb77024d..281dde6cf4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -19,6 +19,7 @@ #include "jit/Lowering.h" #include "jit/MIRGraph.h" #include "vm/ArgumentsObject.h" +#include "vm/EnvironmentObject.h" #include "vm/Opcodes.h" #include "vm/RegExpStatics.h" #include "vm/TraceLogging.h" @@ -2208,6 +2209,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_IMPORTMETA: return jsop_importmeta(); + case JSOP_DYNAMIC_IMPORT: + return jsop_dynamic_import(); + case JSOP_DEBUGCHECKSELFHOSTED: { #ifdef DEBUG @@ -14264,6 +14268,20 @@ IonBuilder::jsop_importmeta() return true; } +bool +IonBuilder::jsop_dynamic_import() +{ + Value referencingPrivate = FindScriptOrModulePrivateForScript(script()); + MConstant* ref = constant(referencingPrivate); + + MDefinition* specifier = current->pop(); + + MDynamicImport* ins = MDynamicImport::New(alloc(), ref, specifier); + current->add(ins); + current->push(ins); + return resumeAfter(ins); +} + MInstruction* IonBuilder::addConvertElementsToDoubles(MDefinition* elements) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 9d4a530b06..6f3fc027f6 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -789,6 +789,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_checkobjcoercible(); MOZ_MUST_USE bool jsop_pushcallobj(); MOZ_MUST_USE bool jsop_importmeta(); + MOZ_MUST_USE bool jsop_dynamic_import(); /* Inlining. */ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index f143ff9e04..a68e2fac98 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2427,6 +2427,15 @@ LIRGenerator::visitNullarySharedStub(MNullarySharedStub* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitDynamicImport(MDynamicImport* ins) +{ + LDynamicImport* lir = new(alloc()) LDynamicImport(useBoxAtStart(ins->referencingPrivate()), + useBoxAtStart(ins->specifier())); + defineReturn(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitLambda(MLambda* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 5340fce762..393d8030e9 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -334,6 +334,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitCheckIsCallable(MCheckIsCallable* ins); void visitCheckObjCoercible(MCheckObjCoercible* ins); void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins); + void visitDynamicImport(MDynamicImport* ins); }; } // namespace jit diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index ea00529e09..f6b26ee762 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -8379,6 +8379,22 @@ class MSubstr } }; +class MDynamicImport : public MBinaryInstruction, + public BoxInputsPolicy::Data +{ + explicit MDynamicImport(MDefinition* referencingPrivate, MDefinition* specifier) + : MBinaryInstruction(referencingPrivate, specifier) + { + setResultType(MIRType::Object); + } + + public: + INSTRUCTION_HEADER(DynamicImport) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, referencingPrivate)) + NAMED_OPERANDS((1, specifier)) +}; + struct LambdaFunctionInfo { // The functions used in lambdas are the canonical original function in diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index c1644ba8b3..8f411b04a0 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -284,6 +284,7 @@ namespace jit { _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ + _(DynamicImport) \ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index f97dc1534f..ffba2583d6 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -4939,6 +4939,26 @@ class LNullarySharedStub : public LCallInstructionHelper } }; +class LDynamicImport : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0> +{ + public: + LIR_HEADER(DynamicImport) + + static const size_t ReferencingPrivateIndex = 0; + static const size_t SpecifierIndex = BOX_PIECES; + + explicit LDynamicImport(const LBoxAllocation& referencingPrivate, + const LBoxAllocation& specifier) + { + setBoxOperand(ReferencingPrivateIndex, referencingPrivate); + setBoxOperand(SpecifierIndex, specifier); + } + + const MDynamicImport* mir() const { + return mir_->toDynamicImport(); + } +}; + class LLambdaForSingleton : public LCallInstructionHelper<1, 1, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 7f4cd770ac..2c8799c6fe 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -401,6 +401,7 @@ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ + _(DynamicImport) \ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ diff --git a/js/src/js.msg b/js/src/js.msg index 218d60f83d..b66de521b2 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -592,6 +592,7 @@ MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT, 0, JSEXN_SYNTAXERR, "export not found f MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found") MSG_DEF(JSMSG_BAD_MODULE_STATUS, 0, JSEXN_INTERNALERR, "module record has unexpected status") MSG_DEF(JSMSG_NO_DYNAMIC_IMPORT, 0, JSEXN_SYNTAXERR, "dynamic module import is not implemented") +MSG_DEF(JSMSG_IMPORT_SCRIPT_NOT_FOUND, 0, JSEXN_TYPEERR, "can't find referencing script for dynamic module import") // Promise MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1837fea056..b675a1b4fc 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4712,6 +4712,29 @@ JS::SetModuleMetadataHook(JSContext* cx, JS::ModuleMetadataHook func) cx->runtime()->moduleMetadataHook = func; } +JS_PUBLIC_API(JS::ModuleDynamicImportHook) +JS::GetModuleDynamicImportHook(JSContext* cx) +{ + AssertHeapIsIdle(cx); + return cx->runtime()->moduleDynamicImportHook; +} + +JS_PUBLIC_API(void) +JS::SetModuleDynamicImportHook(JSContext* cx, JS::ModuleDynamicImportHook func) +{ + AssertHeapIsIdle(cx); + cx->runtime()->moduleDynamicImportHook = func; +} + +JS_PUBLIC_API(bool) +JS::FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier, + HandleObject promise) +{ + AssertHeapIsIdle(cx); + + return js::FinishDynamicModuleImport(cx, referencingPrivate, specifier, promise); +} + JS_PUBLIC_API(bool) JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JS::MutableHandleObject module) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index db6612b700..ad63d9d315 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4353,6 +4353,25 @@ GetModuleMetadataHook(JSContext* cx); extern JS_PUBLIC_API(void) SetModuleMetadataHook(JSContext* cx, ModuleMetadataHook func); +using ModuleDynamicImportHook = bool (*)(JSContext* cx, HandleValue referencingPrivate, + HandleString specifier, HandleObject promise); + +/** + * Get the HostResolveImportedModule hook for the runtime. + */ +extern JS_PUBLIC_API(ModuleDynamicImportHook) +GetModuleDynamicImportHook(JSContext* cx); + +/** + * Set the HostResolveImportedModule hook for the runtime to the given function. + */ +extern JS_PUBLIC_API(void) +SetModuleDynamicImportHook(JSContext* cx, ModuleDynamicImportHook func); + +extern JS_PUBLIC_API(bool) +FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier, + HandleObject promise); + /** * Parse the given source buffer as a module in the scope of the current global * of cx and return a source text module record. diff --git a/js/src/shell/ModuleLoader.js b/js/src/shell/ModuleLoader.js index d8767f05f4..84f5847ba0 100644 --- a/js/src/shell/ModuleLoader.js +++ b/js/src/shell/ModuleLoader.js @@ -33,11 +33,28 @@ Reflect.Loader = new class { return module; } - ["import"](name, referrer) { + ["import"](name, referencingInfo) { let module = this.loadAndParse(name); module.declarationInstantiation(); return module.evaluation(); } }; -setModuleResolveHook((module, requestName) => Reflect.Loader.loadAndParse(requestName)); +setModuleResolveHook((referencingInfo, requestName) => { + let path = ReflectLoader.resolve(requestName, referencingInfo); + return ReflectLoader.loadAndParse(path); +}); + +setModuleMetadataHook((module, metaObject) => { + ReflectLoader.populateImportMeta(module, metaObject); +}); + +setModuleDynamicImportHook((referencingInfo, specifier, promise) => { + try { + let path = ReflectLoader.resolve(specifier, referencingInfo); + ReflectLoader.loadAndExecute(path); + finishDynamicModuleImport(referencingInfo, specifier, promise); + } catch (err) { + abortDynamicModuleImport(referencingInfo, specifier, promise, err); + } +}); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 9e8d5a0cba..7c49d8faa9 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -603,6 +603,32 @@ EnvironmentPreparer::invoke(HandleObject scope, Closure& closure) return; } +static bool +RegisterScriptPathWithModuleLoader(JSContext* cx, HandleScript script, const char* filename) +{ + // Set the private value associated with a script to a object containing the + // script's filename so that the module loader can use it to resolve + // relative imports. + + RootedString path(cx, JS_NewStringCopyZ(cx, filename)); + if (!path) { + return false; + } + + RootedObject infoObject(cx, JS_NewPlainObject(cx)); + if (!infoObject) { + return false; + } + + RootedValue pathValue(cx, StringValue(path)); + if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) { + return false; + } + + JS::SetScriptPrivate(script, ObjectValue(*infoObject)); + return true; +} + static MOZ_MUST_USE bool RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) { @@ -635,6 +661,10 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) MOZ_ASSERT(script); } + if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) { + return false; + } + #ifdef DEBUG if (dumpEntrainedVariables) AnalyzeEntrainedVariables(cx, script); @@ -4035,7 +4065,7 @@ SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) } static JSObject* -CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier) +ShellModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier) { ShellContext* sc = GetShellContext(cx); @@ -4119,6 +4149,109 @@ ShellModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObj return true; } +static bool +SetModuleDynamicImportHook(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "setModuleDynamicImportHook", "0", "s"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is()) { + const char* typeName = InformalValueTypeName(args[0]); + JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName); + return false; + } + + Handle global = cx->global(); + global->setReservedSlot(GlobalAppSlotModuleDynamicImportHook, args[0]); + + args.rval().setUndefined(); + return true; +} + +static bool +FinishDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 3) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "finishDynamicModuleImport", "0", "s"); + return false; + } + + if (!args[1].isString()) { + return ReportArgumentTypeError(cx, args[1], "String"); + } + + if (!args[2].isObject() || !args[2].toObject().is()) { + return ReportArgumentTypeError(cx, args[2], "PromiseObject"); + } + + RootedString specifier(cx, args[1].toString()); + Rooted promise(cx, &args[2].toObject().as()); + + return js::FinishDynamicModuleImport(cx, args[0], specifier, promise); +} + +static bool +AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 4) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "abortDynamicModuleImport", "0", "s"); + return false; + } + + if (!args[1].isString()) { + return ReportArgumentTypeError(cx, args[1], "String"); + } + + if (!args[2].isObject() || !args[2].toObject().is()) { + return ReportArgumentTypeError(cx, args[2], "PromiseObject"); + } + + if (!args[3].isObject() || !args[3].toObject().is()) { + return ReportArgumentTypeError(cx, args[3], "ErrorObject"); + } + + RootedString specifier(cx, args[1].toString()); + Rooted promise(cx, &args[2].toObject().as()); + Rooted error(cx, &args[3].toObject().as()); + + Rooted value(cx, ObjectValue(*error)); + cx->setPendingException(value); + return js::FinishDynamicModuleImport(cx, args[0], specifier, promise); +} + +static bool +ShellModuleDynamicImportHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier, + HandleObject promise) +{ + Handle global = cx->global(); + RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleDynamicImportHook)); + if (hookValue.isUndefined()) { + JS_ReportErrorASCII(cx, "Module resolve hook not set"); + return false; + } + MOZ_ASSERT(hookValue.toObject().is()); + + JS::AutoValueArray<3> args(cx); + args[0].set(referencingPrivate); + args[1].setString(specifier); + args[2].setObject(*promise); + + RootedValue result(cx); + if (!JS_CallFunctionValue(cx, nullptr, hookValue, args, &result)) { + return false; + } + + return true; +} + static bool GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp) { @@ -5984,7 +6117,7 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " Parses source text as a module and returns a Module object."), JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0, -"setModuleResolveHook(function(module, specifier) {})", +"setModuleResolveHook(function(referrer, specifier))", " Set the HostResolveImportedModule hook to |function|.\n" " This hook is used to look up a previously loaded module object. It should\n" " be implemented by the module loader."), @@ -5997,6 +6130,28 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "getModulePrivate(scriptObject)", " Get the private value associated with a module object.\n"), + JS_FN_HELP("setModuleMetadataHook", SetModuleMetadataHook, 1, 0, +"setModuleMetadataHook(function(module) {})", +" Set the HostPopulateImportMeta hook to |function|.\n" +" This hook is used to create the metadata object returned by import.meta for\n" +" a module. It should be implemented by the module loader."), + + JS_FN_HELP("setModuleDynamicImportHook", SetModuleDynamicImportHook, 1, 0, +"setModuleDynamicImportHook(function(referrer, specifier, promise))", +" Set the HostImportModuleDynamically hook to |function|.\n" +" This hook is used to dynamically import a module. It should\n" +" be implemented by the module loader."), + + JS_FN_HELP("finishDynamicModuleImport", FinishDynamicModuleImport, 3, 0, +"finishDynamicModuleImport(referrer, specifier, promise)", +" The module loader's dynamic import hook should call this when the module has" +" been loaded successfully."), + + JS_FN_HELP("abortDynamicModuleImport", AbortDynamicModuleImport, 4, 0, +"abortDynamicModuleImport(referrer, specifier, promise, error)", +" The module loader's dynamic import hook should call this when the module " +" import has failed."), + JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0, "getModuleLoadPath()", " Return any --module-load-path argument passed to the shell. Used by the\n" @@ -8054,7 +8209,8 @@ main(int argc, char** argv, char** envp) js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback); - JS::SetModuleResolveHook(cx->runtime(), CallModuleResolveHook); + JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook); + JS::SetModuleDynamicImportHook(cx, ShellModuleDynamicImportHook); JS::SetModuleMetadataHook(cx, ShellModuleMetadataHook); result = Shell(cx, &op, envp); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 82f0ebadaa..3304ae0305 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -155,6 +155,7 @@ macro(GeneratorFunction, GeneratorFunction, "GeneratorFunction") \ macro(get, get, "get") \ macro(getInternals, getInternals, "getInternals") \ + macro(GetModuleNamespace, GetModuleNamespace, "GetModuleNamespace") \ macro(getOwnPropertyDescriptor, getOwnPropertyDescriptor, "getOwnPropertyDescriptor") \ macro(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames") \ macro(getPrefix, getPrefix, "get ") \ diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index 698b36cb29..a736ba4f27 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -3083,6 +3083,23 @@ js::GetModuleObjectForScript(JSScript* script) return nullptr; } +Value +js::FindScriptOrModulePrivateForScript(JSScript* script) +{ + while (script) { + ScriptSourceObject* sso = &script->scriptSourceUnwrap(); + Value value = sso->getPrivate(); + if (!value.isUndefined()) { + return value; + } + + MOZ_ASSERT(sso->introductionScript() != script); + script = sso->introductionScript(); + } + + return UndefinedValue(); +} + bool js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue res) diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h index b16d079ce6..a74018047a 100644 --- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -1075,7 +1075,11 @@ CreateObjectsForEnvironmentChain(JSContext* cx, AutoObjectVector& chain, HandleObject terminatingEnv, MutableHandleObject envObj); -ModuleObject* GetModuleObjectForScript(JSScript* script); +ModuleObject* +GetModuleObjectForScript(JSScript* script); + +Value +FindScriptOrModulePrivateForScript(JSScript* script); ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 6ed953bedb..5fccc00d54 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4201,6 +4201,22 @@ CASE(JSOP_IMPORTMETA) } END_CASE(JSOP_IMPORTMETA) +CASE(JSOP_DYNAMIC_IMPORT) +{ + ReservedRooted referencingPrivate(&rootValue0); + referencingPrivate = FindScriptOrModulePrivateForScript(script); + + ReservedRooted specifier(&rootValue1); + POP_COPY_TO(specifier); + + JSObject* promise = StartDynamicModuleImport(cx, referencingPrivate, specifier); + if (!promise) + goto error; + + PUSH_OBJECT(*promise); +} +END_CASE(JSOP_DYNAMIC_IMPORT) + CASE(JSOP_SUPERFUN) { ReservedRooted superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS)); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 9c9240702b..8624b5345e 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2345,13 +2345,23 @@ * Operands: * Stack: => import.meta */ \ - macro(JSOP_IMPORTMETA, 233, "importmeta", NULL, 1, 0, 1, JOF_BYTE) + macro(JSOP_IMPORTMETA, 233, "importmeta", NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Dynamic import of the module specified by the string value on the top of + * the stack. + * + * Category: Variables and Scopes + * Type: Modules + * Operands: + * Stack: arg => rval + */ \ + macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) + /* * In certain circumstances it may be useful to "pad out" the opcode space to * a power of two. Use this macro to do so. */ #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \ - macro(234) \ macro(235) \ macro(236) \ macro(237) \ diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index c7e93d17be..c837769e09 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -244,7 +244,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) js::StackFormat::Default : js::StackFormat::SpiderMonkey), moduleResolveHook(), - moduleMetadataHook() + moduleMetadataHook(), + moduleDynamicImportHook() { setGCStoreBufferPtr(&gc.storeBuffer); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index c76383898a..7ac332e007 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1300,6 +1300,10 @@ struct JSRuntime : public JS::shadow::Runtime, // A hook that implements the abstract operations // HostGetImportMetaProperties and HostFinalizeImportMeta. JS::ModuleMetadataHook moduleMetadataHook; + + // A hook that implements the abstract operation + // HostImportModuleDynamically. + JS::ModuleDynamicImportHook moduleDynamicImportHook; }; namespace js { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index efedbc0bb1..4b22001a4e 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1999,15 +1999,9 @@ intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp) RootedModuleObject module(cx, &args[0].toObject().as()); RootedString specifier(cx, args[1].toString()); - JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook; - if (!moduleResolveHook) { - JS_ReportErrorASCII(cx, "Module resolve hook not set"); - return false; - } - - RootedObject result(cx); RootedValue referencingPrivate(cx, JS::GetModulePrivate(module)); - result = moduleResolveHook(cx, referencingPrivate, specifier); + RootedObject result(cx, CallModuleResolveHook(cx, referencingPrivate, specifier)); + if (!result) return false; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index cdc1046bcc..a18874c8f1 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -151,8 +151,8 @@ AssertScopeMatchesEnvironment(Scope* scope, JSObject* originalEnv) break; case ScopeKind::Module: - MOZ_ASSERT(env->as().module().script() == - si.scope()->as().script()); + MOZ_ASSERT(&env->as().module() == + si.scope()->as().module()); env = &env->as().enclosingEnvironment(); break; }