Bug 1482153 - Provide a way of associating a private value with a script or module r=jandem rs=hsivonen
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 16 Oct 2018 13:44:12 +0100
changeset 489764 180eb0ea89bcf02d511d4e05f493583d125177ea
parent 489763 97be1d70d0cd4637a4bd02e984d179b3f1a600e8
child 489765 81cff62bc428d4fc54c81d0445b69ad554b31cda
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjandem, hsivonen
bugs1482153
milestone64.0a1
Bug 1482153 - Provide a way of associating a private value with a script or module r=jandem rs=hsivonen
dom/script/ModuleScript.cpp
dom/script/ScriptLoader.cpp
dom/script/ScriptLoader.h
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/ModuleLoader.js
js/src/shell/js.cpp
js/src/vm/JSScript.h
js/src/vm/SelfHosting.cpp
--- a/dom/script/ModuleScript.cpp
+++ b/dom/script/ModuleScript.cpp
@@ -51,19 +51,18 @@ ModuleScript::ModuleScript(ScriptLoader*
   MOZ_ASSERT(!HasErrorToRethrow());
 }
 
 void
 ModuleScript::UnlinkModuleRecord()
 {
   // Remove module's back reference to this object request if present.
   if (mModuleRecord) {
-    MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() ==
-               this);
-    JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue());
+    MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).toPrivate() == this);
+    JS::SetModulePrivate(mModuleRecord, JS::UndefinedValue());
     mModuleRecord = nullptr;
   }
 }
 
 ModuleScript::~ModuleScript()
 {
   // The object may be destroyed without being unlinked first.
   UnlinkModuleRecord();
@@ -76,17 +75,17 @@ ModuleScript::SetModuleRecord(JS::Handle
   MOZ_ASSERT(!mModuleRecord);
   MOZ_ASSERT(!HasParseError());
   MOZ_ASSERT(!HasErrorToRethrow());
 
   mModuleRecord = aModuleRecord;
 
   // Make module's host defined field point to this module script object.
   // This is cleared in the UnlinkModuleRecord().
-  JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this));
+  JS::SetModulePrivate(mModuleRecord, JS::PrivateValue(this));
   HoldJSObjects(this);
 }
 
 void
 ModuleScript::SetParseError(const JS::Value& aError)
 {
   MOZ_ASSERT(!aError.isUndefined());
   MOZ_ASSERT(!HasParseError());
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -754,23 +754,23 @@ ScriptLoader::StartFetchingModuleAndDepe
     return ready;
   }
 
   return ready;
 }
 
 // 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
 JSObject*
-HostResolveImportedModule(JSContext* aCx, JS::Handle<JSObject*> aModule,
+HostResolveImportedModule(JSContext* aCx,
+                          JS::Handle<JS::Value> aReferencingPrivate,
                           JS::Handle<JSString*> aSpecifier)
 {
   // Let referencing module script be referencingModule.[[HostDefined]].
-  JS::Value value = JS::GetModuleHostDefinedField(aModule);
-  auto script = static_cast<ModuleScript*>(value.toPrivate());
-  MOZ_ASSERT(script->ModuleRecord() == aModule);
+  auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+  MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate);
 
   // Let url be the result of resolving a module specifier given referencing
   // module script and specifier.
   nsAutoJSString string;
   if (!string.init(aCx, aSpecifier)) {
     return nullptr;
   }
 
@@ -787,29 +787,22 @@ HostResolveImportedModule(JSContext* aCx
 
   MOZ_ASSERT(!ms->HasParseError());
   MOZ_ASSERT(ms->ModuleRecord());
 
   return ms->ModuleRecord();
 }
 
 bool
-HostPopulateImportMeta(JSContext* aCx, JS::Handle<JSObject*> aModule,
+HostPopulateImportMeta(JSContext* aCx,
+                       JS::Handle<JS::Value> aReferencingPrivate,
                        JS::Handle<JSObject*> aMetaObject)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aModule);
-
-  JS::Value value = JS::GetModuleHostDefinedField(aModule);
-  if (value.isUndefined()) {
-    JS_ReportErrorASCII(aCx, "Module script not found");
-    return false;
-  }
-
-  auto script = static_cast<ModuleScript*>(value.toPrivate());
-  MOZ_DIAGNOSTIC_ASSERT(script->ModuleRecord() == aModule);
+  auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+  MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate);
 
   nsAutoCString url;
   MOZ_DIAGNOSTIC_ASSERT(script->BaseURL());
   MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url));
 
   JS::Rooted<JSString*> urlString(aCx, JS_NewStringCopyZ(aCx, url.get()));
   if (!urlString) {
     JS_ReportOutOfMemory(aCx);
--- a/dom/script/ScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -516,18 +516,19 @@ private:
 
   bool IsFetchingModule(ModuleLoadRequest* aRequest) const;
 
   bool ModuleMapContainsURL(nsIURI* aURL) const;
   RefPtr<mozilla::GenericPromise> WaitForModuleFetch(nsIURI* aURL);
   ModuleScript* GetFetchedModule(nsIURI* aURL) const;
 
   friend JSObject*
-  HostResolveImportedModule(JSContext* aCx, JS::Handle<JSObject*> aModule,
-                          JS::Handle<JSString*> aSpecifier);
+  HostResolveImportedModule(JSContext* aCx,
+                            JS::Handle<JS::Value> aReferencingPrivate,
+                            JS::Handle<JSString*> aSpecifier);
 
   // Returns wether we should save the bytecode of this script after the
   // execution of the script.
   static bool
   ShouldCacheBytecode(ScriptLoadRequest* aRequest);
 
   nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
   nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -868,32 +868,40 @@ ModuleObject::namespace_()
 {
     Value value = getReservedSlot(NamespaceSlot);
     if (value.isUndefined()) {
         return nullptr;
     }
     return &value.toObject().as<ModuleNamespaceObject>();
 }
 
+ScriptSourceObject*
+ModuleObject::scriptSourceObject() const
+{
+    return &getReservedSlot(ScriptSourceObjectSlot).toObject().as<ScriptSourceObject>();
+}
+
 FunctionDeclarationVector*
 ModuleObject::functionDeclarations()
 {
     Value value = getReservedSlot(FunctionDeclarationsSlot);
     if (value.isUndefined()) {
         return nullptr;
     }
 
     return static_cast<FunctionDeclarationVector*>(value.toPrivate());
 }
 
 void
 ModuleObject::init(HandleScript script)
 {
+    MOZ_ASSERT(script);
     initReservedSlot(ScriptSlot, PrivateGCThingValue(script));
     initReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_UNINSTANTIATED));
+    initReservedSlot(ScriptSourceObjectSlot, ObjectValue(script->scriptSourceUnwrap()));
 }
 
 void
 ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment)
 {
     initReservedSlot(EnvironmentSlot, ObjectValue(*initialEnvironment));
 }
 
@@ -1050,28 +1058,16 @@ ModuleObject::metaObject() const
 void
 ModuleObject::setMetaObject(JSObject* obj)
 {
     MOZ_ASSERT(obj);
     MOZ_ASSERT(!metaObject());
     setReservedSlot(MetaObjectSlot, ObjectValue(*obj));
 }
 
-Value
-ModuleObject::hostDefinedField() const
-{
-    return getReservedSlot(HostDefinedSlot);
-}
-
-void
-ModuleObject::setHostDefinedField(const JS::Value& value)
-{
-    setReservedSlot(HostDefinedSlot, value);
-}
-
 Scope*
 ModuleObject::enclosingScope() const
 {
     return script()->enclosingScope();
 }
 
 /* static */ void
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
@@ -1795,16 +1791,17 @@ js::GetOrCreateModuleMetaObject(JSContex
     }
 
     JS::ModuleMetadataHook func = cx->runtime()->moduleMetadataHook;
     if (!func) {
         JS_ReportErrorASCII(cx, "Module metadata hook not set");
         return nullptr;
     }
 
-    if (!func(cx, module, metaObject)) {
+    RootedValue modulePrivate(cx, JS::GetModulePrivate(module));
+    if (!func(cx, modulePrivate, metaObject)) {
         return nullptr;
     }
 
     module->setMetaObject(metaObject);
 
     return metaObject;
 }
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -255,17 +255,17 @@ class ModuleObject : public NativeObject
     enum ModuleSlot
     {
         ScriptSlot = 0,
         EnvironmentSlot,
         NamespaceSlot,
         StatusSlot,
         EvaluationErrorSlot,
         MetaObjectSlot,
-        HostDefinedSlot,
+        ScriptSourceObjectSlot,
         RequestedModulesSlot,
         ImportEntriesSlot,
         LocalExportEntriesSlot,
         IndirectExportEntriesSlot,
         StarExportEntriesSlot,
         ImportBindingsSlot,
         FunctionDeclarationsSlot,
         DFSIndexSlot,
@@ -307,34 +307,32 @@ class ModuleObject : public NativeObject
     Scope* enclosingScope() const;
     ModuleEnvironmentObject& initialEnvironment() const;
     ModuleEnvironmentObject* environment() const;
     ModuleNamespaceObject* namespace_();
     ModuleStatus status() const;
     bool hadEvaluationError() const;
     Value evaluationError() const;
     JSObject* metaObject() const;
-    Value hostDefinedField() const;
+    ScriptSourceObject* scriptSourceObject() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
     ArrayObject& indirectExportEntries() const;
     ArrayObject& starExportEntries() const;
     IndirectBindingMap& importBindings();
 
     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);
 
-    void setHostDefinedField(const JS::Value& value);
-
     // For BytecodeEmitter.
     bool noteFunctionDeclaration(JSContext* cx, HandleAtom name, HandleFunction fun);
 
     // For intrinsic_InstantiateModuleFunctionDeclarations.
     static bool instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self);
 
     // For intrinsic_ExecuteModule.
     static bool execute(JSContext* cx, HandleModuleObject self, MutableHandleValue rval);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4147,25 +4147,37 @@ JS::CompileModule(JSContext* cx, const R
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
 
     module.set(frontend::CompileModule(cx, options, srcBuf));
     return !!module;
 }
 
 JS_PUBLIC_API(void)
-JS::SetModuleHostDefinedField(JSObject* module, const JS::Value& value)
-{
-    module->as<ModuleObject>().setHostDefinedField(value);
+JS::SetModulePrivate(JSObject* module, const JS::Value& value)
+{
+    module->as<ModuleObject>().scriptSourceObject()->setPrivate(value);
 }
 
 JS_PUBLIC_API(JS::Value)
-JS::GetModuleHostDefinedField(JSObject* module)
-{
-    return module->as<ModuleObject>().hostDefinedField();
+JS::GetModulePrivate(JSObject* module)
+{
+    return module->as<ModuleObject>().scriptSourceObject()->getPrivate();
+}
+
+JS_PUBLIC_API(void)
+JS::SetScriptPrivate(JSScript* script, const JS::Value& value)
+{
+    script->scriptSourceUnwrap().setPrivate(value);
+}
+
+JS_PUBLIC_API(JS::Value)
+JS::GetScriptPrivate(JSScript* script)
+{
+    return script->scriptSourceUnwrap().getPrivate();
 }
 
 JS_PUBLIC_API(bool)
 JS::ModuleInstantiate(JSContext* cx, JS::HandleObject moduleArg)
 {
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     cx->check(moduleArg);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3016,31 +3016,31 @@ extern JS_PUBLIC_API(JSString*)
 JS_DecompileScript(JSContext* cx, JS::Handle<JSScript*> script);
 
 extern JS_PUBLIC_API(JSString*)
 JS_DecompileFunction(JSContext* cx, JS::Handle<JSFunction*> fun);
 
 
 namespace JS {
 
-using ModuleResolveHook = JSObject* (*)(JSContext*, HandleObject, HandleString);
+using ModuleResolveHook = JSObject* (*)(JSContext*, HandleValue, HandleString);
 
 /**
  * Get the HostResolveImportedModule hook for the runtime.
  */
 extern JS_PUBLIC_API(ModuleResolveHook)
 GetModuleResolveHook(JSRuntime* rt);
 
 /**
  * Set the HostResolveImportedModule hook for the runtime to the given function.
  */
 extern JS_PUBLIC_API(void)
 SetModuleResolveHook(JSRuntime* rt, ModuleResolveHook func);
 
-using ModuleMetadataHook = bool (*)(JSContext*, HandleObject, HandleObject);
+using ModuleMetadataHook = bool (*)(JSContext*, HandleValue, HandleObject);
 
 /**
  * Get the hook for populating the import.meta metadata object.
  */
 extern JS_PUBLIC_API(ModuleMetadataHook)
 GetModuleMetadataHook(JSRuntime* rt);
 
 /**
@@ -3054,27 +3054,40 @@ SetModuleMetadataHook(JSRuntime* rt, Mod
  * Parse the given source buffer as a module in the scope of the current global
  * of cx and return a source text module record.
  */
 extern JS_PUBLIC_API(bool)
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
               SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord);
 
 /**
- * Set the [[HostDefined]] field of a source text module record to the given
- * value.
+ * Set a private value associated with a source text module record.
  */
 extern JS_PUBLIC_API(void)
-SetModuleHostDefinedField(JSObject* module, const JS::Value& value);
+SetModulePrivate(JSObject* module, const JS::Value& value);
+
+/**
+ * Get the private value associated with a source text module record.
+ */
+extern JS_PUBLIC_API(JS::Value)
+GetModulePrivate(JSObject* module);
 
 /**
- * Get the [[HostDefined]] field of a source text module record.
+ * Set a private value associated with a script. Note that this value is shared
+ * by all nested scripts compiled from a single source file.
+ */
+extern JS_PUBLIC_API(void)
+SetScriptPrivate(JSScript* script, const JS::Value& value);
+
+/**
+ * Get the private value associated with a script. Note that this value is
+ * shared by all nested scripts compiled from a single source file.
  */
 extern JS_PUBLIC_API(JS::Value)
-GetModuleHostDefinedField(JSObject* module);
+GetScriptPrivate(JSScript* script);
 
 /*
  * Perform the ModuleInstantiate operation on the given source text module
  * record.
  *
  * This transitively resolves all module dependencies (calling the
  * HostResolveImportedModule hook) and initializes the environment record for
  * the module.
--- a/js/src/shell/ModuleLoader.js
+++ b/js/src/shell/ModuleLoader.js
@@ -1,66 +1,71 @@
 /* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global getModuleLoadPath setModuleLoadHook setModuleResolveHook setModuleMetadataHook */
-/* global parseModule os */
+/* global getModulePrivate setModulePrivate parseModule os */
 
 // A basic synchronous module loader for testing the shell.
 {
 // Save standard built-ins before scripts can modify them.
 const ArrayPrototypeJoin = Array.prototype.join;
 const MapPrototypeGet = Map.prototype.get;
 const MapPrototypeHas = Map.prototype.has;
 const MapPrototypeSet = Map.prototype.set;
 const ObjectDefineProperty = Object.defineProperty;
 const ReflectApply = Reflect.apply;
 const StringPrototypeIndexOf = String.prototype.indexOf;
 const StringPrototypeLastIndexOf = String.prototype.lastIndexOf;
 const StringPrototypeStartsWith = String.prototype.startsWith;
 const StringPrototypeSubstring = String.prototype.substring;
+const ErrorClass = Error;
 
 const ReflectLoader = new class {
     constructor() {
         this.registry = new Map();
         this.modulePaths = new Map();
         this.loadPath = getModuleLoadPath();
     }
 
-    resolve(name, module) {
+    resolve(name, referencingInfo) {
         if (os.path.isAbsolute(name))
             return name;
 
         let loadPath = this.loadPath;
-        if (module) {
-            // Treat |name| as a relative path if it starts with either "./"
-            // or "../".
-            let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"])
-                          || ReflectApply(StringPrototypeStartsWith, name, ["../"])
+
+        // Treat |name| as a relative path if it starts with either "./"
+        // or "../".
+        let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"])
+                      || ReflectApply(StringPrototypeStartsWith, name, ["../"])
 #ifdef XP_WIN
-                          || ReflectApply(StringPrototypeStartsWith, name, [".\\"])
-                          || ReflectApply(StringPrototypeStartsWith, name, ["..\\"])
+                      || ReflectApply(StringPrototypeStartsWith, name, [".\\"])
+                      || ReflectApply(StringPrototypeStartsWith, name, ["..\\"])
 #endif
-                             ;
+                         ;
 
-            // If |name| is a relative path and |module|'s path is available,
-            // load |name| relative to the referring module.
-            if (isRelative && ReflectApply(MapPrototypeHas, this.modulePaths, [module])) {
-                let modulePath = ReflectApply(MapPrototypeGet, this.modulePaths, [module]);
-                let sepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["/"]);
+        // If |name| is a relative path and the referencing module's path is
+        // available, load |name| relative to the that path.
+        if (isRelative) {
+            if (!referencingInfo) {
+                throw new ErrorClass("No referencing module for relative import");
+            }
+
+            let path = referencingInfo.path;
+
+            let sepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["/"]);
 #ifdef XP_WIN
-                let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["\\"]);
-                if (otherSepIndex > sepIndex)
-                    sepIndex = otherSepIndex;
+            let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["\\"]);
+            if (otherSepIndex > sepIndex)
+                sepIndex = otherSepIndex;
 #endif
-                if (sepIndex >= 0)
-                    loadPath = ReflectApply(StringPrototypeSubstring, modulePath, [0, sepIndex]);
-            }
+            if (sepIndex >= 0)
+                loadPath = ReflectApply(StringPrototypeSubstring, path, [0, sepIndex]);
         }
 
         return os.path.join(loadPath, name);
     }
 
     normalize(path) {
 #ifdef XP_WIN
         // Replace all forward slashes with backward slashes.
@@ -150,18 +155,19 @@ const ReflectLoader = new class {
 
     loadAndParse(path) {
         let normalized = this.normalize(path);
         if (ReflectApply(MapPrototypeHas, this.registry, [normalized]))
             return ReflectApply(MapPrototypeGet, this.registry, [normalized]);
 
         let source = this.fetch(path);
         let module = parseModule(source, path);
+        let moduleInfo = { path: normalized };
+        setModulePrivate(module, moduleInfo);
         ReflectApply(MapPrototypeSet, this.registry, [normalized, module]);
-        ReflectApply(MapPrototypeSet, this.modulePaths, [module, path]);
         return module;
     }
 
     loadAndExecute(path) {
         let module = this.loadAndParse(path);
         module.declarationInstantiation();
         return module.evaluation();
     }
@@ -170,22 +176,22 @@ const ReflectLoader = new class {
         return this.loadAndExecute(path);
     }
 
     ["import"](name, referrer) {
         let path = this.resolve(name, null);
         return this.loadAndExecute(path);
     }
 
-    populateImportMeta(module, metaObject) {
-        // For the shell, use the script's filename as the base URL.
+    populateImportMeta(moduleInfo, metaObject) {
+        // For the shell, use the module's normalized path as the base URL.
 
         let path;
-        if (ReflectApply(MapPrototypeHas, this.modulePaths, [module])) {
-            path = ReflectApply(MapPrototypeGet, this.modulePaths, [module]);
+        if (moduleInfo) {
+            path = moduleInfo.path;
         } else {
             path = "(unknown)";
         }
         metaObject.url = path;
     }
 };
 
 setModuleLoadHook((path) => ReflectLoader.importRoot(path));
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4718,28 +4718,28 @@ SetModuleResolveHook(JSContext* cx, unsi
     Handle<GlobalObject*> global = cx->global();
     global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
 
     args.rval().setUndefined();
     return true;
 }
 
 static JSObject*
-CallModuleResolveHook(JSContext* cx, HandleObject module, HandleString specifier)
+CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier)
 {
     Handle<GlobalObject*> global = cx->global();
     RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleResolveHook));
     if (hookValue.isUndefined()) {
         JS_ReportErrorASCII(cx, "Module resolve hook not set");
         return nullptr;
     }
     MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
 
     JS::AutoValueArray<2> args(cx);
-    args[0].setObject(*module);
+    args[0].set(referencingPrivate);
     args[1].setString(specifier);
 
     RootedValue result(cx);
     if (!JS_CallFunctionValue(cx, nullptr, hookValue, args, &result)) {
         return nullptr;
     }
 
     if (!result.isObject() || !result.toObject().is<ModuleObject>()) {
@@ -4769,35 +4769,82 @@ SetModuleMetadataHook(JSContext* cx, uns
     Handle<GlobalObject*> global = cx->global();
     global->setReservedSlot(GlobalAppSlotModuleMetadataHook, args[0]);
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
-CallModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject)
+CallModuleMetadataHook(JSContext* cx, HandleValue modulePrivate, HandleObject metaObject)
 {
     Handle<GlobalObject*> 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<JSFunction>());
 
     JS::AutoValueArray<2> args(cx);
-    args[0].setObject(*module);
+    args[0].set(modulePrivate);
     args[1].setObject(*metaObject);
 
     RootedValue dummy(cx);
     return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy);
 }
 
 static bool
+ReportArgumentTypeError(JSContext* cx, HandleValue value, const char* expected)
+{
+    const char* typeName = InformalValueTypeName(value);
+    JS_ReportErrorASCII(cx, "Expected %s, got %s", expected, typeName);
+    return false;
+}
+
+static bool
+ShellSetModulePrivate(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 2) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+                                  "setModulePrivate", "0", "s");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+        return ReportArgumentTypeError(cx, args[0], "module object");
+    }
+
+    JS::SetModulePrivate(&args[0].toObject(), args[1]);
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 1) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+                                  "getModulePrivate", "0", "s");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+        return ReportArgumentTypeError(cx, args[0], "module object");
+    }
+
+    args.rval().set(JS::GetModulePrivate(&args[0].toObject()));
+    return true;
+}
+
+static bool
 GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     ShellContext* sc = GetShellContext(cx);
     if (sc->moduleLoadPath) {
         JSString* str = JS_NewStringCopyZ(cx, sc->moduleLoadPath.get());
         if (!str) {
@@ -8006,16 +8053,24 @@ static const JSFunctionSpecWithHelp shel
 "  be implemented by the module loader."),
 
     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("setModulePrivate", ShellSetModulePrivate, 2, 0,
+"setModulePrivate(scriptObject, privateValue)",
+"  Associate a private value with a module object.\n"),
+
+    JS_FN_HELP("getModulePrivate", ShellGetModulePrivate, 2, 0,
+"getModulePrivate(scriptObject)",
+"  Get the private value associated with a module object.\n"),
+
     JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0,
 "getModuleLoadPath()",
 "  Return any --module-load-path argument passed to the shell.  Used by the\n"
 "  module loader.\n"),
 
 #if defined(JS_BUILD_BINAST)
 
 JS_FN_HELP("parseBin", BinParse, 1, 0,
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1216,22 +1216,32 @@ class ScriptSourceObject : public Native
     JSScript* introductionScript() const {
         Value value = getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
         if (value.isUndefined()) {
             return nullptr;
         }
         return value.toGCThing()->as<JSScript>();
     }
 
+    void setPrivate(const Value& value) {
+        setReservedSlot(PRIVATE_SLOT, value);
+    }
+    Value getPrivate() const {
+        return getReservedSlot(PRIVATE_SLOT);
+    }
+
   private:
-    static const uint32_t SOURCE_SLOT = 0;
-    static const uint32_t ELEMENT_SLOT = 1;
-    static const uint32_t ELEMENT_PROPERTY_SLOT = 2;
-    static const uint32_t INTRODUCTION_SCRIPT_SLOT = 3;
-    static const uint32_t RESERVED_SLOTS = 4;
+    enum {
+        SOURCE_SLOT = 0,
+        ELEMENT_SLOT,
+        ELEMENT_PROPERTY_SLOT,
+        INTRODUCTION_SCRIPT_SLOT,
+        PRIVATE_SLOT,
+        RESERVED_SLOTS
+    };
 };
 
 enum class GeneratorKind : bool { NotGenerator, Generator };
 enum class FunctionAsyncKind : bool { SyncFunction, AsyncFunction };
 
 /*
  * NB: after a successful XDR_DECODE, XDRScript callers must do any required
  * subsequent set-up of owning function or script object and then call
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2185,17 +2185,18 @@ intrinsic_HostResolveImportedModule(JSCo
 
     JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook;
     if (!moduleResolveHook) {
         JS_ReportErrorASCII(cx, "Module resolve hook not set");
         return false;
     }
 
     RootedObject result(cx);
-    result = moduleResolveHook(cx, module, specifier);
+    RootedValue referencingPrivate(cx, JS::GetModulePrivate(module));
+    result = moduleResolveHook(cx, referencingPrivate, specifier);
     if (!result) {
         return false;
     }
 
     if (!result->is<ModuleObject>()) {
         JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
         return false;
     }