Backed out changeset 88fbb17e3c20 (bug 1865637) for causing animation related mochite...
[gecko.git] / js / loader / ImportMap.cpp
blob51543511a7312caa55bf2dd18e0a59f04928eac1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ImportMap.h"
9 #include "js/Array.h" // IsArrayObject
10 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
11 #include "js/JSON.h" // JS_ParseJSON
12 #include "LoadedScript.h"
13 #include "ModuleLoaderBase.h" // ScriptLoaderInterface
14 #include "nsContentUtils.h"
15 #include "nsIScriptElement.h"
16 #include "nsIScriptError.h"
17 #include "nsJSUtils.h" // nsAutoJSString
18 #include "nsNetUtil.h" // NS_NewURI
19 #include "ScriptLoadRequest.h"
21 using JS::SourceText;
22 using mozilla::Err;
23 using mozilla::LazyLogModule;
24 using mozilla::MakeUnique;
25 using mozilla::UniquePtr;
26 using mozilla::WrapNotNull;
28 namespace JS::loader {
30 LazyLogModule ImportMap::gImportMapLog("ImportMap");
32 #undef LOG
33 #define LOG(args) \
34 MOZ_LOG(ImportMap::gImportMapLog, mozilla::LogLevel::Debug, args)
36 #define LOG_ENABLED() \
37 MOZ_LOG_TEST(ImportMap::gImportMapLog, mozilla::LogLevel::Debug)
39 void ReportWarningHelper::Report(const char* aMessageName,
40 const nsTArray<nsString>& aParams) const {
41 mLoader->ReportWarningToConsole(mRequest, aMessageName, aParams);
44 // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-url-like-module-specifier
45 static ResolveResult ResolveURLLikeModuleSpecifier(const nsAString& aSpecifier,
46 nsIURI* aBaseURL) {
47 nsCOMPtr<nsIURI> uri;
48 nsresult rv;
50 // Step 1. If specifier starts with "/", "./", or "../", then:
51 if (StringBeginsWith(aSpecifier, u"/"_ns) ||
52 StringBeginsWith(aSpecifier, u"./"_ns) ||
53 StringBeginsWith(aSpecifier, u"../"_ns)) {
54 // Step 1.1. Let url be the result of parsing specifier with baseURL as the
55 // base URL.
56 rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aBaseURL);
57 // Step 1.2. If url is failure, then return null.
58 if (NS_FAILED(rv)) {
59 return Err(ResolveError::Failure);
62 // Step 1.3. Return url.
63 return WrapNotNull(uri);
66 // Step 2. Let url be the result of parsing specifier (with no base URL).
67 rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
68 // Step 3. If url is failure, then return null.
69 if (NS_FAILED(rv)) {
70 return Err(ResolveError::FailureMayBeBare);
73 // Step 4. Return url.
74 return WrapNotNull(uri);
77 // https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-specifier-key
78 static void NormalizeSpecifierKey(const nsAString& aSpecifierKey,
79 nsIURI* aBaseURL,
80 const ReportWarningHelper& aWarning,
81 nsAString& aRetVal) {
82 // Step 1. If specifierKey is the empty string, then:
83 if (aSpecifierKey.IsEmpty()) {
84 // Step 1.1. Report a warning to the console that specifier keys cannot be
85 // the empty string.
86 aWarning.Report("ImportMapEmptySpecifierKeys");
88 // Step 1.2. Return null.
89 aRetVal = EmptyString();
90 return;
93 // Step 2. Let url be the result of resolving a URL-like module specifier,
94 // given specifierKey and baseURL.
95 auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifierKey, aBaseURL);
97 // Step 3. If url is not null, then return the serialization of url.
98 if (parseResult.isOk()) {
99 nsCOMPtr<nsIURI> url = parseResult.unwrap();
100 aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault());
101 return;
104 // Step 4. Return specifierKey.
105 aRetVal = aSpecifierKey;
108 // https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map
109 static UniquePtr<SpecifierMap> SortAndNormalizeSpecifierMap(
110 JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
111 const ReportWarningHelper& aWarning) {
112 // Step 1. Let normalized be an empty ordered map.
113 UniquePtr<SpecifierMap> normalized = MakeUnique<SpecifierMap>();
115 JS::Rooted<JS::IdVector> specifierKeys(aCx, JS::IdVector(aCx));
116 if (!JS_Enumerate(aCx, aOriginalMap, &specifierKeys)) {
117 return nullptr;
120 // Step 2. For each specifierKey → value of originalMap,
121 for (size_t i = 0; i < specifierKeys.length(); i++) {
122 const JS::RootedId specifierId(aCx, specifierKeys[i]);
123 nsAutoJSString specifierKey;
124 NS_ENSURE_TRUE(specifierKey.init(aCx, specifierId), nullptr);
126 // Step 2.1. Let normalizedSpecifierKey be the result of normalizing a
127 // specifier key given specifierKey and baseURL.
128 nsString normalizedSpecifierKey;
129 NormalizeSpecifierKey(specifierKey, aBaseURL, aWarning,
130 normalizedSpecifierKey);
132 // Step 2.2. If normalizedSpecifierKey is null, then continue.
133 if (normalizedSpecifierKey.IsEmpty()) {
134 continue;
137 JS::RootedValue idVal(aCx);
138 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, specifierId, &idVal),
139 nullptr);
140 // Step 2.3. If value is not a string, then:
141 if (!idVal.isString()) {
142 // Step 2.3.1. The user agent may report a warning to the console
143 // indicating that addresses need to be strings.
144 aWarning.Report("ImportMapAddressesNotStrings");
146 // Step 2.3.2. Set normalized[normalizedSpecifierKey] to null.
147 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
149 // Step 2.3.3. Continue.
150 continue;
153 nsAutoJSString value;
154 NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr);
156 // Step 2.4. Let addressURL be the result of resolving a URL-like module
157 // specifier given value and baseURL.
158 auto parseResult = ResolveURLLikeModuleSpecifier(value, aBaseURL);
160 // Step 2.5. If addressURL is null, then:
161 if (parseResult.isErr()) {
162 // Step 2.5.1. The user agent may report a warning to the console
163 // indicating that the address was invalid.
164 AutoTArray<nsString, 1> params;
165 params.AppendElement(value);
166 aWarning.Report("ImportMapInvalidAddress", params);
168 // Step 2.5.2. Set normalized[normalizedSpecifierKey] to null.
169 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
171 // Step 2.5.3. Continue.
172 continue;
175 nsCOMPtr<nsIURI> addressURL = parseResult.unwrap();
176 nsCString address = addressURL->GetSpecOrDefault();
177 // Step 2.6. If specifierKey ends with U+002F (/), and the serialization
178 // of addressURL does not end with U+002F (/), then:
179 if (StringEndsWith(specifierKey, u"/"_ns) &&
180 !StringEndsWith(address, "/"_ns)) {
181 // Step 2.6.1. The user agent may report a warning to the console
182 // indicating that an invalid address was given for the specifier key
183 // specifierKey; since specifierKey ends with a slash, the address needs
184 // to as well.
185 AutoTArray<nsString, 2> params;
186 params.AppendElement(specifierKey);
187 params.AppendElement(NS_ConvertUTF8toUTF16(address));
188 aWarning.Report("ImportMapAddressNotEndsWithSlash", params);
190 // Step 2.6.2. Set normalized[normalizedSpecifierKey] to null.
191 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
193 // Step 2.6.3. Continue.
194 continue;
197 LOG(("ImportMap::SortAndNormalizeSpecifierMap {%s, %s}",
198 NS_ConvertUTF16toUTF8(normalizedSpecifierKey).get(),
199 addressURL->GetSpecOrDefault().get()));
201 // Step 2.7. Set normalized[normalizedSpecifierKey] to addressURL.
202 normalized->insert_or_assign(normalizedSpecifierKey, addressURL);
205 // Step 3: Return the result of sorting normalized, with an entry a being
206 // less than an entry b if b’s key is code unit less than a’s key.
208 // Impl note: The sorting is done when inserting the entry.
209 return normalized;
212 // Check if it's a map defined in
213 // https://infra.spec.whatwg.org/#ordered-map
215 // If it is, *aIsMap will be set to true.
216 static bool IsMapObject(JSContext* aCx, JS::HandleValue aMapVal, bool* aIsMap) {
217 MOZ_ASSERT(aIsMap);
219 *aIsMap = false;
220 if (!aMapVal.isObject()) {
221 return true;
224 bool isArray;
225 if (!IsArrayObject(aCx, aMapVal, &isArray)) {
226 return false;
229 *aIsMap = !isArray;
230 return true;
233 // https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes
234 static UniquePtr<ScopeMap> SortAndNormalizeScopes(
235 JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
236 const ReportWarningHelper& aWarning) {
237 JS::Rooted<JS::IdVector> scopeKeys(aCx, JS::IdVector(aCx));
238 if (!JS_Enumerate(aCx, aOriginalMap, &scopeKeys)) {
239 return nullptr;
242 // Step 1. Let normalized be an empty map.
243 UniquePtr<ScopeMap> normalized = MakeUnique<ScopeMap>();
245 // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap,
246 for (size_t i = 0; i < scopeKeys.length(); i++) {
247 const JS::RootedId scopeKey(aCx, scopeKeys[i]);
248 nsAutoJSString scopePrefix;
249 NS_ENSURE_TRUE(scopePrefix.init(aCx, scopeKey), nullptr);
251 // Step 2.1. If potentialSpecifierMap is not an ordered map, then throw a
252 // TypeError indicating that the value of the scope with prefix scopePrefix
253 // needs to be a JSON object.
254 JS::RootedValue mapVal(aCx);
255 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, scopeKey, &mapVal),
256 nullptr);
258 bool isMap;
259 if (!IsMapObject(aCx, mapVal, &isMap)) {
260 return nullptr;
262 if (!isMap) {
263 const char16_t* scope = scopePrefix.get();
264 JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
265 JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, scope);
266 return nullptr;
269 // Step 2.2. Let scopePrefixURL be the result of URL parsing scopePrefix
270 // with baseURL.
271 nsCOMPtr<nsIURI> scopePrefixURL;
272 nsresult rv = NS_NewURI(getter_AddRefs(scopePrefixURL), scopePrefix,
273 nullptr, aBaseURL);
275 // Step 2.3. If scopePrefixURL is failure, then:
276 if (NS_FAILED(rv)) {
277 // Step 2.3.1. The user agent may report a warning to the console that
278 // the scope prefix URL was not parseable.
279 AutoTArray<nsString, 1> params;
280 params.AppendElement(scopePrefix);
281 aWarning.Report("ImportMapScopePrefixNotParseable", params);
283 // Step 2.3.2. Continue.
284 continue;
287 // Step 2.4. Let normalizedScopePrefix be the serialization of
288 // scopePrefixURL.
289 nsCString normalizedScopePrefix = scopePrefixURL->GetSpecOrDefault();
291 // Step 2.5. Set normalized[normalizedScopePrefix] to the result of sorting
292 // and normalizing a specifier map given potentialSpecifierMap and baseURL.
293 JS::RootedObject potentialSpecifierMap(aCx, &mapVal.toObject());
294 UniquePtr<SpecifierMap> specifierMap = SortAndNormalizeSpecifierMap(
295 aCx, potentialSpecifierMap, aBaseURL, aWarning);
296 if (!specifierMap) {
297 return nullptr;
300 normalized->insert_or_assign(normalizedScopePrefix,
301 std::move(specifierMap));
304 // Step 3. Return the result of sorting in descending order normalized, with
305 // an entry a being less than an entry b if a's key is code unit less than b's
306 // key.
308 // Impl note: The sorting is done when inserting the entry.
309 return normalized;
312 // https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
313 // static
314 UniquePtr<ImportMap> ImportMap::ParseString(
315 JSContext* aCx, SourceText<char16_t>& aInput, nsIURI* aBaseURL,
316 const ReportWarningHelper& aWarning) {
317 // Step 1. Let parsed be the result of parsing JSON into Infra values given
318 // input.
319 JS::Rooted<JS::Value> parsedVal(aCx);
320 if (!JS_ParseJSON(aCx, aInput.get(), aInput.length(), &parsedVal)) {
321 NS_WARNING("Parsing Import map string failed");
323 // If JS_ParseJSON fails we check if it throws a SyntaxError.
324 // If so we update the error message from JSON parser to make it more clear
325 // that the parsing of import map has failed.
326 MOZ_ASSERT(JS_IsExceptionPending(aCx));
327 JS::Rooted<JS::Value> exn(aCx);
328 if (!JS_GetPendingException(aCx, &exn)) {
329 return nullptr;
331 MOZ_ASSERT(exn.isObject());
332 JS::Rooted<JSObject*> obj(aCx, &exn.toObject());
333 JSErrorReport* err = JS_ErrorFromException(aCx, obj);
334 if (err->exnType == JSEXN_SYNTAXERR) {
335 JS_ClearPendingException(aCx);
336 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
337 JSMSG_IMPORT_MAPS_PARSE_FAILED,
338 err->message().c_str());
341 return nullptr;
344 // Step 2. If parsed is not an ordered map, then throw a TypeError indicating
345 // that the top-level value needs to be a JSON object.
346 bool isMap;
347 if (!IsMapObject(aCx, parsedVal, &isMap)) {
348 return nullptr;
350 if (!isMap) {
351 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
352 JSMSG_IMPORT_MAPS_NOT_A_MAP);
353 return nullptr;
356 JS::RootedObject parsedObj(aCx, &parsedVal.toObject());
357 JS::RootedValue importsVal(aCx);
358 if (!JS_GetProperty(aCx, parsedObj, "imports", &importsVal)) {
359 return nullptr;
362 // Step 3. Let sortedAndNormalizedImports be an empty ordered map.
364 // Impl note: If parsed["imports"] doesn't exist, we will allocate
365 // sortedAndNormalizedImports to an empty map in Step 8 below.
366 UniquePtr<SpecifierMap> sortedAndNormalizedImports = nullptr;
368 // Step 4. If parsed["imports"] exists, then:
369 if (!importsVal.isUndefined()) {
370 // Step 4.1. If parsed["imports"] is not an ordered map, then throw a
371 // TypeError indicating that the "imports" top-level key needs to be a JSON
372 // object.
373 bool isMap;
374 if (!IsMapObject(aCx, importsVal, &isMap)) {
375 return nullptr;
377 if (!isMap) {
378 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
379 JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP);
380 return nullptr;
383 // Step 4.2. Set sortedAndNormalizedImports to the result of sorting and
384 // normalizing a module specifier map given parsed["imports"] and baseURL.
385 JS::RootedObject importsObj(aCx, &importsVal.toObject());
386 sortedAndNormalizedImports =
387 SortAndNormalizeSpecifierMap(aCx, importsObj, aBaseURL, aWarning);
388 if (!sortedAndNormalizedImports) {
389 return nullptr;
393 JS::RootedValue scopesVal(aCx);
394 if (!JS_GetProperty(aCx, parsedObj, "scopes", &scopesVal)) {
395 return nullptr;
398 // Step 5. Let sortedAndNormalizedScopes be an empty ordered map.
400 // Impl note: If parsed["scopes"] doesn't exist, we will allocate
401 // sortedAndNormalizedScopes to an empty map in Step 8 below.
402 UniquePtr<ScopeMap> sortedAndNormalizedScopes = nullptr;
404 // Step 6. If parsed["scopes"] exists, then:
405 if (!scopesVal.isUndefined()) {
406 // Step 6.1. If parsed["scopes"] is not an ordered map, then throw a
407 // TypeError indicating that the "scopes" top-level key needs to be a JSON
408 // object.
409 bool isMap;
410 if (!IsMapObject(aCx, scopesVal, &isMap)) {
411 return nullptr;
413 if (!isMap) {
414 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
415 JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP);
416 return nullptr;
419 // Step 6.2. Set sortedAndNormalizedScopes to the result of sorting and
420 // normalizing scopes given parsed["scopes"] and baseURL.
421 JS::RootedObject scopesObj(aCx, &scopesVal.toObject());
422 sortedAndNormalizedScopes =
423 SortAndNormalizeScopes(aCx, scopesObj, aBaseURL, aWarning);
424 if (!sortedAndNormalizedScopes) {
425 return nullptr;
429 // Step 7. If parsed’s keys contains any items besides "imports" or
430 // "scopes", then the user agent should report a warning to the console
431 // indicating that an invalid top-level key was present in the import map.
432 JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
433 if (!JS_Enumerate(aCx, parsedObj, &keys)) {
434 return nullptr;
437 for (size_t i = 0; i < keys.length(); i++) {
438 const JS::RootedId key(aCx, keys[i]);
439 nsAutoJSString val;
440 NS_ENSURE_TRUE(val.init(aCx, key), nullptr);
441 if (val.EqualsLiteral("imports") || val.EqualsLiteral("scopes")) {
442 continue;
445 AutoTArray<nsString, 1> params;
446 params.AppendElement(val);
447 aWarning.Report("ImportMapInvalidTopLevelKey", params);
450 // Impl note: Create empty maps for sortedAndNormalizedImports and
451 // sortedAndNormalizedImports if they aren't allocated.
452 if (!sortedAndNormalizedImports) {
453 sortedAndNormalizedImports = MakeUnique<SpecifierMap>();
455 if (!sortedAndNormalizedScopes) {
456 sortedAndNormalizedScopes = MakeUnique<ScopeMap>();
459 // Step 8. Return an import map whose imports are
460 // sortedAndNormalizedImports and whose scopes scopes are
461 // sortedAndNormalizedScopes.
462 return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports),
463 std::move(sortedAndNormalizedScopes));
466 // https://url.spec.whatwg.org/#is-special
467 static bool IsSpecialScheme(nsIURI* aURI) {
468 nsAutoCString scheme;
469 aURI->GetScheme(scheme);
470 return scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") ||
471 scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") ||
472 scheme.EqualsLiteral("ws") || scheme.EqualsLiteral("wss");
475 // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match
476 static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch(
477 nsString& aNormalizedSpecifier, nsIURI* aAsURL,
478 const SpecifierMap* aSpecifierMap) {
479 // Step 1. For each specifierKey → resolutionResult of specifierMap,
480 for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) {
481 nsAutoString specifier{aNormalizedSpecifier};
482 nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString();
484 // Step 1.1. If specifierKey is normalizedSpecifier, then:
485 if (specifierKey.Equals(aNormalizedSpecifier)) {
486 // Step 1.1.1. If resolutionResult is null, then throw a TypeError
487 // indicating that resolution of specifierKey was blocked by a null entry.
488 // This will terminate the entire resolve a module specifier algorithm,
489 // without any further fallbacks.
490 if (!resolutionResult) {
491 LOG(
492 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
493 "specifierKey: %s, but resolution is null.",
494 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
495 NS_ConvertUTF16toUTF8(specifierKey).get()));
496 return Err(ResolveError::BlockedByNullEntry);
499 // Step 1.1.2. Assert: resolutionResult is a URL.
500 MOZ_ASSERT(resolutionResult);
502 // Step 1.1.3. Return resolutionResult.
503 return resolutionResult;
506 // Step 1.2. If all of the following are true:
507 // specifierKey ends with U+002F (/),
508 // specifierKey is a code unit prefix of normalizedSpecifier, and
509 // either asURL is null, or asURL is special
510 if (StringEndsWith(specifierKey, u"/"_ns) &&
511 StringBeginsWith(aNormalizedSpecifier, specifierKey) &&
512 (!aAsURL || IsSpecialScheme(aAsURL))) {
513 // Step 1.2.1. If resolutionResult is null, then throw a TypeError
514 // indicating that resolution of specifierKey was blocked by a null entry.
515 // This will terminate the entire resolve a module specifier algorithm,
516 // without any further fallbacks.
517 if (!resolutionResult) {
518 LOG(
519 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
520 "specifierKey: %s, but resolution is null.",
521 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
522 NS_ConvertUTF16toUTF8(specifierKey).get()));
523 return Err(ResolveError::BlockedByNullEntry);
526 // Step 1.2.2. Assert: resolutionResult is a URL.
527 MOZ_ASSERT(resolutionResult);
529 // Step 1.2.3. Let afterPrefix be the portion of normalizedSpecifier after
530 // the initial specifierKey prefix.
531 nsAutoString afterPrefix(
532 Substring(aNormalizedSpecifier, specifierKey.Length()));
534 // Step 1.2.4. Assert: resolutionResult, serialized, ends with U+002F (/),
535 // as enforced during parsing
536 MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(), "/"_ns));
538 // Step 1.2.5. Let url be the result of URL parsing afterPrefix with
539 // resolutionResult.
540 nsCOMPtr<nsIURI> url;
541 nsresult rv = NS_NewURI(getter_AddRefs(url), afterPrefix, nullptr,
542 resolutionResult);
544 // Step 1.2.6. If url is failure, then throw a TypeError indicating that
545 // resolution of normalizedSpecifier was blocked since the afterPrefix
546 // portion could not be URL-parsed relative to the resolutionResult mapped
547 // to by the specifierKey prefix.
549 // This will terminate the entire resolve a module specifier algorithm,
550 // without any further fallbacks.
551 if (NS_FAILED(rv)) {
552 LOG(
553 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
554 "specifierKey: %s, resolutionResult: %s, afterPrefix: %s, "
555 "but URL is not parsable.",
556 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
557 NS_ConvertUTF16toUTF8(specifierKey).get(),
558 resolutionResult->GetSpecOrDefault().get(),
559 NS_ConvertUTF16toUTF8(afterPrefix).get()));
560 return Err(ResolveError::BlockedByAfterPrefix);
563 // Step 1.2.7. Assert: url is a URL.
564 MOZ_ASSERT(url);
566 // Step 1.2.8. If the serialization of resolutionResult is not a code unit
567 // prefix of the serialization of url, then throw a TypeError indicating
568 // that resolution of normalizedSpecifier was blocked due to it
569 // backtracking above its prefix specifierKey.
571 // This will terminate the entire resolve a module specifier algorithm,
572 // without any further fallbacks.
573 if (!StringBeginsWith(url->GetSpecOrDefault(),
574 resolutionResult->GetSpecOrDefault())) {
575 LOG(
576 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
577 "specifierKey: %s, "
578 "url %s does not start with resolutionResult %s.",
579 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
580 NS_ConvertUTF16toUTF8(specifierKey).get(),
581 url->GetSpecOrDefault().get(),
582 resolutionResult->GetSpecOrDefault().get()));
583 return Err(ResolveError::BlockedByBacktrackingPrefix);
586 // Step 1.2.9. Return url.
587 return std::move(url);
591 // Step 2. Return null.
592 return nsCOMPtr<nsIURI>(nullptr);
595 // https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
596 // static
597 ResolveResult ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap,
598 ScriptLoaderInterface* aLoader,
599 LoadedScript* aScript,
600 const nsAString& aSpecifier) {
601 LOG(("ImportMap::ResolveModuleSpecifier specifier: %s",
602 NS_ConvertUTF16toUTF8(aSpecifier).get()));
603 nsCOMPtr<nsIURI> baseURL;
604 if (aScript && !aScript->IsEventScript()) {
605 baseURL = aScript->BaseURL();
606 } else {
607 baseURL = aLoader->GetBaseURI();
610 // Step 7. Let asURL be the result of resolving a URL-like module specifier
611 // given specifier and baseURL.
613 // Impl note: Step 6 is done below if aImportMap exists.
614 auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifier, baseURL);
615 nsCOMPtr<nsIURI> asURL;
616 if (parseResult.isOk()) {
617 asURL = parseResult.unwrap();
620 if (aImportMap) {
621 // Step 6. Let baseURLString be baseURL, serialized.
622 nsCString baseURLString = baseURL->GetSpecOrDefault();
624 // Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL
625 // is non-null; otherwise, specifier.
626 nsAutoString normalizedSpecifier =
627 asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault())
628 : nsAutoString{aSpecifier};
630 // Step 9. For each scopePrefix → scopeImports of importMap’s scopes,
631 for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) {
632 // Step 9.1. If scopePrefix is baseURLString, or if scopePrefix ends with
633 // U+002F (/) and scopePrefix is a code unit prefix of baseURLString,
634 // then:
635 if (scopePrefix.Equals(baseURLString) ||
636 (StringEndsWith(scopePrefix, "/"_ns) &&
637 StringBeginsWith(baseURLString, scopePrefix))) {
638 // Step 9.1.1. Let scopeImportsMatch be the result of resolving an
639 // imports match given normalizedSpecifier, asURL, and scopeImports.
640 auto result =
641 ResolveImportsMatch(normalizedSpecifier, asURL, scopeImports.get());
642 if (result.isErr()) {
643 return result.propagateErr();
646 nsCOMPtr<nsIURI> scopeImportsMatch = result.unwrap();
647 // Step 9.1.2. If scopeImportsMatch is not null, then return
648 // scopeImportsMatch.
649 if (scopeImportsMatch) {
650 LOG((
651 "ImportMap::ResolveModuleSpecifier returns scopeImportsMatch: %s",
652 scopeImportsMatch->GetSpecOrDefault().get()));
653 return WrapNotNull(scopeImportsMatch);
658 // Step 10. Let topLevelImportsMatch be the result of resolving an imports
659 // match given normalizedSpecifier, asURL, and importMap’s imports.
660 auto result = ResolveImportsMatch(normalizedSpecifier, asURL,
661 aImportMap->mImports.get());
662 if (result.isErr()) {
663 return result.propagateErr();
665 nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap();
667 // Step 11. If topLevelImportsMatch is not null, then return
668 // topLevelImportsMatch.
669 if (topLevelImportsMatch) {
670 LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s",
671 topLevelImportsMatch->GetSpecOrDefault().get()));
672 return WrapNotNull(topLevelImportsMatch);
676 // Step 12. At this point, the specifier was able to be turned in to a URL,
677 // but it wasn’t remapped to anything by importMap. If asURL is not null, then
678 // return asURL.
679 if (asURL) {
680 LOG(("ImportMap::ResolveModuleSpecifier returns asURL: %s",
681 asURL->GetSpecOrDefault().get()));
682 return WrapNotNull(asURL);
685 // Step 13. Throw a TypeError indicating that specifier was a bare specifier,
686 // but was not remapped to anything by importMap.
687 if (parseResult.unwrapErr() != ResolveError::FailureMayBeBare) {
688 // We may have failed to parse a non-bare specifier for another reason.
689 return Err(ResolveError::Failure);
692 return Err(ResolveError::InvalidBareSpecifier);
695 #undef LOG
696 #undef LOG_ENABLED
697 } // namespace JS::loader