ImportMap.cpp (31320B)
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/. */ 6 7 #include "ImportMap.h" 8 9 #include "js/Array.h" // IsArrayObject 10 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 11 #include "js/JSON.h" // JS_ParseJSON 12 #include "js/PropertyDescriptor.h" // JS::PropertyDescriptor 13 #include "LoadedScript.h" 14 #include "ModuleLoaderBase.h" // ScriptLoaderInterface 15 #include "nsContentUtils.h" 16 #include "nsIScriptElement.h" 17 #include "nsIScriptError.h" 18 #include "nsJSUtils.h" // nsAutoJSString 19 #include "nsNetUtil.h" // NS_NewURI 20 #include "ScriptLoadRequest.h" 21 22 using JS::SourceText; 23 using mozilla::Err; 24 using mozilla::LazyLogModule; 25 using mozilla::MakeUnique; 26 using mozilla::UniquePtr; 27 using mozilla::WrapNotNull; 28 29 namespace JS::loader { 30 31 LazyLogModule ImportMap::gImportMapLog("ImportMap"); 32 33 #undef LOG 34 #define LOG(args) \ 35 MOZ_LOG(ImportMap::gImportMapLog, mozilla::LogLevel::Debug, args) 36 37 #define LOG_ENABLED() \ 38 MOZ_LOG_TEST(ImportMap::gImportMapLog, mozilla::LogLevel::Debug) 39 40 void ReportWarningHelper::Report(const char* aMessageName, 41 const nsTArray<nsString>& aParams) const { 42 mLoader->ReportWarningToConsole(mRequest, aMessageName, aParams); 43 } 44 45 // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-url-like-module-specifier 46 static ResolveResult ResolveURLLikeModuleSpecifier(const nsAString& aSpecifier, 47 nsIURI* aBaseURL) { 48 nsCOMPtr<nsIURI> uri; 49 nsresult rv; 50 51 // Step 1. If specifier starts with "/", "./", or "../", then: 52 if (StringBeginsWith(aSpecifier, u"/"_ns) || 53 StringBeginsWith(aSpecifier, u"./"_ns) || 54 StringBeginsWith(aSpecifier, u"../"_ns)) { 55 // Step 1.1. Let url be the result of parsing specifier with baseURL as the 56 // base URL. 57 rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aBaseURL); 58 // Step 1.2. If url is failure, then return null. 59 if (NS_FAILED(rv)) { 60 return Err(ResolveError::Failure); 61 } 62 63 // Step 1.3. Return url. 64 return WrapNotNull(uri); 65 } 66 67 // Step 2. Let url be the result of parsing specifier (with no base URL). 68 rv = NS_NewURI(getter_AddRefs(uri), aSpecifier); 69 // Step 3. If url is failure, then return null. 70 if (NS_FAILED(rv)) { 71 return Err(ResolveError::FailureMayBeBare); 72 } 73 74 // Step 4. Return url. 75 return WrapNotNull(uri); 76 } 77 78 // https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-specifier-key 79 static void NormalizeSpecifierKey(const nsAString& aSpecifierKey, 80 nsIURI* aBaseURL, 81 const ReportWarningHelper& aWarning, 82 nsAString& aRetVal) { 83 // Step 1. If specifierKey is the empty string, then: 84 if (aSpecifierKey.IsEmpty()) { 85 // Step 1.1. Report a warning to the console that specifier keys cannot be 86 // the empty string. 87 aWarning.Report("ImportMapEmptySpecifierKeys"); 88 89 // Step 1.2. Return null. 90 aRetVal = EmptyString(); 91 return; 92 } 93 94 // Step 2. Let url be the result of resolving a URL-like module specifier, 95 // given specifierKey and baseURL. 96 auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifierKey, aBaseURL); 97 98 // Step 3. If url is not null, then return the serialization of url. 99 if (parseResult.isOk()) { 100 nsCOMPtr<nsIURI> url = parseResult.unwrap(); 101 aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault()); 102 return; 103 } 104 105 // Step 4. Return specifierKey. 106 aRetVal = aSpecifierKey; 107 } 108 109 // https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map 110 static UniquePtr<SpecifierMap> SortAndNormalizeSpecifierMap( 111 JSContext* aCx, HandleObject aOriginalMap, nsIURI* aBaseURL, 112 const ReportWarningHelper& aWarning) { 113 // Step 1. Let normalized be an empty ordered map. 114 UniquePtr<SpecifierMap> normalized = MakeUnique<SpecifierMap>(); 115 116 Rooted<IdVector> specifierKeys(aCx, IdVector(aCx)); 117 if (!JS_Enumerate(aCx, aOriginalMap, &specifierKeys)) { 118 return nullptr; 119 } 120 121 // Step 2. For each specifierKey → value of originalMap, 122 for (size_t i = 0; i < specifierKeys.length(); i++) { 123 const RootedId specifierId(aCx, specifierKeys[i]); 124 nsAutoJSString specifierKey; 125 NS_ENSURE_TRUE(specifierKey.init(aCx, specifierId), nullptr); 126 127 // Step 2.1. Let normalizedSpecifierKey be the result of normalizing a 128 // specifier key given specifierKey and baseURL. 129 nsString normalizedSpecifierKey; 130 NormalizeSpecifierKey(specifierKey, aBaseURL, aWarning, 131 normalizedSpecifierKey); 132 133 // Step 2.2. If normalizedSpecifierKey is null, then continue. 134 if (normalizedSpecifierKey.IsEmpty()) { 135 continue; 136 } 137 138 RootedValue idVal(aCx); 139 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, specifierId, &idVal), 140 nullptr); 141 // Step 2.3. If value is not a string, then: 142 if (!idVal.isString()) { 143 // Step 2.3.1. The user agent may report a warning to the console 144 // indicating that addresses need to be strings. 145 aWarning.Report("ImportMapAddressesNotStrings"); 146 147 // Step 2.3.2. Set normalized[normalizedSpecifierKey] to null. 148 normalized->insert_or_assign(normalizedSpecifierKey, nullptr); 149 150 // Step 2.3.3. Continue. 151 continue; 152 } 153 154 nsAutoJSString value; 155 NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr); 156 157 // Step 2.4. Let addressURL be the result of resolving a URL-like module 158 // specifier given value and baseURL. 159 auto parseResult = ResolveURLLikeModuleSpecifier(value, aBaseURL); 160 161 // Step 2.5. If addressURL is null, then: 162 if (parseResult.isErr()) { 163 // Step 2.5.1. The user agent may report a warning to the console 164 // indicating that the address was invalid. 165 AutoTArray<nsString, 1> params; 166 params.AppendElement(value); 167 aWarning.Report("ImportMapInvalidAddress", params); 168 169 // Step 2.5.2. Set normalized[normalizedSpecifierKey] to null. 170 normalized->insert_or_assign(normalizedSpecifierKey, nullptr); 171 172 // Step 2.5.3. Continue. 173 continue; 174 } 175 176 nsCOMPtr<nsIURI> addressURL = parseResult.unwrap(); 177 nsCString address = addressURL->GetSpecOrDefault(); 178 // Step 2.6. If specifierKey ends with U+002F (/), and the serialization 179 // of addressURL does not end with U+002F (/), then: 180 if (StringEndsWith(specifierKey, u"/"_ns) && 181 !StringEndsWith(address, "/"_ns)) { 182 // Step 2.6.1. The user agent may report a warning to the console 183 // indicating that an invalid address was given for the specifier key 184 // specifierKey; since specifierKey ends with a slash, the address needs 185 // to as well. 186 AutoTArray<nsString, 2> params; 187 params.AppendElement(specifierKey); 188 params.AppendElement(NS_ConvertUTF8toUTF16(address)); 189 aWarning.Report("ImportMapAddressNotEndsWithSlash", params); 190 191 // Step 2.6.2. Set normalized[normalizedSpecifierKey] to null. 192 normalized->insert_or_assign(normalizedSpecifierKey, nullptr); 193 194 // Step 2.6.3. Continue. 195 continue; 196 } 197 198 LOG(("ImportMap::SortAndNormalizeSpecifierMap {%s, %s}", 199 NS_ConvertUTF16toUTF8(normalizedSpecifierKey).get(), 200 addressURL->GetSpecOrDefault().get())); 201 202 // Step 2.7. Set normalized[normalizedSpecifierKey] to addressURL. 203 normalized->insert_or_assign(normalizedSpecifierKey, addressURL); 204 } 205 206 // Step 3: Return the result of sorting normalized, with an entry a being 207 // less than an entry b if b’s key is code unit less than a’s key. 208 // 209 // Impl note: The sorting is done when inserting the entry. 210 return normalized; 211 } 212 213 // Check if it's a map defined in 214 // https://infra.spec.whatwg.org/#ordered-map 215 // 216 // If it is, *aIsMap will be set to true. 217 static bool IsMapObject(JSContext* aCx, HandleValue aMapVal, bool* aIsMap) { 218 MOZ_ASSERT(aIsMap); 219 220 *aIsMap = false; 221 if (!aMapVal.isObject()) { 222 return true; 223 } 224 225 bool isArray; 226 if (!IsArrayObject(aCx, aMapVal, &isArray)) { 227 return false; 228 } 229 230 *aIsMap = !isArray; 231 return true; 232 } 233 234 // https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes 235 static UniquePtr<ScopeMap> SortAndNormalizeScopes( 236 JSContext* aCx, HandleObject aOriginalMap, nsIURI* aBaseURL, 237 const ReportWarningHelper& aWarning) { 238 Rooted<IdVector> scopeKeys(aCx, IdVector(aCx)); 239 if (!JS_Enumerate(aCx, aOriginalMap, &scopeKeys)) { 240 return nullptr; 241 } 242 243 // Step 1. Let normalized be an empty map. 244 UniquePtr<ScopeMap> normalized = MakeUnique<ScopeMap>(); 245 246 // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap, 247 for (size_t i = 0; i < scopeKeys.length(); i++) { 248 const RootedId scopeKey(aCx, scopeKeys[i]); 249 nsAutoJSString scopePrefix; 250 NS_ENSURE_TRUE(scopePrefix.init(aCx, scopeKey), nullptr); 251 252 // Step 2.1. If potentialSpecifierMap is not an ordered map, then throw a 253 // TypeError indicating that the value of the scope with prefix scopePrefix 254 // needs to be a JSON object. 255 RootedValue mapVal(aCx); 256 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, scopeKey, &mapVal), 257 nullptr); 258 259 bool isMap; 260 if (!IsMapObject(aCx, mapVal, &isMap)) { 261 return nullptr; 262 } 263 if (!isMap) { 264 const char16_t* scope = scopePrefix.get(); 265 JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr, 266 JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, scope); 267 return nullptr; 268 } 269 270 // Step 2.2. Let scopePrefixURL be the result of URL parsing scopePrefix 271 // with baseURL. 272 nsCOMPtr<nsIURI> scopePrefixURL; 273 nsresult rv = NS_NewURI(getter_AddRefs(scopePrefixURL), scopePrefix, 274 nullptr, aBaseURL); 275 276 // Step 2.3. If scopePrefixURL is failure, then: 277 if (NS_FAILED(rv)) { 278 // Step 2.3.1. The user agent may report a warning to the console that 279 // the scope prefix URL was not parseable. 280 AutoTArray<nsString, 1> params; 281 params.AppendElement(scopePrefix); 282 aWarning.Report("ImportMapScopePrefixNotParseable", params); 283 284 // Step 2.3.2. Continue. 285 continue; 286 } 287 288 // Step 2.4. Let normalizedScopePrefix be the serialization of 289 // scopePrefixURL. 290 nsCString normalizedScopePrefix = scopePrefixURL->GetSpecOrDefault(); 291 292 // Step 2.5. Set normalized[normalizedScopePrefix] to the result of sorting 293 // and normalizing a specifier map given potentialSpecifierMap and baseURL. 294 RootedObject potentialSpecifierMap(aCx, &mapVal.toObject()); 295 UniquePtr<SpecifierMap> specifierMap = SortAndNormalizeSpecifierMap( 296 aCx, potentialSpecifierMap, aBaseURL, aWarning); 297 if (!specifierMap) { 298 return nullptr; 299 } 300 301 normalized->insert_or_assign(normalizedScopePrefix, 302 std::move(specifierMap)); 303 } 304 305 // Step 3. Return the result of sorting in descending order normalized, with 306 // an entry a being less than an entry b if a's key is code unit less than b's 307 // key. 308 // 309 // Impl note: The sorting is done when inserting the entry. 310 return normalized; 311 } 312 313 // https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-module-integrity-map 314 static UniquePtr<IntegrityMap> NormalizeIntegrity( 315 JSContext* aCx, HandleObject aOriginalMap, nsIURI* aBaseURL, 316 const ReportWarningHelper& aWarning) { 317 // Step 1. Let normalized be an empty ordered map. 318 UniquePtr<IntegrityMap> normalized = MakeUnique<IntegrityMap>(); 319 320 Rooted<IdVector> keys(aCx, IdVector(aCx)); 321 if (!JS_Enumerate(aCx, aOriginalMap, &keys)) { 322 return nullptr; 323 } 324 325 // Step 2. For each key → value of originalMap, 326 for (size_t i = 0; i < keys.length(); i++) { 327 const RootedId keyId(aCx, keys[i]); 328 nsAutoJSString key; 329 NS_ENSURE_TRUE(key.init(aCx, keyId), nullptr); 330 331 // Step 2.1. Let resolvedURL be the result of resolving a URL-like module 332 // specifier given key and baseURL. 333 auto parseResult = ResolveURLLikeModuleSpecifier(key, aBaseURL); 334 335 // Step 2.2. If resolvedURL is null, then: 336 if (parseResult.isErr()) { 337 // Step 2.2.1. The user agent may report a warning to the console 338 // indicating that the key failed to resolve. 339 AutoTArray<nsString, 1> params; 340 params.AppendElement(key); 341 aWarning.Report("ImportMapInvalidAddress", params); 342 343 // Step 2.2.2. Continue. 344 continue; 345 } 346 347 nsCOMPtr<nsIURI> resolvedURL = parseResult.unwrap(); 348 349 RootedValue idVal(aCx); 350 NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, keyId, &idVal), 351 nullptr); 352 353 // Step 2.3. If value is not a string, then: 354 if (!idVal.isString()) { 355 // Step 2.3.1. The user agent may report a warning to the console 356 // indicating that integrity metadata values need to be strings. 357 aWarning.Report("ImportMapIntegrityValuesNotStrings"); 358 // Step 2.3.2. Continue. 359 continue; 360 } 361 362 nsAutoJSString value; 363 NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr); 364 365 // Step 2.4. Set normalized[resolvedURL] to value. 366 normalized->insert_or_assign(resolvedURL->GetSpecOrDefault(), value); 367 } 368 369 // Step 3: Return normalized. 370 // 371 // Impl note: The sorting is done when inserting the entry. 372 return normalized; 373 } 374 375 static bool GetOwnProperty(JSContext* aCx, Handle<JSObject*> aObj, 376 const char* aName, MutableHandle<Value> aValueOut) { 377 JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(aCx); 378 if (!JS_GetOwnPropertyDescriptor(aCx, aObj, aName, &desc)) { 379 return false; 380 } 381 382 if (desc.isNothing()) { 383 return true; 384 } 385 MOZ_ASSERT(!desc->isAccessorDescriptor()); 386 aValueOut.set(desc->value()); 387 return true; 388 } 389 390 // https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string 391 // static 392 UniquePtr<ImportMap> ImportMap::ParseString( 393 JSContext* aCx, SourceText<char16_t>& aInput, nsIURI* aBaseURL, 394 const ReportWarningHelper& aWarning) { 395 // Step 1. Let parsed be the result of parsing JSON into Infra values given 396 // input. 397 Rooted<Value> parsedVal(aCx); 398 if (!JS_ParseJSON(aCx, aInput.get(), aInput.length(), &parsedVal)) { 399 NS_WARNING("Parsing Import map string failed"); 400 401 // If JS_ParseJSON fails we check if it throws a SyntaxError. 402 // If so we update the error message from JSON parser to make it more clear 403 // that the parsing of import map has failed. 404 MOZ_ASSERT(JS_IsExceptionPending(aCx)); 405 Rooted<Value> exn(aCx); 406 if (!JS_GetPendingException(aCx, &exn)) { 407 return nullptr; 408 } 409 MOZ_ASSERT(exn.isObject()); 410 Rooted<JSObject*> obj(aCx, &exn.toObject()); 411 JSErrorReport* err = JS_ErrorFromException(aCx, obj); 412 if (err->exnType == JSEXN_SYNTAXERR) { 413 JS_ClearPendingException(aCx); 414 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 415 JSMSG_IMPORT_MAPS_PARSE_FAILED, 416 err->message().c_str()); 417 } 418 419 return nullptr; 420 } 421 422 // Step 2. If parsed is not an ordered map, then throw a TypeError indicating 423 // that the top-level value needs to be a JSON object. 424 bool isMap; 425 if (!IsMapObject(aCx, parsedVal, &isMap)) { 426 return nullptr; 427 } 428 if (!isMap) { 429 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 430 JSMSG_IMPORT_MAPS_NOT_A_MAP); 431 return nullptr; 432 } 433 434 RootedObject parsedObj(aCx, &parsedVal.toObject()); 435 RootedValue importsVal(aCx); 436 if (!GetOwnProperty(aCx, parsedObj, "imports", &importsVal)) { 437 return nullptr; 438 } 439 440 // Step 3. Let sortedAndNormalizedImports be an empty ordered map. 441 // 442 // Impl note: If parsed["imports"] doesn't exist, we will allocate 443 // sortedAndNormalizedImports to an empty map in Step 8 below. 444 UniquePtr<SpecifierMap> sortedAndNormalizedImports = nullptr; 445 446 // Step 4. If parsed["imports"] exists, then: 447 if (!importsVal.isUndefined()) { 448 // Step 4.1. If parsed["imports"] is not an ordered map, then throw a 449 // TypeError indicating that the "imports" top-level key needs to be a JSON 450 // object. 451 bool isMap; 452 if (!IsMapObject(aCx, importsVal, &isMap)) { 453 return nullptr; 454 } 455 if (!isMap) { 456 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 457 JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP); 458 return nullptr; 459 } 460 461 // Step 4.2. Set sortedAndNormalizedImports to the result of sorting and 462 // normalizing a module specifier map given parsed["imports"] and baseURL. 463 RootedObject importsObj(aCx, &importsVal.toObject()); 464 sortedAndNormalizedImports = 465 SortAndNormalizeSpecifierMap(aCx, importsObj, aBaseURL, aWarning); 466 if (!sortedAndNormalizedImports) { 467 return nullptr; 468 } 469 } 470 471 RootedValue scopesVal(aCx); 472 if (!GetOwnProperty(aCx, parsedObj, "scopes", &scopesVal)) { 473 return nullptr; 474 } 475 476 // Step 5. Let sortedAndNormalizedScopes be an empty ordered map. 477 // 478 // Impl note: If parsed["scopes"] doesn't exist, we will allocate 479 // sortedAndNormalizedScopes to an empty map in Step 8 below. 480 UniquePtr<ScopeMap> sortedAndNormalizedScopes = nullptr; 481 482 // Step 6. If parsed["scopes"] exists, then: 483 if (!scopesVal.isUndefined()) { 484 // Step 6.1. If parsed["scopes"] is not an ordered map, then throw a 485 // TypeError indicating that the "scopes" top-level key needs to be a JSON 486 // object. 487 bool isMap; 488 if (!IsMapObject(aCx, scopesVal, &isMap)) { 489 return nullptr; 490 } 491 if (!isMap) { 492 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 493 JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP); 494 return nullptr; 495 } 496 497 // Step 6.2. Set sortedAndNormalizedScopes to the result of sorting and 498 // normalizing scopes given parsed["scopes"] and baseURL. 499 RootedObject scopesObj(aCx, &scopesVal.toObject()); 500 sortedAndNormalizedScopes = 501 SortAndNormalizeScopes(aCx, scopesObj, aBaseURL, aWarning); 502 if (!sortedAndNormalizedScopes) { 503 return nullptr; 504 } 505 } 506 507 RootedValue integrityVal(aCx); 508 if (!GetOwnProperty(aCx, parsedObj, "integrity", &integrityVal)) { 509 return nullptr; 510 } 511 512 // Step 7. Let normalizedIntegrity be an empty ordered map. 513 // 514 // Impl note: If parsed["integrity"] doesn't exist, we will allocate 515 // normalizedIntegrity to an empty map in Step 8 below. 516 UniquePtr<IntegrityMap> normalizedIntegrity = nullptr; 517 518 // Step 8. If parsed["integrity"] exists, then: 519 if (!integrityVal.isUndefined()) { 520 // Step 6.1. If parsed["integrity"] is not an ordered map, then throw a 521 // TypeError indicating that the "integrity" top-level key needs to be a 522 // JSON object. 523 bool isMap; 524 if (!IsMapObject(aCx, integrityVal, &isMap)) { 525 return nullptr; 526 } 527 if (!isMap) { 528 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 529 JSMSG_IMPORT_MAPS_INTEGRITY_NOT_A_MAP); 530 return nullptr; 531 } 532 533 // Step 6.2. Set normalizedIntegrity to the result of normalizing 534 // integrities given parsed["integrity"] and baseURL. 535 RootedObject integrityObj(aCx, &integrityVal.toObject()); 536 normalizedIntegrity = 537 NormalizeIntegrity(aCx, integrityObj, aBaseURL, aWarning); 538 if (!normalizedIntegrity) { 539 return nullptr; 540 } 541 } 542 543 // Step 9. If parsed's keys contains any items besides "imports", "scopes", 544 // or "integrity", then the user agent should report a warning to the console 545 // indicating that an invalid top-level key was present in the import map. 546 Rooted<IdVector> keys(aCx, IdVector(aCx)); 547 if (!JS_Enumerate(aCx, parsedObj, &keys)) { 548 return nullptr; 549 } 550 551 for (size_t i = 0; i < keys.length(); i++) { 552 const RootedId key(aCx, keys[i]); 553 nsAutoJSString val; 554 NS_ENSURE_TRUE(val.init(aCx, key), nullptr); 555 if (val.EqualsLiteral("imports") || val.EqualsLiteral("scopes") || 556 val.EqualsLiteral("integrity")) { 557 continue; 558 } 559 560 AutoTArray<nsString, 1> params; 561 params.AppendElement(val); 562 aWarning.Report("ImportMapInvalidTopLevelKey", params); 563 } 564 565 // Impl note: Create empty maps for sortedAndNormalizedImports, 566 // sortedAndNormalizedScopes, and normalizedIntegrity if they 567 // aren't allocated. 568 if (!sortedAndNormalizedImports) { 569 sortedAndNormalizedImports = MakeUnique<SpecifierMap>(); 570 } 571 if (!sortedAndNormalizedScopes) { 572 sortedAndNormalizedScopes = MakeUnique<ScopeMap>(); 573 } 574 if (!normalizedIntegrity) { 575 normalizedIntegrity = MakeUnique<IntegrityMap>(); 576 } 577 578 // Step 10. Return an import map whose imports are 579 // sortedAndNormalizedImports, whose scopes are 580 // sortedAndNormalizedScopes, and whose integrity 581 // are normalizedIntegrity. 582 return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports), 583 std::move(sortedAndNormalizedScopes), 584 std::move(normalizedIntegrity)); 585 } 586 587 // https://url.spec.whatwg.org/#is-special 588 static bool IsSpecialScheme(nsIURI* aURI) { 589 nsAutoCString scheme; 590 aURI->GetScheme(scheme); 591 return scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") || 592 scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") || 593 scheme.EqualsLiteral("ws") || scheme.EqualsLiteral("wss"); 594 } 595 596 // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match 597 static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch( 598 nsString& aNormalizedSpecifier, nsIURI* aAsURL, 599 const SpecifierMap* aSpecifierMap) { 600 // Step 1. For each specifierKey → resolutionResult of specifierMap, 601 for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) { 602 nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString(); 603 604 // Step 1.1. If specifierKey is normalizedSpecifier, then: 605 if (specifierKey.Equals(aNormalizedSpecifier)) { 606 // Step 1.1.1. If resolutionResult is null, then throw a TypeError 607 // indicating that resolution of specifierKey was blocked by a null entry. 608 // This will terminate the entire resolve a module specifier algorithm, 609 // without any further fallbacks. 610 if (!resolutionResult) { 611 LOG( 612 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, " 613 "specifierKey: %s, but resolution is null.", 614 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(), 615 NS_ConvertUTF16toUTF8(specifierKey).get())); 616 return Err(ResolveError::BlockedByNullEntry); 617 } 618 619 // Step 1.1.2. Assert: resolutionResult is a URL. 620 MOZ_ASSERT(resolutionResult); 621 622 // Step 1.1.3. Return resolutionResult. 623 return resolutionResult; 624 } 625 626 // Step 1.2. If all of the following are true: 627 // specifierKey ends with U+002F (/), 628 // specifierKey is a code unit prefix of normalizedSpecifier, and 629 // either asURL is null, or asURL is special 630 if (StringEndsWith(specifierKey, u"/"_ns) && 631 StringBeginsWith(aNormalizedSpecifier, specifierKey) && 632 (!aAsURL || IsSpecialScheme(aAsURL))) { 633 // Step 1.2.1. If resolutionResult is null, then throw a TypeError 634 // indicating that resolution of specifierKey was blocked by a null entry. 635 // This will terminate the entire resolve a module specifier algorithm, 636 // without any further fallbacks. 637 if (!resolutionResult) { 638 LOG( 639 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, " 640 "specifierKey: %s, but resolution is null.", 641 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(), 642 NS_ConvertUTF16toUTF8(specifierKey).get())); 643 return Err(ResolveError::BlockedByNullEntry); 644 } 645 646 // Step 1.2.2. Assert: resolutionResult is a URL. 647 MOZ_ASSERT(resolutionResult); 648 649 // Step 1.2.3. Let afterPrefix be the portion of normalizedSpecifier after 650 // the initial specifierKey prefix. 651 nsAutoString afterPrefix( 652 Substring(aNormalizedSpecifier, specifierKey.Length())); 653 654 // Step 1.2.4. Assert: resolutionResult, serialized, ends with U+002F (/), 655 // as enforced during parsing 656 MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(), "/"_ns)); 657 658 // Step 1.2.5. Let url be the result of URL parsing afterPrefix with 659 // resolutionResult. 660 nsCOMPtr<nsIURI> url; 661 nsresult rv = NS_NewURI(getter_AddRefs(url), afterPrefix, nullptr, 662 resolutionResult); 663 664 // Step 1.2.6. If url is failure, then throw a TypeError indicating that 665 // resolution of normalizedSpecifier was blocked since the afterPrefix 666 // portion could not be URL-parsed relative to the resolutionResult mapped 667 // to by the specifierKey prefix. 668 // 669 // This will terminate the entire resolve a module specifier algorithm, 670 // without any further fallbacks. 671 if (NS_FAILED(rv)) { 672 LOG( 673 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, " 674 "specifierKey: %s, resolutionResult: %s, afterPrefix: %s, " 675 "but URL is not parsable.", 676 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(), 677 NS_ConvertUTF16toUTF8(specifierKey).get(), 678 resolutionResult->GetSpecOrDefault().get(), 679 NS_ConvertUTF16toUTF8(afterPrefix).get())); 680 return Err(ResolveError::BlockedByAfterPrefix); 681 } 682 683 // Step 1.2.7. Assert: url is a URL. 684 MOZ_ASSERT(url); 685 686 // Step 1.2.8. If the serialization of resolutionResult is not a code unit 687 // prefix of the serialization of url, then throw a TypeError indicating 688 // that resolution of normalizedSpecifier was blocked due to it 689 // backtracking above its prefix specifierKey. 690 // 691 // This will terminate the entire resolve a module specifier algorithm, 692 // without any further fallbacks. 693 if (!StringBeginsWith(url->GetSpecOrDefault(), 694 resolutionResult->GetSpecOrDefault())) { 695 LOG( 696 ("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, " 697 "specifierKey: %s, " 698 "url %s does not start with resolutionResult %s.", 699 NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(), 700 NS_ConvertUTF16toUTF8(specifierKey).get(), 701 url->GetSpecOrDefault().get(), 702 resolutionResult->GetSpecOrDefault().get())); 703 return Err(ResolveError::BlockedByBacktrackingPrefix); 704 } 705 706 // Step 1.2.9. Return url. 707 return std::move(url); 708 } 709 } 710 711 // Step 2. Return null. 712 return nsCOMPtr<nsIURI>(nullptr); 713 } 714 715 // https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier 716 // static 717 ResolveResult ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap, 718 ScriptLoaderInterface* aLoader, 719 LoadedScript* aScript, 720 const nsAString& aSpecifier) { 721 LOG(("ImportMap::ResolveModuleSpecifier specifier: %s", 722 NS_ConvertUTF16toUTF8(aSpecifier).get())); 723 nsCOMPtr<nsIURI> baseURL; 724 if (aScript && !aScript->IsEventScript()) { 725 baseURL = aScript->BaseURL(); 726 } else { 727 baseURL = aLoader->GetBaseURI(); 728 } 729 730 // Step 7. Let asURL be the result of resolving a URL-like module specifier 731 // given specifier and baseURL. 732 // 733 // Impl note: Step 6 is done below if aImportMap exists. 734 auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifier, baseURL); 735 nsCOMPtr<nsIURI> asURL; 736 if (parseResult.isOk()) { 737 asURL = parseResult.unwrap(); 738 } 739 740 if (aImportMap) { 741 // Step 6. Let baseURLString be baseURL, serialized. 742 nsCString baseURLString = baseURL->GetSpecOrDefault(); 743 744 // Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL 745 // is non-null; otherwise, specifier. 746 nsAutoString normalizedSpecifier = 747 asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault()) 748 : nsAutoString{aSpecifier}; 749 750 // Step 9. For each scopePrefix → scopeImports of importMap’s scopes, 751 for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) { 752 // Step 9.1. If scopePrefix is baseURLString, or if scopePrefix ends with 753 // U+002F (/) and scopePrefix is a code unit prefix of baseURLString, 754 // then: 755 if (scopePrefix.Equals(baseURLString) || 756 (StringEndsWith(scopePrefix, "/"_ns) && 757 StringBeginsWith(baseURLString, scopePrefix))) { 758 // Step 9.1.1. Let scopeImportsMatch be the result of resolving an 759 // imports match given normalizedSpecifier, asURL, and scopeImports. 760 auto result = 761 ResolveImportsMatch(normalizedSpecifier, asURL, scopeImports.get()); 762 if (result.isErr()) { 763 return result.propagateErr(); 764 } 765 766 nsCOMPtr<nsIURI> scopeImportsMatch = result.unwrap(); 767 // Step 9.1.2. If scopeImportsMatch is not null, then return 768 // scopeImportsMatch. 769 if (scopeImportsMatch) { 770 LOG(( 771 "ImportMap::ResolveModuleSpecifier returns scopeImportsMatch: %s", 772 scopeImportsMatch->GetSpecOrDefault().get())); 773 return WrapNotNull(scopeImportsMatch); 774 } 775 } 776 } 777 778 // Step 10. Let topLevelImportsMatch be the result of resolving an imports 779 // match given normalizedSpecifier, asURL, and importMap’s imports. 780 auto result = ResolveImportsMatch(normalizedSpecifier, asURL, 781 aImportMap->mImports.get()); 782 if (result.isErr()) { 783 return result.propagateErr(); 784 } 785 nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap(); 786 787 // Step 11. If topLevelImportsMatch is not null, then return 788 // topLevelImportsMatch. 789 if (topLevelImportsMatch) { 790 LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s", 791 topLevelImportsMatch->GetSpecOrDefault().get())); 792 return WrapNotNull(topLevelImportsMatch); 793 } 794 } 795 796 // Step 12. At this point, the specifier was able to be turned in to a URL, 797 // but it wasn’t remapped to anything by importMap. If asURL is not null, then 798 // return asURL. 799 if (asURL) { 800 LOG(("ImportMap::ResolveModuleSpecifier returns asURL: %s", 801 asURL->GetSpecOrDefault().get())); 802 return WrapNotNull(asURL); 803 } 804 805 // Step 13. Throw a TypeError indicating that specifier was a bare specifier, 806 // but was not remapped to anything by importMap. 807 if (parseResult.unwrapErr() != ResolveError::FailureMayBeBare) { 808 // We may have failed to parse a non-bare specifier for another reason. 809 return Err(ResolveError::Failure); 810 } 811 812 return Err(ResolveError::InvalidBareSpecifier); 813 } 814 815 mozilla::Maybe<nsString> ImportMap::LookupIntegrity(ImportMap* aImportMap, 816 nsIURI* aURL) { 817 auto it = aImportMap->mIntegrity->find(aURL->GetSpecOrDefault()); 818 if (it == aImportMap->mIntegrity->end()) { 819 return mozilla::Nothing(); 820 } 821 822 return mozilla::Some(it->second); 823 } 824 825 #undef LOG 826 #undef LOG_ENABLED 827 } // namespace JS::loader