tor-browser

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

SRICheck.cpp (18114B)


      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 "SRICheck.h"
      8 
      9 #include "mozilla/Base64.h"
     10 #include "mozilla/LoadTainting.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/dom/SRILogHelper.h"
     13 #include "mozilla/dom/SRIMetadata.h"
     14 #include "nsContentUtils.h"
     15 #include "nsIChannel.h"
     16 #include "nsIConsoleReportCollector.h"
     17 #include "nsIHttpChannel.h"
     18 #include "nsIScriptError.h"
     19 #include "nsIURI.h"
     20 #include "nsNetUtil.h"
     21 #include "nsWhitespaceTokenizer.h"
     22 
     23 #define SRIVERBOSE(args) \
     24  MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Verbose, args)
     25 #define SRILOG(args) \
     26  MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args)
     27 #define SRIERROR(args) \
     28  MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args)
     29 
     30 namespace mozilla::dom {
     31 
     32 static void GetChannelRequestURI(nsIChannel* aChannel, nsACString& aSourceUri) {
     33  nsCOMPtr<nsIURI> originalURI;
     34  aChannel->GetOriginalURI(getter_AddRefs(originalURI));
     35  if (originalURI) {
     36    originalURI->GetAsciiSpec(aSourceUri);
     37  }
     38 }
     39 
     40 static void GetReferrerSpec(nsIChannel* aChannel, nsACString& aReferrer) {
     41  nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(aChannel);
     42  if (!httpChan) {
     43    return;
     44  }
     45  nsCOMPtr<nsIReferrerInfo> referrer = httpChan->GetReferrerInfo();
     46  if (!referrer) {
     47    return;
     48  }
     49  nsCOMPtr<nsIURI> original = referrer->GetOriginalReferrer();
     50  if (!original) {
     51    return;
     52  }
     53  original->GetSpec(aReferrer);
     54 }
     55 
     56 /**
     57 * Returns whether or not the sub-resource about to be loaded is eligible
     58 * for integrity checks. If it's not, the checks will be skipped and the
     59 * sub-resource will be loaded.
     60 */
     61 static nsresult IsEligible(nsIChannel* aChannel,
     62                           mozilla::LoadTainting aTainting,
     63                           nsIConsoleReportCollector* aReporter) {
     64  NS_ENSURE_ARG_POINTER(aReporter);
     65 
     66  if (!aChannel) {
     67    SRILOG(("SRICheck::IsEligible, null channel"));
     68    return NS_ERROR_SRI_NOT_ELIGIBLE;
     69  }
     70 
     71  // Was the sub-resource loaded via CORS?
     72  if (aTainting == LoadTainting::CORS) {
     73    SRILOG(("SRICheck::IsEligible, CORS mode"));
     74    return NS_OK;
     75  }
     76 
     77  // Is the sub-resource same-origin?
     78  if (aTainting == LoadTainting::Basic) {
     79    SRILOG(("SRICheck::IsEligible, same-origin"));
     80    return NS_OK;
     81  }
     82  SRILOG(("SRICheck::IsEligible, NOT same-origin"));
     83 
     84  nsAutoCString requestSpec;
     85  GetChannelRequestURI(aChannel, requestSpec);
     86  nsAutoCString referrer;
     87  GetReferrerSpec(aChannel, referrer);
     88  aReporter->AddConsoleReport(
     89      nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
     90      nsContentUtils::eSECURITY_PROPERTIES, referrer, 0, 0,
     91      "IneligibleResource"_ns, {NS_ConvertUTF8toUTF16(requestSpec)});
     92  return NS_ERROR_SRI_NOT_ELIGIBLE;
     93 }
     94 
     95 /* static */
     96 nsresult SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
     97                                     const nsACString& aSourceFileURI,
     98                                     nsIConsoleReportCollector* aReporter,
     99                                     SRIMetadata* outMetadata) {
    100  NS_ENSURE_ARG_POINTER(outMetadata);
    101  MOZ_ASSERT(outMetadata->IsEmpty());  // caller must pass empty metadata
    102 
    103  NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
    104  SRILOG(("SRICheck::IntegrityMetadata, metadataList=%s", metadataList.get()));
    105 
    106  // the integrity attribute is a list of whitespace-separated hashes
    107  // and options so we need to look at them one by one and pick the
    108  // strongest (valid) one
    109  nsCWhitespaceTokenizer tokenizer(metadataList);
    110  nsAutoCString token;
    111  while (tokenizer.hasMoreTokens()) {
    112    token = tokenizer.nextToken();
    113 
    114    SRIMetadata metadata(token);
    115    if (aReporter && metadata.IsMalformed()) {
    116      aReporter->AddConsoleReport(
    117          nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
    118          nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
    119          "MalformedIntegrityHash"_ns, {NS_ConvertUTF8toUTF16(token)});
    120    } else if (aReporter && !metadata.IsAlgorithmSupported()) {
    121      nsAutoCString alg;
    122      metadata.GetAlgorithm(&alg);
    123      aReporter->AddConsoleReport(
    124          nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
    125          nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
    126          "UnsupportedHashAlg"_ns, {NS_ConvertUTF8toUTF16(alg)});
    127    }
    128 
    129    nsAutoCString alg1, alg2;
    130    if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
    131      outMetadata->GetAlgorithm(&alg1);
    132      metadata.GetAlgorithm(&alg2);
    133    }
    134    if (*outMetadata == metadata) {
    135      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
    136              alg1.get(), alg2.get()));
    137      *outMetadata += metadata;  // add new hash to strongest metadata
    138    } else if (*outMetadata < metadata) {
    139      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
    140              alg1.get(), alg2.get()));
    141      *outMetadata = metadata;  // replace strongest metadata with current
    142    }
    143  }
    144 
    145  outMetadata->mIntegrityString = aMetadataList;
    146 
    147  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
    148    if (outMetadata->IsValid()) {
    149      nsAutoCString alg;
    150      outMetadata->GetAlgorithm(&alg);
    151      SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
    152    } else if (outMetadata->IsEmpty()) {
    153      SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
    154    } else {
    155      SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
    156    }
    157  }
    158  return NS_OK;
    159 }
    160 
    161 //////////////////////////////////////////////////////////////
    162 //
    163 //////////////////////////////////////////////////////////////
    164 SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
    165                                           nsIChannel* aChannel,
    166                                           nsIConsoleReportCollector* aReporter)
    167    : mCryptoHash(nullptr),
    168      mBytesHashed(0),
    169      mHashLength(0),
    170      mHashType('\0'),
    171      mInvalidMetadata(false),
    172      mComplete(false) {
    173  MOZ_ASSERT(!aMetadata.IsEmpty());  // should be checked by caller
    174  MOZ_ASSERT(aReporter);
    175 
    176  if (!aMetadata.IsValid()) {
    177    nsAutoCString referrer;
    178    GetReferrerSpec(aChannel, referrer);
    179    aReporter->AddConsoleReport(nsIScriptError::warningFlag,
    180                                "Sub-resource Integrity"_ns,
    181                                nsContentUtils::eSECURITY_PROPERTIES, referrer,
    182                                0, 0, "NoValidMetadata"_ns, {});
    183    mInvalidMetadata = true;
    184    return;  // ignore invalid metadata for forward-compatibility
    185  }
    186 
    187  aMetadata.GetHashType(&mHashType, &mHashLength);
    188 }
    189 
    190 nsresult SRICheckDataVerifier::EnsureCryptoHash() {
    191  MOZ_ASSERT(!mInvalidMetadata);
    192 
    193  if (mCryptoHash) {
    194    return NS_OK;
    195  }
    196 
    197  nsCOMPtr<nsICryptoHash> cryptoHash;
    198  nsresult rv = NS_NewCryptoHash(mHashType, getter_AddRefs(cryptoHash));
    199  NS_ENSURE_SUCCESS(rv, rv);
    200 
    201  mCryptoHash = cryptoHash;
    202  return NS_OK;
    203 }
    204 
    205 nsresult SRICheckDataVerifier::Update(Span<const uint8_t> aBytes) {
    206  return Update(aBytes.size(), aBytes.data());
    207 }
    208 
    209 nsresult SRICheckDataVerifier::Update(uint32_t aStringLen,
    210                                      const uint8_t* aString) {
    211  NS_ENSURE_ARG_POINTER(aString);
    212  if (mInvalidMetadata) {
    213    return NS_OK;  // ignoring any data updates, see mInvalidMetadata usage
    214  }
    215 
    216  nsresult rv;
    217  rv = EnsureCryptoHash();
    218  NS_ENSURE_SUCCESS(rv, rv);
    219 
    220  mBytesHashed += aStringLen;
    221 
    222  return mCryptoHash->Update(aString, aStringLen);
    223 }
    224 
    225 nsresult SRICheckDataVerifier::Finish() {
    226  if (mInvalidMetadata || mComplete) {
    227    return NS_OK;  // already finished or invalid metadata
    228  }
    229 
    230  nsresult rv;
    231  rv = EnsureCryptoHash();  // we need computed hash even for 0-length data
    232  NS_ENSURE_SUCCESS(rv, rv);
    233 
    234  rv = mCryptoHash->Finish(false, mComputedHash);
    235  mCryptoHash = nullptr;
    236  mComplete = true;
    237  return rv;
    238 }
    239 
    240 nsresult SRICheckDataVerifier::VerifyHash(
    241    nsIChannel* aChannel, const SRIMetadata& aMetadata, uint32_t aHashIndex,
    242    nsIConsoleReportCollector* aReporter) {
    243  NS_ENSURE_ARG_POINTER(aReporter);
    244 
    245  nsAutoCString base64Hash;
    246  aMetadata.GetHash(aHashIndex, &base64Hash);
    247  SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u]=%s", aHashIndex,
    248          base64Hash.get()));
    249 
    250  nsAutoCString binaryHash;
    251 
    252  // We're decoding the supplied hash twice. Trying base64 first.
    253  nsresult rv = Base64Decode(base64Hash, binaryHash);
    254 
    255  if (NS_FAILED(rv)) {
    256    SRILOG(
    257        ("SRICheckDataVerifier::VerifyHash, base64 decoding failed. Trying "
    258         "base64url next."));
    259    FallibleTArray<uint8_t> decoded;
    260    rv = Base64URLDecode(base64Hash, Base64URLDecodePaddingPolicy::Ignore,
    261                         decoded);
    262    if (NS_FAILED(rv)) {
    263      SRILOG(
    264          ("SRICheckDataVerifier::VerifyHash, base64url decoding failed too. "
    265           "Bailing out."));
    266      nsAutoCString referrer;
    267      GetReferrerSpec(aChannel, referrer);
    268      // if neither succeeded, we can bail out and warn
    269      aReporter->AddConsoleReport(
    270          nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
    271          nsContentUtils::eSECURITY_PROPERTIES, referrer, 0, 0,
    272          "InvalidIntegrityBase64"_ns, {});
    273      return NS_ERROR_SRI_CORRUPT;
    274    }
    275    binaryHash.Assign(reinterpret_cast<const char*>(decoded.Elements()),
    276                      decoded.Length());
    277    SRILOG(
    278        ("SRICheckDataVerifier::VerifyHash, decoded supplied base64url hash "
    279         "successfully."));
    280  } else {
    281    SRILOG(
    282        ("SRICheckDataVerifier::VerifyHash, decoded supplied base64 hash "
    283         "successfully."));
    284  }
    285 
    286  uint32_t hashLength;
    287  int8_t hashType;
    288  aMetadata.GetHashType(&hashType, &hashLength);
    289  if (binaryHash.Length() != hashLength) {
    290    SRILOG(
    291        ("SRICheckDataVerifier::VerifyHash, supplied base64(url) hash was "
    292         "incorrect length after decoding."));
    293    nsAutoCString referrer;
    294    GetReferrerSpec(aChannel, referrer);
    295    aReporter->AddConsoleReport(nsIScriptError::errorFlag,
    296                                "Sub-resource Integrity"_ns,
    297                                nsContentUtils::eSECURITY_PROPERTIES, referrer,
    298                                0, 0, "InvalidIntegrityLength"_ns, {});
    299    return NS_ERROR_SRI_CORRUPT;
    300  }
    301 
    302  // the decoded supplied hash should match our computed binary hash.
    303  if (!binaryHash.Equals(mComputedHash)) {
    304    SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] did not match",
    305            aHashIndex));
    306    return NS_ERROR_SRI_CORRUPT;
    307  }
    308 
    309  SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] verified successfully",
    310          aHashIndex));
    311  return NS_OK;
    312 }
    313 
    314 nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
    315                                      nsIChannel* aChannel,
    316                                      nsIConsoleReportCollector* aReporter) {
    317  MOZ_ASSERT(aChannel);
    318  nsCOMPtr loadInfo = aChannel->LoadInfo();
    319  return Verify(aMetadata, aChannel, loadInfo->GetTainting(), aReporter);
    320 }
    321 
    322 nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
    323                                      nsIChannel* aChannel,
    324                                      LoadTainting aLoadTainting,
    325                                      nsIConsoleReportCollector* aReporter) {
    326  NS_ENSURE_ARG_POINTER(aReporter);
    327 
    328  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
    329    nsAutoCString requestURL;
    330    aChannel->GetName(requestURL);
    331    SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%zu)",
    332            requestURL.get(), mBytesHashed));
    333  }
    334 
    335  nsresult rv = Finish();
    336  NS_ENSURE_SUCCESS(rv, rv);
    337 
    338  if (NS_FAILED(IsEligible(aChannel, aLoadTainting, aReporter))) {
    339    return NS_ERROR_SRI_NOT_ELIGIBLE;
    340  }
    341 
    342  if (mInvalidMetadata) {
    343    return NS_OK;  // ignore invalid metadata for forward-compatibility
    344  }
    345 
    346  for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
    347    if (NS_SUCCEEDED(VerifyHash(aChannel, aMetadata, i, aReporter))) {
    348      return NS_OK;  // stop at the first valid hash
    349    }
    350  }
    351 
    352  nsAutoCString alg;
    353  aMetadata.GetAlgorithm(&alg);
    354 
    355  nsCOMPtr<nsIURI> originalURI;
    356  rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
    357  NS_ENSURE_SUCCESS(rv, rv);
    358  nsAutoCString requestSpec;
    359  rv = originalURI->GetSpec(requestSpec);
    360  NS_ENSURE_SUCCESS(rv, rv);
    361 
    362  nsAutoCString encodedHash;
    363  rv = Base64Encode(mComputedHash, encodedHash);
    364  NS_ENSURE_SUCCESS(rv, rv);
    365 
    366  nsAutoCString referrer;
    367  GetReferrerSpec(aChannel, referrer);
    368 
    369  aReporter->AddConsoleReport(
    370      nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
    371      nsContentUtils::eSECURITY_PROPERTIES, referrer, 0, 0,
    372      "IntegrityMismatch3"_ns,
    373      {NS_ConvertUTF8toUTF16(alg), NS_ConvertUTF8toUTF16(requestSpec),
    374       NS_ConvertUTF8toUTF16(encodedHash)});
    375 
    376  return NS_ERROR_SRI_CORRUPT;
    377 }
    378 
    379 uint32_t SRICheckDataVerifier::DataSummaryLength() {
    380  MOZ_ASSERT(!mInvalidMetadata);
    381  return sizeof(mHashType) + sizeof(mHashLength) + mHashLength;
    382 }
    383 
    384 uint32_t SRICheckDataVerifier::EmptyDataSummaryLength() {
    385  return sizeof(int8_t) + sizeof(uint32_t);
    386 }
    387 
    388 nsresult SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen,
    389                                                 const uint8_t* aData,
    390                                                 uint32_t* length) {
    391  *length = 0;
    392  NS_ENSURE_ARG_POINTER(aData);
    393 
    394  // we expect to always encode an SRI, even if it is empty or incomplete
    395  if (aDataLen < EmptyDataSummaryLength()) {
    396    SRILOG(
    397        ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too "
    398         "small",
    399         aDataLen));
    400    return NS_ERROR_SRI_IMPORT;
    401  }
    402 
    403  // decode the content of the buffer
    404  size_t offset = sizeof(mHashType);
    405  decltype(mHashLength) len = 0;
    406  memcpy(&len, &aData[offset], sizeof(mHashLength));
    407  offset += sizeof(mHashLength);
    408 
    409  SRIVERBOSE(
    410      ("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, "
    411       "...}",
    412       aData[0], aData[1], aData[2], aData[3], aData[4]));
    413 
    414  if (offset + len > aDataLen) {
    415    SRILOG(
    416        ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow "
    417         "the buffer size",
    418         aDataLen));
    419    SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]",
    420                uint32_t(offset), uint32_t(len)));
    421    return NS_ERROR_SRI_IMPORT;
    422  }
    423  *length = uint32_t(offset + len);
    424  return NS_OK;
    425 }
    426 
    427 nsresult SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen,
    428                                                 const uint8_t* aData) {
    429  MOZ_ASSERT(!mInvalidMetadata);  // mHashType and mHashLength should be valid
    430  MOZ_ASSERT(!mCryptoHash);  // EnsureCryptoHash should not have been called
    431  NS_ENSURE_ARG_POINTER(aData);
    432  if (mInvalidMetadata) {
    433    return NS_OK;  // ignoring any data updates, see mInvalidMetadata usage
    434  }
    435 
    436  // we expect to always encode an SRI, even if it is empty or incomplete
    437  if (aDataLen < DataSummaryLength()) {
    438    SRILOG(
    439        ("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too "
    440         "small",
    441         aDataLen));
    442    return NS_ERROR_SRI_IMPORT;
    443  }
    444 
    445  SRIVERBOSE(
    446      ("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, "
    447       "...}",
    448       aData[0], aData[1], aData[2], aData[3], aData[4]));
    449 
    450  // decode the content of the buffer
    451  size_t offset = 0;
    452  decltype(mHashType) hashType;
    453  memcpy(&hashType, &aData[offset], sizeof(mHashType));
    454  if (hashType != mHashType) {
    455    SRILOG(
    456        ("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not "
    457         "match[%d]",
    458         hashType, mHashType));
    459    return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
    460  }
    461  offset += sizeof(mHashType);
    462 
    463  decltype(mHashLength) hashLength;
    464  memcpy(&hashLength, &aData[offset], sizeof(mHashLength));
    465  if (hashLength != mHashLength) {
    466    SRILOG(
    467        ("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not "
    468         "match[%d]",
    469         hashLength, mHashLength));
    470    return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
    471  }
    472  offset += sizeof(mHashLength);
    473 
    474  // copy the hash to mComputedHash, as-if we had finished streaming the bytes
    475  mComputedHash.Assign(reinterpret_cast<const char*>(&aData[offset]),
    476                       mHashLength);
    477  mCryptoHash = nullptr;
    478  mComplete = true;
    479  return NS_OK;
    480 }
    481 
    482 nsresult SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen,
    483                                                 uint8_t* aData) {
    484  MOZ_ASSERT(!mInvalidMetadata);  // mHashType and mHashLength should be valid
    485  MOZ_ASSERT(mComplete);          // finished streaming
    486  NS_ENSURE_ARG_POINTER(aData);
    487  NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG);
    488 
    489  // serialize the hash in the buffer
    490  size_t offset = 0;
    491  memcpy(&aData[offset], &mHashType, sizeof(mHashType));
    492  offset += sizeof(mHashType);
    493  memcpy(&aData[offset], &mHashLength, sizeof(mHashLength));
    494  offset += sizeof(mHashLength);
    495 
    496  SRIVERBOSE(
    497      ("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, "
    498       "...}",
    499       aData[0], aData[1], aData[2], aData[3], aData[4]));
    500 
    501  // copy the hash to mComputedHash, as-if we had finished streaming the bytes
    502  nsCharTraits<char>::copy(reinterpret_cast<char*>(&aData[offset]),
    503                           mComputedHash.get(), mHashLength);
    504  return NS_OK;
    505 }
    506 
    507 nsresult SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen,
    508                                                      uint8_t* aData) {
    509  NS_ENSURE_ARG_POINTER(aData);
    510  NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG);
    511 
    512  // serialize an unknown hash in the buffer, to be able to skip it later
    513  size_t offset = 0;
    514  memset(&aData[offset], 0, sizeof(mHashType));
    515  offset += sizeof(mHashType);
    516  memset(&aData[offset], 0, sizeof(mHashLength));
    517 
    518  SRIVERBOSE(
    519      ("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, "
    520       "%x, ...}",
    521       aData[0], aData[1], aData[2], aData[3], aData[4]));
    522 
    523  return NS_OK;
    524 }
    525 
    526 }  // namespace mozilla::dom