Merge mozilla-inbound to mozilla-central. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Thu, 03 Jan 2019 18:22:07 +0200
changeset 509515 d4cbd7e2b41803118f8d16f61fc40bee12259499
parent 509497 5b837856dca7609b3888a866524998fff7bac8a4 (current diff)
parent 509514 5ad9c5b505d3e84d4db7e242d53b881b967a2ffc (diff)
child 509527 31756aa6f98d9de8ce01431c5b45e37e5c215880
child 509548 f87383555d10739ed96ff1a11acc5ed375965215
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)
reviewersmerge
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
Merge mozilla-inbound to mozilla-central. a=merge
dom/script/ModuleScript.cpp
dom/script/ModuleScript.h
js/src/jsapi.cpp
js/src/vm/Runtime.h
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html.ini
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -229,16 +229,17 @@
 #include "mozilla/dom/HashChangeEvent.h"
 #include "mozilla/dom/IntlUtils.h"
 #include "mozilla/dom/PopStateEvent.h"
 #include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/PopupBlockedEvent.h"
 #include "mozilla/dom/PrimitiveConversions.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
+#include "mozilla/dom/LoadedScript.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/InstallTriggerBinding.h"
 #include "mozilla/dom/Report.h"
 #include "mozilla/dom/ReportingObserver.h"
@@ -5978,35 +5979,42 @@ bool nsGlobalWindowInner::RunTimeoutHand
   bool abortIntervalHandler = false;
   nsCOMPtr<nsIScriptTimeoutHandler> handler(
       do_QueryInterface(timeout->mScriptHandler));
   if (handler) {
     RefPtr<Function> callback = handler->GetCallback();
 
     if (!callback) {
       // Evaluate the timeout expression.
-      const nsAString& script = handler->GetHandlerText();
-
       const char* filename = nullptr;
       uint32_t lineNo = 0, dummyColumn = 0;
       handler->GetLocation(&filename, &lineNo, &dummyColumn);
 
       // New script entry point required, due to the "Create a script" sub-step
       // of
       // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps
       nsAutoMicroTask mt;
       AutoEntryScript aes(this, reason, true);
       JS::CompileOptions options(aes.cx());
       options.setFileAndLine(filename, lineNo);
       options.setNoScriptRval(true);
       JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
       nsresult rv;
       {
         nsJSUtils::ExecutionContext exec(aes.cx(), global);
-        rv = exec.CompileAndExec(options, script);
+        rv = exec.Compile(options, handler->GetHandlerText());
+
+        if (rv == NS_OK) {
+          LoadedScript* initiatingScript = handler->GetInitiatingScript();
+          if (initiatingScript) {
+            initiatingScript->AssociateWithScript(exec.GetScript());
+          }
+
+          rv = exec.ExecScript();
+        }
       }
 
       if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
         abortIntervalHandler = true;
       }
     } else {
       // Hold strong ref to ourselves while we call the callback.
       nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this));
--- a/dom/base/nsIScriptTimeoutHandler.h
+++ b/dom/base/nsIScriptTimeoutHandler.h
@@ -9,16 +9,17 @@
 #include "nsITimeoutHandler.h"
 #include "nsTArray.h"
 #include "js/TypeDecls.h"
 #include "mozilla/Maybe.h"
 
 namespace mozilla {
 namespace dom {
 class Function;
+class LoadedScript;
 }  // namespace dom
 }  // namespace mozilla
 
 #define NS_ISCRIPTTIMEOUTHANDLER_IID                 \
   {                                                  \
     0x53c8e80e, 0xcc78, 0x48bc, {                    \
       0xba, 0x63, 0x0c, 0xb9, 0xdb, 0xf7, 0x06, 0x34 \
     }                                                \
@@ -41,14 +42,17 @@ class nsIScriptTimeoutHandler : public n
   virtual const nsAString& GetHandlerText() = 0;
 
   // Get the location of the script.
   // Note: The memory pointed to by aFileName is owned by the
   // nsIScriptTimeoutHandler and should not be freed by the caller.
 
   // If we have a Function, get the arguments for passing to it.
   virtual const nsTArray<JS::Value>& GetArgs() = 0;
+
+  // If we have an expression, get the initiating script.
+  virtual mozilla::dom::LoadedScript* GetInitiatingScript() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptTimeoutHandler,
                               NS_ISCRIPTTIMEOUTHANDLER_IID)
 
 #endif  // nsIScriptTimeoutHandler_h___
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -6,16 +6,17 @@
 
 #include <algorithm>
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/dom/CSPEvalChecker.h"
 #include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/LoadedScript.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsGlobalWindow.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocument.h"
 #include "nsIScriptTimeoutHandler.h"
@@ -34,16 +35,17 @@ class nsJSScriptTimeoutHandler final : p
 
   nsJSScriptTimeoutHandler();
   // This will call SwapElements on aArguments with an empty array.
   nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindowInner* aWindow,
                            Function& aFunction,
                            nsTArray<JS::Heap<JS::Value>>&& aArguments,
                            ErrorResult& aError);
   nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindowInner* aWindow,
+                           LoadedScript* aInitiatingScript,
                            const nsAString& aExpression, bool* aAllowEval,
                            ErrorResult& aError);
   nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                            Function& aFunction,
                            nsTArray<JS::Heap<JS::Value>>&& aArguments);
   nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                            const nsAString& aExpression, bool* aAllowEval,
                            ErrorResult& aRv);
@@ -58,16 +60,20 @@ class nsJSScriptTimeoutHandler final : p
 
   virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
                            uint32_t* aColumn) override {
     *aFileName = mFileName.get();
     *aLineNo = mLineNo;
     *aColumn = mColumn;
   }
 
+  virtual LoadedScript* GetInitiatingScript() override {
+    return mInitiatingScript;
+  }
+
   virtual void MarkForCC() override {
     if (mFunction) {
       mFunction->MarkForCC();
     }
   }
 
   void ReleaseJSObjects();
 
@@ -83,25 +89,31 @@ class nsJSScriptTimeoutHandler final : p
   uint32_t mLineNo;
   uint32_t mColumn;
   nsTArray<JS::Heap<JS::Value>> mArgs;
 
   // The expression to evaluate or function to call. If mFunction is non-null
   // it should be used, else use mExpr.
   nsString mExpr;
   RefPtr<Function> mFunction;
+
+  // Initiating script for use when evaluating mExpr on the main thread.
+  RefPtr<LoadedScript> mInitiatingScript;
 };
 
 // nsJSScriptTimeoutHandler
 // QueryInterface implementation for nsJSScriptTimeoutHandler
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFunction)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInitiatingScript)
   tmp->ReleaseJSObjects();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     nsAutoCString name("nsJSScriptTimeoutHandler");
     if (tmp->mFunction) {
       JSObject* obj = tmp->mFunction->CallablePreserveColor();
       JSFunction* fun =
           JS_GetObjectFunction(js::UncheckedUnwrapWithoutExpose(obj));
       if (fun && JS_GetFunctionId(fun)) {
@@ -129,16 +141,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   } else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler,
                                       tmp->mRefCnt.get())
   }
 
   if (tmp->mFunction) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
   }
+  if (tmp->mInitiatingScript) {
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInitiatingScript)
+  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
   for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i])
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
@@ -162,22 +177,24 @@ nsJSScriptTimeoutHandler::nsJSScriptTime
     // don't let a timer be scheduled on such a window.
     aError.Throw(NS_ERROR_NOT_INITIALIZED);
     return;
   }
 
   Init(aCx, std::move(aArguments));
 }
 
-nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
-                                                   nsGlobalWindowInner* aWindow,
-                                                   const nsAString& aExpression,
-                                                   bool* aAllowEval,
-                                                   ErrorResult& aError)
-    : mLineNo(0), mColumn(0), mExpr(aExpression) {
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(
+    JSContext* aCx, nsGlobalWindowInner* aWindow,
+    LoadedScript* aInitiatingScript, const nsAString& aExpression,
+    bool* aAllowEval, ErrorResult& aError)
+    : mLineNo(0),
+      mColumn(0),
+      mExpr(aExpression),
+      mInitiatingScript(aInitiatingScript) {
   if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
     // This window was already closed, or never properly initialized,
     // don't let a timer be scheduled on such a window.
     aError.Throw(NS_ERROR_NOT_INITIALIZED);
     return;
   }
 
   aError =
@@ -255,19 +272,21 @@ already_AddRefed<nsIScriptTimeoutHandler
   RefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler(
       aCx, aWindow, aFunction, std::move(args), aError);
   return aError.Failed() ? nullptr : handler.forget();
 }
 
 already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
     JSContext* aCx, nsGlobalWindowInner* aWindow, const nsAString& aExpression,
     ErrorResult& aError) {
+  LoadedScript* script = ScriptLoader::GetActiveScript(aCx);
+
   bool allowEval = false;
   RefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler(
-      aCx, aWindow, aExpression, &allowEval, aError);
+      aCx, aWindow, script, aExpression, &allowEval, aError);
   if (aError.Failed() || !allowEval) {
     return nullptr;
   }
 
   return handler.forget();
 }
 
 already_AddRefed<nsIScriptTimeoutHandler> NS_CreateJSTimeoutHandler(
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -119,24 +119,26 @@ nsJSUtils::ExecutionContext::ExecutionCo
       mAutoProfilerLabel("nsJSUtils::ExecutionContext",
                          /* dynamicStr */ nullptr,
                          js::ProfilingStackFrame::Category::JS),
 #endif
       mCx(aCx),
       mRealm(aCx, aGlobal),
       mRetValue(aCx),
       mScopeChain(aCx),
+      mScript(aCx),
       mRv(NS_OK),
       mSkip(false),
       mCoerceToString(false),
       mEncodeBytecode(false)
 #ifdef DEBUG
       ,
       mWantsReturnValue(false),
-      mExpectScopeChain(false)
+      mExpectScopeChain(false),
+      mScriptUsed(false)
 #endif
 {
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(CycleCollectedJSContext::Get() &&
              CycleCollectedJSContext::Get()->MicroTaskLevel());
   MOZ_ASSERT(mRetValue.isUndefined());
 
@@ -169,308 +171,294 @@ void nsJSUtils::ExecutionContext::SetSco
     if (!JS_WrapObject(mCx, mScopeChain[i])) {
       mSkip = true;
       mRv = NS_ERROR_OUT_OF_MEMORY;
       return;
     }
   }
 }
 
-nsresult nsJSUtils::ExecutionContext::JoinAndExec(
-    JS::OffThreadToken** aOffThreadToken,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::JoinCompile(
+    JS::OffThreadToken** aOffThreadToken) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
-  aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
+  MOZ_ASSERT(!mScript);
+  mScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::CompileAndExec(
-    JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::Compile(
+    JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(mRetValue.isUndefined());
 #ifdef DEBUG
   mWantsReturnValue = !aCompileOptions.noScriptRval;
 #endif
 
+  MOZ_ASSERT(!mScript);
   bool compiled = true;
   if (mScopeChain.length() == 0) {
-    compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, aScript);
+    compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, &mScript);
   } else {
-    compiled =
-        JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf, aScript);
+    compiled = JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf,
+                                               &mScript);
   }
 
-  MOZ_ASSERT_IF(compiled, aScript);
+  MOZ_ASSERT_IF(compiled, mScript);
   if (!compiled) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::CompileAndExec(
+nsresult nsJSUtils::ExecutionContext::Compile(
     JS::CompileOptions& aCompileOptions, const nsAString& aScript) {
-  MOZ_ASSERT(!mEncodeBytecode,
-             "A JSScript is needed for calling FinishIncrementalEncoding");
   if (mSkip) {
     return mRv;
   }
 
   const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
   JS::SourceText<char16_t> srcBuf;
   if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(),
                    JS::SourceOwnership::Borrowed)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  JS::Rooted<JSScript*> script(mCx);
-  return CompileAndExec(aCompileOptions, srcBuf, &script);
+  return Compile(aCompileOptions, srcBuf);
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeAndExec(
+nsresult nsJSUtils::ExecutionContext::Decode(
     JS::CompileOptions& aCompileOptions, mozilla::Vector<uint8_t>& aBytecodeBuf,
     size_t aBytecodeIndex) {
-  MOZ_ASSERT(!mEncodeBytecode,
-             "A JSScript is needed for calling FinishIncrementalEncoding");
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
-  JS::Rooted<JSScript*> script(mCx);
   JS::TranscodeResult tr =
-      JS::DecodeScript(mCx, aBytecodeBuf, &script, aBytecodeIndex);
+      JS::DecodeScript(mCx, aBytecodeBuf, &mScript, aBytecodeIndex);
   // These errors are external parameters which should be handled before the
   // decoding phase, and which are the only reasons why you might want to
   // fallback on decoding failures.
   MOZ_ASSERT(tr != JS::TranscodeResult_Failure_BadBuildId &&
              tr != JS::TranscodeResult_Failure_WrongCompileOption);
   if (tr != JS::TranscodeResult_Ok) {
     mSkip = true;
     mRv = NS_ERROR_DOM_JS_DECODING_ERROR;
     return mRv;
   }
 
-  if (!JS_ExecuteScript(mCx, mScopeChain, script)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
   return mRv;
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeJoinAndExec(
+nsresult nsJSUtils::ExecutionContext::JoinDecode(
     JS::OffThreadToken** aOffThreadToken) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
-  JS::Rooted<JSScript*> script(mCx);
-  script.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
+  mScript.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
-  if (!script || !JS_ExecuteScript(mCx, mScopeChain, script)) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeBinASTJoinAndExec(
-    JS::OffThreadToken** aOffThreadToken,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::JoinDecodeBinAST(
+    JS::OffThreadToken** aOffThreadToken) {
 #ifdef JS_BUILD_BINAST
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
 
-  aScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
+  mScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
 
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeBinASTAndExec(
-    JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::DecodeBinAST(
+    JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength) {
 #ifdef JS_BUILD_BINAST
   MOZ_ASSERT(mScopeChain.length() == 0,
              "BinAST decoding is not supported in non-syntactic scopes");
 
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(aBuf);
   MOZ_ASSERT(mRetValue.isUndefined());
 #ifdef DEBUG
   mWantsReturnValue = !aCompileOptions.noScriptRval;
 #endif
 
-  aScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
+  mScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
 
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
+JSScript* nsJSUtils::ExecutionContext::GetScript() {
+#ifdef DEBUG
+  MOZ_ASSERT(!mSkip);
+  MOZ_ASSERT(mScript);
+  mScriptUsed = true;
+#endif
+
+  return mScript;
+}
+
+nsresult nsJSUtils::ExecutionContext::ExecScript() {
+  if (mSkip) {
+    return mRv;
+  }
+
+  MOZ_ASSERT(mScript);
+
+  if (!JS_ExecuteScript(mCx, mScopeChain, mScript)) {
+    mSkip = true;
+    mRv = EvaluationExceptionToNSResult(mCx);
+    return mRv;
+  }
+
+  return NS_OK;
+}
+
 static bool IsPromiseValue(JSContext* aCx, JS::Handle<JS::Value> aValue) {
   if (!aValue.isObject()) {
     return false;
   }
 
   JS::Rooted<JSObject*> obj(aCx, js::CheckedUnwrap(&aValue.toObject()));
   if (!obj) {
     return false;
   }
 
   return JS::IsPromiseObject(obj);
 }
 
-nsresult nsJSUtils::ExecutionContext::ExtractReturnValue(
+nsresult nsJSUtils::ExecutionContext::ExecScript(
     JS::MutableHandle<JS::Value> aRetValue) {
-  MOZ_ASSERT(aRetValue.isUndefined());
   if (mSkip) {
-    // Repeat earlier result, as NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW are not
-    // failures cases.
-#ifdef DEBUG
-    mWantsReturnValue = false;
-#endif
+    aRetValue.setUndefined();
     return mRv;
   }
 
+  MOZ_ASSERT(mScript);
   MOZ_ASSERT(mWantsReturnValue);
+
+  if (!JS_ExecuteScript(mCx, mScopeChain, mScript, aRetValue)) {
+    mSkip = true;
+    mRv = EvaluationExceptionToNSResult(mCx);
+    return mRv;
+  }
+
 #ifdef DEBUG
   mWantsReturnValue = false;
 #endif
-  if (mCoerceToString && IsPromiseValue(mCx, mRetValue)) {
+  if (mCoerceToString && IsPromiseValue(mCx, aRetValue)) {
     // We're a javascript: url and we should treat Promise return values as
     // undefined.
     //
     // Once bug 1477821 is fixed this code might be able to go away, or will
     // become enshrined in the spec, depending.
-    mRetValue.setUndefined();
+    aRetValue.setUndefined();
   }
 
-  if (mCoerceToString && !mRetValue.isUndefined()) {
-    JSString* str = JS::ToString(mCx, mRetValue);
+  if (mCoerceToString && !aRetValue.isUndefined()) {
+    JSString* str = JS::ToString(mCx, aRetValue);
     if (!str) {
       // ToString can be a function call, so an exception can be raised while
       // executing the function.
       mSkip = true;
       return EvaluationExceptionToNSResult(mCx);
     }
-    mRetValue.set(JS::StringValue(str));
+    aRetValue.set(JS::StringValue(str));
   }
 
-  aRetValue.set(mRetValue);
   return NS_OK;
 }
 
 nsresult nsJSUtils::CompileModule(JSContext* aCx,
                                   JS::SourceText<char16_t>& aSrcBuf,
                                   JS::Handle<JSObject*> aEvaluationGlobal,
                                   JS::CompileOptions& aCompileOptions,
                                   JS::MutableHandle<JSObject*> aModule) {
   AUTO_PROFILER_LABEL("nsJSUtils::CompileModule", JS);
-
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(JS_IsGlobalObject(aEvaluationGlobal));
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal);
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(CycleCollectedJSContext::Get() &&
              CycleCollectedJSContext::Get()->MicroTaskLevel());
 
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -76,16 +76,19 @@ class nsJSUtils {
     JSAutoRealm mRealm;
 
     // Set to a valid handle if a return value is expected.
     JS::Rooted<JS::Value> mRetValue;
 
     // Scope chain in which the execution takes place.
     JS::AutoObjectVector mScopeChain;
 
+    // The compiled script.
+    JS::Rooted<JSScript*> mScript;
+
     // returned value forwarded when we have to interupt the execution eagerly
     // with mSkip.
     nsresult mRv;
 
     // Used to skip upcoming phases in case of a failure.  In such case the
     // result is carried by mRv.
     bool mSkip;
 
@@ -95,29 +98,35 @@ class nsJSUtils {
     // Encode the bytecode before it is being executed.
     bool mEncodeBytecode;
 
 #ifdef DEBUG
     // Should we set the return value.
     bool mWantsReturnValue;
 
     bool mExpectScopeChain;
+
+    bool mScriptUsed;
 #endif
 
    public:
     // Enter compartment in which the code would be executed.  The JSContext
     // must come from an AutoEntryScript.
     ExecutionContext(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
 
     ExecutionContext(const ExecutionContext&) = delete;
     ExecutionContext(ExecutionContext&&) = delete;
 
     ~ExecutionContext() {
-      // This flag is resetted, when the returned value is extracted.
-      MOZ_ASSERT(!mWantsReturnValue);
+      // This flag is reset when the returned value is extracted.
+      MOZ_ASSERT_IF(!mSkip, !mWantsReturnValue);
+
+      // If encoding was started we expect the script to have been
+      // used when ending the encoding.
+      MOZ_ASSERT_IF(mEncodeBytecode && mScript && mRv == NS_OK, mScriptUsed);
     }
 
     // The returned value would be converted to a string if the
     // |aCoerceToString| is flag set.
     ExecutionContext& SetCoerceToString(bool aCoerceToString) {
       mCoerceToString = aCoerceToString;
       return *this;
     }
@@ -129,64 +138,63 @@ class nsJSUtils {
     ExecutionContext& SetEncodeBytecode(bool aEncodeBytecode) {
       mEncodeBytecode = aEncodeBytecode;
       return *this;
     }
 
     // Set the scope chain in which the code should be executed.
     void SetScopeChain(const JS::AutoObjectVector& aScopeChain);
 
-    // Copy the returned value in the mutable handle argument, in case of a
+    // After getting a notification that an off-thread compilation terminated,
+    // this function will take the result of the parser and move it to the main
+    // thread.
+    MOZ_MUST_USE nsresult JoinCompile(JS::OffThreadToken** aOffThreadToken);
+
+    // Compile a script contained in a SourceText.
+    nsresult Compile(JS::CompileOptions& aCompileOptions,
+                     JS::SourceText<char16_t>& aSrcBuf);
+
+    // Compile a script contained in a string.
+    nsresult Compile(JS::CompileOptions& aCompileOptions,
+                     const nsAString& aScript);
+
+    // Decode a script contained in a buffer.
+    nsresult Decode(JS::CompileOptions& aCompileOptions,
+                    mozilla::Vector<uint8_t>& aBytecodeBuf,
+                    size_t aBytecodeIndex);
+
+    // After getting a notification that an off-thread decoding terminated, this
+    // function will get the result of the decoder and move it to the main
+    // thread.
+    nsresult JoinDecode(JS::OffThreadToken** aOffThreadToken);
+
+    nsresult JoinDecodeBinAST(JS::OffThreadToken** aOffThreadToken);
+
+    // Decode a BinAST encoded script contained in a buffer.
+    nsresult DecodeBinAST(JS::CompileOptions& aCompileOptions,
+                          const uint8_t* aBuf, size_t aLength);
+
+    // Get a successfully compiled script.
+    JSScript* GetScript();
+
+    // Execute the compiled script and ignore the return value.
+    MOZ_MUST_USE nsresult ExecScript();
+
+    // Execute the compiled script a get the return value.
+    //
+    // Copy the returned value into the mutable handle argument. In case of a
     // evaluation failure either during the execution or the conversion of the
-    // result to a string, the nsresult would be set to the corresponding result
-    // code, and the mutable handle argument would remain unchanged.
+    // result to a string, the nsresult is be set to the corresponding result
+    // code and the mutable handle argument remains unchanged.
     //
     // The value returned in the mutable handle argument is part of the
     // compartment given as argument to the ExecutionContext constructor. If the
     // caller is in a different compartment, then the out-param value should be
     // wrapped by calling |JS_WrapValue|.
-    MOZ_MUST_USE nsresult
-    ExtractReturnValue(JS::MutableHandle<JS::Value> aRetValue);
-
-    // After getting a notification that an off-thread compilation terminated,
-    // this function will take the result of the parser by moving it to the main
-    // thread before starting the execution of the script.
-    //
-    // The compiled script would be returned in the |aScript| out-param.
-    MOZ_MUST_USE nsresult JoinAndExec(JS::OffThreadToken** aOffThreadToken,
-                                      JS::MutableHandle<JSScript*> aScript);
-
-    // Compile a script contained in a SourceText, and execute it.
-    nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
-                            JS::SourceText<char16_t>& aSrcBuf,
-                            JS::MutableHandle<JSScript*> aScript);
-
-    // Compile a script contained in a string, and execute it.
-    nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
-                            const nsAString& aScript);
-
-    // Decode a script contained in a buffer, and execute it.
-    MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions,
-                                        mozilla::Vector<uint8_t>& aBytecodeBuf,
-                                        size_t aBytecodeIndex);
-
-    // After getting a notification that an off-thread decoding terminated, this
-    // function will get the result of the decoder by moving it to the main
-    // thread before starting the execution of the script.
-    MOZ_MUST_USE nsresult
-    DecodeJoinAndExec(JS::OffThreadToken** aOffThreadToken);
-
-    MOZ_MUST_USE nsresult
-    DecodeBinASTJoinAndExec(JS::OffThreadToken** aOffThreadToken,
-                            JS::MutableHandle<JSScript*> aScript);
-
-    // Decode a BinAST encoded script contained in a buffer, and execute it.
-    nsresult DecodeBinASTAndExec(JS::CompileOptions& aCompileOptions,
-                                 const uint8_t* aBuf, size_t aLength,
-                                 JS::MutableHandle<JSScript*> aScript);
+    MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
   };
 
   static nsresult CompileModule(JSContext* aCx,
                                 JS::SourceText<char16_t>& aSrcBuf,
                                 JS::Handle<JSObject*> aEvaluationGlobal,
                                 JS::CompileOptions& aCompileOptions,
                                 JS::MutableHandle<JSObject*> aModule);
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -17,17 +17,19 @@
 #include "mozilla/JSEventHandler.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/LoadedScript.h"
 #include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/TimelineConsumers.h"
 #include "mozilla/EventTimelineMarker.h"
 #include "mozilla/TimeStamp.h"
 
 #include "EventListenerService.h"
 #include "nsCOMArray.h"
@@ -985,16 +987,25 @@ nsresult EventListenerManager::CompileEv
 
   JS::Rooted<JSObject*> handler(cx);
   result = nsJSUtils::CompileFunction(jsapi, scopeChain, options,
                                       nsAtomCString(typeAtom), argCount,
                                       argNames, *body, handler.address());
   NS_ENSURE_SUCCESS(result, result);
   NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE);
 
+  JS::Rooted<JSFunction*> func(cx, JS_GetObjectFunction(handler));
+  MOZ_ASSERT(func);
+  JS::Rooted<JSScript*> jsScript(cx, JS_GetFunctionScript(cx, func));
+  MOZ_ASSERT(jsScript);
+  RefPtr<LoadedScript> loaderScript = ScriptLoader::GetActiveScript(cx);
+  if (loaderScript) {
+    loaderScript->AssociateWithScript(jsScript);
+  }
+
   MOZ_ASSERT(js::IsObjectInContextCompartment(handler, cx));
   JS::Rooted<JSObject*> handlerGlobal(cx, JS::CurrentGlobalOrNull(cx));
 
   if (jsEventHandler->EventName() == nsGkAtoms::onerror && win) {
     RefPtr<OnErrorEventHandlerNonNull> handlerCallback =
         new OnErrorEventHandlerNonNull(static_cast<JSContext*>(nullptr),
                                        handler, handlerGlobal,
                                        /* aIncumbentGlobal = */ nullptr);
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -249,18 +249,18 @@ nsresult nsJSThunk::EvaluateScript(
 
   JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
   // Finally, we have everything needed to evaluate the expression.
   JS::CompileOptions options(cx);
   options.setFileAndLine(mURL.get(), 1);
   {
     nsJSUtils::ExecutionContext exec(cx, globalJSObject);
     exec.SetCoerceToString(true);
-    exec.CompileAndExec(options, NS_ConvertUTF8toUTF16(script));
-    rv = exec.ExtractReturnValue(&v);
+    exec.Compile(options, NS_ConvertUTF8toUTF16(script));
+    rv = exec.ExecScript(&v);
   }
 
   js::AssertSameCompartment(cx, v);
 
   if (NS_FAILED(rv) || !(v.isString() || v.isUndefined())) {
     return NS_ERROR_MALFORMED_URI;
   } else if (v.isUndefined()) {
     return NS_ERROR_DOM_RETVAL_UNDEFINED;
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -993,18 +993,18 @@ bool _evaluate(NPP npp, NPObject *npobj,
       js::GetObjectCompartment(obj) == js::GetContextCompartment(cx),
       "nsNPObjWrapper::GetNewOrUsed must wrap its return value");
   obj = JS::CurrentGlobalOrNull(cx);
   MOZ_ASSERT(obj);
   nsresult rv = NS_OK;
   {
     nsJSUtils::ExecutionContext exec(cx, obj);
     exec.SetScopeChain(scopeChain);
-    exec.CompileAndExec(options, utf16script);
-    rv = exec.ExtractReturnValue(&rval);
+    exec.Compile(options, utf16script);
+    rv = exec.ExecScript(&rval);
   }
 
   if (!JS_WrapValue(cx, &rval)) {
     return false;
   }
 
   return NS_SUCCEEDED(rv) &&
          (!result || JSValToNPVariant(npp, cx, rval, result));
rename from dom/script/ModuleScript.cpp
rename to dom/script/LoadedScript.cpp
--- a/dom/script/ModuleScript.cpp
+++ b/dom/script/LoadedScript.cpp
@@ -1,108 +1,184 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#include "ModuleScript.h"
+#include "LoadedScript.h"
+
 #include "mozilla/HoldDropJSObjects.h"
+
+#include "jsfriendapi.h"
 #include "ScriptLoader.h"
 
 namespace mozilla {
 namespace dom {
 
-// A single module script.  May be used to satisfy multiple load requests.
+//////////////////////////////////////////////////////////////
+// LoadedScript
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadedScript)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(LoadedScript)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LoadedScript)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LoadedScript)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(LoadedScript)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadedScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadedScript)
+
+LoadedScript::LoadedScript(ScriptKind aKind, ScriptFetchOptions* aFetchOptions,
+                           nsIURI* aBaseURL)
+    : 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.
+
+  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());
+
+#ifdef DEBUG
+  if (script->IsModuleScript()) {
+    JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet();
+    MOZ_ASSERT_IF(module, JS::GetModulePrivate(module) == aPrivate);
+  }
+#endif
+
+  script->Release();
+}
+
+//////////////////////////////////////////////////////////////
+// ClassicScript
+//////////////////////////////////////////////////////////////
+
+ClassicScript::ClassicScript(ScriptFetchOptions* aFetchOptions,
+                             nsIURI* aBaseURL)
+    : LoadedScript(ScriptKind::eClassic, aFetchOptions, aBaseURL) {}
+
+//////////////////////////////////////////////////////////////
+// ModuleScript
+//////////////////////////////////////////////////////////////
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
-NS_INTERFACE_MAP_END
+NS_INTERFACE_MAP_END_INHERITING(LoadedScript)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ModuleScript)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleScript, LoadedScript)
   tmp->UnlinkModuleRecord();
   tmp->mParseError.setUndefined();
   tmp->mErrorToRethrow.setUndefined();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ModuleScript)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleScript, LoadedScript)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ModuleScript)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleScript, LoadedScript)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mParseError)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mErrorToRethrow)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleScript)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleScript)
+NS_IMPL_ADDREF_INHERITED(ModuleScript, LoadedScript)
+NS_IMPL_RELEASE_INHERITED(ModuleScript, LoadedScript)
 
-ModuleScript::ModuleScript(ScriptLoader* aLoader, nsIURI* aBaseURL)
-    : mLoader(aLoader), mBaseURL(aBaseURL), mSourceElementAssociated(false) {
-  MOZ_ASSERT(mLoader);
-  MOZ_ASSERT(mBaseURL);
-  MOZ_ASSERT(!mModuleRecord);
+ModuleScript::ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL)
+    : LoadedScript(ScriptKind::eModule, aFetchOptions, aBaseURL),
+      mSourceElementAssociated(false) {
+  MOZ_ASSERT(!ModuleRecord());
   MOZ_ASSERT(!HasParseError());
   MOZ_ASSERT(!HasErrorToRethrow());
 }
 
 void ModuleScript::UnlinkModuleRecord() {
-  // Remove module's back reference to this object request if present.
+  // 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();
-  DropJSObjects(this);
 }
 
 void ModuleScript::SetModuleRecord(JS::Handle<JSObject*> aModuleRecord) {
   MOZ_ASSERT(!mModuleRecord);
-  MOZ_ASSERT(!HasParseError());
-  MOZ_ASSERT(!HasErrorToRethrow());
+  MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError());
+  MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow());
 
   mModuleRecord = aModuleRecord;
 
-  // Make module's host defined field point to this module script object.
-  // This is cleared in the UnlinkModuleRecord().
+  // Make module's host defined field point to this object and
+  // increment our reference count. This is decremented by
+  // UnlinkModuleRecord() above.
+  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();
   mParseError = aError;
   HoldJSObjects(this);
 }
 
 void ModuleScript::SetErrorToRethrow(const JS::Value& aError) {
   MOZ_ASSERT(!aError.isUndefined());
-  MOZ_ASSERT(!HasErrorToRethrow());
 
   // This is only called after SetModuleRecord() or SetParseError() so we don't
   // need to call HoldJSObjects() here.
-  MOZ_ASSERT(mModuleRecord || HasParseError());
+  MOZ_ASSERT(ModuleRecord() || HasParseError());
 
   mErrorToRethrow = aError;
 }
 
 void ModuleScript::SetSourceElementAssociated() {
-  MOZ_ASSERT(mModuleRecord);
+  MOZ_ASSERT(ModuleRecord());
   MOZ_ASSERT(!mSourceElementAssociated);
 
   mSourceElementAssociated = true;
 }
 
 }  // namespace dom
 }  // namespace mozilla
rename from dom/script/ModuleScript.h
rename to dom/script/LoadedScript.h
--- a/dom/script/ModuleScript.h
+++ b/dom/script/LoadedScript.h
@@ -1,57 +1,108 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#ifndef mozilla_dom_ModuleScript_h
-#define mozilla_dom_ModuleScript_h
+#ifndef mozilla_dom_LoadedScript_h
+#define mozilla_dom_LoadedScript_h
 
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "jsapi.h"
+#include "ScriptLoadRequest.h"
 
 class nsIURI;
 
 namespace mozilla {
 namespace dom {
 
 class ScriptLoader;
 
-class ModuleScript final : public nsISupports {
-  RefPtr<ScriptLoader> mLoader;
+void HostFinalizeTopLevelScript(JSFreeOp* aFop, const JS::Value& aPrivate);
+
+class ClassicScript;
+class ModuleScript;
+
+class LoadedScript : public nsISupports {
+  ScriptKind mKind;
+  RefPtr<ScriptFetchOptions> mFetchOptions;
   nsCOMPtr<nsIURI> mBaseURL;
+
+ protected:
+  LoadedScript(ScriptKind aKind, ScriptFetchOptions* aFetchOptions,
+               nsIURI* aBaseURL);
+
+  virtual ~LoadedScript();
+
+ public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(LoadedScript)
+
+  bool IsModuleScript() const { return mKind == ScriptKind::eModule; }
+
+  inline ClassicScript* AsClassicScript();
+  inline ModuleScript* AsModuleScript();
+
+  ScriptFetchOptions* FetchOptions() const { return mFetchOptions; }
+  nsIURI* BaseURL() const { return mBaseURL; }
+
+  void AssociateWithScript(JSScript* aScript);
+};
+
+class ClassicScript final : public LoadedScript {
+  ~ClassicScript() = default;
+
+ public:
+  ClassicScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
+};
+
+// A single module script. May be used to satisfy multiple load requests.
+
+class ModuleScript final : public LoadedScript {
   JS::Heap<JSObject*> mModuleRecord;
   JS::Heap<JS::Value> mParseError;
   JS::Heap<JS::Value> mErrorToRethrow;
   bool mSourceElementAssociated;
 
   ~ModuleScript();
 
  public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ModuleScript)
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleScript,
+                                                         LoadedScript)
 
-  ModuleScript(ScriptLoader* aLoader, nsIURI* aBaseURL);
+  ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
 
   void SetModuleRecord(JS::Handle<JSObject*> aModuleRecord);
   void SetParseError(const JS::Value& aError);
   void SetErrorToRethrow(const JS::Value& aError);
   void SetSourceElementAssociated();
 
-  ScriptLoader* Loader() const { return mLoader; }
   JSObject* ModuleRecord() const { return mModuleRecord; }
-  nsIURI* BaseURL() const { return mBaseURL; }
+
   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&);
 };
 
+ClassicScript* LoadedScript::AsClassicScript() {
+  MOZ_ASSERT(!IsModuleScript());
+  return static_cast<ClassicScript*>(this);
+}
+
+ModuleScript* LoadedScript::AsModuleScript() {
+  MOZ_ASSERT(IsModuleScript());
+  return static_cast<ModuleScript*>(this);
+}
+
 }  // namespace dom
 }  // namespace mozilla
 
-#endif  // mozilla_dom_ModuleScript_h
+#endif  // mozilla_dom_LoadedScript_h
--- a/dom/script/ModuleLoadRequest.cpp
+++ b/dom/script/ModuleLoadRequest.cpp
@@ -1,58 +1,117 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "ModuleLoadRequest.h"
-#include "ModuleScript.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+
+#include "LoadedScript.h"
 #include "ScriptLoader.h"
 
 namespace mozilla {
 namespace dom {
 
 #undef LOG
 #define LOG(args) \
   MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoadRequest)
 NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest,
-                                   mBaseURL, mLoader, mModuleScript, mImports)
+NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleLoadRequest,
+                                                ScriptLoadRequest)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader, mModuleScript, mImports)
+  tmp->ClearDynamicImport();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleLoadRequest,
+                                                  ScriptLoadRequest)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader, mModuleScript, mImports)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleLoadRequest,
+                                               ScriptLoadRequest)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicReferencingPrivate)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicSpecifier)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicPromise)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
 NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
 
-ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI,
-                                     ScriptFetchOptions* aFetchOptions,
-                                     const SRIMetadata& aIntegrity,
-                                     nsIURI* aReferrer, ScriptLoader* aLoader)
-    : ScriptLoadRequest(ScriptKind::eModule, aURI, aFetchOptions, aIntegrity,
-                        aReferrer),
-      mIsTopLevel(true),
-      mLoader(aLoader),
-      mVisitedSet(new VisitedURLSet()) {
-  mVisitedSet->PutEntry(aURI);
+static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) {
+  auto set = new VisitedURLSet();
+  set->PutEntry(aURI);
+  return set;
+}
+
+/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateTopLevel(
+    nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+    const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader) {
+  return new ModuleLoadRequest(aURI, aFetchOptions, aIntegrity, aReferrer,
+                               true,  /* is top level */
+                               false, /* is dynamic import */
+                               aLoader, NewVisitedSetForTopLevelImport(aURI));
+}
+
+/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateStaticImport(
+    nsIURI* aURI, ModuleLoadRequest* aParent) {
+  auto request =
+      new ModuleLoadRequest(aURI, aParent->mFetchOptions, SRIMetadata(),
+                            aParent->mURI, false, /* is top level */
+                            false,                /* is dynamic import */
+                            aParent->mLoader, aParent->mVisitedSet);
+
+  request->mIsInline = false;
+  request->mScriptMode = aParent->mScriptMode;
+
+  return request;
 }
 
-ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI, ModuleLoadRequest* aParent)
-    : ScriptLoadRequest(ScriptKind::eModule, aURI, aParent->mFetchOptions,
-                        SRIMetadata(), aParent->mURI),
-      mIsTopLevel(false),
-      mLoader(aParent->mLoader),
-      mVisitedSet(aParent->mVisitedSet) {
-  MOZ_ASSERT(mVisitedSet->Contains(aURI));
+/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateDynamicImport(
+    nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL,
+    ScriptLoader* aLoader, JS::Handle<JS::Value> aReferencingPrivate,
+    JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) {
+  MOZ_ASSERT(aSpecifier);
+  MOZ_ASSERT(aPromise);
+
+  auto request = new ModuleLoadRequest(
+      aURI, aFetchOptions, SRIMetadata(), aBaseURL, true, /* is top level */
+      true, /* is dynamic import */
+      aLoader, NewVisitedSetForTopLevelImport(aURI));
 
-  mIsInline = false;
-  mScriptMode = aParent->mScriptMode;
+  request->mIsInline = false;
+  request->mScriptMode = ScriptMode::eAsync;
+  request->mDynamicReferencingPrivate = aReferencingPrivate;
+  request->mDynamicSpecifier = aSpecifier;
+  request->mDynamicPromise = aPromise;
+
+  HoldJSObjects(request);
+
+  return request;
 }
 
+ModuleLoadRequest::ModuleLoadRequest(
+    nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+    const SRIMetadata& aIntegrity, nsIURI* aReferrer, bool aIsTopLevel,
+    bool aIsDynamicImport, ScriptLoader* aLoader, VisitedURLSet* aVisitedSet)
+    : ScriptLoadRequest(ScriptKind::eModule, aURI, aFetchOptions, aIntegrity,
+                        aReferrer),
+      mIsTopLevel(aIsTopLevel),
+      mIsDynamicImport(aIsDynamicImport),
+      mLoader(aLoader),
+      mVisitedSet(aVisitedSet) {}
+
 void ModuleLoadRequest::Cancel() {
   ScriptLoadRequest::Cancel();
   mModuleScript = nullptr;
   mProgress = ScriptLoadRequest::Progress::eReady;
   CancelImports();
   mReady.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
 }
 
@@ -133,10 +192,16 @@ void ModuleLoadRequest::LoadFailed() {
 }
 
 void ModuleLoadRequest::LoadFinished() {
   mLoader->ProcessLoadedModuleTree(this);
 
   mLoader = nullptr;
 }
 
+void ModuleLoadRequest::ClearDynamicImport() {
+  mDynamicReferencingPrivate = JS::UndefinedValue();
+  mDynamicSpecifier = nullptr;
+  mDynamicPromise = nullptr;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/script/ModuleLoadRequest.h
+++ b/dom/script/ModuleLoadRequest.h
@@ -31,48 +31,66 @@ class VisitedURLSet : public nsTHashtabl
 // multiple imports of the same module.
 
 class ModuleLoadRequest final : public ScriptLoadRequest {
   ~ModuleLoadRequest() = default;
 
   ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete;
   ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete;
 
+  ModuleLoadRequest(nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+                    const SRIMetadata& aIntegrity, nsIURI* aReferrer,
+                    bool aIsTopLevel, bool aIsDynamicImport,
+                    ScriptLoader* aLoader, VisitedURLSet* aVisitedSet);
+
  public:
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleLoadRequest,
+                                                         ScriptLoadRequest)
 
   // Create a top-level module load request.
-  ModuleLoadRequest(nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
-                    const SRIMetadata& aIntegrity, nsIURI* aReferrer,
-                    ScriptLoader* aLoader);
+  static ModuleLoadRequest* CreateTopLevel(nsIURI* aURI,
+                                           ScriptFetchOptions* aFetchOptions,
+                                           const SRIMetadata& aIntegrity,
+                                           nsIURI* aReferrer,
+                                           ScriptLoader* aLoader);
 
-  // Create a module load request for an imported module.
-  ModuleLoadRequest(nsIURI* aURI, ModuleLoadRequest* aParent);
+  // Create a module load request for a static module import.
+  static ModuleLoadRequest* CreateStaticImport(nsIURI* aURI,
+                                               ModuleLoadRequest* aParent);
+
+  // Create a module load request for dynamic module import.
+  static ModuleLoadRequest* CreateDynamicImport(
+      nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL,
+      ScriptLoader* aLoader, JS::Handle<JS::Value> aReferencingPrivate,
+      JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise);
 
   bool IsTopLevel() const override { return mIsTopLevel; }
 
+  bool IsDynamicImport() const { return mIsDynamicImport; }
+
   void SetReady() override;
   void Cancel() override;
+  void ClearDynamicImport();
 
   void ModuleLoaded();
   void ModuleErrored();
   void DependenciesLoaded();
   void LoadFailed();
 
  private:
   void LoadFinished();
   void CancelImports();
 
  public:
   // Is this a request for a top level module script or an import?
   const bool mIsTopLevel;
 
-  // The base URL used for resolving relative module imports.
-  nsCOMPtr<nsIURI> mBaseURL;
+  // Is this the top level request for a dynamic module import?
+  const bool mIsDynamicImport;
 
   // Pointer to the script loader, used to trigger actions when the module load
   // finishes.
   RefPtr<ScriptLoader> mLoader;
 
   // Set to a module script object after a successful load or nullptr on
   // failure.
   RefPtr<ModuleScript> mModuleScript;
@@ -83,14 +101,19 @@ class ModuleLoadRequest final : public S
   MozPromiseHolder<GenericPromise> mReady;
 
   // Array of imported modules.
   nsTArray<RefPtr<ModuleLoadRequest>> mImports;
 
   // Set of module URLs visited while fetching the module graph this request is
   // part of.
   RefPtr<VisitedURLSet> mVisitedSet;
+
+  // For dynamic imports, the details to pass to FinishDynamicImport.
+  JS::Heap<JS::Value> mDynamicReferencingPrivate;
+  JS::Heap<JSString*> mDynamicSpecifier;
+  JS::Heap<JSObject*> mDynamicPromise;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ModuleLoadRequest_h
--- a/dom/script/ScriptLoadRequest.cpp
+++ b/dom/script/ScriptLoadRequest.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#include "ModuleLoadRequest.h"
+#include "ScriptLoadRequest.h"
 
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/Unused.h"
 
 #include "nsICacheInfoChannel.h"
 #include "ScriptLoadRequest.h"
 #include "ScriptSettings.h"
 
@@ -25,16 +25,17 @@ NS_IMPL_CYCLE_COLLECTION(ScriptFetchOpti
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ScriptFetchOptions, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ScriptFetchOptions, Release)
 
 ScriptFetchOptions::ScriptFetchOptions(
     mozilla::CORSMode aCORSMode, mozilla::net::ReferrerPolicy aReferrerPolicy,
     nsIScriptElement* aElement, nsIPrincipal* aTriggeringPrincipal)
     : mCORSMode(aCORSMode),
       mReferrerPolicy(aReferrerPolicy),
+      mIsPreload(false),
       mElement(aElement),
       mTriggeringPrincipal(aTriggeringPrincipal) {
   MOZ_ASSERT(mTriggeringPrincipal);
 }
 
 ScriptFetchOptions::~ScriptFetchOptions() {}
 
 //////////////////////////////////////////////////////////////
@@ -45,24 +46,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheInfo)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions, mCacheInfo)
+  tmp->mScript = nullptr;
   tmp->DropBytecodeCacheReferences();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheInfo)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
                                      ScriptFetchOptions* aFetchOptions,
@@ -100,16 +100,18 @@ ScriptLoadRequest::~ScriptLoadRequest() 
 
   // But play it safe in release builds and try to clean them up here
   // as a fail safe.
   MaybeCancelOffThreadScript();
 
   if (mScript) {
     DropBytecodeCacheReferences();
   }
+
+  DropJSObjects(this);
 }
 
 void ScriptLoadRequest::SetReady() {
   MOZ_ASSERT(mProgress != Progress::eReady);
   mProgress = Progress::eReady;
 }
 
 void ScriptLoadRequest::Cancel() {
@@ -202,16 +204,22 @@ bool ScriptLoadRequest::ShouldAcceptBinA
 void ScriptLoadRequest::ClearScriptSource() {
   if (IsTextSource()) {
     ScriptText().clearAndFree();
   } else if (IsBinASTSource()) {
     ScriptBinASTData().clearAndFree();
   }
 }
 
+void ScriptLoadRequest::SetScript(JSScript* aScript) {
+  MOZ_ASSERT(!mScript);
+  mScript = aScript;
+  HoldJSObjects(this);
+}
+
 //////////////////////////////////////////////////////////////
 // ScriptLoadRequestList
 //////////////////////////////////////////////////////////////
 
 ScriptLoadRequestList::~ScriptLoadRequestList() { Clear(); }
 
 void ScriptLoadRequestList::Clear() {
   while (!isEmpty()) {
--- a/dom/script/ScriptLoadRequest.h
+++ b/dom/script/ScriptLoadRequest.h
@@ -45,16 +45,17 @@ class ScriptFetchOptions {
 
   ScriptFetchOptions(mozilla::CORSMode aCORSMode,
                      mozilla::net::ReferrerPolicy aReferrerPolicy,
                      nsIScriptElement* aElement,
                      nsIPrincipal* aTriggeringPrincipal);
 
   const mozilla::CORSMode mCORSMode;
   const mozilla::net::ReferrerPolicy mReferrerPolicy;
+  bool mIsPreload;
   nsCOMPtr<nsIScriptElement> mElement;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
 };
 
 /*
  * A class that handles loading and evaluation of <script> elements.
  */
 
@@ -86,17 +87,20 @@ class ScriptLoadRequest
     bool isInlineClassicScript = mIsInline && !IsModuleRequest();
     Element()->ScriptAvailable(aResult, Element(), isInlineClassicScript, mURI,
                                mLineNo);
   }
   void FireScriptEvaluated(nsresult aResult) {
     Element()->ScriptEvaluated(aResult, Element(), mIsInline);
   }
 
-  bool IsPreload() { return Element() == nullptr; }
+  bool IsPreload() const {
+    MOZ_ASSERT_IF(mFetchOptions->mIsPreload, !Element());
+    return mFetchOptions->mIsPreload;
+  }
 
   virtual void Cancel();
 
   bool IsCanceled() const { return mIsCanceled; }
 
   virtual void SetReady();
 
   JS::OffThreadToken** OffThreadTokenPtr() {
@@ -194,27 +198,46 @@ class ScriptLoadRequest
   mozilla::net::ReferrerPolicy ReferrerPolicy() const {
     return mFetchOptions->mReferrerPolicy;
   }
   nsIScriptElement* Element() const { return mFetchOptions->mElement; }
   nsIPrincipal* TriggeringPrincipal() const {
     return mFetchOptions->mTriggeringPrincipal;
   }
 
-  void SetElement(nsIScriptElement* aElement) {
-    // Called when a preload request is later used for an actual request.
+  // Make this request a preload (speculative) request.
+  void SetIsPreloadRequest() {
+    MOZ_ASSERT(!Element());
+    MOZ_ASSERT(!IsPreload());
+    mFetchOptions->mIsPreload = true;
+  }
+
+  // Make a preload request into an actual load request for the given element.
+  void SetIsLoadRequest(nsIScriptElement* aElement) {
     MOZ_ASSERT(aElement);
     MOZ_ASSERT(!Element());
+    MOZ_ASSERT(IsPreload());
     mFetchOptions->mElement = aElement;
+    mFetchOptions->mIsPreload = false;
+  }
+
+  FromParser GetParserCreated() const {
+    nsIScriptElement* element = Element();
+    if (!element) {
+      return NOT_FROM_PARSER;
+    }
+    return element->GetParserCreated();
   }
 
   bool ShouldAcceptBinASTEncoding() const;
 
   void ClearScriptSource();
 
+  void SetScript(JSScript* aScript);
+
   void MaybeCancelOffThreadScript();
   void DropBytecodeCacheReferences();
 
   using super::getNext;
   using super::isInList;
 
   const ScriptKind
       mKind;  // Whether this is a classic script or a module script.
@@ -266,16 +289,19 @@ class ScriptLoadRequest
       mURL;  // Keep the URI's filename alive during off thread parsing.
   int32_t mLineNo;
   const SRIMetadata mIntegrity;
   const nsCOMPtr<nsIURI> mReferrer;
 
   // Holds the Cache information, which is used to register the bytecode
   // on the cache entry, such that we can load it the next time.
   nsCOMPtr<nsICacheInfoChannel> mCacheInfo;
+
+  // The base URL used for resolving relative module imports.
+  nsCOMPtr<nsIURI> mBaseURL;
 };
 
 class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest> {
   typedef mozilla::LinkedList<ScriptLoadRequest> super;
 
  public:
   ~ScriptLoadRequestList();
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -3,37 +3,39 @@
 /* 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/. */
 
 #include "ScriptLoader.h"
 #include "ScriptLoadHandler.h"
 #include "ScriptLoadRequest.h"
 #include "ScriptTrace.h"
+#include "LoadedScript.h"
 #include "ModuleLoadRequest.h"
-#include "ModuleScript.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/MemoryFunctions.h"
 #include "js/OffThreadScriptCompilation.h"
+#include "js/Realm.h"
 #include "js/SourceText.h"
 #include "js/Utility.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/SRILogHelper.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
+#include "nsGlobalWindowInner.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
@@ -108,19 +110,19 @@ inline void ImplCycleCollectionTraverse(
 // ScriptLoader
 //////////////////////////////////////////////////////////////
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
                          mLoadingAsyncRequests, mLoadedAsyncRequests,
-                         mDeferRequests, mXSLTRequests, mParserBlockingRequest,
-                         mBytecodeEncodingQueue, mPreloads,
-                         mPendingChildLoaders, mFetchedModules)
+                         mDeferRequests, mXSLTRequests, mDynamicImportRequests,
+                         mParserBlockingRequest, mBytecodeEncodingQueue,
+                         mPreloads, mPendingChildLoaders, mFetchedModules)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
 
 ScriptLoader::ScriptLoader(nsIDocument* aDocument)
     : mDocument(aDocument),
       mParserBlockingBlockerCount(0),
       mBlockerCount(0),
@@ -128,16 +130,17 @@ ScriptLoader::ScriptLoader(nsIDocument* 
       mEnabled(true),
       mDeferEnabled(false),
       mDocumentParsingDone(false),
       mBlockingDOMContentLoaded(false),
       mLoadEventFired(false),
       mGiveUpEncoding(false),
       mReporter(new ConsoleReportCollector()) {
   LOG(("ScriptLoader::ScriptLoader %p", this));
+  EnsureModuleHooksInitialized();
 }
 
 ScriptLoader::~ScriptLoader() {
   LOG(("ScriptLoader::~ScriptLoader %p", this));
 
   mObservers.Clear();
 
   if (mParserBlockingRequest) {
@@ -159,16 +162,21 @@ ScriptLoader::~ScriptLoader() {
     req->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
        req = req->getNext()) {
     req->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
+  for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
+       req = req->getNext()) {
+    FinishDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
+  }
+
   for (ScriptLoadRequest* req =
            mNonAsyncExternalScriptInsertedRequests.getFirst();
        req; req = req->getNext()) {
     req->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   // Unblock the kids, in case any of them moved to a different document
   // subtree in the meantime and therefore aren't actually going away.
@@ -199,18 +207,16 @@ static void CollectScriptTelemetry(Scrip
   }
 
   // Report the type of source. This is used to monitor the status of the
   // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero
   // source-fallback and alternate-data being roughtly equal to source loads.
   if (aRequest->IsLoadingSource()) {
     if (aRequest->mIsInline) {
       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
-      nsAutoString inlineData;
-      aRequest->Element()->GetScriptText(inlineData);
     } else if (aRequest->IsTextSource()) {
       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
     }
     // TODO: Add telemetry for BinAST encoded source.
   } else {
     MOZ_ASSERT(aRequest->IsLoading());
     if (aRequest->IsTextSource()) {
       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
@@ -466,17 +472,17 @@ nsresult ScriptLoader::CreateModuleScrip
           rv = NS_ERROR_OUT_OF_MEMORY;
         }
       }
     }
 
     MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
 
     RefPtr<ModuleScript> moduleScript =
-        new ModuleScript(this, aRequest->mBaseURL);
+        new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
     aRequest->mModuleScript = moduleScript;
 
     if (!module) {
       LOG(("ScriptLoadRequest (%p):   compilation failed (%d)", aRequest,
            unsigned(rv)));
 
       MOZ_ASSERT(aes.HasException());
       JS::Rooted<JS::Value> error(cx);
@@ -536,17 +542,17 @@ static nsresult HandleResolveFailure(JSC
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   aScript->SetParseError(error);
   return NS_OK;
 }
 
 static already_AddRefed<nsIURI> ResolveModuleSpecifier(
-    ModuleScript* aScript, const nsAString& aSpecifier) {
+    ScriptLoader* aLoader, LoadedScript* aScript, const nsAString& aSpecifier) {
   // The following module specifiers are allowed by the spec:
   //  - a valid absolute URL
   //  - a valid relative URL that starts with "/", "./" or "../"
   //
   // Bareword module specifiers are currently disallowed as these may be given
   // special meanings in the future.
 
   nsCOMPtr<nsIURI> uri;
@@ -560,17 +566,25 @@ static already_AddRefed<nsIURI> ResolveM
   }
 
   if (!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("/")) &&
       !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("./")) &&
       !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("../"))) {
     return nullptr;
   }
 
-  rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aScript->BaseURL());
+  // Get the document's base URL if we don't have a referencing script here.
+  nsCOMPtr<nsIURI> baseURL;
+  if (aScript) {
+    baseURL = aScript->BaseURL();
+  } else {
+    baseURL = aLoader->GetDocument()->GetDocBaseURI();
+  }
+
+  rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
   if (NS_SUCCEEDED(rv)) {
     return uri.forget();
   }
 
   return nullptr;
 }
 
 static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
@@ -604,17 +618,18 @@ static nsresult ResolveRequestedModules(
 
     nsAutoJSString specifier;
     if (!specifier.init(cx, str)) {
       return NS_ERROR_FAILURE;
     }
 
     // Let url be the result of resolving a module specifier given module script
     // and requested.
-    nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(ms, specifier);
+    nsCOMPtr<nsIURI> uri =
+        ResolveModuleSpecifier(aRequest->mLoader, ms, specifier);
     if (!uri) {
       uint32_t lineNumber = 0;
       uint32_t columnNumber = 0;
       JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
 
       nsresult rv =
           HandleResolveFailure(cx, ms, specifier, lineNumber, columnNumber);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -684,17 +699,18 @@ void ScriptLoader::StartFetchingModuleDe
                  &ModuleLoadRequest::DependenciesLoaded,
                  &ModuleLoadRequest::ModuleErrored);
 }
 
 RefPtr<GenericPromise> ScriptLoader::StartFetchingModuleAndDependencies(
     ModuleLoadRequest* aParent, nsIURI* aURI) {
   MOZ_ASSERT(aURI);
 
-  RefPtr<ModuleLoadRequest> childRequest = new ModuleLoadRequest(aURI, aParent);
+  RefPtr<ModuleLoadRequest> childRequest =
+      ModuleLoadRequest::CreateStaticImport(aURI, aParent);
 
   aParent->mImports.AppendElement(childRequest);
 
   if (LOG_ENABLED()) {
     nsAutoCString url1;
     aParent->mURI->GetAsciiSpec(url1);
 
     nsAutoCString url2;
@@ -715,53 +731,112 @@ RefPtr<GenericPromise> ScriptLoader::Sta
          &childRequest->mReady));
     childRequest->mReady.Reject(rv, __func__);
     return ready;
   }
 
   return ready;
 }
 
+static ScriptLoader* GetCurrentScriptLoader(JSContext* aCx) {
+  JSObject* object = JS::CurrentGlobalOrNull(aCx);
+  if (!object) {
+    return nullptr;
+  }
+
+  nsIGlobalObject* global = xpc::NativeGlobal(object);
+  if (!global) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+  nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(win);
+  if (!innerWindow) {
+    return nullptr;
+  }
+
+  nsIDocument* document = innerWindow->GetDocument();
+  if (!document) {
+    return nullptr;
+  }
+
+  ScriptLoader* loader = document->ScriptLoader();
+  if (!loader) {
+    return nullptr;
+  }
+
+  return loader;
+}
+
+static LoadedScript* GetLoadedScriptOrNull(
+    JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate) {
+  if (aReferencingPrivate.isUndefined()) {
+    return nullptr;
+  }
+
+  auto script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
+  MOZ_ASSERT_IF(
+      script->IsModuleScript(),
+      JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) ==
+          aReferencingPrivate);
+
+  return script;
+}
+
 // 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
 JSObject* HostResolveImportedModule(JSContext* aCx,
                                     JS::Handle<JS::Value> aReferencingPrivate,
                                     JS::Handle<JSString*> aSpecifier) {
-  // Let referencing module script be referencingModule.[[HostDefined]].
-  auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
-  MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
-             aReferencingPrivate);
+  JS::Rooted<JSObject*> module(aCx);
+  ScriptLoader::ResolveImportedModule(aCx, aReferencingPrivate, aSpecifier,
+                                      &module);
+  return module;
+}
+
+/* static */ void ScriptLoader::ResolveImportedModule(
+    JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+    JS::Handle<JSString*> aSpecifier, JS::MutableHandle<JSObject*> aModuleOut) {
+  MOZ_ASSERT(!aModuleOut);
+
+  RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, 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;
+    return;
   }
 
-  nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
+  RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
+  if (!loader) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
 
   // This cannot fail because resolving a module specifier must have been
   // previously successful with these same two arguments.
   MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
 
   // Let resolved module script be moduleMap[url]. (This entry must exist for us
   // to have gotten to this point.)
-  ModuleScript* ms = script->Loader()->GetFetchedModule(uri);
+  ModuleScript* ms = loader->GetFetchedModule(uri);
   MOZ_ASSERT(ms, "Resolved module not found in module map");
-
   MOZ_ASSERT(!ms->HasParseError());
   MOZ_ASSERT(ms->ModuleRecord());
 
-  return ms->ModuleRecord();
+  aModuleOut.set(ms->ModuleRecord());
 }
 
 bool HostPopulateImportMeta(JSContext* aCx,
                             JS::Handle<JS::Value> aReferencingPrivate,
                             JS::Handle<JSObject*> aMetaObject) {
-  auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+  RefPtr<ModuleScript> script =
+      static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+  MOZ_ASSERT(script->IsModuleScript());
   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()));
@@ -769,24 +844,138 @@ bool HostPopulateImportMeta(JSContext* a
     JS_ReportOutOfMemory(aCx);
     return false;
   }
 
   return JS_DefineProperty(aCx, aMetaObject, "url", urlString,
                            JSPROP_ENUMERATE);
 }
 
-static void EnsureModuleResolveHook(JSContext* aCx) {
-  JSRuntime* rt = JS_GetRuntime(aCx);
+bool HostImportModuleDynamically(JSContext* aCx,
+                                 JS::Handle<JS::Value> aReferencingPrivate,
+                                 JS::Handle<JSString*> aSpecifier,
+                                 JS::Handle<JSObject*> aPromise) {
+  RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
+
+  // Attempt to resolve the module specifier.
+  nsAutoJSString string;
+  if (!string.init(aCx, aSpecifier)) {
+    return false;
+  }
+
+  RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
+  if (!loader) {
+    return false;
+  }
+
+  nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
+  if (!uri) {
+    JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
+                           JSMSG_BAD_MODULE_SPECIFIER, string.get());
+    return false;
+  }
+
+  // Create a new top-level load request.
+  ScriptFetchOptions* options;
+  nsIURI* baseURL;
+  if (script) {
+    options = script->FetchOptions();
+    baseURL = script->BaseURL();
+  } else {
+    // We don't have a referencing script so fall back on using
+    // options from the document. This can happen when the user
+    // triggers an inline event handler, as there is no active script
+    // there.
+    nsIDocument* document = loader->GetDocument();
+    options = new ScriptFetchOptions(mozilla::CORS_NONE,
+                                     document->GetReferrerPolicy(), nullptr,
+                                     document->NodePrincipal());
+    baseURL = document->GetDocBaseURI();
+  }
+
+  RefPtr<ModuleLoadRequest> request = ModuleLoadRequest::CreateDynamicImport(
+      uri, options, baseURL, loader, aReferencingPrivate, aSpecifier, aPromise);
+
+  loader->StartDynamicImport(request);
+  return true;
+}
+
+void ScriptLoader::StartDynamicImport(ModuleLoadRequest* aRequest) {
+  LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
+
+  mDynamicImportRequests.AppendElement(aRequest);
+
+  nsresult rv = StartLoad(aRequest);
+  if (NS_FAILED(rv)) {
+    FinishDynamicImport(aRequest, rv);
+  }
+}
+
+void ScriptLoader::FinishDynamicImport(ModuleLoadRequest* aRequest,
+                                       nsresult aResult) {
+  AutoJSAPI jsapi;
+  MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
+  FinishDynamicImport(jsapi.cx(), aRequest, aResult);
+}
+
+void ScriptLoader::FinishDynamicImport(JSContext* aCx,
+                                       ModuleLoadRequest* aRequest,
+                                       nsresult aResult) {
+  LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
+       unsigned(aResult), JS_IsExceptionPending(aCx)));
+
+  // Complete the dynamic import, report failures indicated by aResult or as a
+  // pending exception on the context.
+
+  if (NS_FAILED(aResult)) {
+    MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+    JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
+                           JSMSG_DYNAMIC_IMPORT_FAILED);
+  }
+
+  JS::Rooted<JS::Value> referencingScript(aCx,
+                                          aRequest->mDynamicReferencingPrivate);
+  JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
+  JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
+
+  JS::FinishDynamicModuleImport(aCx, referencingScript, specifier, promise);
+
+  // FinishDynamicModuleImport clears any pending exception.
+  MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+  aRequest->ClearDynamicImport();
+}
+
+static void DynamicImportPrefChangedCallback(const char* aPrefName,
+                                             void* aClosure) {
+  bool enabled = Preferences::GetBool(aPrefName);
+  JS::ModuleDynamicImportHook hook =
+      enabled ? HostImportModuleDynamically : nullptr;
+
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSRuntime* rt = JS_GetRuntime(jsapi.cx());
+  JS::SetModuleDynamicImportHook(rt, hook);
+}
+
+void ScriptLoader::EnsureModuleHooksInitialized() {
+  AutoJSAPI jsapi;
+  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);
+
+  Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback,
+                                       "javascript.options.dynamicImport",
+                                       (void*)nullptr);
 }
 
 void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
   LOG(("ScriptLoadRequest (%p): Check dependencies loaded", aRequest));
 
   RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
   if (!moduleScript || moduleScript->HasParseError()) {
     return;
@@ -810,28 +999,44 @@ class ScriptRequestProcessor : public Ru
   RefPtr<ScriptLoader> mLoader;
   RefPtr<ScriptLoadRequest> mRequest;
 
  public:
   ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
       : Runnable("dom::ScriptRequestProcessor"),
         mLoader(aLoader),
         mRequest(aRequest) {}
-  NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); }
+  NS_IMETHOD Run() override {
+    if (mRequest->IsModuleRequest() &&
+        mRequest->AsModuleRequest()->IsDynamicImport()) {
+      mLoader->ProcessDynamicImport(mRequest->AsModuleRequest());
+      return NS_OK;
+    }
+
+    return mLoader->ProcessRequest(mRequest);
+  }
 };
 
+void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
+  auto runnable = new ScriptRequestProcessor(this, aRequest);
+  nsContentUtils::AddScriptRunner(runnable);
+}
+
 void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
   MOZ_ASSERT(aRequest->IsReadyToRun());
 
   if (aRequest->IsTopLevel()) {
-    if (aRequest->mIsInline &&
-        aRequest->Element()->GetParserCreated() == NOT_FROM_PARSER) {
+    if (aRequest->IsDynamicImport()) {
+      MOZ_ASSERT(aRequest->isInList());
+      RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
+      RunScriptWhenSafe(req);
+    } else if (aRequest->mIsInline &&
+               aRequest->GetParserCreated() == NOT_FROM_PARSER) {
       MOZ_ASSERT(!aRequest->isInList());
-      nsContentUtils::AddScriptRunner(
-          new ScriptRequestProcessor(this, aRequest));
+      RunScriptWhenSafe(aRequest);
     } else {
       MaybeMoveToLoadedList(aRequest);
       ProcessPendingRequests();
     }
   }
 
   if (aRequest->mWasCompiledOMT) {
     mDocument->UnblockOnload(false);
@@ -879,18 +1084,16 @@ bool ScriptLoader::InstantiateModuleTree
   MOZ_ASSERT(moduleScript->ModuleRecord());
 
   nsAutoMicroTask mt;
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
     return false;
   }
 
-  EnsureModuleResolveHook(jsapi.cx());
-
   JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
   bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
 
   if (!ok) {
     LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
     MOZ_ASSERT(jsapi.HasException());
     JS::RootedValue exception(jsapi.cx());
     if (!jsapi.StealException(&exception)) {
@@ -919,20 +1122,22 @@ nsresult ScriptLoader::AssociateSourceEl
   for (ModuleLoadRequest* childRequest : aRequest->mImports) {
     nsresult rv = AssociateSourceElementsForModuleTree(aCx, childRequest);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
   MOZ_ASSERT(module);
 
-  nsresult rv =
-      nsJSUtils::InitModuleSourceElement(aCx, module, aRequest->Element());
-  NS_ENSURE_SUCCESS(rv, rv);
-  moduleScript->SetSourceElementAssociated();
+  nsIScriptElement* element = aRequest->Element();
+  if (element) {
+    nsresult rv = nsJSUtils::InitModuleSourceElement(aCx, module, element);
+    NS_ENSURE_SUCCESS(rv, rv);
+    moduleScript->SetSourceElementAssociated();
+  }
 
   // The script is now ready to be exposed to the debugger.
   JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
   JS::ExposeScriptToDebugger(aCx, script);
 
   return NS_OK;
 }
 
@@ -1205,17 +1410,18 @@ ScriptLoadRequest* ScriptLoader::CreateL
       aCORSMode, aReferrerPolicy, aElement, aTriggeringPrincipal);
 
   if (aKind == ScriptKind::eClassic) {
     return new ScriptLoadRequest(aKind, aURI, fetchOptions, aIntegrity,
                                  referrer);
   }
 
   MOZ_ASSERT(aKind == ScriptKind::eModule);
-  return new ModuleLoadRequest(aURI, fetchOptions, aIntegrity, referrer, this);
+  return ModuleLoadRequest::CreateTopLevel(aURI, fetchOptions, aIntegrity,
+                                           referrer, this);
 }
 
 bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, false);
 
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
@@ -1500,20 +1706,20 @@ bool ScriptLoader::ProcessInlineScript(n
   // inline classic scripts ignore both these attributes.
   MOZ_ASSERT(!aElement->GetScriptDeferred());
   MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
   request->SetScriptMode(false, aElement->GetScriptAsync());
 
   LOG(("ScriptLoadRequest (%p): Created request for inline script",
        request.get()));
 
+  request->mBaseURL = mDocument->GetDocBaseURI();
+
   if (request->IsModuleRequest()) {
     ModuleLoadRequest* modReq = request->AsModuleRequest();
-    modReq->mBaseURL = mDocument->GetDocBaseURI();
-
     if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
       if (aElement->GetScriptAsync()) {
         AddAsyncRequest(modReq);
       } else {
         AddDeferRequest(modReq);
       }
     }
 
@@ -1533,17 +1739,17 @@ bool ScriptLoader::ProcessInlineScript(n
                  "Parser-blocking scripts and XSLT scripts in the same doc!");
     mXSLTRequests.AppendElement(request);
     return true;
   }
   if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
     NS_ASSERTION(
         !nsContentUtils::IsSafeToRunScript(),
         "A script-inserted script is inserted without an update batch?");
-    nsContentUtils::AddScriptRunner(new ScriptRequestProcessor(this, request));
+    RunScriptWhenSafe(request);
     return false;
   }
   if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
       !ReadyToExecuteParserBlockingScripts()) {
     NS_ASSERTION(!mParserBlockingRequest,
                  "There can be only one parser-blocking script at a time");
     mParserBlockingRequest = request;
     NS_ASSERTION(mXSLTRequests.isEmpty(),
@@ -1571,17 +1777,17 @@ ScriptLoadRequest* ScriptLoader::LookupP
       mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
   if (i == nsTArray<PreloadInfo>::NoIndex) {
     return nullptr;
   }
 
   // Found preloaded request. Note that a script-inserted script can steal a
   // preload!
   RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
-  request->SetElement(aElement);
+  request->SetIsLoadRequest(aElement);
   nsString preloadCharset(mPreloads[i].mCharset);
   mPreloads.RemoveElementAt(i);
 
   // Double-check that the charset the preload used is the same as the charset
   // we have now.
   nsAutoString elementCharset;
   aElement->GetScriptCharset(elementCharset);
   mozilla::net::ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
@@ -1910,18 +2116,17 @@ nsresult ScriptLoader::ProcessRequest(Sc
                "Processing requests when running scripts is unsafe.");
   NS_ASSERTION(aRequest->IsReadyToRun(),
                "Processing a request that is not ready to run.");
 
   NS_ENSURE_ARG(aRequest);
 
   if (aRequest->IsModuleRequest()) {
     ModuleLoadRequest* request = aRequest->AsModuleRequest();
-    if (request->mModuleScript &&
-        !request->mModuleScript->HasErrorToRethrow()) {
+    if (request->mModuleScript) {
       if (!InstantiateModuleTree(request)) {
         request->mModuleScript = nullptr;
       }
     }
 
     if (!request->mModuleScript) {
       // There was an error fetching a module script.  Nothing to do here.
       LOG(("ScriptLoadRequest (%p):   Error loading request, firing error",
@@ -1934,17 +2139,17 @@ nsresult ScriptLoader::ProcessRequest(Sc
   nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->Element());
 
   nsCOMPtr<nsIDocument> doc;
   if (!aRequest->mIsInline) {
     doc = scriptElem->OwnerDoc();
   }
 
   nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
-  uint32_t parserCreated = aRequest->Element()->GetParserCreated();
+  uint32_t parserCreated = aRequest->GetParserCreated();
   if (parserCreated) {
     oldParserInsertedScript = mCurrentParserInsertedScript;
     mCurrentParserInsertedScript = aRequest->Element();
   }
 
   aRequest->Element()->BeginEvaluating();
 
   FireScriptAvailable(NS_OK, aRequest);
@@ -2013,16 +2218,35 @@ nsresult ScriptLoader::ProcessRequest(Sc
     // encoding the bytecode once more. We can safely clear the content of this
     // buffer.
     aRequest->mScriptBytecode.clearAndFree();
   }
 
   return rv;
 }
 
+void ScriptLoader::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
+  if (aRequest->mModuleScript) {
+    if (!InstantiateModuleTree(aRequest)) {
+      aRequest->mModuleScript = nullptr;
+    }
+  }
+
+  nsresult rv = NS_ERROR_FAILURE;
+  if (aRequest->mModuleScript) {
+    rv = EvaluateScript(aRequest);
+  }
+
+  if (NS_FAILED(rv)) {
+    FinishDynamicImport(aRequest, rv);
+  }
+
+  return;
+}
+
 void ScriptLoader::FireScriptAvailable(nsresult aResult,
                                        ScriptLoadRequest* aRequest) {
   for (int32_t i = 0; i < mObservers.Count(); i++) {
     nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
     obs->ScriptAvailable(aResult, aRequest->Element(), aRequest->mIsInline,
                          aRequest->mURI, aRequest->mLineNo);
   }
 
@@ -2057,17 +2281,17 @@ already_AddRefed<nsIScriptGlobalObject> 
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   return globalObject.forget();
 }
 
 nsresult ScriptLoader::FillCompileOptionsForRequest(
-    const AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
+    const mozilla::dom::AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
     JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions) {
   // It's very important to use aRequest->mURI, not the final URI of the channel
   // aRequest ended up getting script data from, as the script filename.
   nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -2198,35 +2422,63 @@ nsresult ScriptLoader::FillCompileOption
       return false;
     }
   }
 
   LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
   return true;
 }
 
+class MOZ_RAII AutoSetProcessingScriptTag {
+  nsCOMPtr<nsIScriptContext> mContext;
+  bool mOldTag;
+
+ public:
+  explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext)
+      : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) {
+    mContext->SetProcessingScriptTag(true);
+  }
+
+  ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); }
+};
+
+static nsresult ExecuteCompiledScript(JSContext* aCx,
+                                      ScriptLoadRequest* aRequest,
+                                      nsJSUtils::ExecutionContext& aExec) {
+  JS::Rooted<JSScript*> script(aCx, aExec.GetScript());
+
+  // Create a ClassicScript object and associate it with the JSScript.
+  RefPtr<ClassicScript> classicScript = new ClassicScript(
+    aRequest->mFetchOptions, aRequest->mBaseURL);
+  classicScript->AssociateWithScript(script);
+
+  return aExec.ExecScript();
+}
+
 nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
   using namespace mozilla::Telemetry;
   MOZ_ASSERT(aRequest->IsReadyToRun());
 
   // We need a document to evaluate scripts.
   if (!mDocument) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->Element()));
-  nsIDocument* ownerDoc = scriptContent->OwnerDoc();
-  if (ownerDoc != mDocument) {
-    // Willful violation of HTML5 as of 2010-12-01
-    return NS_ERROR_FAILURE;
+  bool isDynamicImport = aRequest->IsModuleRequest() &&
+                         aRequest->AsModuleRequest()->IsDynamicImport();
+  if (!isDynamicImport) {
+    nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->Element()));
+    MOZ_ASSERT(scriptContent);
+    nsIDocument* ownerDoc = scriptContent->OwnerDoc();
+    if (ownerDoc != mDocument) {
+      // Willful violation of HTML5 as of 2010-12-01
+      return NS_ERROR_FAILURE;
+    }
   }
 
-  // Get the script-type to be used by this element.
-  NS_ASSERTION(scriptContent, "no content - what is default script-type?");
-
   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
   if (!globalObject) {
     return NS_ERROR_FAILURE;
   }
 
   // Make sure context is a strong reference since we access it after
   // we've executed a script, which may cause all other references to
   // the context to go away.
@@ -2237,57 +2489,68 @@ nsresult ScriptLoader::EvaluateScript(Sc
 
   // New script entry point required, due to the "Create a script" sub-step of
   // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
   nsAutoMicroTask mt;
   AutoEntryScript aes(globalObject, "<script> element", true);
   JSContext* cx = aes.cx();
   JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
 
-  bool oldProcessingScriptTag = context->GetProcessingScriptTag();
-  context->SetProcessingScriptTag(true);
+  AutoSetProcessingScriptTag setProcessingScriptTag(context);
+
   nsresult rv;
   {
     if (aRequest->IsModuleRequest()) {
       // When a module is already loaded, it is not feched a second time and the
       // mDataType of the request might remain set to DataType::Unknown.
       MOZ_ASSERT(aRequest->IsTextSource() || aRequest->IsUnknownDataType());
       LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
 
       // currentScript is set to null for modules.
       AutoCurrentScriptUpdater scriptUpdater(this, nullptr);
 
-      EnsureModuleResolveHook(cx);
-
       ModuleLoadRequest* request = aRequest->AsModuleRequest();
       MOZ_ASSERT(request->mModuleScript);
       MOZ_ASSERT(!request->mOffThreadToken);
 
       ModuleScript* moduleScript = request->mModuleScript;
       if (moduleScript->HasErrorToRethrow()) {
         LOG(("ScriptLoadRequest (%p):   module has error to rethrow",
              aRequest));
         JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
         JS_SetPendingException(cx, error);
-        return NS_OK;  // An error is reported by AutoEntryScript.
+
+        // For a dynamic import, the promise is rejected.  Otherwise an error is
+        // either reported by AutoEntryScript.
+        if (request->IsDynamicImport()) {
+          FinishDynamicImport(cx, request, NS_OK);
+        }
+        return NS_OK;
       }
 
       JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
       MOZ_ASSERT(module);
 
       if (!moduleScript->SourceElementAssociated()) {
         rv = AssociateSourceElementsForModuleTree(cx, request);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       rv = nsJSUtils::ModuleEvaluate(cx, module);
       MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
+
       if (NS_FAILED(rv)) {
         LOG(("ScriptLoadRequest (%p):   evaluation failed", aRequest));
-        rv = NS_OK;  // An error is reported by AutoEntryScript.
+        // For a dynamic import, the promise is rejected.  Otherwise an error is
+        // either reported by AutoEntryScript.
+        rv = NS_OK;
+      }
+
+      if (request->IsDynamicImport()) {
+        FinishDynamicImport(cx, request, rv);
       }
 
       aRequest->mCacheInfo = nullptr;
     } else {
       // Update our current script.
       AutoCurrentScriptUpdater scriptUpdater(this, aRequest->Element());
 
       JS::CompileOptions options(cx);
@@ -2295,23 +2558,28 @@ nsresult ScriptLoader::EvaluateScript(Sc
 
       if (NS_SUCCEEDED(rv)) {
         if (aRequest->IsBytecode()) {
           TRACE_FOR_TEST(aRequest->Element(), "scriptloader_execute");
           nsJSUtils::ExecutionContext exec(cx, global);
           if (aRequest->mOffThreadToken) {
             LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
                  aRequest));
-            rv = exec.DecodeJoinAndExec(&aRequest->mOffThreadToken);
+            rv = exec.JoinDecode(&aRequest->mOffThreadToken);
           } else {
             LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute",
                  aRequest));
-            rv = exec.DecodeAndExec(options, aRequest->mScriptBytecode,
-                                    aRequest->mBytecodeOffset);
+            rv = exec.Decode(options, aRequest->mScriptBytecode,
+                             aRequest->mBytecodeOffset);
           }
+
+          if (rv == NS_OK) {
+            rv = ExecuteCompiledScript(cx, aRequest, exec);
+          }
+
           // We do not expect to be saving anything when we already have some
           // bytecode.
           MOZ_ASSERT(!aRequest->mCacheInfo);
         } else {
           MOZ_ASSERT(aRequest->IsSource());
           JS::Rooted<JSScript*> script(cx);
           bool encodeBytecode = ShouldCacheBytecode(aRequest);
 
@@ -2321,46 +2589,49 @@ nsresult ScriptLoader::EvaluateScript(Sc
             TRACE_FOR_TEST(aRequest->Element(), "scriptloader_execute");
             if (aRequest->mOffThreadToken) {
               // Off-main-thread parsing.
               LOG(
                   ("ScriptLoadRequest (%p): Join (off-thread parsing) and "
                    "Execute",
                    aRequest));
               if (aRequest->IsBinASTSource()) {
-                rv = exec.DecodeBinASTJoinAndExec(&aRequest->mOffThreadToken,
-                                                  &script);
+                rv = exec.JoinDecodeBinAST(&aRequest->mOffThreadToken);
               } else {
                 MOZ_ASSERT(aRequest->IsTextSource());
-                rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
+                rv = exec.JoinCompile(&aRequest->mOffThreadToken);
               }
             } else {
               // Main thread parsing (inline and small scripts)
               LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
               if (aRequest->IsBinASTSource()) {
-                rv = exec.DecodeBinASTAndExec(
-                    options, aRequest->ScriptBinASTData().begin(),
-                    aRequest->ScriptBinASTData().length(), &script);
+                rv = exec.DecodeBinAST(options,
+                                       aRequest->ScriptBinASTData().begin(),
+                                       aRequest->ScriptBinASTData().length());
               } else {
                 MOZ_ASSERT(aRequest->IsTextSource());
                 auto srcBuf = GetScriptSource(cx, aRequest);
 
                 if (srcBuf) {
-                  rv = exec.CompileAndExec(options, *srcBuf, &script);
+                  rv = exec.Compile(options, *srcBuf);
                 } else {
                   rv = NS_ERROR_OUT_OF_MEMORY;
                 }
               }
             }
+
+            if (rv == NS_OK) {
+              script = exec.GetScript();
+              rv = ExecuteCompiledScript(cx, aRequest, exec);
+            }
           }
 
           // Queue the current script load request to later save the bytecode.
           if (script && encodeBytecode) {
-            aRequest->mScript = script;
-            HoldJSObjects(aRequest);
+            aRequest->SetScript(script);
             TRACE_FOR_TEST(aRequest->Element(), "scriptloader_encode");
             MOZ_ASSERT(aRequest->mBytecodeOffset ==
                        aRequest->mScriptBytecode.length());
             RegisterForBytecodeEncoding(aRequest);
           } else {
             LOG(
                 ("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X, "
                  "script = %p)",
@@ -2374,20 +2645,28 @@ nsresult ScriptLoader::EvaluateScript(Sc
 
     // Even if we are not saving the bytecode of the current script, we have
     // to trigger the encoding of the bytecode, as the current script can
     // call functions of a script for which we are recording the bytecode.
     LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this));
     MaybeTriggerBytecodeEncoding();
   }
 
-  context->SetProcessingScriptTag(oldProcessingScriptTag);
   return rv;
 }
 
+/* static */ LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) {
+  JS::Value value = JS::GetScriptedCallerPrivate(aCx);
+  if (value.isUndefined()) {
+    return nullptr;
+  }
+
+  return static_cast<LoadedScript*>(value.toPrivate());
+}
+
 void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) {
   MOZ_ASSERT(aRequest->mCacheInfo);
   MOZ_ASSERT(aRequest->mScript);
   mBytecodeEncodingQueue.AppendElement(aRequest);
 }
 
 void ScriptLoader::LoadEventFired() {
   mLoadEventFired = true;
@@ -2562,17 +2841,18 @@ void ScriptLoader::GiveUpBytecodeEncodin
     request->DropBytecodeCacheReferences();
   }
 }
 
 bool ScriptLoader::HasPendingRequests() {
   return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
          !mLoadedAsyncRequests.isEmpty() ||
          !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
-         !mDeferRequests.isEmpty() || !mPendingChildLoaders.IsEmpty();
+         !mDeferRequests.isEmpty() || !mDynamicImportRequests.isEmpty() ||
+         !mPendingChildLoaders.IsEmpty();
 }
 
 void ScriptLoader::ProcessPendingRequestsAsync() {
   if (HasPendingRequests()) {
     nsCOMPtr<nsIRunnable> task =
         NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this,
                           &ScriptLoader::ProcessPendingRequests);
     if (mDocument) {
@@ -2904,17 +3184,17 @@ nsresult ScriptLoader::SaveSRIHash(
 
   return NS_OK;
 }
 
 void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
                                         nsresult aResult) const {
   MOZ_ASSERT(aRequest);
 
-  if (!aRequest->Element()) {
+  if (aRequest->IsPreload()) {
     return;
   }
 
   bool isScript = !aRequest->IsModuleRequest();
   const char* message;
   if (aResult == NS_ERROR_MALFORMED_URI) {
     message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
   } else if (aResult == NS_ERROR_DOM_BAD_URI) {
@@ -2924,18 +3204,19 @@ void ScriptLoader::ReportErrorToConsole(
     return;
   } else {
     message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
   }
 
   NS_ConvertUTF8toUTF16 url(aRequest->mURI->GetSpecOrDefault());
   const char16_t* params[] = {url.get()};
 
-  uint32_t lineNo = aRequest->Element()->GetScriptLineNumber();
-  uint32_t columnNo = aRequest->Element()->GetScriptColumnNumber();
+  nsIScriptElement* element = aRequest->Element();
+  uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
+  uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
 
   nsContentUtils::ReportToConsole(
       nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Script Loader"),
       mDocument, nsContentUtils::eDOM_PROPERTIES, message, params,
       ArrayLength(params), nullptr, EmptyString(), lineNo, columnNo);
 }
 
 void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
@@ -2975,46 +3256,55 @@ void ScriptLoader::HandleLoadError(Scrip
           mNonAsyncExternalScriptInsertedRequests.Steal(aRequest);
       FireScriptAvailable(aResult, req);
     }
   } else if (aRequest->mIsXSLT) {
     if (aRequest->isInList()) {
       RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
       FireScriptAvailable(aResult, req);
     }
-  } else if (aRequest->IsModuleRequest() && !aRequest->IsPreload()) {
+  } else if (aRequest->IsPreload()) {
+    if (aRequest->IsModuleRequest()) {
+      aRequest->Cancel();
+    }
+    if (aRequest->IsTopLevel()) {
+      MOZ_ALWAYS_TRUE(
+          mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
+    }
+    MOZ_ASSERT(!aRequest->isInList());
+    AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
+  } else if (aRequest->IsModuleRequest()) {
     ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
-    MOZ_ASSERT(!modReq->IsTopLevel());
-    MOZ_ASSERT(!modReq->isInList());
-    modReq->Cancel();
-    // A single error is fired for the top level module.
+    if (modReq->IsDynamicImport()) {
+      MOZ_ASSERT(modReq->IsTopLevel());
+      if (aRequest->isInList()) {
+        RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
+        modReq->Cancel();
+        FinishDynamicImport(modReq, aResult);
+      }
+    } else {
+      MOZ_ASSERT(!modReq->IsTopLevel());
+      MOZ_ASSERT(!modReq->isInList());
+      modReq->Cancel();
+      // The error is handled for the top level module.
+    }
   } else if (mParserBlockingRequest == aRequest) {
     MOZ_ASSERT(!aRequest->isInList());
     mParserBlockingRequest = nullptr;
     UnblockParser(aRequest);
 
     // Ensure that we treat aRequest->Element() as our current parser-inserted
     // script while firing onerror on it.
     MOZ_ASSERT(aRequest->Element()->GetParserCreated());
     nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
         mCurrentParserInsertedScript;
     mCurrentParserInsertedScript = aRequest->Element();
     FireScriptAvailable(aResult, aRequest);
     ContinueParserAsync(aRequest);
     mCurrentParserInsertedScript = oldParserInsertedScript;
-  } else if (aRequest->IsPreload()) {
-    if (aRequest->IsModuleRequest()) {
-      aRequest->Cancel();
-    }
-    if (aRequest->IsTopLevel()) {
-      MOZ_ALWAYS_TRUE(
-          mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
-    }
-    MOZ_ASSERT(!aRequest->isInList());
-    AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
   } else {
     // This happens for blocking requests cancelled by ParsingComplete().
     MOZ_ASSERT(aRequest->IsCanceled());
     MOZ_ASSERT(!aRequest->isInList());
   }
 }
 
 void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) {
@@ -3105,48 +3395,49 @@ nsresult ScriptLoader::PrepareLoadedRequ
   // This assertion could fire errorously if we ran out of memory when
   // inserting the request in the array. However it's an unlikely case
   // so if you see this assertion it is likely something else that is
   // wrong, especially if you see it more than once.
   NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
                    mLoadingAsyncRequests.Contains(aRequest) ||
                    mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
                    mXSLTRequests.Contains(aRequest) ||
+                   mDynamicImportRequests.Contains(aRequest) ||
                    (aRequest->IsModuleRequest() &&
                     !aRequest->AsModuleRequest()->IsTopLevel() &&
                     !aRequest->isInList()) ||
                    mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
-                   mParserBlockingRequest,
+                   mParserBlockingRequest == aRequest,
                "aRequest should be pending!");
 
+  nsCOMPtr<nsIURI> uri;
+  rv = channel->GetOriginalURI(getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Fixup moz-extension: and resource: URIs, because the channel URI will
+  // point to file:, which won't be allowed to load.
+  if (uri && IsInternalURIScheme(uri)) {
+    aRequest->mBaseURL = uri;
+  } else {
+    channel->GetURI(getter_AddRefs(aRequest->mBaseURL));
+  }
+
   if (aRequest->IsModuleRequest()) {
     MOZ_ASSERT(aRequest->IsSource());
     ModuleLoadRequest* request = aRequest->AsModuleRequest();
 
     // When loading a module, only responses with a JavaScript MIME type are
     // acceptable.
     nsAutoCString mimeType;
     channel->GetContentType(mimeType);
     NS_ConvertUTF8toUTF16 typeString(mimeType);
     if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
       return NS_ERROR_FAILURE;
     }
 
-    nsCOMPtr<nsIURI> uri;
-    rv = channel->GetOriginalURI(getter_AddRefs(uri));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Fixup moz-extension: and resource: URIs, because the channel URI will
-    // point to file:, which won't be allowed to load.
-    if (uri && IsInternalURIScheme(uri)) {
-      request->mBaseURL = uri;
-    } else {
-      channel->GetURI(getter_AddRefs(request->mBaseURL));
-    }
-
     // Attempt to compile off main thread.
     bool couldCompile = false;
     rv = AttemptAsyncScriptCompile(request, &couldCompile);
     NS_ENSURE_SUCCESS(rv, rv);
     if (couldCompile) {
       return NS_OK;
     }
 
@@ -3186,16 +3477,23 @@ void ScriptLoader::ParsingComplete(bool 
   }
   mDeferEnabled = false;
   if (aTerminated) {
     mDeferRequests.Clear();
     mLoadingAsyncRequests.Clear();
     mLoadedAsyncRequests.Clear();
     mNonAsyncExternalScriptInsertedRequests.Clear();
     mXSLTRequests.Clear();
+
+    for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
+         req = req->getNext()) {
+      req->Cancel();
+      FinishDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
+    }
+
     if (mParserBlockingRequest) {
       mParserBlockingRequest->Cancel();
       mParserBlockingRequest = nullptr;
     }
   }
 
   // Have to call this even if aTerminated so we'll correctly unblock
   // onload and all.
@@ -3240,16 +3538,17 @@ void ScriptLoader::PreloadURI(
   GetSRIMetadata(aIntegrity, &sriMetadata);
 
   RefPtr<ScriptLoadRequest> request = CreateLoadRequest(
       scriptKind, aURI, nullptr, mDocument->NodePrincipal(),
       Element::StringToCORSMode(aCrossOrigin), sriMetadata, aReferrerPolicy);
   request->mIsInline = false;
   request->mScriptFromHead = aScriptFromHead;
   request->SetScriptMode(aDefer, aAsync);
+  request->SetIsPreloadRequest();
 
   if (LOG_ENABLED()) {
     nsAutoCString url;
     aURI->GetAsciiSpec(url);
     LOG(("ScriptLoadRequest (%p): Created preload request for %s",
          request.get(), url.get()));
   }
 
--- a/dom/script/ScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -37,16 +37,17 @@ template <typename UnitT>
 class SourceText;
 
 }  // namespace JS
 
 namespace mozilla {
 namespace dom {
 
 class AutoJSAPI;
+class LoadedScript;
 class ModuleLoadRequest;
 class ModuleScript;
 class ScriptLoadHandler;
 class ScriptRequestProcessor;
 
 //////////////////////////////////////////////////////////////
 // Script loader implementation
 //////////////////////////////////////////////////////////////
@@ -305,19 +306,51 @@ class ScriptLoader final : public nsISup
 
   /**
    * Destroy and prevent the ScriptLoader or the ScriptLoadRequests from owning
    * any references to the JSScript or to the Request which might be used for
    * caching the encoded bytecode.
    */
   void Destroy() { GiveUpBytecodeEncoding(); }
 
+  /**
+   * Implement the HostResolveImportedModule abstract operation.
+   *
+   * Resolve a module specifier string and look this up in the module
+   * map, returning the result. This is only called for previously
+   * loaded modules and always succeeds.
+   *
+   * @param aReferencingPrivate A JS::Value which is either undefined
+   *                            or contains a LoadedScript private pointer.
+   * @param aSpecifier The module specifier.
+   * @param aModuleOut This is set to the module found.
+   */
+  static void ResolveImportedModule(JSContext* aCx,
+                                    JS::Handle<JS::Value> aReferencingPrivate,
+                                    JS::Handle<JSString*> aSpecifier,
+                                    JS::MutableHandle<JSObject*> aModuleOut);
+
+  void StartDynamicImport(ModuleLoadRequest* aRequest);
+  void FinishDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult);
+  void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
+                           nsresult aResult);
+
+  /*
+   * Get the currently active script. This is used as the initiating script when
+   * executing timeout handler scripts.
+   */
+  static LoadedScript* GetActiveScript(JSContext* aCx);
+
+  nsIDocument* GetDocument() const { return mDocument; }
+
  private:
   virtual ~ScriptLoader();
 
+  void EnsureModuleHooksInitialized();
+
   ScriptLoadRequest* CreateLoadRequest(
       ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
       nsIPrincipal* aTriggeringPrincipal, mozilla::CORSMode aCORSMode,
       const SRIMetadata& aIntegrity,
       mozilla::net::ReferrerPolicy aReferrerPolicy);
 
   /**
    * Unblocks the creator parser of the parser-blocking scripts.
@@ -414,16 +447,17 @@ class ScriptLoader final : public nsISup
                        SRICheckDataVerifier* aSRIDataVerifier) const;
 
   void ReportErrorToConsole(ScriptLoadRequest* aRequest,
                             nsresult aResult) const;
 
   nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
                                      bool* aCouldCompileOut);
   nsresult ProcessRequest(ScriptLoadRequest* aRequest);
+  void ProcessDynamicImport(ModuleLoadRequest* aRequest);
   nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
   void FireScriptAvailable(nsresult aResult, ScriptLoadRequest* aRequest);
   void FireScriptEvaluated(nsresult aResult, ScriptLoadRequest* aRequest);
   nsresult EvaluateScript(ScriptLoadRequest* aRequest);
 
   /**
    * Queue the current script load request to be saved, when the page
    * initialization ends. The page initialization end is defined as being the
@@ -495,25 +529,28 @@ class ScriptLoader final : public nsISup
   void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
 
   RefPtr<mozilla::GenericPromise> StartFetchingModuleAndDependencies(
       ModuleLoadRequest* aParent, nsIURI* aURI);
 
   nsresult AssociateSourceElementsForModuleTree(JSContext* aCx,
                                                 ModuleLoadRequest* aRequest);
 
+  void RunScriptWhenSafe(ScriptLoadRequest* aRequest);
+
   nsIDocument* mDocument;  // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
   ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
   // mLoadingAsyncRequests holds async requests while they're loading; when they
   // have been loaded they are moved to mLoadedAsyncRequests.
   ScriptLoadRequestList mLoadingAsyncRequests;
   ScriptLoadRequestList mLoadedAsyncRequests;
   ScriptLoadRequestList mDeferRequests;
   ScriptLoadRequestList mXSLTRequests;
+  ScriptLoadRequestList mDynamicImportRequests;
   RefPtr<ScriptLoadRequest> mParserBlockingRequest;
 
   // List of script load request that are holding a buffer which has to be saved
   // on the cache.
   ScriptLoadRequestList mBytecodeEncodingQueue;
 
   // In mRequests, the additional information here is stored by the element.
   struct PreloadInfo {
@@ -555,16 +592,17 @@ class ScriptLoader final : public nsISup
   // Module map
   nsRefPtrHashtable<nsURIHashKey, mozilla::GenericNonExclusivePromise::Private>
       mFetchingModules;
   nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
 
   nsCOMPtr<nsIConsoleReportCollector> mReporter;
 
   // Logging
+ public:
   static LazyLogModule gCspPRLog;
   static LazyLogModule gScriptLoaderLog;
 };
 
 class nsAutoScriptLoaderDisabler {
  public:
   explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc) {
     mLoader = aDoc->ScriptLoader();
--- a/dom/script/moz.build
+++ b/dom/script/moz.build
@@ -13,25 +13,26 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS += [
     'nsIScriptElement.h',
 ]
 
 EXPORTS.mozilla.dom += [
+    'LoadedScript.h',
     'ScriptElement.h',
     'ScriptLoader.h',
     'ScriptLoadRequest.h',
     'ScriptSettings.h',
 ]
 
 UNIFIED_SOURCES += [
+    'LoadedScript.cpp',
     'ModuleLoadRequest.cpp',
-    'ModuleScript.cpp',
     'ScriptElement.cpp',
     'ScriptLoader.cpp',
     'ScriptLoadHandler.cpp',
     'ScriptLoadRequest.cpp',
     'ScriptSettings.cpp',
     'ScriptTrace.cpp',
 ]
 
--- a/dom/xbl/nsXBLProtoImplField.cpp
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -410,19 +410,18 @@ nsresult nsXBLProtoImplField::InstallFie
   if (!nsJSUtils::GetScopeChainForXBL(cx, boundElement, aProtoBinding,
                                       scopeChain)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   rv = NS_OK;
   {
     nsJSUtils::ExecutionContext exec(cx, scopeObject);
     exec.SetScopeChain(scopeChain);
-    exec.CompileAndExec(options,
-                        nsDependentString(mFieldText, mFieldTextLength));
-    rv = exec.ExtractReturnValue(&result);
+    exec.Compile(options, nsDependentString(mFieldText, mFieldTextLength));
+    rv = exec.ExecScript(&result);
   }
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW) {
     // Report the exception now, before we try using the JSContext for
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -620,16 +620,21 @@ struct ImplicitEdgeHolderType<JSObject*>
   typedef JSObject* Type;
 };
 
 template <>
 struct ImplicitEdgeHolderType<JSScript*> {
   typedef JSScript* Type;
 };
 
+template <>
+struct ImplicitEdgeHolderType<LazyScript*> {
+  typedef LazyScript* Type;
+};
+
 void GCMarker::markEphemeronValues(gc::Cell* markedCell,
                                    WeakEntryVector& values) {
   DebugOnly<size_t> initialLen = values.length();
 
   for (const auto& markable : values) {
     if (color == gc::MarkColor::Black &&
         markable.weakmap->markColor == gc::MarkColor::Gray) {
       continue;
@@ -833,16 +838,17 @@ void js::GCMarker::markAndScan(T* thing)
 namespace js {
 template <>
 void GCMarker::traverse(JSString* thing) {
   markAndScan(thing);
 }
 template <>
 void GCMarker::traverse(LazyScript* thing) {
   markAndScan(thing);
+  markImplicitEdges(thing);
 }
 template <>
 void GCMarker::traverse(Shape* thing) {
   markAndScan(thing);
 }
 template <>
 void GCMarker::traverse(js::Scope* thing) {
   markAndScan(thing);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-1515993.js
@@ -0,0 +1,13 @@
+function checkGetOffsetsCoverage() {
+    var g = newGlobal();
+    var dbg = Debugger(g);
+    var topLevel;
+    dbg.onNewScript = function(s) {
+        topLevel = s;
+    };
+    g.eval(`import(() => 1)`);
+    topLevel.getChildScripts();
+}
+checkGetOffsetsCoverage();
+gczeal(14, 10);
+Object.defineProperty(this, "fuzzutils", {});
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -614,17 +614,18 @@ MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT,       
 MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT,   0, JSEXN_SYNTAXERR, "indirect export not found")
 MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export")
 MSG_DEF(JSMSG_MISSING_IMPORT,            0, JSEXN_SYNTAXERR, "import not found")
 MSG_DEF(JSMSG_AMBIGUOUS_IMPORT,          0, JSEXN_SYNTAXERR, "ambiguous import")
 MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT,  0, JSEXN_SYNTAXERR, "export not found for namespace")
 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")
+MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED,     0, JSEXN_TYPEERR, "error loading dynamically imported module")
+MSG_DEF(JSMSG_BAD_MODULE_SPECIFIER,      1, JSEXN_TYPEERR, "error resolving module specifier '{0}'")
 
 // Promise
 MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3735,26 +3735,50 @@ JS_PUBLIC_API bool JS::CompileModule(JSC
 }
 
 JS_PUBLIC_API void JS::SetModulePrivate(JSObject* module,
                                         const JS::Value& value) {
   module->as<ModuleObject>().scriptSourceObject()->setPrivate(value);
 }
 
 JS_PUBLIC_API JS::Value JS::GetModulePrivate(JSObject* module) {
-  return module->as<ModuleObject>().scriptSourceObject()->unwrappedPrivate();
+  return module->as<ModuleObject>().scriptSourceObject()->canonicalPrivate();
 }
 
 JS_PUBLIC_API void JS::SetScriptPrivate(JSScript* script,
                                         const JS::Value& value) {
   script->sourceObject()->setPrivate(value);
 }
 
 JS_PUBLIC_API JS::Value JS::GetScriptPrivate(JSScript* script) {
-  return script->sourceObject()->unwrappedPrivate();
+  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;
 }
 
 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
@@ -3099,23 +3099,27 @@ extern JS_PUBLIC_API void SetModuleMetad
                                                 ModuleMetadataHook func);
 
 using ModuleDynamicImportHook = bool (*)(JSContext* cx,
                                          HandleValue referencingPrivate,
                                          HandleString specifier,
                                          HandleObject promise);
 
 /**
- * Get the HostResolveImportedModule hook for the runtime.
+ * Get the HostImportModuleDynamically hook for the runtime.
  */
 extern JS_PUBLIC_API ModuleDynamicImportHook
 GetModuleDynamicImportHook(JSRuntime* rt);
 
 /**
- * Set the HostResolveImportedModule hook for the runtime to the given function.
+ * Set the HostImportModuleDynamically hook for the runtime to the given
+ * function.
+ *
+ * If this hook is not set (or set to nullptr) then the JS engine will throw an
+ * exception if dynamic module import is attempted.
  */
 extern JS_PUBLIC_API void SetModuleDynamicImportHook(
     JSRuntime* rt, ModuleDynamicImportHook func);
 
 extern JS_PUBLIC_API bool FinishDynamicModuleImport(
     JSContext* cx, HandleValue referencingPrivate, HandleString specifier,
     HandleObject promise);
 
@@ -3148,16 +3152,42 @@ extern JS_PUBLIC_API void SetScriptPriva
 
 /**
  * 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 GetScriptPrivate(JSScript* script);
 
 /*
+ * 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.
+ */
+using ScriptPrivateFinalizeHook = void (*)(JSFreeOp*, const JS::Value&);
+
+/**
+ * Get the script private finalize hook for the runtime.
+ */
+extern JS_PUBLIC_API ScriptPrivateFinalizeHook
+GetScriptPrivateFinalizeHook(JSRuntime* rt);
+
+/**
+ * Set the script private finalize hook for the runtime to the given function.
+ */
+extern JS_PUBLIC_API void SetScriptPrivateFinalizeHook(
+    JSRuntime* rt, ScriptPrivateFinalizeHook func);
+
+/*
  * 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.
  */
 extern JS_PUBLIC_API bool ModuleInstantiate(JSContext* cx,
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1399,11 +1399,19 @@ JS_FRIEND_API void js::LogCtor(void* sel
 }
 
 JS_FRIEND_API void js::LogDtor(void* self, const char* type, uint32_t sz) {
   if (LogCtorDtor fun = sLogDtor) {
     fun(self, type, sz);
   }
 }
 
+JS_FRIEND_API JS::Value js::MaybeGetScriptPrivate(JSObject* object) {
+  if (!object->is<ScriptSourceObject>()) {
+    return UndefinedValue();
+  }
+
+  return object->as<ScriptSourceObject>().canonicalPrivate();
+}
+
 JS_FRIEND_API uint64_t js::GetGCHeapUsageForObjectZone(JSObject* obj) {
   return obj->zone()->usage.gcBytes();
 }
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -101,16 +101,32 @@ extern JS_FRIEND_API bool JS_IsDeadWrapp
  * attempting to wrap objects from scopes which are already dead.
  *
  * If origObject is passed, it must be an proxy object, and will be
  * used to determine the characteristics of the new dead wrapper.
  */
 extern JS_FRIEND_API JSObject* JS_NewDeadWrapper(
     JSContext* cx, JSObject* origObject = nullptr);
 
+namespace js {
+
+/**
+ * Get the script private value associated with an object, if any.
+ *
+ * The private value is set with SetScriptPrivate() or SetModulePrivate() and is
+ * internally stored on the relevant ScriptSourceObject.
+ *
+ * This is used by the cycle collector to trace through
+ * ScriptSourceObjects. This allows private values to contain an nsISupports
+ * pointer and hence support references to cycle collected C++ objects.
+ */
+JS_FRIEND_API JS::Value MaybeGetScriptPrivate(JSObject* object);
+
+}  // namespace js
+
 /*
  * Used by the cycle collector to trace through a shape or object group and
  * all cycle-participating data it reaches, using bounded stack space.
  */
 extern JS_FRIEND_API void JS_TraceShapeCycleCollectorChildren(
     JS::CallbackTracer* trc, JS::GCCellPtr shape);
 extern JS_FRIEND_API void JS_TraceObjectGroupCycleCollectorChildren(
     JS::CallbackTracer* trc, JS::GCCellPtr group);
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3360,25 +3360,27 @@ ModuleObject* js::GetModuleObjectForScri
     if (si.kind() == ScopeKind::Module) {
       return si.scope()->as<ModuleScope>().module();
     }
   }
   return nullptr;
 }
 
 Value js::FindScriptOrModulePrivateForScript(JSScript* script) {
-  while (script) {
-    ScriptSourceObject* sso = script->sourceObject();
-    Value value = sso->unwrappedPrivate();
+  MOZ_ASSERT(script);
+  ScriptSourceObject* sso = script->sourceObject();
+  while (sso) {
+    Value value = sso->canonicalPrivate();
     if (!value.isUndefined()) {
       return value;
     }
 
-    MOZ_ASSERT(sso->unwrappedIntroductionScript() != script);
-    script = sso->unwrappedIntroductionScript();
+    ScriptSourceObject* parent = sso->unwrappedIntroductionSourceObject();
+    MOZ_ASSERT(parent != sso);
+    sso = parent;
   }
 
   return UndefinedValue();
 }
 
 bool js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx,
                                                   AbstractFramePtr frame,
                                                   jsbytecode* pc,
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1312,16 +1312,26 @@ bool JSScript::hasScriptName() {
   auto p = realm()->scriptNameMap->lookup(this);
   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);
+    }
+  }
 }
 
 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);
@@ -1367,16 +1377,17 @@ 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);
 }
@@ -1405,34 +1416,42 @@ 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. In that case, simply don't bother to retain the introduction
-  // script.
-  Value introductionScript = UndefinedValue();
-  if (options.introductionScript() &&
-      options.introductionScript()->compartment() == cx->compartment()) {
-    introductionScript.setPrivateGCThing(options.introductionScript());
-  }
-  source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
+  // 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());
+    }
+    introdutionSource.setObject(*options.introductionScript()->sourceObject());
+    if (!cx->compartment()->wrap(cx, &introdutionSource)) {
+      return false;
+    }
+  }
+  source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introdutionScript);
+  source->setReservedSlot(INTRODUCTION_SOURCE_OBJECT_SLOT, introdutionSource);
 
   return true;
 }
 
 /* static */ bool ScriptSourceObject::initElementProperties(
     JSContext* cx, HandleScriptSourceObject source, HandleObject element,
     HandleString elementAttrName) {
   MOZ_ASSERT(source->isCanonical());
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1165,33 +1165,44 @@ 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);
   }
 
-  Value unwrappedPrivate() const {
-    return unwrappedCanonical()->getReservedSlot(PRIVATE_SLOT);
+  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
@@ -158,17 +158,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
       oomCallback(nullptr),
       debuggerMallocSizeOf(ReturnZeroSize),
       performanceMonitoring_(),
       stackFormat_(parentRuntime ? js::StackFormat::Default
                                  : js::StackFormat::SpiderMonkey),
       wasmInstances(mutexid::WasmRuntimeInstances),
       moduleResolveHook(),
       moduleMetadataHook(),
-      moduleDynamicImportHook() {
+      moduleDynamicImportHook(),
+      scriptPrivateFinalizeHook() {
   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
@@ -970,16 +970,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;
+
  public:
 #if defined(JS_BUILD_BINAST)
   js::BinaryASTSupport& binast() { return binast_; }
 
  private:
   js::BinaryASTSupport binast_;
 #endif  // defined(JS_BUILD_BINAST)
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1579,16 +1579,19 @@ pref("javascript.options.spectre.jit_to_
 #endif
 
 // Streams API
 pref("javascript.options.streams", true);
 
 // BigInt API
 pref("javascript.options.bigint", false);
 
+// Dynamic module import.
+pref("javascript.options.dynamicImport", false);
+
 // advanced prefs
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
 // Same-origin policy for file URIs, "false" is traditional
 pref("security.fileuri.strict_origin_policy", true);
 
 // If this pref is true, prefs in the logging.config branch will be cleared on
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/__dir__.ini
+++ b/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/__dir__.ini
@@ -1,1 +1,2 @@
 lsan-allowed: [Init, nsHostResolver::ResolveHost]
+prefs: [javascript.options.dynamicImport:true, security.csp.experimentalEnabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[dynamic-imports-fetch-error.sub.html]
-  [import(): error cases occuring during fetching]
-    expected: FAIL
-    bug: 1342012
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[dynamic-imports-script-error.html]
-  [import(): error cases caused by the imported module script]
-    expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[dynamic-imports.html]
-  [Basic dynamic imports]
-    expected: FAIL
-
-  [Dynamic imports should resolve module.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[inline-event-handler.html]
-  expected: ERROR
-  [dynamic import should work when triggered from inline event handlers]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[propagate-nonce-external-classic.html]
-  [Untitled]
-    expected: FAIL
-
-  [propagate-nonce-external-classic]
-    expected: FAIL
-
-  [Dynamically imported module should eval when imported from script w/ a valid nonce.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[propagate-nonce-external-module.html]
-  [Untitled]
-    expected: FAIL
-
-  [propagate-nonce-external-module]
-    expected: FAIL
-
-  [Dynamically imported module should eval when imported from script w/ a valid nonce.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[propagate-nonce-inline-classic.html]
-  [Untitled]
-    expected: FAIL
-
-  [propagate-nonce-inline-classic]
-    expected: FAIL
-
-  [Dynamically imported module should eval when imported from script w/ a valid nonce.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[propagate-nonce-inline-module.html]
-  [Untitled]
-    expected: FAIL
-
-  [propagate-nonce-inline-module]
-    expected: FAIL
-
-  [Dynamically imported module should eval when imported from script w/ a valid nonce.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-base-url-external-classic.html]
-  expected: ERROR
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [eval should successfully import]
-    expected: NOTRUN
-
-  [Function should successfully import]
-    expected: NOTRUN
-
-  [reflected-inline-event-handlers should successfully import]
-    expected: NOTRUN
-
-  [inline-event-handlers-UA-code should successfully import]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-base-url-external-module.html]
-  expected: ERROR
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [eval should successfully import]
-    expected: NOTRUN
-
-  [Function should successfully import]
-    expected: NOTRUN
-
-  [reflected-inline-event-handlers should successfully import]
-    expected: NOTRUN
-
-  [inline-event-handlers-UA-code should successfully import]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-base-url-inline-classic.html]
-  expected: ERROR
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [eval should successfully import]
-    expected: NOTRUN
-
-  [the Function constructor should successfully import]
-    expected: NOTRUN
-
-  [reflected inline event handlers should successfully import]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code should successfully import]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-base-url-inline-module.html]
-  expected: ERROR
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [eval should successfully import]
-    expected: NOTRUN
-
-  [the Function constructor should successfully import]
-    expected: NOTRUN
-
-  [reflected inline event handlers should successfully import]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code should successfully import]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-classic.html]
-  expected: ERROR
-  [eval should successfully import]
-    expected: FAIL
-
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [the Function constructor should successfully import]
-    expected: NOTRUN
-
-  [reflected inline event handlers should successfully import]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code should successfully import]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[string-compilation-module.html]
-  expected: ERROR
-  [eval should successfully import]
-    expected: FAIL
-
-  [setTimeout should successfully import]
-    expected: TIMEOUT
-
-  [the Function constructor should successfully import]
-    expected: NOTRUN
-
-  [reflected inline event handlers should successfully import]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code should successfully import]
-    expected: NOTRUN
-
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html.ini
+++ b/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html.ini
@@ -1,20 +1,4 @@
 [string-compilation-nonce-classic.html]
-  expected: ERROR
   [setTimeout must inherit the nonce from the triggering script, thus execute]
-    expected: TIMEOUT
-
-  [direct eval must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
+    expected: FAIL
 
-  [indirect eval must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [the Function constructor must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [reflected inline event handlers must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html.ini
+++ b/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html.ini
@@ -1,20 +1,3 @@
 [string-compilation-nonce-module.html]
-  expected: ERROR
   [setTimeout must inherit the nonce from the triggering script, thus execute]
-    expected: TIMEOUT
-
-  [direct eval must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [indirect eval must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [the Function constructor must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [reflected inline event handlers must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
-  [inline event handlers triggered via UA code must inherit the nonce from the triggering script, thus execute]
-    expected: NOTRUN
-
+    expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-[string-compilation-of-promise-result.html]
-  [Evaled the script via eval, successful import]
-    expected: FAIL
-
-  [Evaled the script via eval, failed import]
-    expected: FAIL
-
-  [Evaled the script via Function, successful import]
-    expected: FAIL
-
-  [Evaled the script via Function, failed import]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/empty-iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <div id="dummy"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check import() works when active script is in another document</title>
+<link rel="author" title="Jon Coppeard" href="mailto:jcoppeard@mozilla.com">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<iframe id="frame" src="resources/empty-iframe.html"></iframe>
+
+<script>
+
+function startTest() {
+  const otherWindow = document.getElementById("frame").contentWindow;
+  const otherDiv = otherWindow.document.getElementById("dummy");
+
+  function createTestPromise() {
+    return new Promise((resolve, reject) => {
+      otherWindow.continueTest = resolve;
+      otherWindow.errorTest = reject;
+    });
+  }
+
+  const evaluators = {
+    eval: otherWindow.eval,
+    setTimeout: otherWindow.setTimeout,
+    "the Function constructor"(x) {
+      otherWindow.Function(x)();
+    },
+    "reflected inline event handlers"(x) {
+      otherDiv.setAttribute("onclick", x);
+      otherDiv.onclick();
+    },
+    "inline event handlers triggered by JS"(x) {
+      otherDiv.setAttribute("onclick", x);
+      otherDiv.click(); // different from .**on**click()
+    }
+  };
+
+  for (const [label, evaluator] of Object.entries(evaluators)) {
+    promise_test(t => {
+      t.add_cleanup(() => {
+        otherDiv.removeAttribute("onclick");
+        delete otherWindow.evaluated_imports_a;
+      });
+
+      const promise = createTestPromise();
+
+      evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+      return promise.then(module => {
+        assert_true(otherWindow.evaluated_imports_a, "The module must have been evaluated");
+        assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+      });
+    }, label + " should successfully import");
+  };
+}
+</script>
+<body onLoad="startTest()"></body>
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -70,16 +70,17 @@
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "js/Debug.h"
 #include "js/GCAPI.h"
+#include "jsfriendapi.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsExceptionHandler.h"
 #include "nsJSUtils.h"
 #include "nsWrapperCache.h"
@@ -622,38 +623,46 @@ void CycleCollectedJSRuntime::NoteGCThin
     nsCycleCollectionTraversalCallback& aCb) const {
   MOZ_ASSERT(aClasp);
   MOZ_ASSERT(aClasp == js::GetObjectClass(aObj));
 
   if (NoteCustomGCThingXPCOMChildren(aClasp, aObj, aCb)) {
     // Nothing else to do!
     return;
   }
+
   // XXX This test does seem fragile, we should probably whitelist classes
   //     that do hold a strong reference, but that might not be possible.
-  else if (aClasp->flags & JSCLASS_HAS_PRIVATE &&
-           aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
+  if (aClasp->flags & JSCLASS_HAS_PRIVATE &&
+      aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "js::GetObjectPrivate(obj)");
     aCb.NoteXPCOMChild(static_cast<nsISupports*>(js::GetObjectPrivate(aObj)));
-  } else {
-    const DOMJSClass* domClass = GetDOMClass(aObj);
-    if (domClass) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
-      // It's possible that our object is an unforgeable holder object, in
-      // which case it doesn't actually have a C++ DOM object associated with
-      // it.  Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
-      // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
-      if (domClass->mDOMObjectIsISupports) {
-        aCb.NoteXPCOMChild(
-            UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObj));
-      } else if (domClass->mParticipant) {
-        aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(aObj),
-                            domClass->mParticipant);
-      }
+    return;
+  }
+
+  const DOMJSClass* domClass = GetDOMClass(aObj);
+  if (domClass) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
+    // It's possible that our object is an unforgeable holder object, in
+    // which case it doesn't actually have a C++ DOM object associated with
+    // it.  Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
+    // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
+    if (domClass->mDOMObjectIsISupports) {
+      aCb.NoteXPCOMChild(
+          UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObj));
+    } else if (domClass->mParticipant) {
+      aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(aObj),
+                          domClass->mParticipant);
     }
+    return;
+  }
+
+  JS::Value value = js::MaybeGetScriptPrivate(aObj);
+  if (!value.isUndefined()) {
+    aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
   }
 }
 
 void CycleCollectedJSRuntime::TraverseGCThing(
     TraverseSelect aTs, JS::GCCellPtr aThing,
     nsCycleCollectionTraversalCallback& aCb) {
   bool isMarkedGray = JS::GCThingIsMarkedGray(aThing);