diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index e40ba110b8..5efd594b62 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -835,18 +835,22 @@ ModuleObject::fixEnvironmentsAfterCompartmentMerge() AssertModuleScopesMatch(this); } -bool -ModuleObject::hasScript() const +JSScript* +ModuleObject::maybeScript() const { - // When modules are parsed via the Reflect.parse() API, the module object - // doesn't have a script. - return !getReservedSlot(ScriptSlot).isUndefined(); + Value value = getReservedSlot(ScriptSlot); + if (value.isUndefined()) + return nullptr; + + return value.toGCThing()->as(); } JSScript* ModuleObject::script() const { - return static_cast(getReservedSlot(ScriptSlot).toPrivate()); + JSScript* ptr = maybeScript(); + MOZ_RELEASE_ASSERT(ptr); + return ptr; } static inline void @@ -906,8 +910,8 @@ ModuleObject::enclosingScope() const ModuleObject::trace(JSTracer* trc, JSObject* obj) { ModuleObject& module = obj->as(); - if (module.hasScript()) { - JSScript* script = module.script(); + JSScript* script = module.maybeScript(); + if (script) { TraceManuallyBarrieredEdge(trc, &script, "Module script"); module.setReservedSlot(ScriptSlot, PrivateValue(script)); } @@ -976,6 +980,11 @@ ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue #endif RootedScript script(cx, self->script()); + + // The top-level script if a module is only ever executed once. Clear the + // reference to prevent us keeping this alive unnecessarily. + self->setReservedSlot(ScriptSlot, UndefinedValue()); + RootedModuleEnvironmentObject scope(cx, self->environment()); if (!scope) { JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated"); diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index aaeeb2faaa..db75591b7c 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -266,6 +266,7 @@ class ModuleObject : public NativeObject #endif void fixEnvironmentsAfterCompartmentMerge(); + JSScript* maybeScript() const; JSScript* script() const; Scope* enclosingScope() const; ModuleEnvironmentObject& initialEnvironment() const; @@ -312,7 +313,6 @@ class ModuleObject : public NativeObject static void trace(JSTracer* trc, JSObject* obj); static void finalize(js::FreeOp* fop, JSObject* obj); - bool hasScript() const; bool hasImportBindings() const; FunctionDeclarationVector* functionDeclarations(); }; diff --git a/js/src/shell/ModuleLoader.js b/js/src/shell/ModuleLoader.js index 84f5847ba0..a457d7aa44 100644 --- a/js/src/shell/ModuleLoader.js +++ b/js/src/shell/ModuleLoader.js @@ -38,6 +38,18 @@ Reflect.Loader = new class { module.declarationInstantiation(); return module.evaluation(); } + + populateImportMeta(module, metaObject) { + // For the shell, use the script's filename as the base URL. + + let path; + if (ReflectApply(MapPrototypeHas, this.modulePaths, [module])) { + path = ReflectApply(MapPrototypeGet, this.modulePaths, [module]); + } else { + path = "(unknown)"; + } + metaObject.url = path; + } }; setModuleResolveHook((referencingInfo, requestName) => { diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 7c49d8faa9..6b1675bc09 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2830,7 +2830,7 @@ DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, Sprinter* sprinte RootedScript script(cx); RootedValue value(cx, p.argv[i]); if (value.isObject() && value.toObject().is()) - script = value.toObject().as().script(); + script = value.toObject().as().maybeScript(); else script = ValueToScript(cx, value, fun.address()); if (!script) @@ -4132,23 +4132,48 @@ ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp) return true; } -ShellModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject) +static bool +SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp) { - // For the shell, just use the script's filename as the base URL. - RootedScript script(cx, module->as().script()); - const char* filename = script->scriptSource()->filename(); - MOZ_ASSERT(filename); - - RootedString url(cx, NewStringCopyZ(cx, filename)); - if (!url) + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + "setModuleMetadataHook", "0", "s"); return false; + } - if (!JS_DefineProperty(cx, metaObject, "url", url, JSPROP_ENUMERATE)) + 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(GlobalAppSlotModuleMetadataHook, args[0]); + + args.rval().setUndefined(); return true; } +static bool +CallModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject) +{ + Handle global = cx->global(); + RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleMetadataHook)); + if (hookValue.isUndefined()) { + JS_ReportErrorASCII(cx, "Module metadata hook not set"); + return false; + } + MOZ_ASSERT(hookValue.toObject().is()); + + JS::AutoValueArray<2> args(cx); + args[0].setObject(*module); + args[1].setObject(*metaObject); + + RootedValue dummy(cx); + return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy); +} + static bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc, Value* vp) { @@ -5616,7 +5641,11 @@ DumpScopeChain(JSContext* cx, unsigned argc, Value* vp) } script = JSFunction::getOrCreateScript(cx, fun); } else { - script = obj->as().script(); + script = obj->as().maybeScript(); + if (!script) { + JS_ReportErrorASCII(cx, "module does not have an associated script"); + return false; + } } script->bodyScope()->dump(); @@ -7451,7 +7480,7 @@ ProcessArgs(JSContext* cx, OptionParser* op) if (const char* path = op->getStringOption("module-load-path")) moduleLoadPath = path; - if (!modulePaths.empty() && !InitModuleLoader(cx)) + if (!InitModuleLoader(cx)) return false; while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) {