Bug 1769170 [wpt PR 34049] - Update wpt metadata, a=testonly
[gecko.git] / js / loader / ImportMap.cpp
blob35764be8440715720407155cb22570a6c5296ff4
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://wicg.github.io/import-maps/#parse-a-url-like-import-specifier
45 static already_AddRefed<nsIURI> ParseURLLikeImportSpecifier(
46 const nsAString& aSpecifier, 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 nullptr;
62 // Step 1.3. Return url.
63 return uri.forget();
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 nullptr;
73 // Step 4. Return url.
74 return uri.forget();
77 // https://wicg.github.io/import-maps/#normalize-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 parsing a URL-like import specifier, given
94 // specifierKey and baseURL.
95 nsCOMPtr<nsIURI> url = ParseURLLikeImportSpecifier(aSpecifierKey, aBaseURL);
97 // Step 3. If url is not null, then return the serialization of url.
98 if (url) {
99 aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault());
100 return;
103 // Step 4. Return specifierKey.
104 aRetVal = aSpecifierKey;
107 // https://wicg.github.io/import-maps/#sort-and-normalize-a-specifier-map
108 static UniquePtr<SpecifierMap> SortAndNormalizeSpecifierMap(
109 JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
110 const ReportWarningHelper& aWarning) {
111 // Step 1. Let normalized be an empty map.
112 UniquePtr<SpecifierMap> normalized = MakeUnique<SpecifierMap>();
114 JS::Rooted<JS::IdVector> specifierKeys(aCx, JS::IdVector(aCx));
115 if (!JS_Enumerate(aCx, aOriginalMap, &specifierKeys)) {
116 return nullptr;
119 // Step 2. For each specifierKey → value of originalMap,
120 for (size_t i = 0; i < specifierKeys.length(); i++) {
121 const JS::RootedId specifierId(aCx, specifierKeys[i]);
122 nsAutoJSString specifierKey;
123 NS_ENSURE_TRUE(specifierKey.init(aCx, specifierId), nullptr);
125 // Step 2.1. Let normalizedSpecifierKey be the result of normalizing a
126 // specifier key given specifierKey and baseURL.
127 nsString normalizedSpecifierKey;
128 NormalizeSpecifierKey(specifierKey, aBaseURL, aWarning,
129 normalizedSpecifierKey);
131 // Step 2.2. If normalizedSpecifierKey is null, then continue.
132 if (normalizedSpecifierKey.IsEmpty()) {
133 continue;
136 JS::RootedValue idVal(aCx);
137 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, specifierId, &idVal),
138 nullptr);
139 // Step 2.3. If value is not a string, then:
140 if (!idVal.isString()) {
141 // Step 2.3.1. Report a warning to the console that addresses need to
142 // be strings.
143 aWarning.Report("ImportMapAddressesNotStrings");
145 // Step 2.3.2. Set normalized[normalizedSpecifierKey] to null.
146 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
148 // Step 2.3.3. Continue.
149 continue;
152 nsAutoJSString value;
153 NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr);
155 // Step 2.4. Let addressURL be the result of parsing a URL-like import
156 // specifier given value and baseURL.
157 nsCOMPtr<nsIURI> addressURL = ParseURLLikeImportSpecifier(value, aBaseURL);
159 // Step 2.5. If addressURL is null, then:
160 if (!addressURL) {
161 // Step 2.5.1. Report a warning to the console that the address was
162 // invalid.
163 AutoTArray<nsString, 1> params;
164 params.AppendElement(value);
165 aWarning.Report("ImportMapInvalidAddress", params);
167 // Step 2.5.2. Set normalized[normalizedSpecifierKey] to null.
168 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
170 // Step 2.5.3. Continue.
171 continue;
174 nsCString address = addressURL->GetSpecOrDefault();
175 // Step 2.6. If specifierKey ends with U+002F (/), and the serialization
176 // of addressURL does not end with U+002F (/), then:
177 if (StringEndsWith(specifierKey, u"/"_ns) &&
178 !StringEndsWith(address, "/"_ns)) {
179 // Step 2.6.1. Report a warning to the console that an invalid address
180 // was given for the specifier key specifierKey; since specifierKey
181 // ended in a slash, the address needs to as well.
182 AutoTArray<nsString, 2> params;
183 params.AppendElement(specifierKey);
184 params.AppendElement(NS_ConvertUTF8toUTF16(address));
185 aWarning.Report("ImportMapAddressNotEndsWithSlash", params);
187 // Step 2.6.2. Set normalized[normalizedSpecifierKey] to null.
188 normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
190 // Step 2.6.3. Continue.
191 continue;
194 LOG(("ImportMap::SortAndNormalizeSpecifierMap {%s, %s}",
195 NS_ConvertUTF16toUTF8(normalizedSpecifierKey).get(),
196 addressURL->GetSpecOrDefault().get()));
198 // Step 2.7. Set normalized[normalizedSpecifierKey] to addressURL.
199 normalized->insert_or_assign(normalizedSpecifierKey, addressURL);
202 // Step 3: Return the result of sorting normalized, with an entry a being
203 // less than an entry b if b’s key is code unit less than a’s key.
205 // Impl note: The sorting is done when inserting the entry.
206 return normalized;
209 // Check if it's a map defined in
210 // https://infra.spec.whatwg.org/#ordered-map
212 // If it is, *aIsMap will be set to true.
213 static bool IsMapObject(JSContext* aCx, JS::HandleValue aMapVal, bool* aIsMap) {
214 MOZ_ASSERT(aIsMap);
216 *aIsMap = false;
217 if (!aMapVal.isObject()) {
218 return true;
221 bool isArray;
222 if (!IsArrayObject(aCx, aMapVal, &isArray)) {
223 return false;
226 *aIsMap = !isArray;
227 return true;
230 // https://wicg.github.io/import-maps/#sort-and-normalize-scopes
231 static UniquePtr<ScopeMap> SortAndNormalizeScopes(
232 JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
233 const ReportWarningHelper& aWarning) {
234 JS::Rooted<JS::IdVector> scopeKeys(aCx, JS::IdVector(aCx));
235 if (!JS_Enumerate(aCx, aOriginalMap, &scopeKeys)) {
236 return nullptr;
239 // Step 1. Let normalized be an empty map.
240 UniquePtr<ScopeMap> normalized = MakeUnique<ScopeMap>();
242 // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap,
243 for (size_t i = 0; i < scopeKeys.length(); i++) {
244 const JS::RootedId scopeKey(aCx, scopeKeys[i]);
245 nsAutoJSString scopePrefix;
246 NS_ENSURE_TRUE(scopePrefix.init(aCx, scopeKey), nullptr);
248 // Step 2.1. If potentialSpecifierMap is not a map, then throw a TypeError
249 // indicating that the value of the scope with prefix scopePrefix needs to
250 // be a JSON object.
251 JS::RootedValue mapVal(aCx);
252 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, scopeKey, &mapVal),
253 nullptr);
255 bool isMap;
256 if (!IsMapObject(aCx, mapVal, &isMap)) {
257 return nullptr;
259 if (!isMap) {
260 const char16_t* scope = scopePrefix.get();
261 JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
262 JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, scope);
263 return nullptr;
266 // Step 2.2. Let scopePrefixURL be the result of parsing scopePrefix with
267 // baseURL as the base URL.
268 nsCOMPtr<nsIURI> scopePrefixURL;
269 nsresult rv = NS_NewURI(getter_AddRefs(scopePrefixURL), scopePrefix,
270 nullptr, aBaseURL);
272 // Step 2.3. If scopePrefixURL is failure, then:
273 if (NS_FAILED(rv)) {
274 // Step 2.3.1. Report a warning to the console that the scope prefix URL
275 // was not parseable.
276 AutoTArray<nsString, 1> params;
277 params.AppendElement(scopePrefix);
278 aWarning.Report("ImportMapScopePrefixNotParseable", params);
280 // Step 2.3.2. Continue.
281 continue;
284 // Step 2.4. Let normalizedScopePrefix be the serialization of
285 // scopePrefixURL.
286 nsCString normalizedScopePrefix = scopePrefixURL->GetSpecOrDefault();
288 // Step 2.5. Set normalized[normalizedScopePrefix] to the result of sorting
289 // and normalizing a specifier map given potentialSpecifierMap and baseURL.
290 JS::RootedObject potentialSpecifierMap(aCx, &mapVal.toObject());
291 UniquePtr<SpecifierMap> specifierMap = SortAndNormalizeSpecifierMap(
292 aCx, potentialSpecifierMap, aBaseURL, aWarning);
293 if (!specifierMap) {
294 return nullptr;
297 normalized->insert_or_assign(normalizedScopePrefix,
298 std::move(specifierMap));
301 // Step 3. Return the result of sorting normalized, with an entry a being less
302 // than an entry b if b’s key is code unit less than a’s key.
304 // Impl note: The sorting is done when inserting the entry.
305 return normalized;
308 // https://wicg.github.io/import-maps/#parse-an-import-map-string
309 // static
310 UniquePtr<ImportMap> ImportMap::ParseString(
311 JSContext* aCx, SourceText<char16_t>& aInput, nsIURI* aBaseURL,
312 const ReportWarningHelper& aWarning) {
313 // Step 1. Let parsed be the result of parsing JSON into Infra values given
314 // input.
315 JS::Rooted<JS::Value> parsedVal(aCx);
316 if (!JS_ParseJSON(aCx, aInput.get(), aInput.length(), &parsedVal)) {
317 // If JS_ParseJSON fail it will throw SyntaxError.
318 NS_WARNING("Parsing Import map string failed");
319 return nullptr;
322 // Step 2. If parsed is not a map, then throw a TypeError indicating that
323 // the top-level value needs to be a JSON object.
324 bool isMap;
325 if (!IsMapObject(aCx, parsedVal, &isMap)) {
326 return nullptr;
328 if (!isMap) {
329 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
330 JSMSG_IMPORT_MAPS_NOT_A_MAP);
331 return nullptr;
334 JS::RootedObject parsedObj(aCx, &parsedVal.toObject());
335 JS::RootedValue importsVal(aCx);
336 if (!JS_GetProperty(aCx, parsedObj, "imports", &importsVal)) {
337 return nullptr;
340 // Step 3. Let sortedAndNormalizedImports be an empty map.
342 // Impl note: If parsed["imports"] doesn't exist, we will allocate
343 // sortedAndNormalizedImports to an empty map in Step 8 below.
344 UniquePtr<SpecifierMap> sortedAndNormalizedImports = nullptr;
346 // Step 4. If parsed["imports"] exists, then:
347 if (!importsVal.isUndefined()) {
348 // Step 4.1. If parsed["imports"] is not a map, then throw a TypeError
349 // indicating that the "imports" top-level key needs to be a JSON object.
350 bool isMap;
351 if (!IsMapObject(aCx, importsVal, &isMap)) {
352 return nullptr;
354 if (!isMap) {
355 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
356 JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP);
357 return nullptr;
360 // Step 4.2. Set sortedAndNormalizedImports to the result of sorting and
361 // normalizing a specifier map given parsed["imports"] and baseURL.
362 JS::RootedObject importsObj(aCx, &importsVal.toObject());
363 sortedAndNormalizedImports =
364 SortAndNormalizeSpecifierMap(aCx, importsObj, aBaseURL, aWarning);
365 if (!sortedAndNormalizedImports) {
366 return nullptr;
370 JS::RootedValue scopesVal(aCx);
371 if (!JS_GetProperty(aCx, parsedObj, "scopes", &scopesVal)) {
372 return nullptr;
375 // Step 5. Let sortedAndNormalizedScopes be an empty map.
377 // Impl note: If parsed["scopes"] doesn't exist, we will allocate
378 // sortedAndNormalizedScopes to an empty map in Step 8 below.
379 UniquePtr<ScopeMap> sortedAndNormalizedScopes = nullptr;
381 // Step 6. If parsed["scopes"] exists, then:
382 if (!scopesVal.isUndefined()) {
383 // Step 6.1. If parsed["scopes"] is not a map, then throw a TypeError
384 // indicating that the "scopes" top-level key needs to be a JSON object.
385 bool isMap;
386 if (!IsMapObject(aCx, scopesVal, &isMap)) {
387 return nullptr;
389 if (!isMap) {
390 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
391 JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP);
392 return nullptr;
395 // Step 6.2. Set sortedAndNormalizedScopes to the result of sorting and
396 // normalizing scopes given parsed["scopes"] and baseURL.
397 JS::RootedObject scopesObj(aCx, &scopesVal.toObject());
398 sortedAndNormalizedScopes =
399 SortAndNormalizeScopes(aCx, scopesObj, aBaseURL, aWarning);
400 if (!sortedAndNormalizedScopes) {
401 return nullptr;
405 // Step 7. If parsed’s keys contains any items besides "imports" or
406 // "scopes", report a warning to the console that an invalid top-level key
407 // was present in the import map.
408 JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
409 if (!JS_Enumerate(aCx, parsedObj, &keys)) {
410 return nullptr;
413 for (size_t i = 0; i < keys.length(); i++) {
414 const JS::RootedId key(aCx, keys[i]);
415 nsAutoJSString val;
416 NS_ENSURE_TRUE(val.init(aCx, key), nullptr);
417 if (val.EqualsLiteral("imports") || val.EqualsLiteral("scopes")) {
418 continue;
421 AutoTArray<nsString, 1> params;
422 params.AppendElement(val);
423 aWarning.Report("ImportMapInvalidTopLevelKey", params);
426 // Impl note: Create empty maps for sortedAndNormalizedImports and
427 // sortedAndNormalizedImports if they aren't allocated.
428 if (!sortedAndNormalizedImports) {
429 sortedAndNormalizedImports = MakeUnique<SpecifierMap>();
431 if (!sortedAndNormalizedScopes) {
432 sortedAndNormalizedScopes = MakeUnique<ScopeMap>();
435 // Step 8. Return the import map whose imports are
436 // sortedAndNormalizedImports and whose scopes scopes are
437 // sortedAndNormalizedScopes.
438 return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports),
439 std::move(sortedAndNormalizedScopes));
442 // https://url.spec.whatwg.org/#is-special
443 static bool IsSpecialScheme(nsIURI* aURI) {
444 nsAutoCString scheme;
445 aURI->GetScheme(scheme);
446 return scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") ||
447 scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") ||
448 scheme.EqualsLiteral("ws") || scheme.EqualsLiteral("wss");
451 // https://wicg.github.io/import-maps/#resolve-an-imports-match
452 static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch(
453 nsString& aNormalizedSpecifier, nsIURI* aAsURL,
454 const SpecifierMap* aSpecifierMap) {
455 // Step 1. For each specifierKey → resolutionResult of specifierMap,
456 for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) {
457 nsAutoString specifier{aNormalizedSpecifier};
458 nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString();
460 // Step 1.1. If specifierKey is normalizedSpecifier, then:
461 if (specifierKey.Equals(aNormalizedSpecifier)) {
462 // Step 1.1.1. If resolutionResult is null, then throw a TypeError
463 // indicating that resolution of specifierKey was blocked by a null entry.
464 // This will terminate the entire resolve a module specifier algorithm,
465 // without any further fallbacks.
466 if (!resolutionResult) {
467 LOG(
468 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
469 "specifierKey: %s, but resolution is null.",
470 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
471 NS_ConvertUTF16toUTF8(specifierKey).get()));
472 return Err(ResolveError::BlockedByNullEntry);
475 // Step 1.1.2. Assert: resolutionResult is a URL.
476 MOZ_ASSERT(resolutionResult);
478 // Step 1.1.3. Return resolutionResult.
479 return resolutionResult;
482 // Step 1.2. If all of the following are true:
483 // specifierKey ends with U+002F (/),
484 // normalizedSpecifier starts with specifierKey, and
485 // either asURL is null, or asURL is special
486 if (StringEndsWith(specifierKey, u"/"_ns) &&
487 StringBeginsWith(aNormalizedSpecifier, specifierKey) &&
488 (!aAsURL || IsSpecialScheme(aAsURL))) {
489 // Step 1.2.1. If resolutionResult is null, then throw a TypeError
490 // indicating that resolution of specifierKey was blocked by a null entry.
491 // This will terminate the entire resolve a module specifier algorithm,
492 // without any further fallbacks.
493 if (!resolutionResult) {
494 LOG(
495 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
496 "specifierKey: %s, but resolution is null.",
497 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
498 NS_ConvertUTF16toUTF8(specifierKey).get()));
499 return Err(ResolveError::BlockedByNullEntry);
502 // Step 1.2.2. Assert: resolutionResult is a URL.
503 MOZ_ASSERT(resolutionResult);
505 // Step 1.2.3. Let afterPrefix be the portion of normalizedSpecifier after
506 // the initial specifierKey prefix.
507 nsAutoString afterPrefix(
508 Substring(aNormalizedSpecifier, specifierKey.Length()));
510 // Step 1.2.4. Assert: resolutionResult, serialized, ends with "/", as
511 // enforced during parsing.
512 MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(), "/"_ns));
514 // Step 1.2.5. Let url be the result of parsing afterPrefix relative to
515 // the base URL resolutionResult.
516 nsCOMPtr<nsIURI> url;
517 nsresult rv = NS_NewURI(getter_AddRefs(url), afterPrefix, nullptr,
518 resolutionResult);
520 // Step 1.2.6. If url is failure, then throw a TypeError indicating that
521 // resolution of normalizedSpecifier was blocked since the afterPrefix
522 // portion could not be URL-parsed relative to the resolutionResult mapped
523 // to by the specifierKey prefix.
525 // This will terminate the entire resolve a module specifier algorithm,
526 // without any further fallbacks.
527 if (NS_FAILED(rv)) {
528 LOG(
529 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
530 "specifierKey: %s, resolutionResult: %s, afterPrefix: %s, "
531 "but URL is not parsable.",
532 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
533 NS_ConvertUTF16toUTF8(specifierKey).get(),
534 resolutionResult->GetSpecOrDefault().get(),
535 NS_ConvertUTF16toUTF8(afterPrefix).get()));
536 return Err(ResolveError::BlockedByAfterPrefix);
539 // Step 1.2.7. Assert: url is a URL.
540 MOZ_ASSERT(url);
542 // Step 1.2.8. If the serialization of url does not start with the
543 // serialization of resolutionResult, then throw a TypeError indicating
544 // that resolution of normalizedSpecifier was blocked due to it
545 // backtracking above its prefix specifierKey.
547 // This will terminate the entire resolve a module specifier algorithm,
548 // without any further fallbacks.
549 if (!StringBeginsWith(url->GetSpecOrDefault(),
550 resolutionResult->GetSpecOrDefault())) {
551 LOG(
552 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
553 "specifierKey: %s, "
554 "url %s does not start with resolutionResult %s.",
555 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
556 NS_ConvertUTF16toUTF8(specifierKey).get(),
557 url->GetSpecOrDefault().get(),
558 resolutionResult->GetSpecOrDefault().get()));
559 return Err(ResolveError::BlockedByBacktrackingPrefix);
562 // Step 1.2.9. Return url.
563 return std::move(url);
567 // Step 2. Return null.
568 return nsCOMPtr<nsIURI>(nullptr);
571 // https://wicg.github.io/import-maps/#resolve-a-module-specifier
572 // static
573 ResolveResult ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap,
574 ScriptLoaderInterface* aLoader,
575 LoadedScript* aScript,
576 const nsAString& aSpecifier) {
577 LOG(("ImportMap::ResolveModuleSpecifier specifier: %s",
578 NS_ConvertUTF16toUTF8(aSpecifier).get()));
579 nsCOMPtr<nsIURI> baseURL;
580 if (aScript) {
581 baseURL = aScript->BaseURL();
582 } else {
583 baseURL = aLoader->GetBaseURI();
586 // Step 6. Let asURL be the result of parsing a URL-like import specifier
587 // given specifier and baseURL.
589 // Impl note: Step 5 is done below if aImportMap exists.
590 nsCOMPtr<nsIURI> asURL = ParseURLLikeImportSpecifier(aSpecifier, baseURL);
592 if (aImportMap) {
593 // Step 5. Let baseURLString be baseURL, serialized.
594 nsCString baseURLString = baseURL->GetSpecOrDefault();
596 // Step 7. Let normalizedSpecifier be the serialization of asURL, if asURL
597 // is non-null; otherwise, specifier.
598 nsAutoString normalizedSpecifier =
599 asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault())
600 : nsAutoString{aSpecifier};
602 // Step 8. For each scopePrefix → scopeImports of importMap’s scopes,
603 for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) {
604 // Step 8.1. If scopePrefix is baseURLString, or if scopePrefix ends with
605 // U+002F (/) and baseURLString starts with scopePrefix, then:
606 if (scopePrefix.Equals(baseURLString) ||
607 (StringEndsWith(scopePrefix, "/"_ns) &&
608 StringBeginsWith(baseURLString, scopePrefix))) {
609 // Step 8.1.1. Let scopeImportsMatch be the result of resolving an
610 // imports match given normalizedSpecifier, asURL, and scopeImports.
611 auto result =
612 ResolveImportsMatch(normalizedSpecifier, asURL, scopeImports.get());
613 if (result.isErr()) {
614 return result.propagateErr();
617 nsCOMPtr<nsIURI> scopeImportsMatch = result.unwrap();
618 // Step 8.1.2. If scopeImportsMatch is not null, then return
619 // scopeImportsMatch.
620 if (scopeImportsMatch) {
621 LOG((
622 "ImportMap::ResolveModuleSpecifier returns scopeImportsMatch: %s",
623 scopeImportsMatch->GetSpecOrDefault().get()));
624 return WrapNotNull(scopeImportsMatch);
629 // Step 9. Let topLevelImportsMatch be the result of resolving an imports
630 // match given normalizedSpecifier, asURL, and importMap’s imports.
631 auto result = ResolveImportsMatch(normalizedSpecifier, asURL,
632 aImportMap->mImports.get());
633 if (result.isErr()) {
634 return result.propagateErr();
636 nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap();
638 // Step 10. If topLevelImportsMatch is not null, then return
639 // topLevelImportsMatch.
640 if (topLevelImportsMatch) {
641 LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s",
642 topLevelImportsMatch->GetSpecOrDefault().get()));
643 return WrapNotNull(topLevelImportsMatch);
647 // Step 11. At this point, the specifier was able to be turned in to a URL,
648 // but it wasn’t remapped to anything by importMap. If asURL is not null, then
649 // return asURL.
650 if (asURL) {
651 LOG(("ImportMap::ResolveModuleSpecifier returns asURL: %s",
652 asURL->GetSpecOrDefault().get()));
653 return WrapNotNull(asURL);
656 // Step 12. Throw a TypeError indicating that specifier was a bare specifier,
657 // but was not remapped to anything by importMap.
658 return Err(ResolveError::InvalidBareSpecifier);
661 #undef LOG
662 #undef LOG_ENABLED
663 } // namespace JS::loader