summaryrefslogtreecommitdiffstats
path: root/js/loader
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /js/loader
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/loader')
-rw-r--r--js/loader/ImportMap.cpp697
-rw-r--r--js/loader/ImportMap.h118
-rw-r--r--js/loader/LoadContextBase.cpp54
-rw-r--r--js/loader/LoadContextBase.h69
-rw-r--r--js/loader/LoadedScript.cpp211
-rw-r--r--js/loader/LoadedScript.h119
-rw-r--r--js/loader/ModuleLoadRequest.cpp207
-rw-r--r--js/loader/ModuleLoadRequest.h164
-rw-r--r--js/loader/ModuleLoaderBase.cpp1317
-rw-r--r--js/loader/ModuleLoaderBase.h420
-rw-r--r--js/loader/ResolveResult.h54
-rw-r--r--js/loader/ScriptKind.h16
-rw-r--r--js/loader/ScriptLoadRequest.cpp283
-rw-r--r--js/loader/ScriptLoadRequest.h425
-rw-r--r--js/loader/moz.build29
15 files changed, 4183 insertions, 0 deletions
diff --git a/js/loader/ImportMap.cpp b/js/loader/ImportMap.cpp
new file mode 100644
index 0000000000..bf9eafe8e6
--- /dev/null
+++ b/js/loader/ImportMap.cpp
@@ -0,0 +1,697 @@
+/* -*- 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 "ImportMap.h"
+
+#include "js/Array.h" // IsArrayObject
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/JSON.h" // JS_ParseJSON
+#include "LoadedScript.h"
+#include "ModuleLoaderBase.h" // ScriptLoaderInterface
+#include "nsContentUtils.h"
+#include "nsIScriptElement.h"
+#include "nsIScriptError.h"
+#include "nsJSUtils.h" // nsAutoJSString
+#include "nsNetUtil.h" // NS_NewURI
+#include "ScriptLoadRequest.h"
+
+using JS::SourceText;
+using mozilla::Err;
+using mozilla::LazyLogModule;
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+using mozilla::WrapNotNull;
+
+namespace JS::loader {
+
+LazyLogModule ImportMap::gImportMapLog("ImportMap");
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ImportMap::gImportMapLog, mozilla::LogLevel::Debug, args)
+
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(ImportMap::gImportMapLog, mozilla::LogLevel::Debug)
+
+void ReportWarningHelper::Report(const char* aMessageName,
+ const nsTArray<nsString>& aParams) const {
+ mLoader->ReportWarningToConsole(mRequest, aMessageName, aParams);
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-url-like-module-specifier
+static ResolveResult ResolveURLLikeModuleSpecifier(const nsAString& aSpecifier,
+ nsIURI* aBaseURL) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv;
+
+ // Step 1. If specifier starts with "/", "./", or "../", then:
+ if (StringBeginsWith(aSpecifier, u"/"_ns) ||
+ StringBeginsWith(aSpecifier, u"./"_ns) ||
+ StringBeginsWith(aSpecifier, u"../"_ns)) {
+ // Step 1.1. Let url be the result of parsing specifier with baseURL as the
+ // base URL.
+ rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aBaseURL);
+ // Step 1.2. If url is failure, then return null.
+ if (NS_FAILED(rv)) {
+ return Err(ResolveError::Failure);
+ }
+
+ // Step 1.3. Return url.
+ return WrapNotNull(uri);
+ }
+
+ // Step 2. Let url be the result of parsing specifier (with no base URL).
+ rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
+ // Step 3. If url is failure, then return null.
+ if (NS_FAILED(rv)) {
+ return Err(ResolveError::FailureMayBeBare);
+ }
+
+ // Step 4. Return url.
+ return WrapNotNull(uri);
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-specifier-key
+static void NormalizeSpecifierKey(const nsAString& aSpecifierKey,
+ nsIURI* aBaseURL,
+ const ReportWarningHelper& aWarning,
+ nsAString& aRetVal) {
+ // Step 1. If specifierKey is the empty string, then:
+ if (aSpecifierKey.IsEmpty()) {
+ // Step 1.1. Report a warning to the console that specifier keys cannot be
+ // the empty string.
+ aWarning.Report("ImportMapEmptySpecifierKeys");
+
+ // Step 1.2. Return null.
+ aRetVal = EmptyString();
+ return;
+ }
+
+ // Step 2. Let url be the result of resolving a URL-like module specifier,
+ // given specifierKey and baseURL.
+ auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifierKey, aBaseURL);
+
+ // Step 3. If url is not null, then return the serialization of url.
+ if (parseResult.isOk()) {
+ nsCOMPtr<nsIURI> url = parseResult.unwrap();
+ aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault());
+ return;
+ }
+
+ // Step 4. Return specifierKey.
+ aRetVal = aSpecifierKey;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map
+static UniquePtr<SpecifierMap> SortAndNormalizeSpecifierMap(
+ JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
+ const ReportWarningHelper& aWarning) {
+ // Step 1. Let normalized be an empty ordered map.
+ UniquePtr<SpecifierMap> normalized = MakeUnique<SpecifierMap>();
+
+ JS::Rooted<JS::IdVector> specifierKeys(aCx, JS::IdVector(aCx));
+ if (!JS_Enumerate(aCx, aOriginalMap, &specifierKeys)) {
+ return nullptr;
+ }
+
+ // Step 2. For each specifierKey → value of originalMap,
+ for (size_t i = 0; i < specifierKeys.length(); i++) {
+ const JS::RootedId specifierId(aCx, specifierKeys[i]);
+ nsAutoJSString specifierKey;
+ NS_ENSURE_TRUE(specifierKey.init(aCx, specifierId), nullptr);
+
+ // Step 2.1. Let normalizedSpecifierKey be the result of normalizing a
+ // specifier key given specifierKey and baseURL.
+ nsString normalizedSpecifierKey;
+ NormalizeSpecifierKey(specifierKey, aBaseURL, aWarning,
+ normalizedSpecifierKey);
+
+ // Step 2.2. If normalizedSpecifierKey is null, then continue.
+ if (normalizedSpecifierKey.IsEmpty()) {
+ continue;
+ }
+
+ JS::RootedValue idVal(aCx);
+ NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, specifierId, &idVal),
+ nullptr);
+ // Step 2.3. If value is not a string, then:
+ if (!idVal.isString()) {
+ // Step 2.3.1. The user agent may report a warning to the console
+ // indicating that addresses need to be strings.
+ aWarning.Report("ImportMapAddressesNotStrings");
+
+ // Step 2.3.2. Set normalized[normalizedSpecifierKey] to null.
+ normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
+
+ // Step 2.3.3. Continue.
+ continue;
+ }
+
+ nsAutoJSString value;
+ NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr);
+
+ // Step 2.4. Let addressURL be the result of resolving a URL-like module
+ // specifier given value and baseURL.
+ auto parseResult = ResolveURLLikeModuleSpecifier(value, aBaseURL);
+
+ // Step 2.5. If addressURL is null, then:
+ if (parseResult.isErr()) {
+ // Step 2.5.1. The user agent may report a warning to the console
+ // indicating that the address was invalid.
+ AutoTArray<nsString, 1> params;
+ params.AppendElement(value);
+ aWarning.Report("ImportMapInvalidAddress", params);
+
+ // Step 2.5.2. Set normalized[normalizedSpecifierKey] to null.
+ normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
+
+ // Step 2.5.3. Continue.
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> addressURL = parseResult.unwrap();
+ nsCString address = addressURL->GetSpecOrDefault();
+ // Step 2.6. If specifierKey ends with U+002F (/), and the serialization
+ // of addressURL does not end with U+002F (/), then:
+ if (StringEndsWith(specifierKey, u"/"_ns) &&
+ !StringEndsWith(address, "/"_ns)) {
+ // Step 2.6.1. The user agent may report a warning to the console
+ // indicating that an invalid address was given for the specifier key
+ // specifierKey; since specifierKey ends with a slash, the address needs
+ // to as well.
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(specifierKey);
+ params.AppendElement(NS_ConvertUTF8toUTF16(address));
+ aWarning.Report("ImportMapAddressNotEndsWithSlash", params);
+
+ // Step 2.6.2. Set normalized[normalizedSpecifierKey] to null.
+ normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
+
+ // Step 2.6.3. Continue.
+ continue;
+ }
+
+ LOG(("ImportMap::SortAndNormalizeSpecifierMap {%s, %s}",
+ NS_ConvertUTF16toUTF8(normalizedSpecifierKey).get(),
+ addressURL->GetSpecOrDefault().get()));
+
+ // Step 2.7. Set normalized[normalizedSpecifierKey] to addressURL.
+ normalized->insert_or_assign(normalizedSpecifierKey, addressURL);
+ }
+
+ // Step 3: Return the result of sorting normalized, with an entry a being
+ // less than an entry b if b’s key is code unit less than a’s key.
+ //
+ // Impl note: The sorting is done when inserting the entry.
+ return normalized;
+}
+
+// Check if it's a map defined in
+// https://infra.spec.whatwg.org/#ordered-map
+//
+// If it is, *aIsMap will be set to true.
+static bool IsMapObject(JSContext* aCx, JS::HandleValue aMapVal, bool* aIsMap) {
+ MOZ_ASSERT(aIsMap);
+
+ *aIsMap = false;
+ if (!aMapVal.isObject()) {
+ return true;
+ }
+
+ bool isArray;
+ if (!IsArrayObject(aCx, aMapVal, &isArray)) {
+ return false;
+ }
+
+ *aIsMap = !isArray;
+ return true;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes
+static UniquePtr<ScopeMap> SortAndNormalizeScopes(
+ JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
+ const ReportWarningHelper& aWarning) {
+ JS::Rooted<JS::IdVector> scopeKeys(aCx, JS::IdVector(aCx));
+ if (!JS_Enumerate(aCx, aOriginalMap, &scopeKeys)) {
+ return nullptr;
+ }
+
+ // Step 1. Let normalized be an empty map.
+ UniquePtr<ScopeMap> normalized = MakeUnique<ScopeMap>();
+
+ // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap,
+ for (size_t i = 0; i < scopeKeys.length(); i++) {
+ const JS::RootedId scopeKey(aCx, scopeKeys[i]);
+ nsAutoJSString scopePrefix;
+ NS_ENSURE_TRUE(scopePrefix.init(aCx, scopeKey), nullptr);
+
+ // Step 2.1. If potentialSpecifierMap is not an ordered map, then throw a
+ // TypeError indicating that the value of the scope with prefix scopePrefix
+ // needs to be a JSON object.
+ JS::RootedValue mapVal(aCx);
+ NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, scopeKey, &mapVal),
+ nullptr);
+
+ bool isMap;
+ if (!IsMapObject(aCx, mapVal, &isMap)) {
+ return nullptr;
+ }
+ if (!isMap) {
+ const char16_t* scope = scopePrefix.get();
+ JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, scope);
+ return nullptr;
+ }
+
+ // Step 2.2. Let scopePrefixURL be the result of URL parsing scopePrefix
+ // with baseURL.
+ nsCOMPtr<nsIURI> scopePrefixURL;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopePrefixURL), scopePrefix,
+ nullptr, aBaseURL);
+
+ // Step 2.3. If scopePrefixURL is failure, then:
+ if (NS_FAILED(rv)) {
+ // Step 2.3.1. The user agent may report a warning to the console that
+ // the scope prefix URL was not parseable.
+ AutoTArray<nsString, 1> params;
+ params.AppendElement(scopePrefix);
+ aWarning.Report("ImportMapScopePrefixNotParseable", params);
+
+ // Step 2.3.2. Continue.
+ continue;
+ }
+
+ // Step 2.4. Let normalizedScopePrefix be the serialization of
+ // scopePrefixURL.
+ nsCString normalizedScopePrefix = scopePrefixURL->GetSpecOrDefault();
+
+ // Step 2.5. Set normalized[normalizedScopePrefix] to the result of sorting
+ // and normalizing a specifier map given potentialSpecifierMap and baseURL.
+ JS::RootedObject potentialSpecifierMap(aCx, &mapVal.toObject());
+ UniquePtr<SpecifierMap> specifierMap = SortAndNormalizeSpecifierMap(
+ aCx, potentialSpecifierMap, aBaseURL, aWarning);
+ if (!specifierMap) {
+ return nullptr;
+ }
+
+ normalized->insert_or_assign(normalizedScopePrefix,
+ std::move(specifierMap));
+ }
+
+ // Step 3. Return the result of sorting in descending order normalized, with
+ // an entry a being less than an entry b if a's key is code unit less than b's
+ // key.
+ //
+ // Impl note: The sorting is done when inserting the entry.
+ return normalized;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
+// static
+UniquePtr<ImportMap> ImportMap::ParseString(
+ JSContext* aCx, SourceText<char16_t>& aInput, nsIURI* aBaseURL,
+ const ReportWarningHelper& aWarning) {
+ // Step 1. Let parsed be the result of parsing JSON into Infra values given
+ // input.
+ JS::Rooted<JS::Value> parsedVal(aCx);
+ if (!JS_ParseJSON(aCx, aInput.get(), aInput.length(), &parsedVal)) {
+ NS_WARNING("Parsing Import map string failed");
+
+ // If JS_ParseJSON fails we check if it throws a SyntaxError.
+ // If so we update the error message from JSON parser to make it more clear
+ // that the parsing of import map has failed.
+ MOZ_ASSERT(JS_IsExceptionPending(aCx));
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(exn.isObject());
+ JS::Rooted<JSObject*> obj(aCx, &exn.toObject());
+ JSErrorReport* err = JS_ErrorFromException(aCx, obj);
+ if (err->exnType == JSEXN_SYNTAXERR) {
+ JS_ClearPendingException(aCx);
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_IMPORT_MAPS_PARSE_FAILED,
+ err->message().c_str());
+ }
+
+ return nullptr;
+ }
+
+ // Step 2. If parsed is not an ordered map, then throw a TypeError indicating
+ // that the top-level value needs to be a JSON object.
+ bool isMap;
+ if (!IsMapObject(aCx, parsedVal, &isMap)) {
+ return nullptr;
+ }
+ if (!isMap) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_IMPORT_MAPS_NOT_A_MAP);
+ return nullptr;
+ }
+
+ JS::RootedObject parsedObj(aCx, &parsedVal.toObject());
+ JS::RootedValue importsVal(aCx);
+ if (!JS_GetProperty(aCx, parsedObj, "imports", &importsVal)) {
+ return nullptr;
+ }
+
+ // Step 3. Let sortedAndNormalizedImports be an empty ordered map.
+ //
+ // Impl note: If parsed["imports"] doesn't exist, we will allocate
+ // sortedAndNormalizedImports to an empty map in Step 8 below.
+ UniquePtr<SpecifierMap> sortedAndNormalizedImports = nullptr;
+
+ // Step 4. If parsed["imports"] exists, then:
+ if (!importsVal.isUndefined()) {
+ // Step 4.1. If parsed["imports"] is not an ordered map, then throw a
+ // TypeError indicating that the "imports" top-level key needs to be a JSON
+ // object.
+ bool isMap;
+ if (!IsMapObject(aCx, importsVal, &isMap)) {
+ return nullptr;
+ }
+ if (!isMap) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP);
+ return nullptr;
+ }
+
+ // Step 4.2. Set sortedAndNormalizedImports to the result of sorting and
+ // normalizing a module specifier map given parsed["imports"] and baseURL.
+ JS::RootedObject importsObj(aCx, &importsVal.toObject());
+ sortedAndNormalizedImports =
+ SortAndNormalizeSpecifierMap(aCx, importsObj, aBaseURL, aWarning);
+ if (!sortedAndNormalizedImports) {
+ return nullptr;
+ }
+ }
+
+ JS::RootedValue scopesVal(aCx);
+ if (!JS_GetProperty(aCx, parsedObj, "scopes", &scopesVal)) {
+ return nullptr;
+ }
+
+ // Step 5. Let sortedAndNormalizedScopes be an empty ordered map.
+ //
+ // Impl note: If parsed["scopes"] doesn't exist, we will allocate
+ // sortedAndNormalizedScopes to an empty map in Step 8 below.
+ UniquePtr<ScopeMap> sortedAndNormalizedScopes = nullptr;
+
+ // Step 6. If parsed["scopes"] exists, then:
+ if (!scopesVal.isUndefined()) {
+ // Step 6.1. If parsed["scopes"] is not an ordered map, then throw a
+ // TypeError indicating that the "scopes" top-level key needs to be a JSON
+ // object.
+ bool isMap;
+ if (!IsMapObject(aCx, scopesVal, &isMap)) {
+ return nullptr;
+ }
+ if (!isMap) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP);
+ return nullptr;
+ }
+
+ // Step 6.2. Set sortedAndNormalizedScopes to the result of sorting and
+ // normalizing scopes given parsed["scopes"] and baseURL.
+ JS::RootedObject scopesObj(aCx, &scopesVal.toObject());
+ sortedAndNormalizedScopes =
+ SortAndNormalizeScopes(aCx, scopesObj, aBaseURL, aWarning);
+ if (!sortedAndNormalizedScopes) {
+ return nullptr;
+ }
+ }
+
+ // Step 7. If parsed’s keys contains any items besides "imports" or
+ // "scopes", then the user agent should report a warning to the console
+ // indicating that an invalid top-level key was present in the import map.
+ JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
+ if (!JS_Enumerate(aCx, parsedObj, &keys)) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < keys.length(); i++) {
+ const JS::RootedId key(aCx, keys[i]);
+ nsAutoJSString val;
+ NS_ENSURE_TRUE(val.init(aCx, key), nullptr);
+ if (val.EqualsLiteral("imports") || val.EqualsLiteral("scopes")) {
+ continue;
+ }
+
+ AutoTArray<nsString, 1> params;
+ params.AppendElement(val);
+ aWarning.Report("ImportMapInvalidTopLevelKey", params);
+ }
+
+ // Impl note: Create empty maps for sortedAndNormalizedImports and
+ // sortedAndNormalizedImports if they aren't allocated.
+ if (!sortedAndNormalizedImports) {
+ sortedAndNormalizedImports = MakeUnique<SpecifierMap>();
+ }
+ if (!sortedAndNormalizedScopes) {
+ sortedAndNormalizedScopes = MakeUnique<ScopeMap>();
+ }
+
+ // Step 8. Return an import map whose imports are
+ // sortedAndNormalizedImports and whose scopes scopes are
+ // sortedAndNormalizedScopes.
+ return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports),
+ std::move(sortedAndNormalizedScopes));
+}
+
+// https://url.spec.whatwg.org/#is-special
+static bool IsSpecialScheme(nsIURI* aURI) {
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ return scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") ||
+ scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") ||
+ scheme.EqualsLiteral("ws") || scheme.EqualsLiteral("wss");
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match
+static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch(
+ nsString& aNormalizedSpecifier, nsIURI* aAsURL,
+ const SpecifierMap* aSpecifierMap) {
+ // Step 1. For each specifierKey → resolutionResult of specifierMap,
+ for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) {
+ nsAutoString specifier{aNormalizedSpecifier};
+ nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString();
+
+ // Step 1.1. If specifierKey is normalizedSpecifier, then:
+ if (specifierKey.Equals(aNormalizedSpecifier)) {
+ // Step 1.1.1. If resolutionResult is null, then throw a TypeError
+ // indicating that resolution of specifierKey was blocked by a null entry.
+ // This will terminate the entire resolve a module specifier algorithm,
+ // without any further fallbacks.
+ if (!resolutionResult) {
+ LOG(
+ ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
+ "specifierKey: %s, but resolution is null.",
+ NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
+ NS_ConvertUTF16toUTF8(specifierKey).get()));
+ return Err(ResolveError::BlockedByNullEntry);
+ }
+
+ // Step 1.1.2. Assert: resolutionResult is a URL.
+ MOZ_ASSERT(resolutionResult);
+
+ // Step 1.1.3. Return resolutionResult.
+ return resolutionResult;
+ }
+
+ // Step 1.2. If all of the following are true:
+ // specifierKey ends with U+002F (/),
+ // specifierKey is a code unit prefix of normalizedSpecifier, and
+ // either asURL is null, or asURL is special
+ if (StringEndsWith(specifierKey, u"/"_ns) &&
+ StringBeginsWith(aNormalizedSpecifier, specifierKey) &&
+ (!aAsURL || IsSpecialScheme(aAsURL))) {
+ // Step 1.2.1. If resolutionResult is null, then throw a TypeError
+ // indicating that resolution of specifierKey was blocked by a null entry.
+ // This will terminate the entire resolve a module specifier algorithm,
+ // without any further fallbacks.
+ if (!resolutionResult) {
+ LOG(
+ ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
+ "specifierKey: %s, but resolution is null.",
+ NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
+ NS_ConvertUTF16toUTF8(specifierKey).get()));
+ return Err(ResolveError::BlockedByNullEntry);
+ }
+
+ // Step 1.2.2. Assert: resolutionResult is a URL.
+ MOZ_ASSERT(resolutionResult);
+
+ // Step 1.2.3. Let afterPrefix be the portion of normalizedSpecifier after
+ // the initial specifierKey prefix.
+ nsAutoString afterPrefix(
+ Substring(aNormalizedSpecifier, specifierKey.Length()));
+
+ // Step 1.2.4. Assert: resolutionResult, serialized, ends with U+002F (/),
+ // as enforced during parsing
+ MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(), "/"_ns));
+
+ // Step 1.2.5. Let url be the result of URL parsing afterPrefix with
+ // resolutionResult.
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), afterPrefix, nullptr,
+ resolutionResult);
+
+ // Step 1.2.6. If url is failure, then throw a TypeError indicating that
+ // resolution of normalizedSpecifier was blocked since the afterPrefix
+ // portion could not be URL-parsed relative to the resolutionResult mapped
+ // to by the specifierKey prefix.
+ //
+ // This will terminate the entire resolve a module specifier algorithm,
+ // without any further fallbacks.
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
+ "specifierKey: %s, resolutionResult: %s, afterPrefix: %s, "
+ "but URL is not parsable.",
+ NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
+ NS_ConvertUTF16toUTF8(specifierKey).get(),
+ resolutionResult->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(afterPrefix).get()));
+ return Err(ResolveError::BlockedByAfterPrefix);
+ }
+
+ // Step 1.2.7. Assert: url is a URL.
+ MOZ_ASSERT(url);
+
+ // Step 1.2.8. If the serialization of resolutionResult is not a code unit
+ // prefix of the serialization of url, then throw a TypeError indicating
+ // that resolution of normalizedSpecifier was blocked due to it
+ // backtracking above its prefix specifierKey.
+ //
+ // This will terminate the entire resolve a module specifier algorithm,
+ // without any further fallbacks.
+ if (!StringBeginsWith(url->GetSpecOrDefault(),
+ resolutionResult->GetSpecOrDefault())) {
+ LOG(
+ ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
+ "specifierKey: %s, "
+ "url %s does not start with resolutionResult %s.",
+ NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
+ NS_ConvertUTF16toUTF8(specifierKey).get(),
+ url->GetSpecOrDefault().get(),
+ resolutionResult->GetSpecOrDefault().get()));
+ return Err(ResolveError::BlockedByBacktrackingPrefix);
+ }
+
+ // Step 1.2.9. Return url.
+ return std::move(url);
+ }
+ }
+
+ // Step 2. Return null.
+ return nsCOMPtr<nsIURI>(nullptr);
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
+// static
+ResolveResult ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap,
+ ScriptLoaderInterface* aLoader,
+ LoadedScript* aScript,
+ const nsAString& aSpecifier) {
+ LOG(("ImportMap::ResolveModuleSpecifier specifier: %s",
+ NS_ConvertUTF16toUTF8(aSpecifier).get()));
+ nsCOMPtr<nsIURI> baseURL;
+ if (aScript) {
+ baseURL = aScript->BaseURL();
+ } else {
+ baseURL = aLoader->GetBaseURI();
+ }
+
+ // Step 7. Let asURL be the result of resolving a URL-like module specifier
+ // given specifier and baseURL.
+ //
+ // Impl note: Step 6 is done below if aImportMap exists.
+ auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifier, baseURL);
+ nsCOMPtr<nsIURI> asURL;
+ if (parseResult.isOk()) {
+ asURL = parseResult.unwrap();
+ }
+
+ if (aImportMap) {
+ // Step 6. Let baseURLString be baseURL, serialized.
+ nsCString baseURLString = baseURL->GetSpecOrDefault();
+
+ // Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL
+ // is non-null; otherwise, specifier.
+ nsAutoString normalizedSpecifier =
+ asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault())
+ : nsAutoString{aSpecifier};
+
+ // Step 9. For each scopePrefix → scopeImports of importMap’s scopes,
+ for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) {
+ // Step 9.1. If scopePrefix is baseURLString, or if scopePrefix ends with
+ // U+002F (/) and scopePrefix is a code unit prefix of baseURLString,
+ // then:
+ if (scopePrefix.Equals(baseURLString) ||
+ (StringEndsWith(scopePrefix, "/"_ns) &&
+ StringBeginsWith(baseURLString, scopePrefix))) {
+ // Step 9.1.1. Let scopeImportsMatch be the result of resolving an
+ // imports match given normalizedSpecifier, asURL, and scopeImports.
+ auto result =
+ ResolveImportsMatch(normalizedSpecifier, asURL, scopeImports.get());
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ nsCOMPtr<nsIURI> scopeImportsMatch = result.unwrap();
+ // Step 9.1.2. If scopeImportsMatch is not null, then return
+ // scopeImportsMatch.
+ if (scopeImportsMatch) {
+ LOG((
+ "ImportMap::ResolveModuleSpecifier returns scopeImportsMatch: %s",
+ scopeImportsMatch->GetSpecOrDefault().get()));
+ return WrapNotNull(scopeImportsMatch);
+ }
+ }
+ }
+
+ // Step 10. Let topLevelImportsMatch be the result of resolving an imports
+ // match given normalizedSpecifier, asURL, and importMap’s imports.
+ auto result = ResolveImportsMatch(normalizedSpecifier, asURL,
+ aImportMap->mImports.get());
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+ nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap();
+
+ // Step 11. If topLevelImportsMatch is not null, then return
+ // topLevelImportsMatch.
+ if (topLevelImportsMatch) {
+ LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s",
+ topLevelImportsMatch->GetSpecOrDefault().get()));
+ return WrapNotNull(topLevelImportsMatch);
+ }
+ }
+
+ // Step 12. At this point, the specifier was able to be turned in to a URL,
+ // but it wasn’t remapped to anything by importMap. If asURL is not null, then
+ // return asURL.
+ if (asURL) {
+ LOG(("ImportMap::ResolveModuleSpecifier returns asURL: %s",
+ asURL->GetSpecOrDefault().get()));
+ return WrapNotNull(asURL);
+ }
+
+ // Step 13. Throw a TypeError indicating that specifier was a bare specifier,
+ // but was not remapped to anything by importMap.
+ if (parseResult.unwrapErr() != ResolveError::FailureMayBeBare) {
+ // We may have failed to parse a non-bare specifier for another reason.
+ return Err(ResolveError::Failure);
+ }
+
+ return Err(ResolveError::InvalidBareSpecifier);
+}
+
+#undef LOG
+#undef LOG_ENABLED
+} // namespace JS::loader
diff --git a/js/loader/ImportMap.h b/js/loader/ImportMap.h
new file mode 100644
index 0000000000..d304a52856
--- /dev/null
+++ b/js/loader/ImportMap.h
@@ -0,0 +1,118 @@
+/* -*- 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 js_loader_ImportMap_h
+#define js_loader_ImportMap_h
+
+#include <functional>
+#include <map>
+
+#include "js/SourceText.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "ResolveResult.h"
+
+struct JSContext;
+class nsIScriptElement;
+class nsIURI;
+
+namespace JS::loader {
+class LoadedScript;
+class ScriptLoaderInterface;
+class ScriptLoadRequest;
+
+/**
+ * A helper class to report warning to ScriptLoaderInterface.
+ */
+class ReportWarningHelper {
+ public:
+ ReportWarningHelper(ScriptLoaderInterface* aLoader,
+ ScriptLoadRequest* aRequest)
+ : mLoader(aLoader), mRequest(aRequest) {}
+
+ void Report(const char* aMessageName,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
+
+ private:
+ RefPtr<ScriptLoaderInterface> mLoader;
+ ScriptLoadRequest* mRequest;
+};
+
+// Specifier map from import maps.
+// https://html.spec.whatwg.org/multipage/webappapis.html#module-specifier-map
+using SpecifierMap =
+ std::map<nsString, nsCOMPtr<nsIURI>, std::greater<nsString>>;
+
+// Scope map from import maps.
+// https://html.spec.whatwg.org/multipage/webappapis.html#concept-import-map-scopes
+using ScopeMap = std::map<nsCString, mozilla::UniquePtr<SpecifierMap>,
+ std::greater<nsCString>>;
+
+/**
+ * Implementation of Import maps.
+ * https://html.spec.whatwg.org/multipage/webappapis.html#import-maps
+ */
+class ImportMap {
+ public:
+ ImportMap(mozilla::UniquePtr<SpecifierMap> aImports,
+ mozilla::UniquePtr<ScopeMap> aScopes)
+ : mImports(std::move(aImports)), mScopes(std::move(aScopes)) {}
+
+ /**
+ * Parse the JSON string from the Import map script.
+ * This function will throw a TypeError if there's any invalid key or value in
+ * the JSON text according to the spec.
+ *
+ * https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
+ */
+ static mozilla::UniquePtr<ImportMap> ParseString(
+ JSContext* aCx, JS::SourceText<char16_t>& aInput, nsIURI* aBaseURL,
+ const ReportWarningHelper& aWarning);
+
+ /**
+ * This implements "Resolve a module specifier" algorithm defined in the
+ * Import maps spec.
+ *
+ * See
+ * https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
+ *
+ * Impl note: According to the spec, if the specifier cannot be resolved, this
+ * method will throw a TypeError(Step 13). But the tricky part is when
+ * creating a module script,
+ * see
+ * https://html.spec.whatwg.org/multipage/webappapis.html#validate-requested-module-specifiers
+ * If the resolving failed, it shall catch the exception and set to the
+ * script's parse error.
+ * For implementation we return a ResolveResult here, and the callers will
+ * need to convert the result to a TypeError if it fails.
+ */
+ static ResolveResult ResolveModuleSpecifier(ImportMap* aImportMap,
+ ScriptLoaderInterface* aLoader,
+ LoadedScript* aScript,
+ const nsAString& aSpecifier);
+
+ // Logging
+ static mozilla::LazyLogModule gImportMapLog;
+
+ private:
+ /**
+ * https://html.spec.whatwg.org/multipage/webappapis.html#import-map-processing-model
+ *
+ * Formally, an import map is a struct with two items:
+ * 1. imports, a module specifier map, and
+ * 2. scopes, an ordered map of URLs to module specifier maps.
+ */
+ mozilla::UniquePtr<SpecifierMap> mImports;
+ mozilla::UniquePtr<ScopeMap> mScopes;
+};
+
+} // namespace JS::loader
+
+#endif // js_loader_ImportMap_h
diff --git a/js/loader/LoadContextBase.cpp b/js/loader/LoadContextBase.cpp
new file mode 100644
index 0000000000..980da9b510
--- /dev/null
+++ b/js/loader/LoadContextBase.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "mozilla/dom/ScriptLoadContext.h"
+#include "mozilla/loader/ComponentModuleLoader.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "js/loader/LoadContextBase.h"
+#include "js/loader/ScriptLoadRequest.h"
+
+namespace JS::loader {
+
+////////////////////////////////////////////////////////////////
+// LoadContextBase
+////////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadContextBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadContextBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadContextBase)
+
+NS_IMPL_CYCLE_COLLECTION(LoadContextBase, mRequest)
+
+LoadContextBase::LoadContextBase(ContextKind kind) : mKind(kind) {}
+
+void LoadContextBase::SetRequest(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(!mRequest);
+ mRequest = aRequest;
+}
+
+void LoadContextBase::GetProfilerLabel(nsACString& aOutString) {
+ aOutString.Append("Unknown Script Element");
+}
+
+mozilla::dom::ScriptLoadContext* LoadContextBase::AsWindowContext() {
+ MOZ_ASSERT(IsWindowContext());
+ return static_cast<mozilla::dom::ScriptLoadContext*>(this);
+}
+
+mozilla::loader::ComponentLoadContext* LoadContextBase::AsComponentContext() {
+ MOZ_ASSERT(IsComponentContext());
+ return static_cast<mozilla::loader::ComponentLoadContext*>(this);
+}
+
+mozilla::dom::WorkerLoadContext* LoadContextBase::AsWorkerContext() {
+ MOZ_ASSERT(IsWorkerContext());
+ return static_cast<mozilla::dom::WorkerLoadContext*>(this);
+}
+
+} // namespace JS::loader
diff --git a/js/loader/LoadContextBase.h b/js/loader/LoadContextBase.h
new file mode 100644
index 0000000000..3753209dd4
--- /dev/null
+++ b/js/loader/LoadContextBase.h
@@ -0,0 +1,69 @@
+/* -*- 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 js_loader_BaseLoadContext_h
+#define js_loader_BaseLoadContext_h
+
+#include "js/loader/ScriptLoadRequest.h"
+#include "nsIStringBundle.h"
+
+namespace mozilla::dom {
+class ScriptLoadContext;
+class WorkerLoadContext;
+} // namespace mozilla::dom
+
+namespace mozilla::loader {
+class ComponentLoadContext;
+}
+
+namespace JS::loader {
+
+class ScriptLoadRequest;
+
+/*
+ * LoadContextBase
+ *
+ * LoadContexts augment the loading of a ScriptLoadRequest. This class
+ * is used as a base for all LoadContexts, and provides shared functionality.
+ *
+ */
+
+enum class ContextKind { Window, Component, Worker };
+
+class LoadContextBase : public nsISupports {
+ private:
+ ContextKind mKind;
+
+ protected:
+ virtual ~LoadContextBase() = default;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(LoadContextBase)
+
+ explicit LoadContextBase(ContextKind kind);
+
+ void SetRequest(JS::loader::ScriptLoadRequest* aRequest);
+
+ // Used to output a string for the Gecko Profiler.
+ virtual void GetProfilerLabel(nsACString& aOutString);
+
+ // Casting to the different contexts
+ bool IsWindowContext() const { return mKind == ContextKind::Window; }
+ mozilla::dom::ScriptLoadContext* AsWindowContext();
+
+ bool IsComponentContext() const { return mKind == ContextKind::Component; }
+ mozilla::loader::ComponentLoadContext* AsComponentContext();
+
+ bool IsWorkerContext() const { return mKind == ContextKind::Worker; }
+ mozilla::dom::WorkerLoadContext* AsWorkerContext();
+
+ RefPtr<JS::loader::ScriptLoadRequest> mRequest;
+};
+
+} // namespace JS::loader
+
+#endif // js_loader_BaseLoadContext_h
diff --git a/js/loader/LoadedScript.cpp b/js/loader/LoadedScript.cpp
new file mode 100644
index 0000000000..d117ab55b4
--- /dev/null
+++ b/js/loader/LoadedScript.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "LoadedScript.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+
+#include "jsfriendapi.h"
+#include "js/Modules.h" // JS::{Get,Set}ModulePrivate
+
+namespace JS::loader {
+
+//////////////////////////////////////////////////////////////
+// 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() { mozilla::DropJSObjects(this); }
+
+void LoadedScript::AssociateWithScript(JSScript* aScript) {
+ // Set a JSScript's private value to point to this object. The JS engine will
+ // increment our reference count by calling HostAddRefTopLevelScript(). This
+ // is decremented by HostReleaseTopLevelScript() below when the JSScript dies.
+
+ MOZ_ASSERT(JS::GetScriptPrivate(aScript).isUndefined());
+ JS::SetScriptPrivate(aScript, JS::PrivateValue(this));
+}
+
+inline void CheckModuleScriptPrivate(LoadedScript* script,
+ const JS::Value& aPrivate) {
+#ifdef DEBUG
+ if (script->IsModuleScript()) {
+ JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet();
+ MOZ_ASSERT_IF(module, JS::GetModulePrivate(module) == aPrivate);
+ }
+#endif
+}
+
+void HostAddRefTopLevelScript(const JS::Value& aPrivate) {
+ // Increment the reference count of a LoadedScript object that is now pointed
+ // to by a JSScript. The reference count is decremented by
+ // HostReleaseTopLevelScript() below.
+
+ auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+ CheckModuleScriptPrivate(script, aPrivate);
+ script->AddRef();
+}
+
+void HostReleaseTopLevelScript(const JS::Value& aPrivate) {
+ // Decrement the reference count of a LoadedScript object that was pointed to
+ // by a JSScript. The reference count was originally incremented by
+ // HostAddRefTopLevelScript() above.
+
+ auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+ CheckModuleScriptPrivate(script, aPrivate);
+ script->Release();
+}
+
+//////////////////////////////////////////////////////////////
+// EventScript
+//////////////////////////////////////////////////////////////
+
+EventScript::EventScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eEvent, aFetchOptions, aBaseURL) {}
+
+//////////////////////////////////////////////////////////////
+// ClassicScript
+//////////////////////////////////////////////////////////////
+
+ClassicScript::ClassicScript(ScriptFetchOptions* aFetchOptions,
+ nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eClassic, aFetchOptions, aBaseURL) {}
+
+//////////////////////////////////////////////////////////////
+// ModuleScript
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
+NS_INTERFACE_MAP_END_INHERITING(LoadedScript)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
+
+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_INHERITED(ModuleScript, LoadedScript)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+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_ADDREF_INHERITED(ModuleScript, LoadedScript)
+NS_IMPL_RELEASE_INHERITED(ModuleScript, LoadedScript)
+
+ModuleScript::ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eModule, aFetchOptions, aBaseURL),
+ mDebuggerDataInitialized(false) {
+ MOZ_ASSERT(!ModuleRecord());
+ MOZ_ASSERT(!HasParseError());
+ MOZ_ASSERT(!HasErrorToRethrow());
+}
+
+void ModuleScript::Shutdown() {
+ if (mModuleRecord) {
+ JS::ClearModuleEnvironment(mModuleRecord);
+ }
+
+ UnlinkModuleRecord();
+}
+
+void ModuleScript::UnlinkModuleRecord() {
+ // Remove the module record's pointer to this object if present and decrement
+ // our reference count. The reference is added by SetModuleRecord() below.
+ //
+ // This takes care not to trigger gray unmarking because this takes a lot of
+ // time when we're tearing down the entire page. This is safe because we are
+ // only writing undefined into the module private, so it won't create any
+ // black-gray edges.
+ if (mModuleRecord) {
+ JSObject* module = mModuleRecord.unbarrieredGet();
+ MOZ_ASSERT(JS::GetModulePrivate(module).toPrivate() == this);
+ JS::ClearModulePrivate(module);
+ mModuleRecord = nullptr;
+ }
+}
+
+ModuleScript::~ModuleScript() {
+ // The object may be destroyed without being unlinked first.
+ UnlinkModuleRecord();
+}
+
+void ModuleScript::SetModuleRecord(JS::Handle<JSObject*> aModuleRecord) {
+ MOZ_ASSERT(!mModuleRecord);
+ MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError());
+ MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow());
+
+ mModuleRecord = aModuleRecord;
+
+ // Make module's host defined field point to this object. The JS engine will
+ // increment our reference count by calling HostAddRefTopLevelScript(). This
+ // is decremented when the field is cleared in UnlinkModuleRecord() above or
+ // when the module record dies.
+ MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).isUndefined());
+ JS::SetModulePrivate(mModuleRecord, JS::PrivateValue(this));
+
+ mozilla::HoldJSObjects(this);
+}
+
+void ModuleScript::SetParseError(const JS::Value& aError) {
+ MOZ_ASSERT(!aError.isUndefined());
+ MOZ_ASSERT(!HasParseError());
+ MOZ_ASSERT(!HasErrorToRethrow());
+
+ UnlinkModuleRecord();
+ mParseError = aError;
+ mozilla::HoldJSObjects(this);
+}
+
+void ModuleScript::SetErrorToRethrow(const JS::Value& aError) {
+ MOZ_ASSERT(!aError.isUndefined());
+
+ // This is only called after SetModuleRecord() or SetParseError() so we don't
+ // need to call HoldJSObjects() here.
+ MOZ_ASSERT(ModuleRecord() || HasParseError());
+
+ mErrorToRethrow = aError;
+}
+
+void ModuleScript::SetDebuggerDataInitialized() {
+ MOZ_ASSERT(ModuleRecord());
+ MOZ_ASSERT(!mDebuggerDataInitialized);
+
+ mDebuggerDataInitialized = true;
+}
+
+} // namespace JS::loader
diff --git a/js/loader/LoadedScript.h b/js/loader/LoadedScript.h
new file mode 100644
index 0000000000..a61884f4a2
--- /dev/null
+++ b/js/loader/LoadedScript.h
@@ -0,0 +1,119 @@
+/* -*- 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 js_loader_LoadedScript_h
+#define js_loader_LoadedScript_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "jsapi.h"
+#include "ScriptLoadRequest.h"
+
+class nsIURI;
+
+namespace JS::loader {
+
+void HostAddRefTopLevelScript(const JS::Value& aPrivate);
+void HostReleaseTopLevelScript(const JS::Value& aPrivate);
+
+class ClassicScript;
+class ModuleScript;
+class EventScript;
+
+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; }
+ bool IsEventScript() const { return mKind == ScriptKind::eEvent; }
+
+ inline ClassicScript* AsClassicScript();
+ inline ModuleScript* AsModuleScript();
+ inline EventScript* AsEventScript();
+
+ // Used to propagate Fetch Options to child modules
+ ScriptFetchOptions* GetFetchOptions() 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);
+};
+
+class EventScript final : public LoadedScript {
+ ~EventScript() = default;
+
+ public:
+ EventScript(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 mDebuggerDataInitialized;
+
+ ~ModuleScript();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleScript,
+ LoadedScript)
+
+ ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
+
+ void SetModuleRecord(JS::Handle<JSObject*> aModuleRecord);
+ void SetParseError(const JS::Value& aError);
+ void SetErrorToRethrow(const JS::Value& aError);
+ void SetDebuggerDataInitialized();
+
+ JSObject* ModuleRecord() const { return mModuleRecord; }
+
+ 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 DebuggerDataInitialized() const { return mDebuggerDataInitialized; }
+
+ void Shutdown();
+
+ void UnlinkModuleRecord();
+
+ friend void CheckModuleScriptPrivate(LoadedScript*, 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 JS::loader
+
+#endif // js_loader_LoadedScript_h
diff --git a/js/loader/ModuleLoadRequest.cpp b/js/loader/ModuleLoadRequest.cpp
new file mode 100644
index 0000000000..c86b91b34d
--- /dev/null
+++ b/js/loader/ModuleLoadRequest.cpp
@@ -0,0 +1,207 @@
+/* -*- 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 "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ScriptLoadContext.h"
+
+#include "LoadedScript.h"
+#include "LoadContextBase.h"
+#include "ModuleLoaderBase.h"
+
+namespace JS::loader {
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
+ args)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader, mModuleScript, mImports, mRootModule)
+ 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,
+ mRootModule)
+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)
+
+/* static */
+VisitedURLSet* ModuleLoadRequest::NewVisitedSetForTopLevelImport(nsIURI* aURI) {
+ auto set = new VisitedURLSet();
+ set->PutEntry(aURI);
+ return set;
+}
+
+ModuleLoadRequest::ModuleLoadRequest(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const mozilla::dom::SRIMetadata& aIntegrity, nsIURI* aReferrer,
+ LoadContextBase* aContext, bool aIsTopLevel, bool aIsDynamicImport,
+ ModuleLoaderBase* aLoader, VisitedURLSet* aVisitedSet,
+ ModuleLoadRequest* aRootModule)
+ : ScriptLoadRequest(ScriptKind::eModule, aURI, aFetchOptions, aIntegrity,
+ aReferrer, aContext),
+ mIsTopLevel(aIsTopLevel),
+ mIsDynamicImport(aIsDynamicImport),
+ mLoader(aLoader),
+ mRootModule(aRootModule),
+ mVisitedSet(aVisitedSet) {
+ MOZ_ASSERT(mLoader);
+}
+
+nsIGlobalObject* ModuleLoadRequest::GetGlobalObject() {
+ return mLoader->GetGlobalObject();
+}
+
+void ModuleLoadRequest::Cancel() {
+ ScriptLoadRequest::Cancel();
+ mModuleScript = nullptr;
+ CancelImports();
+ mReady.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+}
+
+void ModuleLoadRequest::CancelImports() {
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ mImports[i]->Cancel();
+ }
+}
+
+void ModuleLoadRequest::SetReady() {
+ // Mark a module as ready to execute. This means that this module and all it
+ // dependencies have had their source loaded, parsed as a module and the
+ // modules instantiated.
+ //
+ // The mReady promise is used to ensure that when all dependencies of a module
+ // have become ready, DependenciesLoaded is called on that module
+ // request. This is set up in StartFetchingModuleDependencies.
+
+#ifdef DEBUG
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ MOZ_ASSERT(mImports[i]->IsReadyToRun());
+ }
+#endif
+
+ ScriptLoadRequest::SetReady();
+ mReady.ResolveIfExists(true, __func__);
+}
+
+void ModuleLoadRequest::ModuleLoaded() {
+ // A module that was found to be marked as fetching in the module map has now
+ // been loaded.
+
+ LOG(("ScriptLoadRequest (%p): Module loaded", this));
+
+ mModuleScript = mLoader->GetFetchedModule(mURI);
+ if (!mModuleScript || mModuleScript->HasParseError()) {
+ ModuleErrored();
+ return;
+ }
+
+ mLoader->StartFetchingModuleDependencies(this);
+}
+
+void ModuleLoadRequest::ModuleErrored() {
+ if (IsCanceled()) {
+ return;
+ }
+
+ LOG(("ScriptLoadRequest (%p): Module errored", this));
+
+ CheckModuleDependenciesLoaded();
+ MOZ_ASSERT(!mModuleScript || mModuleScript->HasParseError());
+
+ CancelImports();
+ SetReady();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::DependenciesLoaded() {
+ if (IsCanceled()) {
+ return;
+ }
+
+ // The module and all of its dependencies have been successfully fetched and
+ // compiled.
+
+ LOG(("ScriptLoadRequest (%p): Module dependencies loaded", this));
+
+ MOZ_ASSERT(mModuleScript);
+
+ CheckModuleDependenciesLoaded();
+ SetReady();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::CheckModuleDependenciesLoaded() {
+ LOG(("ScriptLoadRequest (%p): Check dependencies loaded", this));
+
+ if (!mModuleScript || mModuleScript->HasParseError()) {
+ return;
+ }
+ for (const auto& childRequest : mImports) {
+ ModuleScript* childScript = childRequest->mModuleScript;
+ if (!childScript) {
+ mModuleScript = nullptr;
+ LOG(("ScriptLoadRequest (%p): %p failed (load error)", this,
+ childRequest.get()));
+ return;
+ }
+ }
+
+ LOG(("ScriptLoadRequest (%p): all ok", this));
+}
+
+void ModuleLoadRequest::LoadFailed() {
+ // We failed to load the source text or an error occurred unrelated to the
+ // content of the module (e.g. OOM).
+
+ LOG(("ScriptLoadRequest (%p): Module load failed", this));
+
+ if (IsCanceled()) {
+ return;
+ }
+
+ MOZ_ASSERT(!IsReadyToRun());
+ MOZ_ASSERT(!mModuleScript);
+
+ Cancel();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::LoadFinished() {
+ RefPtr<ModuleLoadRequest> request(this);
+ if (IsTopLevel() && IsDynamicImport()) {
+ mLoader->RemoveDynamicImport(request);
+ }
+
+ mLoader->OnModuleLoadComplete(request);
+}
+
+void ModuleLoadRequest::ClearDynamicImport() {
+ mDynamicReferencingPrivate = JS::UndefinedValue();
+ mDynamicSpecifier = nullptr;
+ mDynamicPromise = nullptr;
+}
+
+} // namespace JS::loader
diff --git a/js/loader/ModuleLoadRequest.h b/js/loader/ModuleLoadRequest.h
new file mode 100644
index 0000000000..249ca65a44
--- /dev/null
+++ b/js/loader/ModuleLoadRequest.h
@@ -0,0 +1,164 @@
+/* -*- 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 js_loader_ModuleLoadRequest_h
+#define js_loader_ModuleLoadRequest_h
+
+#include "LoadContextBase.h"
+#include "ScriptLoadRequest.h"
+#include "ModuleLoaderBase.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MozPromise.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "nsURIHashKey.h"
+#include "nsTHashtable.h"
+
+namespace JS::loader {
+
+class ModuleScript;
+class ModuleLoaderBase;
+
+// A reference counted set of URLs we have visited in the process of loading a
+// module graph.
+class VisitedURLSet : public nsTHashtable<nsURIHashKey> {
+ NS_INLINE_DECL_REFCOUNTING(VisitedURLSet)
+
+ private:
+ ~VisitedURLSet() = default;
+};
+
+// A load request for a module, created for every top level module script and
+// every module import. Load request can share an ModuleScript if there are
+// multiple imports of the same module.
+
+class ModuleLoadRequest final : public ScriptLoadRequest {
+ ~ModuleLoadRequest() = default;
+
+ ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete;
+ ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+ using SRIMetadata = mozilla::dom::SRIMetadata;
+
+ template <typename T>
+ using MozPromiseHolder = mozilla::MozPromiseHolder<T>;
+ using GenericPromise = mozilla::GenericPromise;
+
+ ModuleLoadRequest(nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer,
+ LoadContextBase* aContext, bool aIsTopLevel,
+ bool aIsDynamicImport, ModuleLoaderBase* aLoader,
+ VisitedURLSet* aVisitedSet, ModuleLoadRequest* aRootModule);
+
+ static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI);
+
+ bool IsTopLevel() const override { return mIsTopLevel; }
+
+ bool IsDynamicImport() const { return mIsDynamicImport; }
+
+ nsIGlobalObject* GetGlobalObject();
+
+ void SetReady() override;
+ void Cancel() override;
+ void ClearDynamicImport();
+
+ void ModuleLoaded();
+ void ModuleErrored();
+ void DependenciesLoaded();
+ void LoadFailed();
+
+ ModuleLoadRequest* GetRootModule() {
+ if (!mRootModule) {
+ return this;
+ }
+ return mRootModule;
+ }
+
+ bool IsModuleMarkedForBytecodeEncoding() const {
+ return mIsMarkedForBytecodeEncoding;
+ }
+ void MarkModuleForBytecodeEncoding() {
+ MOZ_ASSERT(!IsModuleMarkedForBytecodeEncoding());
+ mIsMarkedForBytecodeEncoding = true;
+ }
+
+ // Convenience methods to call into the module loader for this request.
+
+ void CancelDynamicImport(nsresult aResult) {
+ MOZ_ASSERT(IsDynamicImport());
+ mLoader->CancelDynamicImport(this, aResult);
+ }
+#ifdef DEBUG
+ bool IsRegisteredDynamicImport() const {
+ return IsDynamicImport() && mLoader->HasDynamicImport(this);
+ }
+#endif
+ nsresult StartModuleLoad() { return mLoader->StartModuleLoad(this); }
+ nsresult RestartModuleLoad() { return mLoader->RestartModuleLoad(this); }
+ nsresult OnFetchComplete(nsresult aRv) {
+ return mLoader->OnFetchComplete(this, aRv);
+ }
+ bool InstantiateModuleGraph() {
+ return mLoader->InstantiateModuleGraph(this);
+ }
+ nsresult EvaluateModule() { return mLoader->EvaluateModule(this); }
+ void StartDynamicImport() { mLoader->StartDynamicImport(this); }
+ void ProcessDynamicImport() { mLoader->ProcessDynamicImport(this); }
+
+ private:
+ void LoadFinished();
+ void CancelImports();
+ void CheckModuleDependenciesLoaded();
+
+ public:
+ // Is this a request for a top level module script or an import?
+ const bool mIsTopLevel;
+
+ // Is this the top level request for a dynamic module import?
+ const bool mIsDynamicImport;
+
+ // True if this module is planned to be saved in the bytecode cache.
+ // ModuleLoadRequest doesn't use ScriptLoadRequest::mScriptForBytecodeEncoding
+ // field because the JSScript reference isn't always avaialble for module.
+ bool mIsMarkedForBytecodeEncoding = false;
+
+ // Pointer to the script loader, used to trigger actions when the module load
+ // finishes.
+ RefPtr<ModuleLoaderBase> mLoader;
+
+ // Pointer to the top level module of this module graph, nullptr if this is a
+ // top level module
+ RefPtr<ModuleLoadRequest> mRootModule;
+
+ // Set to a module script object after a successful load or nullptr on
+ // failure.
+ RefPtr<ModuleScript> mModuleScript;
+
+ // A promise that is completed on successful load of this module and all of
+ // its dependencies, indicating that the module is ready for instantiation and
+ // evaluation.
+ 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 JS::loader
+
+#endif // js_loader_ModuleLoadRequest_h
diff --git a/js/loader/ModuleLoaderBase.cpp b/js/loader/ModuleLoaderBase.cpp
new file mode 100644
index 0000000000..784ae26322
--- /dev/null
+++ b/js/loader/ModuleLoaderBase.cpp
@@ -0,0 +1,1317 @@
+/* -*- 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 "GeckoProfiler.h"
+#include "LoadedScript.h"
+#include "ModuleLoadRequest.h"
+#include "ScriptLoadRequest.h"
+#include "mozilla/dom/ScriptSettings.h" // AutoJSAPI
+#include "mozilla/dom/ScriptTrace.h"
+
+#include "js/Array.h" // JS::GetArrayLength
+#include "js/CompilationAndEvaluation.h"
+#include "js/ContextOptions.h" // JS::ContextOptionsRef
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
+#include "js/OffThreadScriptCompilation.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement
+#include "js/SourceText.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ScriptLoadContext.h"
+#include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsContentUtils.h"
+#include "nsICacheInfoChannel.h" // nsICacheInfoChannel
+#include "nsNetUtil.h" // NS_NewURI
+#include "xpcpublic.h"
+
+using mozilla::Err;
+using mozilla::Preferences;
+using mozilla::UniquePtr;
+using mozilla::WrapNotNull;
+using mozilla::dom::AutoJSAPI;
+
+namespace JS::loader {
+
+mozilla::LazyLogModule ModuleLoaderBase::gCspPRLog("CSP");
+mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog(
+ "ModuleLoaderBase");
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
+ args)
+
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)
+
+//////////////////////////////////////////////////////////////
+// ModuleLoaderBase
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchedModules,
+ mDynamicImportRequests, mGlobalObject, mEventTarget,
+ mLoader)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase)
+
+// static
+void ModuleLoaderBase::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::SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript,
+ HostReleaseTopLevelScript);
+ JS::SetModuleDynamicImportHook(rt, HostImportModuleDynamically);
+
+ JS::ImportAssertionVector assertions;
+ // ImportAssertionVector has inline storage for one element so this cannot
+ // fail.
+ MOZ_ALWAYS_TRUE(assertions.reserve(1));
+ assertions.infallibleAppend(JS::ImportAssertion::Type);
+ JS::SetSupportedImportAssertions(rt, assertions);
+}
+
+// 8.1.3.8.1 HostResolveImportedModule(referencingModule, moduleRequest)
+/**
+ * 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 aModuleRequest A module request object.
+ * @returns module This is set to the module found.
+ */
+// static
+JSObject* ModuleLoaderBase::HostResolveImportedModule(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest) {
+ JS::Rooted<JSObject*> module(aCx);
+
+ {
+ // LoadedScript should only live in this block, otherwise it will be a GC
+ // hazard
+ RefPtr<LoadedScript> script(
+ GetLoadedScriptOrNull(aCx, aReferencingPrivate));
+
+ JS::Rooted<JSString*> specifierString(
+ aCx, JS::GetModuleRequestSpecifier(aCx, aModuleRequest));
+ if (!specifierString) {
+ return nullptr;
+ }
+
+ // Let url be the result of resolving a module specifier given referencing
+ // module script and specifier.
+ nsAutoJSString string;
+ if (!string.init(aCx, specifierString)) {
+ return nullptr;
+ }
+
+ RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
+ if (!loader) {
+ return nullptr;
+ }
+
+ auto result = loader->ResolveModuleSpecifier(script, string);
+ // This cannot fail because resolving a module specifier must have been
+ // previously successful with these same two arguments.
+ MOZ_ASSERT(result.isOk());
+ nsCOMPtr<nsIURI> uri = result.unwrap();
+ 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 = loader->GetFetchedModule(uri);
+ MOZ_ASSERT(ms, "Resolved module not found in module map");
+ MOZ_ASSERT(!ms->HasParseError());
+ MOZ_ASSERT(ms->ModuleRecord());
+
+ module.set(ms->ModuleRecord());
+ }
+ return module;
+}
+
+// static
+bool ModuleLoaderBase::ImportMetaResolve(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue modulePrivate(
+ cx, js::GetFunctionNativeReserved(&args.callee(), ModulePrivateSlot));
+
+ // https://html.spec.whatwg.org/#hostgetimportmetaproperties
+ // Step 4.1. Set specifier to ? ToString(specifier).
+ //
+ // https://tc39.es/ecma262/#sec-tostring
+ RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg));
+ RootedString specifier(cx, JS::ToString(cx, v));
+ if (!specifier) {
+ return false;
+ }
+
+ // Step 4.2, 4.3 are implemented in ImportMetaResolveImpl.
+ RootedString url(cx, ImportMetaResolveImpl(cx, modulePrivate, specifier));
+ if (!url) {
+ return false;
+ }
+
+ // Step 4.4. Return the serialization of url.
+ args.rval().setString(url);
+ return true;
+}
+
+// static
+JSString* ModuleLoaderBase::ImportMetaResolveImpl(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier) {
+ RootedString urlString(aCx);
+
+ {
+ // ModuleScript should only live in this block, otherwise it will be a GC
+ // hazard
+ RefPtr<ModuleScript> script =
+ static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+ MOZ_ASSERT(script->IsModuleScript());
+ MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
+ aReferencingPrivate);
+
+ RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
+ if (!loader) {
+ return nullptr;
+ }
+
+ nsAutoJSString specifier;
+ if (!specifier.init(aCx, aSpecifier)) {
+ return nullptr;
+ }
+
+ auto result = loader->ResolveModuleSpecifier(script, specifier);
+ if (result.isErr()) {
+ JS::Rooted<JS::Value> error(aCx);
+ nsresult rv = HandleResolveFailure(aCx, script, specifier,
+ result.unwrapErr(), 0, 0, &error);
+ if (NS_FAILED(rv)) {
+ JS_ReportOutOfMemory(aCx);
+ return nullptr;
+ }
+
+ JS_SetPendingException(aCx, error);
+
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri = result.unwrap();
+ nsAutoCString url;
+ MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url));
+
+ urlString.set(JS_NewStringCopyZ(aCx, url.get()));
+ }
+
+ return urlString;
+}
+
+// static
+bool ModuleLoaderBase::HostPopulateImportMeta(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aMetaObject) {
+ 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()));
+ if (!urlString) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ // https://html.spec.whatwg.org/#import-meta-url
+ if (!JS_DefineProperty(aCx, aMetaObject, "url", urlString,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // https://html.spec.whatwg.org/#import-meta-resolve
+ // Define 'resolve' function on the import.meta object.
+ JSFunction* resolveFunc = js::DefineFunctionWithReserved(
+ aCx, aMetaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs,
+ JSPROP_ENUMERATE);
+ if (!resolveFunc) {
+ return false;
+ }
+
+ // Store the 'active script' of the meta object into the function slot.
+ // https://html.spec.whatwg.org/#active-script
+ RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc));
+ js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot,
+ aReferencingPrivate);
+
+ return true;
+}
+
+// static
+bool ModuleLoaderBase::HostImportModuleDynamically(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(aModuleRequest);
+ MOZ_DIAGNOSTIC_ASSERT(aPromise);
+
+ RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
+
+ JS::Rooted<JSString*> specifierString(
+ aCx, JS::GetModuleRequestSpecifier(aCx, aModuleRequest));
+ if (!specifierString) {
+ return false;
+ }
+
+ // Attempt to resolve the module specifier.
+ nsAutoJSString specifier;
+ if (!specifier.init(aCx, specifierString)) {
+ return false;
+ }
+
+ RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
+ if (!loader) {
+ return false;
+ }
+
+ auto result = loader->ResolveModuleSpecifier(script, specifier);
+ if (result.isErr()) {
+ JS::Rooted<JS::Value> error(aCx);
+ nsresult rv = HandleResolveFailure(aCx, script, specifier,
+ result.unwrapErr(), 0, 0, &error);
+ if (NS_FAILED(rv)) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ JS_SetPendingException(aCx, error);
+ return false;
+ }
+
+ // Create a new top-level load request.
+ nsCOMPtr<nsIURI> uri = result.unwrap();
+ RefPtr<ModuleLoadRequest> request = loader->CreateDynamicImport(
+ aCx, uri, script, aReferencingPrivate, specifierString, aPromise);
+
+ if (!request) {
+ JS_ReportErrorASCII(aCx, "Dynamic import not supported in this context");
+ return false;
+ }
+
+ loader->StartDynamicImport(request);
+ return true;
+}
+
+// static
+ModuleLoaderBase* ModuleLoaderBase::GetCurrentModuleLoader(JSContext* aCx) {
+ auto reportError = mozilla::MakeScopeExit([aCx]() {
+ JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
+ });
+
+ JS::Rooted<JSObject*> object(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (!object) {
+ return nullptr;
+ }
+
+ nsIGlobalObject* global = xpc::NativeGlobal(object);
+ if (!global) {
+ return nullptr;
+ }
+
+ ModuleLoaderBase* loader = global->GetModuleLoader(aCx);
+ if (!loader) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(loader->mGlobalObject == global);
+
+ reportError.release();
+ return loader;
+}
+
+// static
+LoadedScript* ModuleLoaderBase::GetLoadedScriptOrNull(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate) {
+ if (aReferencingPrivate.isUndefined()) {
+ return nullptr;
+ }
+
+ auto* script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
+ if (script->IsEventScript()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(
+ script->IsModuleScript(),
+ JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) ==
+ aReferencingPrivate);
+
+ return script;
+}
+
+nsresult ModuleLoaderBase::StartModuleLoad(ModuleLoadRequest* aRequest) {
+ return StartOrRestartModuleLoad(aRequest, RestartRequest::No);
+}
+
+nsresult ModuleLoaderBase::RestartModuleLoad(ModuleLoadRequest* aRequest) {
+ return StartOrRestartModuleLoad(aRequest, RestartRequest::Yes);
+}
+
+nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
+ RestartRequest aRestart) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+ MOZ_ASSERT(aRequest->IsFetching());
+
+ aRequest->SetUnknownDataType();
+
+ // If we're restarting the request, the module should already be in the
+ // "fetching" map.
+ MOZ_ASSERT_IF(aRestart == RestartRequest::Yes,
+ IsModuleFetching(aRequest->mURI));
+
+ // Check with the derived class whether we should load this module.
+ nsresult rv = NS_OK;
+ if (!CanStartLoad(aRequest, &rv)) {
+ return rv;
+ }
+
+ // Check whether the module has been fetched or is currently being fetched,
+ // and if so wait for it rather than starting a new fetch.
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+
+ if (aRestart == RestartRequest::No && ModuleMapContainsURL(request->mURI)) {
+ LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
+ WaitForModuleFetch(request->mURI)
+ ->Then(mEventTarget, __func__, request,
+ &ModuleLoadRequest::ModuleLoaded,
+ &ModuleLoadRequest::LoadFailed);
+ return NS_OK;
+ }
+
+ rv = StartFetch(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We successfully started fetching a module so put its URL in the module
+ // map and mark it as fetching.
+ if (aRestart == RestartRequest::No) {
+ SetModuleFetchStarted(aRequest->AsModuleRequest());
+ }
+
+ return NS_OK;
+}
+
+bool ModuleLoaderBase::ModuleMapContainsURL(nsIURI* aURL) const {
+ return IsModuleFetching(aURL) || IsModuleFetched(aURL);
+}
+
+bool ModuleLoaderBase::IsModuleFetching(nsIURI* aURL) const {
+ return mFetchingModules.Contains(aURL);
+}
+
+bool ModuleLoaderBase::IsModuleFetched(nsIURI* aURL) const {
+ return mFetchedModules.Contains(aURL);
+}
+
+nsresult ModuleLoaderBase::GetFetchedModuleURLs(nsTArray<nsCString>& aURLs) {
+ for (const auto& entry : mFetchedModules) {
+ nsIURI* uri = entry.GetData()->BaseURL();
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aURLs.AppendElement(spec);
+ }
+
+ return NS_OK;
+}
+
+void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
+ // Update the module map to indicate that a module is currently being fetched.
+
+ MOZ_ASSERT(aRequest->IsFetching());
+ MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI));
+
+ mFetchingModules.InsertOrUpdate(
+ aRequest->mURI, RefPtr<mozilla::GenericNonExclusivePromise::Private>{});
+}
+
+void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
+ ModuleLoadRequest* aRequest, nsresult aResult) {
+ // Update module map with the result of fetching a single module script.
+ //
+ // If any requests for the same URL are waiting on this one to complete, they
+ // will have ModuleLoaded or LoadFailed on them when the promise is
+ // resolved/rejected. This is set up in StartLoad.
+
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ LOG(
+ ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
+ "%u)",
+ aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
+
+ RefPtr<mozilla::GenericNonExclusivePromise::Private> promise;
+ if (!mFetchingModules.Remove(aRequest->mURI, getter_AddRefs(promise))) {
+ LOG(
+ ("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
+ "assuming we have an inline module or have finished fetching already",
+ aRequest));
+ return;
+ }
+
+ RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
+ MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
+
+ mFetchedModules.InsertOrUpdate(aRequest->mURI, RefPtr{moduleScript});
+
+ if (promise) {
+ if (moduleScript) {
+ LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
+ promise->Resolve(true, __func__);
+ } else {
+ LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest, promise.get()));
+ promise->Reject(aResult, __func__);
+ }
+ }
+}
+
+RefPtr<mozilla::GenericNonExclusivePromise>
+ModuleLoaderBase::WaitForModuleFetch(nsIURI* aURL) {
+ MOZ_ASSERT(ModuleMapContainsURL(aURL));
+
+ nsURIHashKey key(aURL);
+ if (auto entry = mFetchingModules.Lookup(aURL)) {
+ if (!entry.Data()) {
+ entry.Data() = new mozilla::GenericNonExclusivePromise::Private(__func__);
+ }
+ return entry.Data();
+ }
+
+ RefPtr<ModuleScript> ms;
+ MOZ_ALWAYS_TRUE(mFetchedModules.Get(aURL, getter_AddRefs(ms)));
+ if (!ms) {
+ return mozilla::GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ return mozilla::GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+}
+
+ModuleScript* ModuleLoaderBase::GetFetchedModule(nsIURI* aURL) const {
+ if (LOG_ENABLED()) {
+ nsAutoCString url;
+ aURL->GetAsciiSpec(url);
+ LOG(("GetFetchedModule %s", url.get()));
+ }
+
+ bool found;
+ ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
+ MOZ_ASSERT(found);
+ return ms;
+}
+
+nsresult ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest* aRequest,
+ nsresult aRv) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+ MOZ_ASSERT(!aRequest->mModuleScript);
+
+ nsresult rv = aRv;
+ if (NS_SUCCEEDED(rv)) {
+ rv = CreateModuleScript(aRequest);
+
+ // If a module script was created, it should either have a module record
+ // object or a parse error.
+ if (ModuleScript* ms = aRequest->mModuleScript) {
+ MOZ_DIAGNOSTIC_ASSERT(bool(ms->ModuleRecord()) != ms->HasParseError());
+ }
+
+ aRequest->ClearScriptSource();
+
+ if (NS_FAILED(rv)) {
+ aRequest->LoadFailed();
+ return rv;
+ }
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv) == bool(aRequest->mModuleScript));
+ SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
+
+ if (aRequest->mModuleScript && !aRequest->mModuleScript->HasParseError()) {
+ StartFetchingModuleDependencies(aRequest);
+ }
+
+ return NS_OK;
+}
+
+nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(!aRequest->mModuleScript);
+ MOZ_ASSERT(aRequest->mBaseURL);
+
+ LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ {
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> module(cx);
+
+ JS::CompileOptions options(cx);
+ JS::RootedScript introductionScript(cx);
+ rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, &options,
+ &introductionScript);
+
+ if (NS_SUCCEEDED(rv)) {
+ JS::Rooted<JSObject*> global(cx, mGlobalObject->GetGlobalJSObject());
+ rv = CompileFetchedModule(cx, global, options, aRequest, &module);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
+
+ if (module) {
+ JS::RootedValue privateValue(cx);
+ JS::RootedScript moduleScript(cx, JS::GetModuleScript(module));
+ JS::InstantiateOptions instantiateOptions(options);
+ if (!JS::UpdateDebugMetadata(cx, moduleScript, instantiateOptions,
+ privateValue, nullptr, introductionScript,
+ nullptr)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ RefPtr<ModuleScript> moduleScript =
+ new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
+ aRequest->mModuleScript = moduleScript;
+
+ if (!module) {
+ LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
+ unsigned(rv)));
+
+ JS::Rooted<JS::Value> error(cx);
+ if (!jsapi.HasException() || !jsapi.StealException(&error) ||
+ error.isUndefined()) {
+ aRequest->mModuleScript = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ moduleScript->SetParseError(error);
+ aRequest->ModuleErrored();
+ return NS_OK;
+ }
+
+ moduleScript->SetModuleRecord(module);
+
+ // Validate requested modules and treat failure to resolve module specifiers
+ // the same as a parse error.
+ rv = ResolveRequestedModules(aRequest, nullptr);
+ if (NS_FAILED(rv)) {
+ aRequest->ModuleErrored();
+ return NS_OK;
+ }
+ }
+
+ LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
+ aRequest->mModuleScript.get()));
+
+ return rv;
+}
+
+nsresult ModuleLoaderBase::HandleResolveFailure(
+ JSContext* aCx, LoadedScript* aScript, const nsAString& aSpecifier,
+ ResolveError aError, uint32_t aLineNumber, uint32_t aColumnNumber,
+ JS::MutableHandle<JS::Value> aErrorOut) {
+ JS::Rooted<JSString*> filename(aCx);
+ if (aScript) {
+ nsAutoCString url;
+ aScript->BaseURL()->GetAsciiSpec(url);
+ filename = JS_NewStringCopyZ(aCx, url.get());
+ } else {
+ filename = JS_NewStringCopyZ(aCx, "(unknown)");
+ }
+
+ if (!filename) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ AutoTArray<nsString, 1> errorParams;
+ errorParams.AppendElement(aSpecifier);
+
+ nsAutoString errorText;
+ nsresult rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, ResolveErrorInfo::GetString(aError),
+ errorParams, errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
+ if (!string) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
+ aColumnNumber, nullptr, string, JS::NothingHandleValue,
+ aErrorOut)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+// Helper for getting import maps pref across main thread and workers
+bool ImportMapsEnabled() {
+ if (NS_IsMainThread()) {
+ return mozilla::StaticPrefs::dom_importMaps_enabled();
+ }
+ return false;
+}
+
+ResolveResult ModuleLoaderBase::ResolveModuleSpecifier(
+ LoadedScript* aScript, const nsAString& aSpecifier) {
+ // If import map is enabled, forward to the updated 'Resolve a module
+ // specifier' algorithm defined in Import maps spec.
+ //
+ // Once import map is enabled by default,
+ // ModuleLoaderBase::ResolveModuleSpecifier should be replaced by
+ // ImportMap::ResolveModuleSpecifier.
+ if (ImportMapsEnabled()) {
+ return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader, aScript,
+ 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 handled in Import maps.
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
+ if (NS_SUCCEEDED(rv)) {
+ return WrapNotNull(uri);
+ }
+
+ if (rv != NS_ERROR_MALFORMED_URI) {
+ return Err(ResolveError::Failure);
+ }
+
+ if (!StringBeginsWith(aSpecifier, u"/"_ns) &&
+ !StringBeginsWith(aSpecifier, u"./"_ns) &&
+ !StringBeginsWith(aSpecifier, u"../"_ns)) {
+ return Err(ResolveError::FailureMayBeBare);
+ }
+
+ // 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 = mLoader->GetBaseURI();
+ }
+
+ rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
+ if (NS_SUCCEEDED(rv)) {
+ return WrapNotNull(uri);
+ }
+
+ return Err(ResolveError::Failure);
+}
+
+nsresult ModuleLoaderBase::ResolveRequestedModules(
+ ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>* aUrlsOut) {
+ ModuleScript* ms = aRequest->mModuleScript;
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
+ uint32_t length = JS::GetRequestedModulesCount(cx, moduleRecord);
+
+ JS::Rooted<JS::Value> requestedModule(cx);
+ for (uint32_t i = 0; i < length; i++) {
+ JS::Rooted<JSString*> str(
+ cx, JS::GetRequestedModuleSpecifier(cx, moduleRecord, i));
+ MOZ_ASSERT(str);
+
+ 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.
+ ModuleLoaderBase* loader = aRequest->mLoader;
+ auto result = loader->ResolveModuleSpecifier(ms, specifier);
+ if (result.isErr()) {
+ uint32_t lineNumber = 0;
+ uint32_t columnNumber = 0;
+ JS::GetRequestedModuleSourcePos(cx, moduleRecord, i, &lineNumber,
+ &columnNumber);
+
+ JS::Rooted<JS::Value> error(cx);
+ nsresult rv = HandleResolveFailure(cx, ms, specifier, result.unwrapErr(),
+ lineNumber, columnNumber, &error);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ms->SetParseError(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> uri = result.unwrap();
+ if (aUrlsOut) {
+ aUrlsOut->AppendElement(uri.forget());
+ }
+ }
+
+ return NS_OK;
+}
+
+void ModuleLoaderBase::StartFetchingModuleDependencies(
+ ModuleLoadRequest* aRequest) {
+ LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
+
+ if (aRequest->IsCanceled()) {
+ return;
+ }
+
+ MOZ_ASSERT(aRequest->mModuleScript);
+ MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
+ MOZ_ASSERT(!aRequest->IsReadyToRun());
+
+ auto visitedSet = aRequest->mVisitedSet;
+ MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
+
+ aRequest->mState = ModuleLoadRequest::State::LoadingImports;
+
+ nsCOMArray<nsIURI> urls;
+ nsresult rv = ResolveRequestedModules(aRequest, &urls);
+ if (NS_FAILED(rv)) {
+ aRequest->mModuleScript = nullptr;
+ aRequest->ModuleErrored();
+ return;
+ }
+
+ // Remove already visited URLs from the list. Put unvisited URLs into the
+ // visited set.
+ int32_t i = 0;
+ while (i < urls.Count()) {
+ nsIURI* url = urls[i];
+ if (visitedSet->Contains(url)) {
+ urls.RemoveObjectAt(i);
+ } else {
+ visitedSet->PutEntry(url);
+ i++;
+ }
+ }
+
+ if (urls.Count() == 0) {
+ // There are no descendants to load so this request is ready.
+ aRequest->DependenciesLoaded();
+ return;
+ }
+
+ // For each url in urls, fetch a module script graph given url, module
+ // script's CORS setting, and module script's settings object.
+ nsTArray<RefPtr<mozilla::GenericPromise>> importsReady;
+ for (auto* url : urls) {
+ RefPtr<mozilla::GenericPromise> childReady =
+ StartFetchingModuleAndDependencies(aRequest, url);
+ importsReady.AppendElement(childReady);
+ }
+
+ // Wait for all imports to become ready.
+ RefPtr<mozilla::GenericPromise::AllPromiseType> allReady =
+ mozilla::GenericPromise::All(mEventTarget, importsReady);
+ allReady->Then(mEventTarget, __func__, aRequest,
+ &ModuleLoadRequest::DependenciesLoaded,
+ &ModuleLoadRequest::ModuleErrored);
+}
+
+RefPtr<mozilla::GenericPromise>
+ModuleLoaderBase::StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
+ nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ RefPtr<ModuleLoadRequest> childRequest = CreateStaticImport(aURI, aParent);
+
+ aParent->mImports.AppendElement(childRequest);
+
+ if (LOG_ENABLED()) {
+ nsAutoCString url1;
+ aParent->mURI->GetAsciiSpec(url1);
+
+ nsAutoCString url2;
+ aURI->GetAsciiSpec(url2);
+
+ LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
+ childRequest.get()));
+ LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
+ url2.get()));
+ }
+
+ RefPtr<mozilla::GenericPromise> ready = childRequest->mReady.Ensure(__func__);
+
+ nsresult rv = StartModuleLoad(childRequest);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!childRequest->mModuleScript);
+ LOG(("ScriptLoadRequest (%p): rejecting %p", aParent,
+ &childRequest->mReady));
+
+ mLoader->ReportErrorToConsole(childRequest, rv);
+ childRequest->mReady.Reject(rv, __func__);
+ return ready;
+ }
+
+ return ready;
+}
+
+void ModuleLoaderBase::StartDynamicImport(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
+
+ mDynamicImportRequests.AppendElement(aRequest);
+
+ nsresult rv = StartModuleLoad(aRequest);
+ if (NS_FAILED(rv)) {
+ mLoader->ReportErrorToConsole(aRequest, rv);
+ FinishDynamicImportAndReject(aRequest, rv);
+ }
+}
+
+void ModuleLoaderBase::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
+ nsresult aResult) {
+ AutoJSAPI jsapi;
+ MOZ_ASSERT(NS_FAILED(aResult));
+ if (!jsapi.Init(mGlobalObject)) {
+ return;
+ }
+
+ FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
+}
+
+void ModuleLoaderBase::FinishDynamicImport(
+ JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult,
+ JS::Handle<JSObject*> aEvaluationPromise) {
+ // If aResult is a failed result, we don't have an EvaluationPromise. If it
+ // succeeded, evaluationPromise may still be null, but in this case it will
+ // be handled by rejecting the dynamic module import promise in the JSAPI.
+ MOZ_ASSERT_IF(NS_FAILED(aResult), !aEvaluationPromise);
+ 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 (!aRequest->mDynamicPromise) {
+ // Import has already been completed.
+ return;
+ }
+
+ if (NS_FAILED(aResult) &&
+ aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+ 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::Rooted<JSObject*> moduleRequest(aCx,
+ JS::CreateModuleRequest(aCx, specifier));
+
+ JS::FinishDynamicModuleImport(aCx, aEvaluationPromise, referencingScript,
+ moduleRequest, promise);
+
+ // FinishDynamicModuleImport clears any pending exception.
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ aRequest->ClearDynamicImport();
+}
+
+ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface* aLoader,
+ nsIGlobalObject* aGlobalObject,
+ nsISerialEventTarget* aEventTarget)
+ : mGlobalObject(aGlobalObject),
+ mEventTarget(aEventTarget),
+ mLoader(aLoader) {
+ MOZ_ASSERT(mGlobalObject);
+ MOZ_ASSERT(mEventTarget);
+ MOZ_ASSERT(mLoader);
+
+ EnsureModuleHooksInitialized();
+}
+
+ModuleLoaderBase::~ModuleLoaderBase() {
+ mDynamicImportRequests.CancelRequestsAndClear();
+
+ LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this));
+}
+
+void ModuleLoaderBase::Shutdown() {
+ MOZ_ASSERT(mFetchingModules.IsEmpty());
+
+ for (const auto& entry : mFetchedModules) {
+ entry.GetData()->Shutdown();
+ }
+
+ mFetchedModules.Clear();
+ mGlobalObject = nullptr;
+ mEventTarget = nullptr;
+ mLoader = nullptr;
+}
+
+bool ModuleLoaderBase::HasPendingDynamicImports() const {
+ return !mDynamicImportRequests.isEmpty();
+}
+
+void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest* aRequest,
+ nsresult aResult) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
+ if (!aRequest->IsCanceled()) {
+ aRequest->Cancel();
+ // FinishDynamicImport must happen exactly once for each dynamic import
+ // request. If the load is aborted we do it when we remove the request
+ // from mDynamicImportRequests.
+ FinishDynamicImportAndReject(aRequest, aResult);
+ }
+}
+
+void ModuleLoaderBase::RemoveDynamicImport(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsDynamicImport());
+ mDynamicImportRequests.Remove(aRequest);
+}
+
+#ifdef DEBUG
+bool ModuleLoaderBase::HasDynamicImport(
+ const ModuleLoadRequest* aRequest) const {
+ MOZ_ASSERT(aRequest->mLoader == this);
+ return mDynamicImportRequests.Contains(
+ const_cast<ModuleLoadRequest*>(aRequest));
+}
+#endif
+
+JS::Value ModuleLoaderBase::FindFirstParseError(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest);
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ MOZ_ASSERT(moduleScript);
+
+ if (moduleScript->HasParseError()) {
+ return moduleScript->ParseError();
+ }
+
+ for (ModuleLoadRequest* childRequest : aRequest->mImports) {
+ JS::Value error = FindFirstParseError(childRequest);
+ if (!error.isUndefined()) {
+ return error;
+ }
+ }
+
+ return JS::UndefinedValue();
+}
+
+bool ModuleLoaderBase::InstantiateModuleGraph(ModuleLoadRequest* aRequest) {
+ // Instantiate a top-level module and record any error.
+
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aRequest->mLoader == this);
+ MOZ_ASSERT(aRequest->IsTopLevel());
+
+ LOG(("ScriptLoadRequest (%p): Instantiate module graph", aRequest));
+
+ AUTO_PROFILER_LABEL("ModuleLoaderBase::InstantiateModuleGraph", JS);
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ MOZ_ASSERT(moduleScript);
+
+ JS::Value parseError = FindFirstParseError(aRequest);
+ if (!parseError.isUndefined()) {
+ moduleScript->SetErrorToRethrow(parseError);
+ LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
+ return true;
+ }
+
+ MOZ_ASSERT(moduleScript->ModuleRecord());
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
+ if (!xpc::Scriptability::AllowedIfExists(module)) {
+ return true;
+ }
+
+ if (!JS::ModuleLink(jsapi.cx(), module)) {
+ LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
+ MOZ_ASSERT(jsapi.HasException());
+ JS::RootedValue exception(jsapi.cx());
+ if (!jsapi.StealException(&exception)) {
+ return false;
+ }
+ MOZ_ASSERT(!exception.isUndefined());
+ moduleScript->SetErrorToRethrow(exception);
+ }
+
+ return true;
+}
+
+nsresult ModuleLoaderBase::InitDebuggerDataForModuleGraph(
+ JSContext* aCx, ModuleLoadRequest* aRequest) {
+ // JS scripts can be associated with a DOM element for use by the debugger,
+ // but preloading can cause scripts to be compiled before DOM script element
+ // nodes have been created. This method ensures that this association takes
+ // place before the first time a module script is run.
+
+ MOZ_ASSERT(aRequest);
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ if (moduleScript->DebuggerDataInitialized()) {
+ return NS_OK;
+ }
+
+ for (ModuleLoadRequest* childRequest : aRequest->mImports) {
+ nsresult rv = InitDebuggerDataForModuleGraph(aCx, childRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
+ MOZ_ASSERT(module);
+
+ // The script is now ready to be exposed to the debugger.
+ JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
+ JS::ExposeScriptToDebugger(aCx, script);
+
+ moduleScript->SetDebuggerDataInitialized();
+ return NS_OK;
+}
+
+void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ if (aRequest->mModuleScript) {
+ if (!InstantiateModuleGraph(aRequest)) {
+ aRequest->mModuleScript = nullptr;
+ }
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aRequest->mModuleScript) {
+ rv = EvaluateModule(aRequest);
+ }
+
+ if (NS_FAILED(rv)) {
+ FinishDynamicImportAndReject(aRequest, rv);
+ }
+}
+
+nsresult ModuleLoaderBase::EvaluateModule(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ mozilla::nsAutoMicroTask mt;
+ mozilla::dom::AutoEntryScript aes(mGlobalObject, "EvaluateModule",
+ NS_IsMainThread());
+
+ return EvaluateModuleInContext(aes.cx(), aRequest,
+ JS::ReportModuleErrorsAsync);
+}
+
+nsresult ModuleLoaderBase::EvaluateModuleInContext(
+ JSContext* aCx, ModuleLoadRequest* aRequest,
+ JS::ModuleErrorBehaviour errorBehaviour) {
+ MOZ_ASSERT(aRequest->mLoader == this);
+
+ AUTO_PROFILER_LABEL("ModuleLoaderBase::EvaluateModule", JS);
+
+ nsAutoCString profilerLabelString;
+ if (aRequest->HasScriptLoadContext()) {
+ aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString);
+ }
+
+ LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
+ AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS,
+ MarkerInnerWindowIdFromJSContext(aCx),
+ profilerLabelString);
+
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ MOZ_ASSERT(request->mModuleScript);
+ MOZ_ASSERT_IF(request->HasScriptLoadContext(),
+ !request->GetScriptLoadContext()->mOffThreadToken);
+
+ ModuleScript* moduleScript = request->mModuleScript;
+ if (moduleScript->HasErrorToRethrow()) {
+ LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest));
+ JS::Rooted<JS::Value> error(aCx, moduleScript->ErrorToRethrow());
+ JS_SetPendingException(aCx, error);
+ // For a dynamic import, the promise is rejected. Otherwise an error
+ // is either reported by AutoEntryScript.
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport(aCx, request, NS_OK, nullptr);
+ }
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
+ MOZ_ASSERT(module);
+
+ if (!xpc::Scriptability::AllowedIfExists(module)) {
+ return NS_OK;
+ }
+
+ nsresult rv = InitDebuggerDataForModuleGraph(aCx, request);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (request->HasScriptLoadContext()) {
+ TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
+ "scriptloader_evaluate_module");
+ }
+
+ JS::Rooted<JS::Value> rval(aCx);
+
+ mLoader->MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx, request);
+
+ bool ok = JS::ModuleEvaluate(aCx, module, &rval);
+
+ // ModuleEvaluate will usually set a pending exception if it returns false,
+ // unless the user cancels execution.
+ MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aCx));
+
+ if (!ok) {
+ LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
+ // For a dynamic import, the promise is rejected. Otherwise an error is
+ // reported by AutoEntryScript.
+ }
+
+ // ModuleEvaluate returns a promise unless the user cancels the execution in
+ // which case rval will be undefined. We should treat it as a failed
+ // evaluation, and reject appropriately.
+ JS::Rooted<JSObject*> evaluationPromise(aCx);
+ if (rval.isObject()) {
+ evaluationPromise.set(&rval.toObject());
+ }
+
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport(aCx, request, NS_OK, evaluationPromise);
+ } else {
+ // If this is not a dynamic import, and if the promise is rejected,
+ // the value is unwrapped from the promise value.
+ if (!JS::ThrowOnModuleEvaluationFailure(aCx, evaluationPromise,
+ errorBehaviour)) {
+ LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest));
+ // For a dynamic import, the promise is rejected. Otherwise an error is
+ // reported by AutoEntryScript.
+ }
+ }
+
+ rv = mLoader->MaybePrepareModuleForBytecodeEncodingAfterExecute(request,
+ NS_OK);
+
+ mLoader->MaybeTriggerBytecodeEncoding();
+
+ return rv;
+}
+
+void ModuleLoaderBase::CancelAndClearDynamicImports() {
+ while (ScriptLoadRequest* req = mDynamicImportRequests.getFirst()) {
+ // This also removes the request from the list.
+ CancelDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
+ }
+}
+
+UniquePtr<ImportMap> ModuleLoaderBase::ParseImportMap(
+ ScriptLoadRequest* aRequest) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetGlobalObject())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ JS::SourceText<char16_t>& text = maybeSource.ref<SourceText<char16_t>>();
+ ReportWarningHelper warning{mLoader, aRequest};
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result
+ // Step 2. Parse an import map string given input and baseURL, catching any
+ // exceptions. If this threw an exception, then set result's error to rethrow
+ // to that exception. Otherwise, set result's import map to the return value.
+ //
+ // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
+ // Step 1. If result's error to rethrow is not null, then report the exception
+ // given by result's error to rethrow and return.
+ //
+ // Impl note: We didn't implement 'Import map parse result' from the spec,
+ // https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result
+ // As the struct has another item called 'error to rethow' to store the
+ // exception thrown during parsing import-maps, and report that exception
+ // while registering an import map. Currently only inline import-maps are
+ // supported, therefore parsing and registering import-maps will be executed
+ // consecutively. To simplify the implementation, we didn't create the 'error
+ // to rethow' item and report the exception immediately(done in ~AutoJSAPI).
+ return ImportMap::ParseString(jsapi.cx(), text, aRequest->mBaseURL, warning);
+}
+
+void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap) {
+ // Check for aImportMap is done in ScriptLoader.
+ MOZ_ASSERT(aImportMap);
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
+ // The step 1(report the exception if there's an error) is done in
+ // ParseImportMap.
+ //
+ // Step 2. Assert: global's import map is an empty import map.
+ // Impl note: The default import map from the spec is an empty import map, but
+ // from the implementation it defaults to nullptr, so we check if the global's
+ // import map is null here.
+ //
+ // Also see
+ // https://html.spec.whatwg.org/multipage/webappapis.html#empty-import-map
+ MOZ_ASSERT(!mImportMap);
+
+ // Step 3. Set global's import map to result's import map.
+ mImportMap = std::move(aImportMap);
+}
+
+#undef LOG
+#undef LOG_ENABLED
+
+} // namespace JS::loader
diff --git a/js/loader/ModuleLoaderBase.h b/js/loader/ModuleLoaderBase.h
new file mode 100644
index 0000000000..ebbcc42ebb
--- /dev/null
+++ b/js/loader/ModuleLoaderBase.h
@@ -0,0 +1,420 @@
+/* -*- 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 js_loader_ModuleLoaderBase_h
+#define js_loader_ModuleLoaderBase_h
+
+#include "LoadedScript.h"
+#include "ScriptLoadRequest.h"
+
+#include "ImportMap.h"
+#include "js/TypeDecls.h" // JS::MutableHandle, JS::Handle, JS::Root
+#include "js/Modules.h"
+#include "nsRefPtrHashtable.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsILoadInfo.h" // nsSecurityFlags
+#include "nsINode.h" // nsIURI
+#include "nsThreadUtils.h" // GetMainThreadSerialEventTarget
+#include "nsURIHashKey.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/JSExecutionContext.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "ResolveResult.h"
+
+class nsIURI;
+
+namespace mozilla {
+
+class LazyLogModule;
+union Utf8Unit;
+
+} // namespace mozilla
+
+namespace JS {
+
+class CompileOptions;
+
+template <typename UnitT>
+class SourceText;
+
+namespace loader {
+
+class ModuleLoaderBase;
+class ModuleLoadRequest;
+class ModuleScript;
+
+/*
+ * [DOMDOC] Shared Classic/Module Script Methods
+ *
+ * The ScriptLoaderInterface defines the shared methods needed by both
+ * ScriptLoaders (loading classic scripts) and ModuleLoaders (loading module
+ * scripts). These include:
+ *
+ * * Error Logging
+ * * Generating the compile options
+ * * Optional: Bytecode Encoding
+ *
+ * ScriptLoaderInterface does not provide any implementations.
+ * It enables the ModuleLoaderBase to reference back to the behavior implemented
+ * by a given ScriptLoader.
+ *
+ * Not all methods will be used by all ModuleLoaders. For example, Bytecode
+ * Encoding does not apply to workers, as we only work with source text there.
+ * Fully virtual methods are implemented by all.
+ *
+ */
+
+class ScriptLoaderInterface : public nsISupports {
+ public:
+ // alias common classes
+ using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
+ using ScriptKind = JS::loader::ScriptKind;
+ using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
+ using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList;
+ using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
+
+ virtual ~ScriptLoaderInterface() = default;
+
+ // In some environments, we will need to default to a base URI
+ virtual nsIURI* GetBaseURI() const = 0;
+
+ virtual void ReportErrorToConsole(ScriptLoadRequest* aRequest,
+ nsresult aResult) const = 0;
+
+ virtual void ReportWarningToConsole(
+ ScriptLoadRequest* aRequest, const char* aMessageName,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>()) const = 0;
+
+ // Fill in CompileOptions, as well as produce the introducer script for
+ // subsequent calls to UpdateDebuggerMetadata
+ virtual nsresult FillCompileOptionsForRequest(
+ JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
+ JS::MutableHandle<JSScript*> aIntroductionScript) = 0;
+
+ virtual void MaybePrepareModuleForBytecodeEncodingBeforeExecute(
+ JSContext* aCx, ModuleLoadRequest* aRequest) {}
+
+ virtual nsresult MaybePrepareModuleForBytecodeEncodingAfterExecute(
+ ModuleLoadRequest* aRequest, nsresult aRv) {
+ return NS_OK;
+ }
+
+ virtual void MaybeTriggerBytecodeEncoding() {}
+};
+
+/*
+ * [DOMDOC] Module Loading
+ *
+ * ModuleLoaderBase provides support for loading module graphs as defined in the
+ * EcmaScript specification. A derived module loader class must be created for a
+ * specific use case (for example loading HTML module scripts). The derived
+ * class provides operations such as fetching of source code and scheduling of
+ * module execution.
+ *
+ * Module loading works in terms of 'requests' which hold data about modules as
+ * they move through the loading process. There may be more than one load
+ * request active for a single module URI, but the module is only loaded
+ * once. This is achieved by tracking all fetching and fetched modules in the
+ * module map.
+ *
+ * The module map is made up of two parts. A module that has been requested but
+ * has not yet loaded is represented by a promise in the mFetchingModules map. A
+ * module which has been loaded is represented by a ModuleScript in the
+ * mFetchedModules map.
+ *
+ * Module loading typically works as follows:
+ *
+ * 1. The client ensures there is an instance of the derived module loader
+ * class for its global or creates one if necessary.
+ *
+ * 2. The client creates a ModuleLoadRequest object for the module to load and
+ * calls the loader's StartModuleLoad() method. This is a top-level request,
+ * i.e. not an import.
+ *
+ * 3. The module loader calls the virtual method CanStartLoad() to check
+ * whether the request should be loaded.
+ *
+ * 4. If the module is not already present in the module map, the loader calls
+ * the virtual method StartFetch() to set up an asynchronous operation to
+ * fetch the module source.
+ *
+ * 5. When the fetch operation is complete, the derived loader calls
+ * OnFetchComplete() passing an error code to indicate success or failure.
+ *
+ * 6. On success, the loader attempts to create a module script by calling the
+ * virtual CompileFetchedModule() method.
+ *
+ * 7. If compilation is successful, the loader creates load requests for any
+ * imported modules if present. If so, the process repeats from step 3.
+ *
+ * 8. When a load request is completed, the virtual OnModuleLoadComplete()
+ * method is called. This is called for the top-level request and import
+ * requests.
+ *
+ * 9. The client calls InstantiateModuleGraph() for the top-level request. This
+ * links the loaded module graph.
+ *
+ * 10. The client calls EvaluateModule() to execute the top-level module.
+ */
+class ModuleLoaderBase : public nsISupports {
+ private:
+ using GenericNonExclusivePromise = mozilla::GenericNonExclusivePromise;
+ using GenericPromise = mozilla::GenericPromise;
+
+ // Module map
+ nsRefPtrHashtable<nsURIHashKey, GenericNonExclusivePromise::Private>
+ mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
+
+ // List of dynamic imports that are currently being loaded.
+ ScriptLoadRequestList mDynamicImportRequests;
+
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+
+ // Event handler used to process MozPromise actions, used internally to wait
+ // for fetches to finish and for imports to become avilable.
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed
+ //
+ // Each Window has an import maps allowed boolean, initially true.
+ bool mImportMapsAllowed = true;
+
+ protected:
+ RefPtr<ScriptLoaderInterface> mLoader;
+
+ mozilla::UniquePtr<ImportMap> mImportMap;
+
+ virtual ~ModuleLoaderBase();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase)
+ explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader,
+ nsIGlobalObject* aGlobalObject,
+ nsISerialEventTarget* aEventTarget =
+ mozilla::GetMainThreadSerialEventTarget());
+
+ // Called to break cycles during shutdown to prevent memory leaks.
+ void Shutdown();
+
+ using LoadedScript = JS::loader::LoadedScript;
+ using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
+ using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
+ using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
+
+ using MaybeSourceText =
+ mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
+
+ // Methods that must be implemented by an extending class. These are called
+ // internally by ModuleLoaderBase.
+
+ private:
+ // Create a module load request for a static module import.
+ virtual already_AddRefed<ModuleLoadRequest> CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) = 0;
+
+ // Called by HostImportModuleDynamically hook.
+ virtual already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) = 0;
+
+ // Check whether we can load a module. May return false with |aRvOut| set to
+ // NS_OK to abort load without returning an error.
+ virtual bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) = 0;
+
+ // Start the process of fetching module source (or bytecode). This is only
+ // called if CanStartLoad returned true.
+ virtual nsresult StartFetch(ModuleLoadRequest* aRequest) = 0;
+
+ // Create a JS module for a fetched module request. This might compile source
+ // text or decode cached bytecode.
+ virtual nsresult CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
+ JS::MutableHandle<JSObject*> aModuleOut) = 0;
+
+ // Called when a module script has been loaded, including imports.
+ virtual void OnModuleLoadComplete(ModuleLoadRequest* aRequest) = 0;
+
+ // Public API methods.
+
+ public:
+ ScriptLoaderInterface* GetScriptLoaderInterface() const { return mLoader; }
+
+ nsIGlobalObject* GetGlobalObject() const { return mGlobalObject; }
+
+ bool HasPendingDynamicImports() const;
+ void CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult);
+#ifdef DEBUG
+ bool HasDynamicImport(const ModuleLoadRequest* aRequest) const;
+#endif
+
+ // Start a load for a module script URI. Returns immediately if the module is
+ // already being loaded.
+ nsresult StartModuleLoad(ModuleLoadRequest* aRequest);
+ nsresult RestartModuleLoad(ModuleLoadRequest* aRequest);
+
+ // Notify the module loader when a fetch started by StartFetch() completes.
+ nsresult OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv);
+
+ // Link the module and all its imports. This must occur prior to evaluation.
+ bool InstantiateModuleGraph(ModuleLoadRequest* aRequest);
+
+ // Executes the module.
+ // Implements https://html.spec.whatwg.org/#run-a-module-script
+ nsresult EvaluateModule(ModuleLoadRequest* aRequest);
+
+ // Evaluate a module in the given context. Does not push an entry to the
+ // execution stack.
+ nsresult EvaluateModuleInContext(JSContext* aCx, ModuleLoadRequest* aRequest,
+ JS::ModuleErrorBehaviour errorBehaviour);
+
+ void StartDynamicImport(ModuleLoadRequest* aRequest);
+ void ProcessDynamicImport(ModuleLoadRequest* aRequest);
+ void CancelAndClearDynamicImports();
+
+ // Process <script type="importmap">
+ mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
+
+ // Implements
+ // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
+ void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
+
+ // Getter for mImportMapsAllowed.
+ bool IsImportMapAllowed() const { return mImportMapsAllowed; }
+ // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
+ void DisallowImportMaps() { mImportMapsAllowed = false; }
+
+ // Returns true if the module for given URL is already fetched.
+ bool IsModuleFetched(nsIURI* aURL) const;
+
+ nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
+
+ // Internal methods.
+
+ private:
+ friend class JS::loader::ModuleLoadRequest;
+
+ static ModuleLoaderBase* GetCurrentModuleLoader(JSContext* aCx);
+ static LoadedScript* GetLoadedScriptOrNull(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate);
+
+ static void EnsureModuleHooksInitialized();
+
+ static JSObject* HostResolveImportedModule(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest);
+ static bool HostPopulateImportMeta(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aMetaObject);
+ static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
+ static JSString* ImportMetaResolveImpl(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier);
+ static bool HostImportModuleDynamically(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
+ static bool HostGetSupportedImportAssertions(
+ JSContext* aCx, JS::ImportAssertionVector& aValues);
+
+ ResolveResult ResolveModuleSpecifier(LoadedScript* aScript,
+ const nsAString& aSpecifier);
+
+ static nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
+ const nsAString& aSpecifier,
+ ResolveError aError,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ JS::MutableHandle<JS::Value> aErrorOut);
+
+ enum class RestartRequest { No, Yes };
+ nsresult StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
+ RestartRequest aRestart);
+
+ bool ModuleMapContainsURL(nsIURI* aURL) const;
+ bool IsModuleFetching(nsIURI* aURL) const;
+ RefPtr<GenericNonExclusivePromise> WaitForModuleFetch(nsIURI* aURL);
+ void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
+
+ ModuleScript* GetFetchedModule(nsIURI* aURL) const;
+
+ JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
+ static nsresult InitDebuggerDataForModuleGraph(JSContext* aCx,
+ ModuleLoadRequest* aRequest);
+ nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
+ nsCOMArray<nsIURI>* aUrlsOut);
+
+ void SetModuleFetchFinishedAndResumeWaitingRequests(
+ ModuleLoadRequest* aRequest, nsresult aResult);
+
+ void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
+
+ RefPtr<GenericPromise> StartFetchingModuleAndDependencies(
+ ModuleLoadRequest* aParent, nsIURI* aURI);
+
+ /**
+ * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
+ * case where we do not have `aEvaluationPromise`. As there is no evaluation
+ * Promise, JS::FinishDynamicImport will always reject.
+ *
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ */
+ void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
+ nsresult aResult);
+
+ /**
+ * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
+ * `aEvaluationPromise` which, if null, exits early.
+ *
+ * This is the Top Level Await version, which works with modules which return
+ * promises.
+ *
+ * @param aCX
+ * The JSContext for the module.
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ * @param aEvaluationPromise
+ * The evaluation promise returned from evaluating the module. If this
+ * is null, JS::FinishDynamicImport will reject the dynamic import
+ * module promise.
+ */
+ static void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
+ nsresult aResult,
+ JS::Handle<JSObject*> aEvaluationPromise);
+
+ void RemoveDynamicImport(ModuleLoadRequest* aRequest);
+
+ nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
+
+ // The slot stored in ImportMetaResolve function.
+ enum { ModulePrivateSlot = 0, SlotCount };
+
+ // The number of args in ImportMetaResolve.
+ static const uint32_t ImportMetaResolveNumArgs = 1;
+ // The index of the 'specifier' argument in ImportMetaResolve.
+ static const uint32_t ImportMetaResolveSpecifierArg = 0;
+
+ public:
+ static mozilla::LazyLogModule gCspPRLog;
+ static mozilla::LazyLogModule gModuleLoaderBaseLog;
+};
+
+} // namespace loader
+} // namespace JS
+
+#endif // js_loader_ModuleLoaderBase_h
diff --git a/js/loader/ResolveResult.h b/js/loader/ResolveResult.h
new file mode 100644
index 0000000000..e625e0e43e
--- /dev/null
+++ b/js/loader/ResolveResult.h
@@ -0,0 +1,54 @@
+/* -*- 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 js_loader_ResolveResult_h
+#define js_loader_ResolveResult_h
+
+#include "mozilla/ResultVariant.h"
+#include "mozilla/NotNull.h"
+#include "nsIURI.h"
+
+namespace JS::loader {
+
+enum class ResolveError : uint8_t {
+ Failure,
+ FailureMayBeBare,
+ BlockedByNullEntry,
+ BlockedByAfterPrefix,
+ BlockedByBacktrackingPrefix,
+ InvalidBareSpecifier
+};
+
+struct ResolveErrorInfo {
+ static const char* GetString(ResolveError aError) {
+ switch (aError) {
+ case ResolveError::Failure:
+ return "ModuleResolveFailureNoWarn";
+ case ResolveError::FailureMayBeBare:
+ return "ModuleResolveFailureWarnRelative";
+ case ResolveError::BlockedByNullEntry:
+ return "ImportMapResolutionBlockedByNullEntry";
+ case ResolveError::BlockedByAfterPrefix:
+ return "ImportMapResolutionBlockedByAfterPrefix";
+ case ResolveError::BlockedByBacktrackingPrefix:
+ return "ImportMapResolutionBlockedByBacktrackingPrefix";
+ case ResolveError::InvalidBareSpecifier:
+ return "ImportMapResolveInvalidBareSpecifierWarnRelative";
+ default:
+ MOZ_CRASH("Unexpected ResolveError value");
+ }
+ }
+};
+
+/**
+ * ResolveResult is used to store the result of 'resolving a module specifier',
+ * which could be an URI on success or a ResolveError on failure.
+ */
+using ResolveResult =
+ mozilla::Result<mozilla::NotNull<nsCOMPtr<nsIURI>>, ResolveError>;
+} // namespace JS::loader
+
+#endif // js_loader_ResolveResult_h
diff --git a/js/loader/ScriptKind.h b/js/loader/ScriptKind.h
new file mode 100644
index 0000000000..70ab0b9c3e
--- /dev/null
+++ b/js/loader/ScriptKind.h
@@ -0,0 +1,16 @@
+/* -*- 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 js_loader_ScriptKind_h
+#define js_loader_ScriptKind_h
+
+namespace JS::loader {
+
+enum class ScriptKind { eClassic, eModule, eEvent, eImportMap };
+
+} // namespace JS::loader
+
+#endif
diff --git a/js/loader/ScriptLoadRequest.cpp b/js/loader/ScriptLoadRequest.cpp
new file mode 100644
index 0000000000..ef6c7b80ff
--- /dev/null
+++ b/js/loader/ScriptLoadRequest.cpp
@@ -0,0 +1,283 @@
+/* -*- 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 "ScriptLoadRequest.h"
+#include "GeckoProfiler.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ScriptLoadContext.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/OffThreadScriptCompilation.h"
+#include "js/SourceText.h"
+
+#include "ModuleLoadRequest.h"
+#include "nsContentUtils.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIClassOfService.h"
+#include "nsISupportsPriority.h"
+
+using JS::SourceText;
+
+namespace JS::loader {
+
+//////////////////////////////////////////////////////////////
+// ScriptFetchOptions
+//////////////////////////////////////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION(ScriptFetchOptions, mTriggeringPrincipal, mElement)
+
+ScriptFetchOptions::ScriptFetchOptions(
+ mozilla::CORSMode aCORSMode, mozilla::dom::ReferrerPolicy aReferrerPolicy,
+ nsIPrincipal* aTriggeringPrincipal, mozilla::dom::Element* aElement)
+ : mCORSMode(aCORSMode),
+ mReferrerPolicy(aReferrerPolicy),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mElement(aElement) {}
+
+ScriptFetchOptions::~ScriptFetchOptions() = default;
+
+//////////////////////////////////////////////////////////////
+// ScriptLoadRequest
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+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, mCacheInfo, mLoadContext)
+ tmp->mScriptForBytecodeEncoding = nullptr;
+ tmp->DropBytecodeCacheReferences();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo, mLoadContext)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptForBytecodeEncoding)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
+ ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity,
+ nsIURI* aReferrer,
+ LoadContextBase* aContext)
+ : mKind(aKind),
+ mState(State::Fetching),
+ mFetchSourceOnly(false),
+ mDataType(DataType::eUnknown),
+ mFetchOptions(aFetchOptions),
+ mIntegrity(aIntegrity),
+ mReferrer(aReferrer),
+ mScriptTextLength(0),
+ mScriptBytecode(),
+ mBytecodeOffset(0),
+ mURI(aURI),
+ mLoadContext(aContext) {
+ MOZ_ASSERT(mFetchOptions);
+ if (mLoadContext) {
+ mLoadContext->SetRequest(this);
+ }
+}
+
+ScriptLoadRequest::~ScriptLoadRequest() {
+ if (IsMarkedForBytecodeEncoding()) {
+ DropBytecodeCacheReferences();
+ }
+ mLoadContext = nullptr;
+ DropJSObjects(this);
+}
+
+void ScriptLoadRequest::SetReady() {
+ MOZ_ASSERT(!IsReadyToRun());
+ mState = State::Ready;
+}
+
+void ScriptLoadRequest::Cancel() {
+ mState = State::Canceled;
+ if (HasScriptLoadContext()) {
+ GetScriptLoadContext()->MaybeCancelOffThreadScript();
+ }
+}
+
+void ScriptLoadRequest::DropBytecodeCacheReferences() {
+ mCacheInfo = nullptr;
+ DropJSObjects(this);
+}
+
+bool ScriptLoadRequest::HasScriptLoadContext() const {
+ return HasLoadContext() && mLoadContext->IsWindowContext();
+}
+
+bool ScriptLoadRequest::HasWorkerLoadContext() const {
+ return HasLoadContext() && mLoadContext->IsWorkerContext();
+}
+
+mozilla::dom::ScriptLoadContext* ScriptLoadRequest::GetScriptLoadContext() {
+ MOZ_ASSERT(mLoadContext);
+ return mLoadContext->AsWindowContext();
+}
+
+mozilla::loader::ComponentLoadContext*
+ScriptLoadRequest::GetComponentLoadContext() {
+ MOZ_ASSERT(mLoadContext);
+ return mLoadContext->AsComponentContext();
+}
+
+mozilla::dom::WorkerLoadContext* ScriptLoadRequest::GetWorkerLoadContext() {
+ MOZ_ASSERT(mLoadContext);
+ return mLoadContext->AsWorkerContext();
+}
+
+ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() {
+ MOZ_ASSERT(IsModuleRequest());
+ return static_cast<ModuleLoadRequest*>(this);
+}
+
+const ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() const {
+ MOZ_ASSERT(IsModuleRequest());
+ return static_cast<const ModuleLoadRequest*>(this);
+}
+
+void ScriptLoadRequest::SetBytecode() {
+ MOZ_ASSERT(IsUnknownDataType());
+ mDataType = DataType::eBytecode;
+}
+
+bool ScriptLoadRequest::IsUTF8ParsingEnabled() {
+ if (HasLoadContext()) {
+ if (mLoadContext->IsWindowContext()) {
+ return mozilla::StaticPrefs::
+ dom_script_loader_external_scripts_utf8_parsing_enabled();
+ }
+ if (mLoadContext->IsWorkerContext()) {
+ return mozilla::StaticPrefs::
+ dom_worker_script_loader_utf8_parsing_enabled();
+ }
+ }
+ return false;
+}
+
+void ScriptLoadRequest::ClearScriptSource() {
+ if (IsTextSource()) {
+ ClearScriptText();
+ }
+}
+
+void ScriptLoadRequest::MarkForBytecodeEncoding(JSScript* aScript) {
+ MOZ_ASSERT(!IsModuleRequest());
+ MOZ_ASSERT(!IsMarkedForBytecodeEncoding());
+ mScriptForBytecodeEncoding = aScript;
+ HoldJSObjects(this);
+}
+
+bool ScriptLoadRequest::IsMarkedForBytecodeEncoding() const {
+ if (IsModuleRequest()) {
+ return AsModuleRequest()->IsModuleMarkedForBytecodeEncoding();
+ }
+
+ return !!mScriptForBytecodeEncoding;
+}
+
+nsresult ScriptLoadRequest::GetScriptSource(JSContext* aCx,
+ MaybeSourceText* aMaybeSource) {
+ // If there's no script text, we try to get it from the element
+ if (HasScriptLoadContext() && GetScriptLoadContext()->mIsInline) {
+ nsAutoString inlineData;
+ GetScriptLoadContext()->GetScriptElement()->GetScriptText(inlineData);
+
+ size_t nbytes = inlineData.Length() * sizeof(char16_t);
+ JS::UniqueTwoByteChars chars(
+ static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
+ if (!chars) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(chars.get(), inlineData.get(), nbytes);
+
+ SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
+ return NS_OK;
+ }
+
+ size_t length = ScriptTextLength();
+ if (IsUTF16Text()) {
+ JS::UniqueTwoByteChars chars;
+ chars.reset(ScriptText<char16_t>().extractOrCopyRawBuffer());
+ if (!chars) {
+ JS_ReportOutOfMemory(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(aCx, std::move(chars), length)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(IsUTF8Text());
+ UniquePtr<Utf8Unit[], JS::FreePolicy> chars;
+ chars.reset(ScriptText<Utf8Unit>().extractOrCopyRawBuffer());
+ if (!chars) {
+ JS_ReportOutOfMemory(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SourceText<Utf8Unit> srcBuf;
+ if (!srcBuf.init(aCx, std::move(chars), length)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aMaybeSource->construct<SourceText<Utf8Unit>>(std::move(srcBuf));
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////
+// ScriptLoadRequestList
+//////////////////////////////////////////////////////////////
+
+ScriptLoadRequestList::~ScriptLoadRequestList() { CancelRequestsAndClear(); }
+
+void ScriptLoadRequestList::CancelRequestsAndClear() {
+ while (!isEmpty()) {
+ RefPtr<ScriptLoadRequest> first = StealFirst();
+ first->Cancel();
+ // And just let it go out of scope and die.
+ }
+}
+
+#ifdef DEBUG
+bool ScriptLoadRequestList::Contains(ScriptLoadRequest* aElem) const {
+ for (const ScriptLoadRequest* req = getFirst(); req; req = req->getNext()) {
+ if (req == aElem) {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // DEBUG
+
+} // namespace JS::loader
diff --git a/js/loader/ScriptLoadRequest.h b/js/loader/ScriptLoadRequest.h
new file mode 100644
index 0000000000..4aa75517e3
--- /dev/null
+++ b/js/loader/ScriptLoadRequest.h
@@ -0,0 +1,425 @@
+/* -*- 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 js_loader_ScriptLoadRequest_h
+#define js_loader_ScriptLoadRequest_h
+
+#include "js/AllocPolicy.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/PreloaderBase.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "mozilla/Variant.h"
+#include "mozilla/Vector.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "ScriptKind.h"
+#include "nsIScriptElement.h"
+
+class nsICacheInfoChannel;
+
+namespace mozilla::dom {
+
+class ScriptLoadContext;
+class WorkerLoadContext;
+
+} // namespace mozilla::dom
+
+namespace mozilla::loader {
+class ComponentLoadContext;
+} // namespace mozilla::loader
+
+namespace JS {
+class OffThreadToken;
+
+namespace loader {
+
+using Utf8Unit = mozilla::Utf8Unit;
+
+class LoadContextBase;
+class ModuleLoadRequest;
+class ScriptLoadRequestList;
+
+/*
+ * ScriptFetchOptions loosely corresponds to HTML's "script fetch options",
+ * https://html.spec.whatwg.org/multipage/webappapis.html#script-fetch-options
+ * with the exception of the following properties:
+ * cryptographic nonce
+ * The cryptographic nonce metadata used for the initial fetch and for
+ * fetching any imported modules. As this is populated by a DOM element,
+ * this is implemented via mozilla::dom::Element as the field
+ * mElement. The default value is an empty string, and is indicated
+ * when this field is a nullptr. Nonce is not represented on the dom
+ * side as per bug 1374612.
+ * parser metadata
+ * The parser metadata used for the initial fetch and for fetching any
+ * imported modules. This is populated from a mozilla::dom::Element and is
+ * handled by the field mElement. The default value is an empty string,
+ * and is indicated when this field is a nullptr.
+ * integrity metadata
+ * The integrity metadata used for the initial fetch. This is
+ * implemented in ScriptLoadRequest, as it changes for every
+ * ScriptLoadRequest.
+ *
+ * In the case of classic scripts without dynamic import, this object is
+ * used once. For modules, this object is propogated throughout the module
+ * tree. If there is a dynamically imported module in any type of script,
+ * the ScriptFetchOptions object will be propogated from its importer.
+ */
+
+class ScriptFetchOptions {
+ ~ScriptFetchOptions();
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ScriptFetchOptions)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ScriptFetchOptions)
+
+ ScriptFetchOptions(mozilla::CORSMode aCORSMode,
+ enum mozilla::dom::ReferrerPolicy aReferrerPolicy,
+ nsIPrincipal* aTriggeringPrincipal,
+ mozilla::dom::Element* aElement = nullptr);
+
+ /*
+ * The credentials mode used for the initial fetch (for module scripts)
+ * and for fetching any imported modules (for both module scripts and
+ * classic scripts)
+ */
+ const mozilla::CORSMode mCORSMode;
+
+ /*
+ * The referrer policy used for the initial fetch and for fetching any
+ * imported modules
+ */
+ const enum mozilla::dom::ReferrerPolicy mReferrerPolicy;
+
+ /*
+ * Used to determine CSP and if we are on the About page.
+ * Only used in DOM content scripts.
+ * TODO: Move to ScriptLoadContext
+ */
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ /*
+ * Represents fields populated by DOM elements (nonce, parser metadata)
+ * Leave this field as a nullptr for any fetch that requires the
+ * default classic script options.
+ * (https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options)
+ * TODO: extract necessary fields rather than passing this object
+ */
+ nsCOMPtr<mozilla::dom::Element> mElement;
+};
+
+/*
+ * ScriptLoadRequest
+ *
+ * ScriptLoadRequest is a generic representation of a JavaScript script that
+ * will be loaded by a Script/Module loader. This representation is used by the
+ * DOM ScriptLoader and will be used by workers and MOZJSComponentLoader.
+ *
+ * The ScriptLoadRequest contains information about the kind of script (classic
+ * or module), the URI, and the ScriptFetchOptions associated with the script.
+ * It is responsible for holding the script data once the fetch is complete, or
+ * if the request is cached, the bytecode.
+ *
+ * Relationship to ScriptLoadContext:
+ *
+ * ScriptLoadRequest and ScriptLoadContexts have a circular pointer. A
+ * ScriptLoadContext augments the loading of a ScriptLoadRequest by providing
+ * additional information regarding the loading and evaluation behavior (see
+ * the ScriptLoadContext class for details). In terms of responsibility,
+ * the ScriptLoadRequest represents "What" is being loaded, and the
+ * ScriptLoadContext represents "How".
+ *
+ * TODO: see if we can use it in the jsshell script loader. We need to either
+ * remove ISUPPORTS or find a way to encorporate that in the jsshell. We would
+ * then only have one implementation of the script loader, and it would be
+ * tested whenever jsshell tests are run. This would mean finding another way to
+ * create ScriptLoadRequest lists.
+ *
+ */
+
+class ScriptLoadRequest
+ : public nsISupports,
+ private mozilla::LinkedListElement<ScriptLoadRequest> {
+ using super = LinkedListElement<ScriptLoadRequest>;
+
+ // Allow LinkedListElement<ScriptLoadRequest> to cast us to itself as needed.
+ friend class mozilla::LinkedListElement<ScriptLoadRequest>;
+ friend class ScriptLoadRequestList;
+
+ protected:
+ virtual ~ScriptLoadRequest();
+
+ public:
+ using SRIMetadata = mozilla::dom::SRIMetadata;
+ ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
+ ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer,
+ LoadContextBase* aContext);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScriptLoadRequest)
+
+ using super::getNext;
+ using super::isInList;
+
+ template <typename T>
+ using VariantType = mozilla::VariantType<T>;
+
+ template <typename... Ts>
+ using Variant = mozilla::Variant<Ts...>;
+
+ template <typename T, typename D = JS::DeletePolicy<T>>
+ using UniquePtr = mozilla::UniquePtr<T, D>;
+
+ using MaybeSourceText =
+ mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
+
+ bool IsModuleRequest() const { return mKind == ScriptKind::eModule; }
+ bool IsImportMapRequest() const { return mKind == ScriptKind::eImportMap; }
+
+ ModuleLoadRequest* AsModuleRequest();
+ const ModuleLoadRequest* AsModuleRequest() const;
+
+ virtual bool IsTopLevel() const { return true; };
+
+ virtual void Cancel();
+
+ virtual void SetReady();
+
+ enum class State : uint8_t {
+ Fetching,
+ Compiling,
+ LoadingImports,
+ Ready,
+ Canceled
+ };
+
+ bool IsFetching() const { return mState == State::Fetching; }
+
+ bool IsCompiling() const { return mState == State::Compiling; }
+
+ bool IsReadyToRun() const {
+ return mState == State::Ready || mState == State::Canceled;
+ }
+
+ bool IsCanceled() const { return mState == State::Canceled; }
+
+ // Type of data provided by the nsChannel.
+ enum class DataType : uint8_t { eUnknown, eTextSource, eBytecode };
+
+ bool IsUnknownDataType() const { return mDataType == DataType::eUnknown; }
+ bool IsTextSource() const { return mDataType == DataType::eTextSource; }
+ bool IsSource() const { return IsTextSource(); }
+
+ void SetUnknownDataType() {
+ mDataType = DataType::eUnknown;
+ mScriptData.reset();
+ }
+
+ bool IsUTF8ParsingEnabled();
+
+ void SetTextSource() {
+ MOZ_ASSERT(IsUnknownDataType());
+ mDataType = DataType::eTextSource;
+ if (IsUTF8ParsingEnabled()) {
+ mScriptData.emplace(VariantType<ScriptTextBuffer<Utf8Unit>>());
+ } else {
+ mScriptData.emplace(VariantType<ScriptTextBuffer<char16_t>>());
+ }
+ }
+
+ // Use a vector backed by the JS allocator for script text so that contents
+ // can be transferred in constant time to the JS engine, not copied in linear
+ // time.
+ template <typename Unit>
+ using ScriptTextBuffer = mozilla::Vector<Unit, 0, js::MallocAllocPolicy>;
+
+ bool IsUTF16Text() const {
+ return mScriptData->is<ScriptTextBuffer<char16_t>>();
+ }
+ bool IsUTF8Text() const {
+ return mScriptData->is<ScriptTextBuffer<Utf8Unit>>();
+ }
+
+ template <typename Unit>
+ const ScriptTextBuffer<Unit>& ScriptText() const {
+ MOZ_ASSERT(IsTextSource());
+ return mScriptData->as<ScriptTextBuffer<Unit>>();
+ }
+ template <typename Unit>
+ ScriptTextBuffer<Unit>& ScriptText() {
+ MOZ_ASSERT(IsTextSource());
+ return mScriptData->as<ScriptTextBuffer<Unit>>();
+ }
+
+ size_t ScriptTextLength() const {
+ MOZ_ASSERT(IsTextSource());
+ return IsUTF16Text() ? ScriptText<char16_t>().length()
+ : ScriptText<Utf8Unit>().length();
+ }
+
+ // Get source text. On success |aMaybeSource| will contain either UTF-8 or
+ // UTF-16 source; on failure it will remain in its initial state.
+ nsresult GetScriptSource(JSContext* aCx, MaybeSourceText* aMaybeSource);
+
+ void ClearScriptText() {
+ MOZ_ASSERT(IsTextSource());
+ return IsUTF16Text() ? ScriptText<char16_t>().clearAndFree()
+ : ScriptText<Utf8Unit>().clearAndFree();
+ }
+
+ enum mozilla::dom::ReferrerPolicy ReferrerPolicy() const {
+ return mFetchOptions->mReferrerPolicy;
+ }
+
+ nsIPrincipal* TriggeringPrincipal() const {
+ return mFetchOptions->mTriggeringPrincipal;
+ }
+
+ void ClearScriptSource();
+
+ void MarkForBytecodeEncoding(JSScript* aScript);
+
+ bool IsMarkedForBytecodeEncoding() const;
+
+ bool IsBytecode() const { return mDataType == DataType::eBytecode; }
+
+ void SetBytecode();
+
+ mozilla::CORSMode CORSMode() const { return mFetchOptions->mCORSMode; }
+
+ void DropBytecodeCacheReferences();
+
+ bool HasLoadContext() const { return mLoadContext; }
+ bool HasScriptLoadContext() const;
+ bool HasWorkerLoadContext() const;
+
+ mozilla::dom::ScriptLoadContext* GetScriptLoadContext();
+
+ mozilla::loader::ComponentLoadContext* GetComponentLoadContext();
+
+ mozilla::dom::WorkerLoadContext* GetWorkerLoadContext();
+
+ const ScriptKind mKind; // Whether this is a classic script or a module
+ // script.
+
+ State mState; // Are we still waiting for a load to complete?
+ bool mFetchSourceOnly; // Request source, not cached bytecode.
+ DataType mDataType; // Does this contain Source or Bytecode?
+ RefPtr<ScriptFetchOptions> mFetchOptions;
+ const SRIMetadata mIntegrity;
+ const nsCOMPtr<nsIURI> mReferrer;
+ mozilla::Maybe<nsString>
+ mSourceMapURL; // Holds source map url for loaded scripts
+
+ // Holds script source data for non-inline scripts.
+ mozilla::Maybe<
+ Variant<ScriptTextBuffer<char16_t>, ScriptTextBuffer<Utf8Unit>>>
+ mScriptData;
+
+ // The length of script source text, set when reading completes. This is used
+ // since mScriptData is cleared when the source is passed to the JS engine.
+ size_t mScriptTextLength;
+
+ // Holds the SRI serialized hash and the script bytecode for non-inline
+ // scripts. The data is laid out according to ScriptBytecodeDataLayout
+ // or, if compression is enabled, ScriptBytecodeCompressedDataLayout.
+ mozilla::Vector<uint8_t> mScriptBytecode;
+ uint32_t mBytecodeOffset; // Offset of the bytecode in mScriptBytecode
+
+ const nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mOriginPrincipal;
+
+ // Keep the URI's filename alive during off thread parsing.
+ // Also used by workers to report on errors while loading.
+ nsAutoCString mURL;
+
+ // The base URL used for resolving relative module imports.
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ // Holds the top-level JSScript that corresponds to the current source, once
+ // it is parsed, and planned to be saved in the bytecode cache.
+ //
+ // NOTE: This field is not used for ModuleLoadRequest.
+ // See ModuleLoadRequest::mIsMarkedForBytecodeEncoding.
+ JS::Heap<JSScript*> mScriptForBytecodeEncoding;
+
+ // 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;
+
+ // LoadContext for augmenting the load depending on the loading
+ // context (DOM, Worker, etc.)
+ RefPtr<LoadContextBase> mLoadContext;
+};
+
+class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest> {
+ using super = mozilla::LinkedList<ScriptLoadRequest>;
+
+ public:
+ ~ScriptLoadRequestList();
+
+ void CancelRequestsAndClear();
+
+#ifdef DEBUG
+ bool Contains(ScriptLoadRequest* aElem) const;
+#endif // DEBUG
+
+ using super::getFirst;
+ using super::isEmpty;
+
+ void AppendElement(ScriptLoadRequest* aElem) {
+ MOZ_ASSERT(!aElem->isInList());
+ NS_ADDREF(aElem);
+ insertBack(aElem);
+ }
+
+ already_AddRefed<ScriptLoadRequest> Steal(ScriptLoadRequest* aElem) {
+ aElem->removeFrom(*this);
+ return dont_AddRef(aElem);
+ }
+
+ already_AddRefed<ScriptLoadRequest> StealFirst() {
+ MOZ_ASSERT(!isEmpty());
+ return Steal(getFirst());
+ }
+
+ void Remove(ScriptLoadRequest* aElem) {
+ aElem->removeFrom(*this);
+ NS_RELEASE(aElem);
+ }
+};
+
+inline void ImplCycleCollectionUnlink(ScriptLoadRequestList& aField) {
+ while (!aField.isEmpty()) {
+ RefPtr<ScriptLoadRequest> first = aField.StealFirst();
+ }
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ ScriptLoadRequestList& aField, const char* aName, uint32_t aFlags) {
+ for (ScriptLoadRequest* request = aField.getFirst(); request;
+ request = request->getNext()) {
+ CycleCollectionNoteChild(aCallback, request, aName, aFlags);
+ }
+}
+
+} // namespace loader
+} // namespace JS
+
+#endif // js_loader_ScriptLoadRequest_h
diff --git a/js/loader/moz.build b/js/loader/moz.build
new file mode 100644
index 0000000000..e80f43bb5f
--- /dev/null
+++ b/js/loader/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.js.loader += [
+ "ImportMap.h",
+ "LoadContextBase.h",
+ "LoadedScript.h",
+ "ModuleLoaderBase.h",
+ "ModuleLoadRequest.h",
+ "ResolveResult.h",
+ "ScriptKind.h",
+ "ScriptLoadRequest.h",
+]
+
+UNIFIED_SOURCES += [
+ "ImportMap.cpp",
+ "LoadContextBase.cpp",
+ "LoadedScript.cpp",
+ "ModuleLoaderBase.cpp",
+ "ModuleLoadRequest.cpp",
+ "ScriptLoadRequest.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")