tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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