Bug 1519140 - Add AddRef/Release hooks for embedding's script or module private value and set this script source object where appropriate r=jandem
authorJon Coppeard <jcoppeard@mozilla.com>
Fri, 18 Jan 2019 13:37:43 +0000
changeset 511639 5f4b3423f6fefaeffaf130898b2384756f22877f
parent 511638 c82870227fa524ca423e781d27c98c683ebfe8f6
child 511640 47e7e2dac7c99f9c5fa555bdb579ec18f1674c72
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1519140
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1519140 - Add AddRef/Release hooks for embedding's script or module private value and set this script source object where appropriate r=jandem
dom/script/LoadedScript.cpp
dom/script/LoadedScript.h
dom/script/ScriptLoader.cpp
js/public/CompileOptions.h
js/src/builtin/Eval.cpp
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/jit-test/tests/modules/bug-1519140.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
js/src/jit/shared/LIR-shared.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/Interpreter.cpp
js/src/vm/JSFunction.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
--- a/dom/script/LoadedScript.cpp
+++ b/dom/script/LoadedScript.cpp
@@ -43,39 +43,50 @@ LoadedScript::LoadedScript(ScriptKind aK
     : mKind(aKind), mFetchOptions(aFetchOptions), mBaseURL(aBaseURL) {
   MOZ_ASSERT(mFetchOptions);
   MOZ_ASSERT(mBaseURL);
 }
 
 LoadedScript::~LoadedScript() { DropJSObjects(this); }
 
 void LoadedScript::AssociateWithScript(JSScript* aScript) {
-  // Set a JSScript's private value to point to this object and
-  // increment our reference count. This is decremented by
-  // HostFinalizeTopLevelScript() below when the JSScript dies.
+  // Set a JSScript's private value to point to this object. The JS engine will
+  // increment our reference count by calling HostAddRefTopLevelScript(). This
+  // is decremented by HostReleaseTopLevelScript() below when the JSScript dies.
 
   MOZ_ASSERT(JS::GetScriptPrivate(aScript).isUndefined());
   JS::SetScriptPrivate(aScript, JS::PrivateValue(this));
-  AddRef();
 }
 
-void HostFinalizeTopLevelScript(JSFreeOp* aFop, const JS::Value& aPrivate) {
-  // Decrement the reference count of a LoadedScript object that is
-  // pointed to by a dying JSScript. The reference count was
-  // originally incremented by AssociateWithScript() above.
-
-  auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
-
+inline void CheckModuleScriptPrivate(LoadedScript* script, const JS::Value& aPrivate) {
 #ifdef DEBUG
   if (script->IsModuleScript()) {
     JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet();
     MOZ_ASSERT_IF(module, JS::GetModulePrivate(module) == aPrivate);
   }
 #endif
+}
 
+void HostAddRefTopLevelScript(const JS::Value& aPrivate) {
+  // Increment the reference count of a LoadedScript object that is now pointed
+  // to by a JSScript. The reference count is decremented by
+  // HostReleaseTopLevelScript() below.
+
+  auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+  CheckModuleScriptPrivate(script, aPrivate);
+  script->AddRef();
+}
+
+void HostReleaseTopLevelScript(const JS::Value& aPrivate) {
+  // Decrement the reference count of a LoadedScript object that was pointed to
+  // by a JSScript. The reference count was originally incremented by
+  // HostAddRefTopLevelScript() above.
+
+  auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+  CheckModuleScriptPrivate(script, aPrivate);
   script->Release();
 }
 
 //////////////////////////////////////////////////////////////
 // ClassicScript
 //////////////////////////////////////////////////////////////
 
 ClassicScript::ClassicScript(ScriptFetchOptions* aFetchOptions,
@@ -120,40 +131,39 @@ ModuleScript::ModuleScript(ScriptFetchOp
 void ModuleScript::UnlinkModuleRecord() {
   // Remove the module record's pointer to this object if present and
   // decrement our reference count. The reference is added by
   // SetModuleRecord() below.
   if (mModuleRecord) {
     MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).toPrivate() == this);
     JS::SetModulePrivate(mModuleRecord, JS::UndefinedValue());
     mModuleRecord = nullptr;
-    Release();
   }
 }
 
 ModuleScript::~ModuleScript() {
   // The object may be destroyed without being unlinked first.
   UnlinkModuleRecord();
 }
 
 void ModuleScript::SetModuleRecord(JS::Handle<JSObject*> aModuleRecord) {
   MOZ_ASSERT(!mModuleRecord);
   MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError());
   MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow());
 
   mModuleRecord = aModuleRecord;
 
-  // Make module's host defined field point to this object and
-  // increment our reference count. This is decremented by
-  // UnlinkModuleRecord() above.
+  // Make module's host defined field point to this object. The JS engine will
+  // increment our reference count by calling HostAddRefTopLevelScript(). This
+  // is decremented when the field is cleared in UnlinkModuleRecord() above or
+  // when the module record dies.
   MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).isUndefined());
   JS::SetModulePrivate(mModuleRecord, JS::PrivateValue(this));
 
   HoldJSObjects(this);
-  AddRef();
 }
 
 void ModuleScript::SetParseError(const JS::Value& aError) {
   MOZ_ASSERT(!aError.isUndefined());
   MOZ_ASSERT(!HasParseError());
   MOZ_ASSERT(!HasErrorToRethrow());
 
   UnlinkModuleRecord();
--- a/dom/script/LoadedScript.h
+++ b/dom/script/LoadedScript.h
@@ -14,17 +14,18 @@
 
 class nsIURI;
 
 namespace mozilla {
 namespace dom {
 
 class ScriptLoader;
 
-void HostFinalizeTopLevelScript(JSFreeOp* aFop, const JS::Value& aPrivate);
+void HostAddRefTopLevelScript(const JS::Value& aPrivate);
+void HostReleaseTopLevelScript(const JS::Value& aPrivate);
 
 class ClassicScript;
 class ModuleScript;
 
 class LoadedScript : public nsISupports {
   ScriptKind mKind;
   RefPtr<ScriptFetchOptions> mFetchOptions;
   nsCOMPtr<nsIURI> mBaseURL;
@@ -84,17 +85,17 @@ class ModuleScript final : public Loaded
   JS::Value ParseError() const { return mParseError; }
   JS::Value ErrorToRethrow() const { return mErrorToRethrow; }
   bool HasParseError() const { return !mParseError.isUndefined(); }
   bool HasErrorToRethrow() const { return !mErrorToRethrow.isUndefined(); }
   bool SourceElementAssociated() const { return mSourceElementAssociated; }
 
   void UnlinkModuleRecord();
 
-  friend void HostFinalizeTopLevelScript(JSFreeOp*, const JS::Value&);
+  friend void CheckModuleScriptPrivate(LoadedScript*, const JS::Value&);
 };
 
 ClassicScript* LoadedScript::AsClassicScript() {
   MOZ_ASSERT(!IsModuleScript());
   return static_cast<ClassicScript*>(this);
 }
 
 ModuleScript* LoadedScript::AsModuleScript() {
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -961,17 +961,19 @@ void ScriptLoader::EnsureModuleHooksInit
   jsapi.Init();
   JSRuntime* rt = JS_GetRuntime(jsapi.cx());
   if (JS::GetModuleResolveHook(rt)) {
     return;
   }
 
   JS::SetModuleResolveHook(rt, HostResolveImportedModule);
   JS::SetModuleMetadataHook(rt, HostPopulateImportMeta);
-  JS::SetScriptPrivateFinalizeHook(rt, HostFinalizeTopLevelScript);
+  JS::SetScriptPrivateReferenceHooks(rt,
+                                     HostAddRefTopLevelScript,
+                                     HostReleaseTopLevelScript);
 
   Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback,
                                        "javascript.options.dynamicImport",
                                        (void*)nullptr);
 }
 
 void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
   LOG(("ScriptLoadRequest (%p): Check dependencies loaded", aRequest));
--- a/js/public/CompileOptions.h
+++ b/js/public/CompileOptions.h
@@ -140,16 +140,25 @@ class JS_PUBLIC_API TransitiveCompileOpt
   bool mutedErrors() const { return mutedErrors_; }
   const char* filename() const { return filename_; }
   const char* introducerFilename() const { return introducerFilename_; }
   const char16_t* sourceMapURL() const { return sourceMapURL_; }
   virtual JSObject* element() const = 0;
   virtual JSString* elementAttributeName() const = 0;
   virtual JSScript* introductionScript() const = 0;
 
+  // For some compilations the spec requires the ScriptOrModule field of the
+  // resulting script to be set to the currently executing script. This can be
+  // achieved by setting this option with setScriptOrModule() below.
+  //
+  // Note that this field doesn't explicitly exist in our implementation;
+  // instead the ScriptSourceObject's private value is set to that associated
+  // with the specified script.
+  virtual JSScript* scriptOrModule() const = 0;
+
  private:
   void operator=(const TransitiveCompileOptions&) = delete;
 };
 
 class JS_PUBLIC_API CompileOptions;
 
 /**
  * The class representing a full set of compile options.
@@ -197,19 +206,20 @@ class JS_PUBLIC_API ReadOnlyCompileOptio
 
  public:
   // Read-only accessors for non-POD options. The proper way to set these
   // depends on the derived type.
   bool mutedErrors() const { return mutedErrors_; }
   const char* filename() const { return filename_; }
   const char* introducerFilename() const { return introducerFilename_; }
   const char16_t* sourceMapURL() const { return sourceMapURL_; }
-  virtual JSObject* element() const override = 0;
-  virtual JSString* elementAttributeName() const override = 0;
-  virtual JSScript* introductionScript() const override = 0;
+  JSObject* element() const override = 0;
+  JSString* elementAttributeName() const override = 0;
+  JSScript* introductionScript() const override = 0;
+  JSScript* scriptOrModule() const override = 0;
 
  private:
   void operator=(const ReadOnlyCompileOptions&) = delete;
 };
 
 /**
  * Compilation options, with dynamic lifetime. An instance of this type
  * makes a copy of / holds / roots all dynamically allocated resources
@@ -222,29 +232,33 @@ class JS_PUBLIC_API ReadOnlyCompileOptio
  * or indirectly, by a JavaScript object: if any value that this roots ever
  * comes to refer to the object that owns this, then the whole cycle, and
  * anything else it entrains, will never be freed.
  */
 class JS_PUBLIC_API OwningCompileOptions final : public ReadOnlyCompileOptions {
   PersistentRooted<JSObject*> elementRoot;
   PersistentRooted<JSString*> elementAttributeNameRoot;
   PersistentRooted<JSScript*> introductionScriptRoot;
+  PersistentRooted<JSScript*> scriptOrModuleRoot;
 
  public:
   // A minimal constructor, for use with OwningCompileOptions::copy.
   explicit OwningCompileOptions(JSContext* cx);
   ~OwningCompileOptions();
 
   JSObject* element() const override { return elementRoot; }
   JSString* elementAttributeName() const override {
     return elementAttributeNameRoot;
   }
   JSScript* introductionScript() const override {
     return introductionScriptRoot;
   }
+  JSScript* scriptOrModule() const override {
+    return scriptOrModuleRoot;
+  }
 
   /** Set this to a copy of |rhs|.  Return false on OOM. */
   bool copy(JSContext* cx, const ReadOnlyCompileOptions& rhs);
 
   /* These setters make copies of their string arguments and are fallible. */
   MOZ_MUST_USE bool setFile(JSContext* cx, const char* f);
   MOZ_MUST_USE bool setFileAndLine(JSContext* cx, const char* f, unsigned l);
   MOZ_MUST_USE bool setSourceMapURL(JSContext* cx, const char16_t* s);
@@ -267,16 +281,21 @@ class JS_PUBLIC_API OwningCompileOptions
     return *this;
   }
 
   OwningCompileOptions& setIntroductionScript(JSScript* s) {
     introductionScriptRoot = s;
     return *this;
   }
 
+  OwningCompileOptions& setScriptOrModule(JSScript* s) {
+    scriptOrModuleRoot = s;
+    return *this;
+  }
+
   OwningCompileOptions& setMutedErrors(bool mute) {
     mutedErrors_ = mute;
     return *this;
   }
 
   OwningCompileOptions& setColumn(unsigned c) {
     column = c;
     return *this;
@@ -356,60 +375,69 @@ class JS_PUBLIC_API OwningCompileOptions
  * everything you store in it will outlive it.
  */
 class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final
     : public ReadOnlyCompileOptions {
  private:
   Rooted<JSObject*> elementRoot;
   Rooted<JSString*> elementAttributeNameRoot;
   Rooted<JSScript*> introductionScriptRoot;
+  Rooted<JSScript*> scriptOrModuleRoot;
 
  public:
   explicit CompileOptions(JSContext* cx);
 
   CompileOptions(JSContext* cx, const ReadOnlyCompileOptions& rhs)
       : ReadOnlyCompileOptions(),
         elementRoot(cx),
         elementAttributeNameRoot(cx),
-        introductionScriptRoot(cx) {
+        introductionScriptRoot(cx),
+        scriptOrModuleRoot(cx) {
     copyPODOptions(rhs);
 
     filename_ = rhs.filename();
     introducerFilename_ = rhs.introducerFilename();
     sourceMapURL_ = rhs.sourceMapURL();
     elementRoot = rhs.element();
     elementAttributeNameRoot = rhs.elementAttributeName();
     introductionScriptRoot = rhs.introductionScript();
+    scriptOrModuleRoot = rhs.scriptOrModule();
   }
 
   CompileOptions(JSContext* cx, const TransitiveCompileOptions& rhs)
       : ReadOnlyCompileOptions(),
         elementRoot(cx),
         elementAttributeNameRoot(cx),
-        introductionScriptRoot(cx) {
+        introductionScriptRoot(cx),
+        scriptOrModuleRoot(cx) {
     copyPODTransitiveOptions(rhs);
 
     filename_ = rhs.filename();
     introducerFilename_ = rhs.introducerFilename();
     sourceMapURL_ = rhs.sourceMapURL();
     elementRoot = rhs.element();
     elementAttributeNameRoot = rhs.elementAttributeName();
     introductionScriptRoot = rhs.introductionScript();
+    scriptOrModuleRoot = rhs.scriptOrModule();
   }
 
   JSObject* element() const override { return elementRoot; }
 
   JSString* elementAttributeName() const override {
     return elementAttributeNameRoot;
   }
 
   JSScript* introductionScript() const override {
     return introductionScriptRoot;
   }
 
+  JSScript* scriptOrModule() const override {
+    return scriptOrModuleRoot;
+  }
+
   CompileOptions& setFile(const char* f) {
     filename_ = f;
     return *this;
   }
 
   CompileOptions& setLine(unsigned l) {
     lineno = l;
     return *this;
@@ -436,16 +464,21 @@ class MOZ_STACK_CLASS JS_PUBLIC_API Comp
     return *this;
   }
 
   CompileOptions& setIntroductionScript(JSScript* s) {
     introductionScriptRoot = s;
     return *this;
   }
 
+  CompileOptions& setScriptOrModule(JSScript* s) {
+    scriptOrModuleRoot = s;
+    return *this;
+  }
+
   CompileOptions& setMutedErrors(bool mute) {
     mutedErrors_ = mute;
     return *this;
   }
 
   CompileOptions& setColumn(unsigned c) {
     column = c;
     return *this;
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -278,17 +278,18 @@ static bool EvalKernel(JSContext* cx, Ha
     } else {
       enclosing = &cx->global()->emptyGlobalScope();
     }
 
     CompileOptions options(cx);
     options.setIsRunOnce(true)
         .setNoScriptRval(false)
         .setMutedErrors(mutedErrors)
-        .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
+        .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc))
+        .setScriptOrModule(maybeScript);
 
     if (introducerFilename) {
       options.setFileAndLine(filename, 1);
       options.setIntroductionInfo(introducerFilename, "eval", lineno,
                                   maybeScript, pcOffset);
     } else {
       options.setFileAndLine("eval", 1);
       options.setIntroductionType("eval");
@@ -305,17 +306,17 @@ static bool EvalKernel(JSContext* cx, Ha
     SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
                                     ? SourceOwnership::TakeOwnership
                                     : SourceOwnership::Borrowed;
     if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) {
       return false;
     }
 
     frontend::EvalScriptInfo info(cx, options, env, enclosing);
-    JSScript* compiled = frontend::CompileEvalScript(info, srcBuf);
+    RootedScript compiled(cx, frontend::CompileEvalScript(info, srcBuf));
     if (!compiled) {
       return false;
     }
 
     esg.setNewScript(compiled);
   }
 
   // Look up the newTarget from the frame iterator.
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1674,18 +1674,25 @@ JSObject* js::CallModuleResolveHook(JSCo
     JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
     return nullptr;
   }
 
   return result;
 }
 
 JSObject* js::StartDynamicModuleImport(JSContext* cx,
-                                       HandleValue referencingPrivate,
+                                       HandleObject referencingScriptSource,
                                        HandleValue specifierArg) {
+  RootedValue referencingPrivate(cx);
+  if (referencingScriptSource) {
+    ScriptSourceObject* sso =
+      &UncheckedUnwrap(referencingScriptSource)->as<ScriptSourceObject>();
+    referencingPrivate = sso->canonicalPrivate();
+  }
+
   RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx));
   if (!promiseConstructor) {
     return nullptr;
   }
 
   RootedObject promiseObject(cx, JS::NewPromiseObject(cx, nullptr));
   if (!promiseObject) {
     return nullptr;
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -402,17 +402,17 @@ 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,
+                                   HandleObject referencingScriptSource,
                                    HandleValue specifier);
 
 bool FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate,
                                HandleString specifier, HandleObject promise);
 
 }  // namespace js
 
 template <>
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug-1519140.js
@@ -0,0 +1,3 @@
+// |jit-test| --more-compartments
+fullcompartmentchecks(true);
+newGlobal().eval(`import("javascript:")`).catch(() => {});
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -5408,33 +5408,32 @@ bool BaselineCodeGen<Handler>::emit_JSOP
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleValue,
+typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleObject,
                                                 HandleValue);
 static const VMFunction StartDynamicModuleImportInfo =
     FunctionInfo<StartDynamicModuleImportFn>(js::StartDynamicModuleImport,
                                              "StartDynamicModuleImport");
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DYNAMIC_IMPORT() {
-  RootedValue referencingPrivate(cx,
-                                 FindScriptOrModulePrivateForScript(script));
+  RootedObject referencingScriptSource(cx, script->sourceObject());
 
   // Put specifier value in R0.
   frame.popRegsAndSync(1);
 
   prepareVMCall();
   pushArg(R0);
-  pushArg(referencingPrivate);
+  pushArg(ImmGCPtr(referencingScriptSource));
   if (!callVM(StartDynamicModuleImportInfo)) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2977,25 +2977,25 @@ static const VMFunction GetOrCreateModul
     FunctionInfo<GetOrCreateModuleMetaObjectFn>(js::GetOrCreateModuleMetaObject,
                                                 "GetOrCreateModuleMetaObject");
 
 void CodeGenerator::visitModuleMetadata(LModuleMetadata* lir) {
   pushArg(ImmPtr(lir->mir()->module()));
   callVM(GetOrCreateModuleMetaObjectInfo, lir);
 }
 
-typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleValue,
+typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleObject,
                                                 HandleValue);
 static const VMFunction StartDynamicModuleImportInfo =
     FunctionInfo<StartDynamicModuleImportFn>(js::StartDynamicModuleImport,
                                              "StartDynamicModuleImport");
 
 void CodeGenerator::visitDynamicImport(LDynamicImport* lir) {
   pushArg(ToValue(lir, LDynamicImport::SpecifierIndex));
-  pushArg(ToValue(lir, LDynamicImport::ReferencingPrivateIndex));
+  pushArg(ImmGCPtr(lir->mir()->referencingScriptSource()));
   callVM(StartDynamicModuleImportInfo, lir);
 }
 
 typedef JSObject* (*LambdaFn)(JSContext*, HandleFunction, HandleObject);
 static const VMFunction LambdaInfo =
     FunctionInfo<LambdaFn>(js::Lambda, "Lambda");
 
 void CodeGenerator::visitLambdaForSingleton(LLambdaForSingleton* lir) {
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -13670,22 +13670,21 @@ AbortReasonOr<Ok> IonBuilder::jsop_impor
 
   MModuleMetadata* meta = MModuleMetadata::New(alloc(), module);
   current->add(meta);
   current->push(meta);
   return resumeAfter(meta);
 }
 
 AbortReasonOr<Ok> IonBuilder::jsop_dynamic_import() {
-  Value referencingPrivate = FindScriptOrModulePrivateForScript(script());
-  MConstant* ref = constant(referencingPrivate);
+  JSObject* referencingScriptSource = script()->sourceObject();
 
   MDefinition* specifier = current->pop();
 
-  MDynamicImport* ins = MDynamicImport::New(alloc(), ref, specifier);
+  MDynamicImport* ins = MDynamicImport::New(alloc(), referencingScriptSource, specifier);
   current->add(ins);
   current->push(ins);
   return resumeAfter(ins);
 }
 
 MInstruction* IonBuilder::addConvertElementsToDoubles(MDefinition* elements) {
   MInstruction* convert = MConvertElementsToDoubles::New(alloc(), elements);
   current->add(convert);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2367,18 +2367,17 @@ void LIRGenerator::visitClassConstructor
 void LIRGenerator::visitModuleMetadata(MModuleMetadata* ins) {
   LModuleMetadata* lir = new (alloc()) LModuleMetadata();
   defineReturn(lir, ins);
   assignSafepoint(lir, ins);
 }
 
 void LIRGenerator::visitDynamicImport(MDynamicImport* ins) {
   LDynamicImport* lir =
-      new (alloc()) LDynamicImport(useBoxAtStart(ins->referencingPrivate()),
-                                   useBoxAtStart(ins->specifier()));
+      new (alloc()) LDynamicImport(useBoxAtStart(ins->specifier()));
   defineReturn(lir, ins);
   assignSafepoint(lir, ins);
 }
 
 void LIRGenerator::visitLambda(MLambda* ins) {
   if (ins->info().singletonType || ins->info().useSingletonForClone) {
     // If the function has a singleton type, this instruction will only be
     // executed once so we don't bother inlining it.
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6602,28 +6602,36 @@ class MModuleMetadata : public MNullaryI
 
   AliasSet getAliasSet() const override { return AliasSet::None(); }
 
   bool appendRoots(MRootList& roots) const override {
     return roots.append(module_);
   }
 };
 
-class MDynamicImport : public MBinaryInstruction, public BoxInputsPolicy::Data {
-  explicit MDynamicImport(MDefinition* referencingPrivate,
+class MDynamicImport : public MUnaryInstruction, public BoxInputsPolicy::Data {
+  CompilerObject referencingScriptSource_;
+
+  explicit MDynamicImport(JSObject* referencingScriptSource,
                           MDefinition* specifier)
-      : MBinaryInstruction(classOpcode, referencingPrivate, specifier) {
+      : MUnaryInstruction(classOpcode, specifier),
+        referencingScriptSource_(referencingScriptSource) {
     setResultType(MIRType::Object);
   }
 
  public:
   INSTRUCTION_HEADER(DynamicImport)
   TRIVIAL_NEW_WRAPPERS
-  NAMED_OPERANDS((0, referencingPrivate))
-  NAMED_OPERANDS((1, specifier))
+  NAMED_OPERANDS((0, specifier))
+
+  JSObject* referencingScriptSource() const { return referencingScriptSource_; }
+
+  bool appendRoots(MRootList& roots) const override {
+    return roots.append(referencingScriptSource_);
+  }
 };
 
 struct LambdaFunctionInfo {
   // The functions used in lambdas are the canonical original function in
   // the script, and are immutable except for delazification. Record this
   // information while still on the main thread to avoid races.
  private:
   CompilerFunction fun_;
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -3541,27 +3541,24 @@ class LModuleMetadata : public LCallInst
  public:
   LIR_HEADER(ModuleMetadata)
 
   const MModuleMetadata* mir() const { return mir_->toModuleMetadata(); }
 
   LModuleMetadata() : LCallInstructionHelper(classOpcode) {}
 };
 
-class LDynamicImport : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0> {
+class LDynamicImport : public LCallInstructionHelper<1, 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)
+  static const size_t SpecifierIndex = 0;
+
+  explicit LDynamicImport(const LBoxAllocation& specifier)
       : LCallInstructionHelper(classOpcode) {
-    setBoxOperand(ReferencingPrivateIndex, referencingPrivate);
     setBoxOperand(SpecifierIndex, specifier);
   }
 
   const MDynamicImport* mir() const { return mir_->toDynamicImport(); }
 };
 
 class LLambdaForSingleton : public LCallInstructionHelper<1, 1, 0> {
  public:
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3434,17 +3434,18 @@ void JS::ReadOnlyCompileOptions::copyPOD
   noScriptRval = rhs.noScriptRval;
   nonSyntacticScope = rhs.nonSyntacticScope;
 }
 
 JS::OwningCompileOptions::OwningCompileOptions(JSContext* cx)
     : ReadOnlyCompileOptions(),
       elementRoot(cx),
       elementAttributeNameRoot(cx),
-      introductionScriptRoot(cx) {}
+      introductionScriptRoot(cx),
+      scriptOrModuleRoot(cx) {}
 
 JS::OwningCompileOptions::~OwningCompileOptions() {
   // OwningCompileOptions always owns these, so these casts are okay.
   js_free(const_cast<char*>(filename_));
   js_free(const_cast<char16_t*>(sourceMapURL_));
   js_free(const_cast<char*>(introducerFilename_));
 }
 
@@ -3456,16 +3457,17 @@ size_t JS::OwningCompileOptions::sizeOfE
 
 bool JS::OwningCompileOptions::copy(JSContext* cx,
                                     const ReadOnlyCompileOptions& rhs) {
   copyPODOptions(rhs);
 
   setElement(rhs.element());
   setElementAttributeName(rhs.elementAttributeName());
   setIntroductionScript(rhs.introductionScript());
+  setScriptOrModule(rhs.scriptOrModule());
 
   return setFileAndLine(cx, rhs.filename(), rhs.lineno) &&
          setSourceMapURL(cx, rhs.sourceMapURL()) &&
          setIntroducerFilename(cx, rhs.introducerFilename());
 }
 
 bool JS::OwningCompileOptions::setFile(JSContext* cx, const char* f) {
   char* copy = nullptr;
@@ -3526,17 +3528,18 @@ bool JS::OwningCompileOptions::setIntrod
   introducerFilename_ = copy;
   return true;
 }
 
 JS::CompileOptions::CompileOptions(JSContext* cx)
     : ReadOnlyCompileOptions(),
       elementRoot(cx),
       elementAttributeNameRoot(cx),
-      introductionScriptRoot(cx) {
+      introductionScriptRoot(cx),
+      scriptOrModuleRoot(cx) {
   strictOption = cx->options().strictMode();
   extraWarningsOption = cx->realm()->behaviors().extraWarnings(cx);
   isProbablySystemCode = cx->realm()->isProbablySystemCode();
   werrorOption = cx->options().werror();
   if (!cx->options().asmJS()) {
     asmJSOption = AsmJSOption::Disabled;
   } else if (cx->realm()->debuggerObservesAsmJS()) {
     asmJSOption = AsmJSOption::DisabledByDebugger;
@@ -3716,54 +3719,53 @@ JS_PUBLIC_API bool JS::CompileModule(JSC
   CHECK_THREAD(cx);
 
   module.set(frontend::CompileModule(cx, options, srcBuf));
   return !!module;
 }
 
 JS_PUBLIC_API void JS::SetModulePrivate(JSObject* module,
                                         const JS::Value& value) {
-  module->as<ModuleObject>().scriptSourceObject()->setPrivate(value);
+  JSRuntime* rt = module->zone()->runtimeFromMainThread();
+  module->as<ModuleObject>().scriptSourceObject()->setPrivate(rt, value);
 }
 
 JS_PUBLIC_API JS::Value JS::GetModulePrivate(JSObject* module) {
   return module->as<ModuleObject>().scriptSourceObject()->canonicalPrivate();
 }
 
 JS_PUBLIC_API void JS::SetScriptPrivate(JSScript* script,
                                         const JS::Value& value) {
-  script->sourceObject()->setPrivate(value);
+  JSRuntime* rt = script->zone()->runtimeFromMainThread();
+  script->sourceObject()->setPrivate(rt, value);
 }
 
 JS_PUBLIC_API JS::Value JS::GetScriptPrivate(JSScript* script) {
   return script->sourceObject()->canonicalPrivate();
 }
 
 JS_PUBLIC_API JS::Value JS::GetScriptedCallerPrivate(JSContext* cx) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
 
   NonBuiltinFrameIter iter(cx, cx->realm()->principals());
   if (iter.done() || !iter.hasScript()) {
     return UndefinedValue();
   }
 
-  return FindScriptOrModulePrivateForScript(iter.script());
-}
-
-JS_PUBLIC_API JS::ScriptPrivateFinalizeHook JS::GetScriptPrivateFinalizeHook(
-    JSRuntime* rt) {
-  AssertHeapIsIdle();
-  return rt->scriptPrivateFinalizeHook;
-}
-
-JS_PUBLIC_API void JS::SetScriptPrivateFinalizeHook(
-    JSRuntime* rt, JS::ScriptPrivateFinalizeHook func) {
-  AssertHeapIsIdle();
-  rt->scriptPrivateFinalizeHook = func;
+  return iter.script()->sourceObject()->canonicalPrivate();
+}
+
+JS_PUBLIC_API void JS::SetScriptPrivateReferenceHooks(
+  JSRuntime* rt,
+  JS::ScriptPrivateReferenceHook addRefHook,
+  JS::ScriptPrivateReferenceHook releaseHook) {
+  AssertHeapIsIdle();
+  rt->scriptPrivateAddRefHook = addRefHook;
+  rt->scriptPrivateReleaseHook = releaseHook;
 }
 
 JS_PUBLIC_API bool JS::ModuleInstantiate(JSContext* cx,
                                          JS::HandleObject moduleArg) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   cx->check(moduleArg);
   return ModuleObject::Instantiate(cx, moduleArg.as<ModuleObject>());
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2570,34 +2570,29 @@ extern JS_PUBLIC_API JS::Value GetScript
 
 /*
  * Return the private value associated with currently executing script or
  * module, or undefined if there is no such script.
  */
 extern JS_PUBLIC_API JS::Value GetScriptedCallerPrivate(JSContext* cx);
 
 /**
- * A hook that's called whenever a script or module which has a private value
- * set with SetScriptPrivate() or SetModulePrivate() is finalized. This can be
- * used to clean up the private state. The private value is passed as an
- * argument.
+ * Hooks called when references to a script private value are created or
+ * destroyed. This allows use of a reference counted object as the
+ * script private.
  */
-using ScriptPrivateFinalizeHook = void (*)(JSFreeOp*, const JS::Value&);
-
-/**
- * Get the script private finalize hook for the runtime.
- */
-extern JS_PUBLIC_API ScriptPrivateFinalizeHook
-GetScriptPrivateFinalizeHook(JSRuntime* rt);
+using ScriptPrivateReferenceHook = void (*)(const JS::Value&);
 
 /**
  * Set the script private finalize hook for the runtime to the given function.
  */
-extern JS_PUBLIC_API void SetScriptPrivateFinalizeHook(
-    JSRuntime* rt, ScriptPrivateFinalizeHook func);
+extern JS_PUBLIC_API void SetScriptPrivateReferenceHooks(
+  JSRuntime* rt,
+  ScriptPrivateReferenceHook addRefHook,
+  ScriptPrivateReferenceHook releaseHook);
 
 /*
  * 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/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3362,33 +3362,16 @@ ModuleObject* js::GetModuleObjectForScri
   for (ScopeIter si(script); si; si++) {
     if (si.kind() == ScopeKind::Module) {
       return si.scope()->as<ModuleScope>().module();
     }
   }
   return nullptr;
 }
 
-Value js::FindScriptOrModulePrivateForScript(JSScript* script) {
-  MOZ_ASSERT(script);
-  ScriptSourceObject* sso = script->sourceObject();
-  while (sso) {
-    Value value = sso->canonicalPrivate();
-    if (!value.isUndefined()) {
-      return value;
-    }
-
-    ScriptSourceObject* parent = sso->unwrappedIntroductionSourceObject();
-    MOZ_ASSERT(parent != sso);
-    sso = parent;
-  }
-
-  return UndefinedValue();
-}
-
 bool js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx,
                                                   AbstractFramePtr frame,
                                                   jsbytecode* pc,
                                                   MutableHandleValue res) {
   RootedObject scopeChain(cx);
   RootedScope scope(cx);
   if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope)) {
     return false;
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -1144,18 +1144,16 @@ inline bool IsFrameInitialEnvironment(Ab
 
 extern bool CreateObjectsForEnvironmentChain(JSContext* cx,
                                              AutoObjectVector& chain,
                                              HandleObject terminatingEnv,
                                              MutableHandleObject envObj);
 
 ModuleObject* GetModuleObjectForScript(JSScript* script);
 
-Value FindScriptOrModulePrivateForScript(JSScript* script);
-
 ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script);
 
 MOZ_MUST_USE bool GetThisValueForDebuggerMaybeOptimizedOut(
     JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
     MutableHandleValue res);
 
 MOZ_MUST_USE bool CheckVarNameConflict(
     JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4254,24 +4254,24 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         goto error;
       }
 
       PUSH_OBJECT(*metaObject);
     }
     END_CASE(JSOP_IMPORTMETA)
 
     CASE(JSOP_DYNAMIC_IMPORT) {
-      ReservedRooted<Value> referencingPrivate(&rootValue0);
-      referencingPrivate = FindScriptOrModulePrivateForScript(script);
+      ReservedRooted<JSObject*> referencingScriptSource(&rootObject0);
+      referencingScriptSource = script->sourceObject();
 
       ReservedRooted<Value> specifier(&rootValue1);
       POP_COPY_TO(specifier);
 
       JSObject* promise =
-          StartDynamicModuleImport(cx, referencingPrivate, specifier);
+          StartDynamicModuleImport(cx, referencingScriptSource, specifier);
       if (!promise) goto error;
 
       PUSH_OBJECT(*promise);
     }
     END_CASE(JSOP_DYNAMIC_IMPORT)
 
     CASE(JSOP_ENVCALLEE) {
       uint8_t numHops = GET_UINT8(REGS.pc);
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1846,17 +1846,18 @@ static bool CreateDynamicFunction(JSCont
     introducerFilename = maybeScript->scriptSource()->introducerFilename();
   }
 
   CompileOptions options(cx);
   options.setMutedErrors(mutedErrors)
       .setFileAndLine(filename, 1)
       .setNoScriptRval(false)
       .setIntroductionInfo(introducerFilename, introductionType, lineno,
-                           maybeScript, pcOffset);
+                           maybeScript, pcOffset)
+      .setScriptOrModule(maybeScript);
 
   StringBuffer sb(cx);
 
   if (isAsync) {
     if (!sb.append("async ")) {
       return false;
     }
   }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1313,25 +1313,18 @@ bool JSScript::hasScriptName() {
   return p.found();
 }
 
 void ScriptSourceObject::finalize(FreeOp* fop, JSObject* obj) {
   MOZ_ASSERT(fop->onMainThread());
   ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
   sso->source()->decref();
 
-  Value value = sso->canonicalPrivate();
-  if (!value.isUndefined()) {
-    // The embedding may need to dispose of its private data.
-    JS::AutoSuppressGCAnalysis suppressGC;
-    if (JS::ScriptPrivateFinalizeHook hook =
-            fop->runtime()->scriptPrivateFinalizeHook) {
-      hook(fop, value);
-    }
-  }
+  // Clear the private value, calling the release hook if necessary.
+  sso->setPrivate(fop->runtime(), UndefinedValue());
 }
 
 void ScriptSourceObject::trace(JSTracer* trc, JSObject* obj) {
   // This can be invoked during allocation of the SSO itself, before we've had a
   // chance to initialize things properly. In that case, there's nothing to
   // trace.
   if (obj->as<ScriptSourceObject>().hasSource()) {
     obj->as<ScriptSourceObject>().source()->trace(trc);
@@ -1377,18 +1370,16 @@ ScriptSourceObject* ScriptSourceObject::
   }
 
   // The slots below should either be populated by a call to initFromOptions or,
   // if this is a non-canonical ScriptSourceObject, they are unused. Poison
   // them.
   obj->initReservedSlot(ELEMENT_SLOT, MagicValue(JS_GENERIC_MAGIC));
   obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
   obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
-  obj->initReservedSlot(INTRODUCTION_SOURCE_OBJECT_SLOT,
-                        MagicValue(JS_GENERIC_MAGIC));
 
   return obj;
 }
 
 ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
                                                ScriptSource* source) {
   return createInternal(cx, source, nullptr);
 }
@@ -1417,42 +1408,45 @@ ScriptSourceObject* ScriptSourceObject::
     const ReadOnlyCompileOptions& options) {
   cx->releaseCheck(source);
   MOZ_ASSERT(source->isCanonical());
   MOZ_ASSERT(source->getReservedSlot(ELEMENT_SLOT).isMagic(JS_GENERIC_MAGIC));
   MOZ_ASSERT(
       source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
   MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
                  .isMagic(JS_GENERIC_MAGIC));
-  MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SOURCE_OBJECT_SLOT)
-                 .isMagic(JS_GENERIC_MAGIC));
 
   RootedObject element(cx, options.element());
   RootedString elementAttributeName(cx, options.elementAttributeName());
   if (!initElementProperties(cx, source, element, elementAttributeName)) {
     return false;
   }
 
   // There is no equivalent of cross-compartment wrappers for scripts. If the
   // introduction script and ScriptSourceObject are in different compartments,
   // we would be creating a cross-compartment script reference, which is
   // forbidden. We can still store a CCW to the script source object though.
-  RootedValue introdutionScript(cx);
-  RootedValue introdutionSource(cx);
-  if (options.introductionScript()) {
-    if (options.introductionScript()->compartment() == cx->compartment()) {
-      introdutionScript.setPrivateGCThing(options.introductionScript());
+  RootedValue introductionScript(cx);
+  if (JSScript* script = options.introductionScript()) {
+    if (script->compartment() == cx->compartment()) {
+      introductionScript.setPrivateGCThing(options.introductionScript());
     }
-    introdutionSource.setObject(*options.introductionScript()->sourceObject());
-    if (!cx->compartment()->wrap(cx, &introdutionSource)) {
+  }
+  source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
+
+  // Set the private value to that of the script or module that this source is
+  // part of, if any.
+  RootedValue privateValue(cx);
+  if (JSScript* script = options.scriptOrModule()) {
+    privateValue = script->sourceObject()->canonicalPrivate();
+    if (!JS_WrapValue(cx, &privateValue)) {
       return false;
     }
   }
-  source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introdutionScript);
-  source->setReservedSlot(INTRODUCTION_SOURCE_OBJECT_SLOT, introdutionSource);
+  source->setPrivate(cx->runtime(), privateValue);
 
   return true;
 }
 
 /* static */ bool ScriptSourceObject::initElementProperties(
     JSContext* cx, HandleScriptSourceObject source, HandleObject element,
     HandleString elementAttrName) {
   MOZ_ASSERT(source->isCanonical());
@@ -1471,16 +1465,35 @@ ScriptSourceObject* ScriptSourceObject::
   }
 
   source->setReservedSlot(ELEMENT_SLOT, elementValue);
   source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue);
 
   return true;
 }
 
+void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
+  // Update the private value, calling addRef/release hooks if necessary
+  // to allow the embedding to maintain a reference count for the
+  // private data.
+  JS::AutoSuppressGCAnalysis nogc;
+  Value prevValue = getReservedSlot(PRIVATE_SLOT);
+  if (!prevValue.isUndefined()) {
+    if (auto releaseHook = rt->scriptPrivateReleaseHook) {
+      releaseHook(prevValue);
+    }
+  }
+  setReservedSlot(PRIVATE_SLOT, value);
+  if (!value.isUndefined()) {
+    if (auto addRefHook = rt->scriptPrivateAddRefHook) {
+      addRefHook(value);
+    }
+  }
+}
+
 /* static */ bool JSScript::loadSource(JSContext* cx, ScriptSource* ss,
                                        bool* worked) {
   MOZ_ASSERT(!ss->hasSourceText());
   *worked = false;
   if (!cx->runtime()->sourceHook.ref() || !ss->sourceRetrievable()) {
     return true;
   }
   char16_t* src = nullptr;
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1184,44 +1184,32 @@ class ScriptSourceObject : public Native
   JSScript* unwrappedIntroductionScript() const {
     Value value =
         unwrappedCanonical()->getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
     if (value.isUndefined()) {
       return nullptr;
     }
     return value.toGCThing()->as<JSScript>();
   }
-  ScriptSourceObject* unwrappedIntroductionSourceObject() const {
-    Value value =
-        unwrappedCanonical()->getReservedSlot(INTRODUCTION_SOURCE_OBJECT_SLOT);
-    if (value.isUndefined()) {
-      return nullptr;
-    }
-    return &UncheckedUnwrap(&value.toObject())->as<ScriptSourceObject>();
-  }
-
-  void setPrivate(const Value& value) {
-    MOZ_ASSERT(isCanonical());
-    setReservedSlot(PRIVATE_SLOT, value);
-  }
+
+  void setPrivate(JSRuntime* rt, const Value& value);
 
   Value canonicalPrivate() const {
     Value value = getReservedSlot(PRIVATE_SLOT);
     MOZ_ASSERT_IF(!isCanonical(), value.isUndefined());
     return value;
   }
 
  private:
   enum {
     SOURCE_SLOT = 0,
     CANONICAL_SLOT,
     ELEMENT_SLOT,
     ELEMENT_PROPERTY_SLOT,
     INTRODUCTION_SCRIPT_SLOT,
-    INTRODUCTION_SOURCE_OBJECT_SLOT,
     PRIVATE_SLOT,
     RESERVED_SLOTS
   };
 };
 
 enum class GeneratorKind : bool { NotGenerator, Generator };
 enum class FunctionAsyncKind : bool { SyncFunction, AsyncFunction };
 
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -157,17 +157,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
       oomCallback(nullptr),
       debuggerMallocSizeOf(ReturnZeroSize),
       stackFormat_(parentRuntime ? js::StackFormat::Default
                                  : js::StackFormat::SpiderMonkey),
       wasmInstances(mutexid::WasmRuntimeInstances),
       moduleResolveHook(),
       moduleMetadataHook(),
       moduleDynamicImportHook(),
-      scriptPrivateFinalizeHook() {
+      scriptPrivateAddRefHook(),
+      scriptPrivateReleaseHook() {
   JS_COUNT_CTOR(JSRuntime);
   liveRuntimesCount++;
 
   // See function comment for why we call this now, not in JS_Init().
   wasm::EnsureEagerProcessSignalHandlers();
 }
 
 JSRuntime::~JSRuntime() {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -963,18 +963,19 @@ struct JSRuntime : public js::MallocProv
   // HostGetImportMetaProperties and HostFinalizeImportMeta.
   js::MainThreadData<JS::ModuleMetadataHook> moduleMetadataHook;
 
   // A hook that implements the abstract operation
   // HostImportModuleDynamically. This is also used to enable/disable dynamic
   // module import and can accessed by off-thread parsing.
   mozilla::Atomic<JS::ModuleDynamicImportHook> moduleDynamicImportHook;
 
-  // A hook called on script finalization.
-  js::MainThreadData<JS::ScriptPrivateFinalizeHook> scriptPrivateFinalizeHook;
+  // Hooks called when script private references are created and destroyed.
+  js::MainThreadData<JS::ScriptPrivateReferenceHook> scriptPrivateAddRefHook;
+  js::MainThreadData<JS::ScriptPrivateReferenceHook> scriptPrivateReleaseHook;
 
  public:
 #if defined(JS_BUILD_BINAST)
   js::BinaryASTSupport& binast() { return binast_; }
 
  private:
   js::BinaryASTSupport binast_;
 #endif  // defined(JS_BUILD_BINAST)