Bug 1803810 - Part 5: Add global option to ChromeUtils.importESModule. r=jonco,ochameau
☠☠ backed out by 7682952aed89 ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Tue, 13 Feb 2024 14:34:22 +0000 (17 months ago)
changeset 695519 9699c18e5bf7719e0cbee26e4d27521576ed12f2
parent 695518 84cdfd738db6ab8687b3138e3bf70894e4292184
child 695520 2e2f01296233a4f7adb3d1fac1c4e67d5f7b16ab
push id199908
push userarai_a@mac.com
push dateTue, 13 Feb 2024 14:42:56 +0000 (17 months ago)
treeherderautoland@d5df64b38425 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco, ochameau
bugs1803810
milestone124.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1803810 - Part 5: Add global option to ChromeUtils.importESModule. r=jonco,ochameau Add { global: "current" } option to import the module into the current global, either shared or non-shared. Also add the following: * { global: "shared" }: the existing default behavior, except for the implicit contextual behavior * { global: "devtools" }: equivalent of loadInDevToolsLoader: true * { global: "contextual" }: the explicit version of the contextual behavior The later patch is going to drop the loadInDevToolsLoader option and also the implicit contextual behavior, and require global option for modules in the DevTools global. Differential Revision: https://phabricator.services.mozilla.com/D199457
dom/base/ChromeUtils.cpp
dom/chrome-webidl/ChromeUtils.webidl
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -27,16 +27,17 @@
 #include "mozilla/IntentionalCrash.h"
 #include "mozilla/PerfStats.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcInfo.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/ScrollingMetrics.h"
 #include "mozilla/SharedStyleSheetCache.h"
+#include "mozilla/SpinEventLoopUntil.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/IdleDeadline.h"
 #include "mozilla/dom/InProcessParent.h"
 #include "mozilla/dom/JSActorService.h"
 #include "mozilla/dom/MediaSessionBinding.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/dom/Performance.h"
@@ -613,47 +614,156 @@ static mozJSModuleLoader* GetContextualE
       (devToolsModuleloader && !aLoadInDevToolsLoader.WasPassed() &&
        devToolsModuleloader->IsLoaderGlobal(aGlobal));
   if (shouldUseDevToolsLoader) {
     return mozJSModuleLoader::GetOrCreateDevToolsLoader();
   }
   return mozJSModuleLoader::Get();
 }
 
+static mozJSModuleLoader* GetModuleLoaderForCurrentGlobal(
+    JSContext* aCx, const GlobalObject& aGlobal,
+    Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
+        aMaybeSyncLoaderScope) {
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+  if (mozJSModuleLoader::IsSharedSystemGlobal(global)) {
+    return mozJSModuleLoader::Get();
+  }
+  if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global)) {
+    return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+  }
+
+  if (loader::NonSharedGlobalSyncModuleLoaderScope::IsActive()) {
+    mozJSModuleLoader* moduleloader =
+        loader::NonSharedGlobalSyncModuleLoaderScope::ActiveLoader();
+
+    if (!moduleloader->IsLoaderGlobal(global->GetGlobalJSObject())) {
+      JS_ReportErrorASCII(aCx,
+                          "global: \"current\" option cannot be used for "
+                          "different global while other importESModule "
+                          "with global: \"current\" is on the stack");
+      return nullptr;
+    }
+
+    return moduleloader;
+  }
+
+  RefPtr targetModuleLoader = global->GetModuleLoader(aCx);
+  if (!targetModuleLoader) {
+    // Sandbox without associated window returns nullptr for GetModuleLoader.
+    JS_ReportErrorASCII(aCx, "No ModuleLoader found for the current context");
+    return nullptr;
+  }
+
+  if (targetModuleLoader->HasFetchingModules()) {
+    if (!mozilla::SpinEventLoopUntil(
+            "importESModule for current global"_ns, [&]() -> bool {
+              return !targetModuleLoader->HasFetchingModules();
+            })) {
+      JS_ReportErrorASCII(aCx, "Failed to wait for ongoing module requests");
+      return nullptr;
+    }
+  }
+
+  aMaybeSyncLoaderScope.emplace(aCx, global);
+  return aMaybeSyncLoaderScope->ActiveLoader();
+}
+
+static mozJSModuleLoader* GetModuleLoaderForOptions(
+    JSContext* aCx, const GlobalObject& aGlobal,
+    const ImportESModuleOptionsDictionary& aOptions,
+    Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
+        aMaybeSyncLoaderScope) {
+  if (!aOptions.mGlobal.WasPassed()) {
+    return GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get());
+  }
+
+  switch (aOptions.mGlobal.Value()) {
+    case ImportESModuleTargetGlobal::Shared:
+      return mozJSModuleLoader::Get();
+
+    case ImportESModuleTargetGlobal::Devtools:
+      return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+
+    case ImportESModuleTargetGlobal::Contextual: {
+      RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader();
+      if (devToolsModuleloader &&
+          devToolsModuleloader->IsLoaderGlobal(aGlobal.Get())) {
+        return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+      }
+      return mozJSModuleLoader::Get();
+    }
+
+    case ImportESModuleTargetGlobal::Current:
+      return GetModuleLoaderForCurrentGlobal(aCx, aGlobal,
+                                             aMaybeSyncLoaderScope);
+
+    default:
+      MOZ_CRASH("Unknown ImportESModuleTargetGlobal");
+  }
+}
+
+static bool ValidateImportOptions(
+    JSContext* aCx, const ImportESModuleOptionsDictionary& aOptions) {
+  if (aOptions.mGlobal.WasPassed() &&
+      aOptions.mLoadInDevToolsLoader.WasPassed()) {
+    JS_ReportErrorASCII(aCx,
+                        "global option and loadInDevToolsLoader option "
+                        "cannot be used at the same time");
+    return false;
+  }
+
+  return true;
+}
+
 /* static */
 void ChromeUtils::ImportESModule(
     const GlobalObject& aGlobal, const nsAString& aResourceURI,
     const ImportESModuleOptionsDictionary& aOptions,
     JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
-  RefPtr moduleloader =
-      GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get());
-  MOZ_ASSERT(moduleloader);
+  JSContext* cx = aGlobal.Context();
+
+  if (!ValidateImportOptions(cx, aOptions)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope;
+  RefPtr<mozJSModuleLoader> moduleloader =
+      GetModuleLoaderForOptions(cx, aGlobal, aOptions, maybeSyncLoaderScope);
+  if (!moduleloader) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   NS_ConvertUTF16toUTF8 registryLocation(aResourceURI);
 
   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
       "ChromeUtils::ImportESModule", OTHER, registryLocation);
 
-  JSContext* cx = aGlobal.Context();
-
   JS::Rooted<JSObject*> moduleNamespace(cx);
   nsresult rv =
       moduleloader->ImportESModule(cx, registryLocation, &moduleNamespace);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   MOZ_ASSERT(!JS_IsExceptionPending(cx));
 
   if (!JS_WrapObject(cx, &moduleNamespace)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
   aRetval.set(moduleNamespace);
+
+  if (maybeSyncLoaderScope) {
+    maybeSyncLoaderScope->Finish();
+  }
 }
 
 namespace lazy_getter {
 
 static const size_t SLOT_ID = 0;
 static const size_t SLOT_URI = 1;
 static const size_t SLOT_PARAMS = 1;
 
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -508,24 +508,26 @@ partial namespace ChromeUtils {
   [Throws]
   object import(UTF8String aResourceURI, optional object aTargetObj);
 
   /**
    * Synchronously loads and evaluates the JS module source located at
    * 'aResourceURI'.
    *
    * @param aResourceURI A resource:// URI string to load the module from.
+   * @param aOption An option to specify where to load the module into.
    * @returns the module's namespace object.
    *
    * The implementation maintains a hash of aResourceURI->global obj.
    * Subsequent invocations of import with 'aResourceURI' pointing to
    * the same file will not cause the module to be re-evaluated.
    */
   [Throws]
-  object importESModule(DOMString aResourceURI, optional ImportESModuleOptionsDictionary options = {});
+  object importESModule(DOMString aResourceURI,
+                        optional ImportESModuleOptionsDictionary aOptions = {});
 
   /**
    * Defines a property on the given target which lazily imports a JavaScript
    * module when accessed.
    *
    * The first time the property is accessed, the module at the given URL is
    * imported, and the property is replaced with the module's exported symbol
    * of the same name.
@@ -978,23 +980,59 @@ dictionary CompileScriptOptionsDictionar
    * If true, the script will be compiled so that its last expression will be
    * returned as the value of its execution. This makes certain types of
    * optimization impossible, and disables the JIT in many circumstances, so
    * should not be used when not absolutely necessary.
    */
   boolean hasReturnValue = false;
 };
 
+/**
+ * Where the modules are loaded into with importESModule.
+ */
+enum ImportESModuleTargetGlobal {
+  /**
+   * Load into the shared system global.
+   * This is the default value.
+   */
+  "shared",
+
+  /**
+   * Load into a distinct system global for DevTools, so that the DevTools can
+   * load a distinct set of modules and do not interfere with its debuggee.
+   */
+  "devtools",
+
+  /**
+   * If the current global is DevTools' distinct system global, load into the
+   * DevTools' distinct system global.
+   * Otherwise load into the shared system global.
+   *
+   * This is a temporary workaround until DevTools modules are ESMified.
+   */
+  "contextual",
+
+  /**
+   * Load into current global.
+   *
+   * This can be used for any global.  If this is used for shared global or
+   * devtools global, this has the same effect as "shared" or "devtools".
+   */
+  "current",
+};
+
 dictionary ImportESModuleOptionsDictionary {
   /**
    * If true, a distinct module loader will be used, in the system principal,
    * but with a distinct global so that the DevTools can load a distinct set
    * of modules and do not interfere with its debuggee.
    */
   boolean loadInDevToolsLoader;
+
+  ImportESModuleTargetGlobal global;
 };
 
 /**
  * A JS object whose properties specify what portion of the heap graph to
  * write. The recognized properties are:
  *
  * * globals: [ global, ... ]
  *   Dump only nodes that either: