tor-browser

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

Sanitizer.cpp (74655B)


      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 "Sanitizer.h"
      8 
      9 #include "mozilla/ClearOnShutdown.h"
     10 #include "mozilla/Span.h"
     11 #include "mozilla/StaticPtr.h"
     12 #include "mozilla/dom/BindingDeclarations.h"
     13 #include "mozilla/dom/BindingUtils.h"
     14 #include "mozilla/dom/DocumentFragment.h"
     15 #include "mozilla/dom/HTMLTemplateElement.h"
     16 #include "mozilla/dom/SanitizerBinding.h"
     17 #include "mozilla/dom/SanitizerDefaultConfig.h"
     18 #include "nsContentUtils.h"
     19 #include "nsFmtString.h"
     20 #include "nsGenericHTMLElement.h"
     21 #include "nsIContentInlines.h"
     22 #include "nsNameSpaceManager.h"
     23 
     24 namespace mozilla::dom {
     25 using namespace sanitizer;
     26 
     27 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Sanitizer, mGlobal)
     28 
     29 NS_IMPL_CYCLE_COLLECTING_ADDREF(Sanitizer)
     30 NS_IMPL_CYCLE_COLLECTING_RELEASE(Sanitizer)
     31 
     32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Sanitizer)
     33  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     34  NS_INTERFACE_MAP_ENTRY(nsISupports)
     35 NS_INTERFACE_MAP_END
     36 
     37 // Map[ElementName -> ?Set[Attributes]]
     38 using ElementsWithAttributes =
     39    nsTHashMap<const nsStaticAtom*, UniquePtr<StaticAtomSet>>;
     40 
     41 StaticAutoPtr<ElementsWithAttributes> sDefaultHTMLElements;
     42 StaticAutoPtr<ElementsWithAttributes> sDefaultMathMLElements;
     43 StaticAutoPtr<ElementsWithAttributes> sDefaultSVGElements;
     44 StaticAutoPtr<StaticAtomSet> sDefaultAttributes;
     45 
     46 JSObject* Sanitizer::WrapObject(JSContext* aCx,
     47                                JS::Handle<JSObject*> aGivenProto) {
     48  return Sanitizer_Binding::Wrap(aCx, this, aGivenProto);
     49 }
     50 
     51 /* static */
     52 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-get-a-sanitizer-instance-from-options
     53 already_AddRefed<Sanitizer> Sanitizer::GetInstance(
     54    nsIGlobalObject* aGlobal,
     55    const OwningSanitizerOrSanitizerConfigOrSanitizerPresets& aOptions,
     56    bool aSafe, ErrorResult& aRv) {
     57  // Step 4. If sanitizerSpec is a string:
     58  if (aOptions.IsSanitizerPresets()) {
     59    // Step 4.1. Assert: sanitizerSpec is "default"
     60    MOZ_ASSERT(aOptions.GetAsSanitizerPresets() == SanitizerPresets::Default);
     61 
     62    // Step 4.2. Set sanitizerSpec to the built-in safe default configuration.
     63    // NOTE: The built-in safe default configuration is complete and not
     64    // influenced by |safe|.
     65    RefPtr<Sanitizer> sanitizer = new Sanitizer(aGlobal);
     66    sanitizer->SetDefaultConfig();
     67    return sanitizer.forget();
     68  }
     69 
     70  // Step 5. Assert: sanitizerSpec is either a Sanitizer instance, or a
     71  // dictionary. Step 6. If sanitizerSpec is a dictionary:
     72  if (aOptions.IsSanitizerConfig()) {
     73    // Step 6.1. Let sanitizer be a new Sanitizer instance.
     74    RefPtr<Sanitizer> sanitizer = new Sanitizer(aGlobal);
     75 
     76    // Step 6.2. Let setConfigurationResult be the result of set a
     77    // configuration with sanitizerSpec and not safe on sanitizer.
     78    sanitizer->SetConfig(aOptions.GetAsSanitizerConfig(), !aSafe, aRv);
     79 
     80    // Step 6.3. If setConfigurationResult is false, throw a TypeError.
     81    if (aRv.Failed()) {
     82      return nullptr;
     83    }
     84 
     85    // Step 6.4. Set sanitizerSpec to sanitizer.
     86    return sanitizer.forget();
     87  }
     88 
     89  // Step 7. Assert: sanitizerSpec is a Sanitizer instance.
     90  MOZ_ASSERT(aOptions.IsSanitizer());
     91 
     92  // Step 8. Return sanitizerSpec.
     93  RefPtr<Sanitizer> sanitizer = aOptions.GetAsSanitizer();
     94  return sanitizer.forget();
     95 }
     96 
     97 /* static */
     98 // https://wicg.github.io/sanitizer-api/#sanitizer-constructor
     99 already_AddRefed<Sanitizer> Sanitizer::Constructor(
    100    const GlobalObject& aGlobal,
    101    const SanitizerConfigOrSanitizerPresets& aConfig, ErrorResult& aRv) {
    102  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    103  RefPtr<Sanitizer> sanitizer = new Sanitizer(global);
    104 
    105  // Step 1. If configuration is a SanitizerPresets string, then:
    106  if (aConfig.IsSanitizerPresets()) {
    107    // Step 1.1. Assert: configuration is default.
    108    MOZ_ASSERT(aConfig.GetAsSanitizerPresets() == SanitizerPresets::Default);
    109 
    110    // Step 1.2. Set configuration to the built-in safe default configuration.
    111    sanitizer->SetDefaultConfig();
    112 
    113    // NOTE: Early return because we don't need to do any
    114    // processing/verification of the default config.
    115    return sanitizer.forget();
    116  }
    117 
    118  // Step 2. Let valid be the return value of set a configuration with
    119  // configuration and true on this.
    120  sanitizer->SetConfig(aConfig.GetAsSanitizerConfig(), true, aRv);
    121 
    122  // Step 3. If valid is false, then throw a TypeError.
    123  if (aRv.Failed()) {
    124    return nullptr;
    125  }
    126 
    127  return sanitizer.forget();
    128 }
    129 
    130 void Sanitizer::SetDefaultConfig() {
    131  MOZ_ASSERT(NS_IsMainThread());
    132  AssertNoLists();
    133  MOZ_ASSERT(!mComments);
    134  MOZ_ASSERT(mDataAttributes.isNothing());
    135 
    136  mIsDefaultConfig = true;
    137 
    138  // https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration
    139  // {
    140  //   ...
    141  //   "comments": false,
    142  //   "dataAttributes": false
    143  // }
    144  mComments = false;
    145  mDataAttributes = Some(false);
    146 
    147  if (sDefaultHTMLElements) {
    148    // Already initialized.
    149    return;
    150  }
    151 
    152  auto createElements = [](mozilla::Span<nsStaticAtom* const> aElements,
    153                           nsStaticAtom* const* aElementWithAttributes) {
    154    auto elements = new ElementsWithAttributes(aElements.Length());
    155 
    156    size_t i = 0;
    157    for (nsStaticAtom* name : aElements) {
    158      UniquePtr<StaticAtomSet> attributes = nullptr;
    159 
    160      // Walkthrough the element specific attribute list in lockstep.
    161      // The last "name" in the array is a nullptr sentinel.
    162      if (name == aElementWithAttributes[i]) {
    163        attributes = MakeUnique<StaticAtomSet>();
    164        while (aElementWithAttributes[++i]) {
    165          attributes->Insert(aElementWithAttributes[i]);
    166        }
    167        i++;
    168      }
    169 
    170      elements->InsertOrUpdate(name, std::move(attributes));
    171    }
    172 
    173    return elements;
    174  };
    175 
    176  sDefaultHTMLElements =
    177      createElements(Span(kDefaultHTMLElements), kHTMLElementWithAttributes);
    178  sDefaultMathMLElements = createElements(Span(kDefaultMathMLElements),
    179                                          kMathMLElementWithAttributes);
    180  sDefaultSVGElements =
    181      createElements(Span(kDefaultSVGElements), kSVGElementWithAttributes);
    182 
    183  sDefaultAttributes = new StaticAtomSet(std::size(kDefaultAttributes));
    184  for (nsStaticAtom* name : kDefaultAttributes) {
    185    sDefaultAttributes->Insert(name);
    186  }
    187 
    188  ClearOnShutdown(&sDefaultHTMLElements);
    189  ClearOnShutdown(&sDefaultMathMLElements);
    190  ClearOnShutdown(&sDefaultAttributes);
    191 }
    192 
    193 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element
    194 template <typename SanitizerElement>
    195 static CanonicalElement CanonicalizeElement(const SanitizerElement& aElement) {
    196  // return the result of canonicalize a sanitizer name with element and the
    197  // HTML namespace as the default namespace.
    198 
    199  // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name
    200  // Step 1. Assert: name is either a DOMString or a dictionary. (implicit)
    201 
    202  // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace"
    203  // → defaultNamespace]».
    204  if (aElement.IsString()) {
    205    RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aElement.GetAsString());
    206    return CanonicalElement(nameAtom, nsGkAtoms::nsuri_xhtml);
    207  }
    208 
    209  // Step 3. Assert: name is a dictionary and both name["name"] and
    210  // name["namespace"] exist.
    211  const auto& elem = GetAsDictionary(aElement);
    212  MOZ_ASSERT(!elem.mName.IsVoid());
    213 
    214  // Step 4. If name["namespace"] is the empty string, then set it to null.
    215  RefPtr<nsAtom> namespaceAtom;
    216  if (!elem.mNamespace.IsEmpty()) {
    217    namespaceAtom = NS_AtomizeMainThread(elem.mNamespace);
    218  }
    219 
    220  // Step 5. Return «[
    221  //  "name" → name["name"],
    222  //  "namespace" → name["namespace"]
    223  //  )
    224  // ]».
    225  RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elem.mName);
    226  return CanonicalElement(nameAtom, namespaceAtom);
    227 }
    228 
    229 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute
    230 template <typename SanitizerAttribute>
    231 static CanonicalAttribute CanonicalizeAttribute(
    232    const SanitizerAttribute& aAttribute) {
    233  // return the result of canonicalize a sanitizer name with attribute and
    234  // null as the default namespace.
    235 
    236  // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name
    237  // Step 1. Assert: name is either a DOMString or a dictionary. (implicit)
    238 
    239  // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace"
    240  // → defaultNamespace]».
    241  if (aAttribute.IsString()) {
    242    RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aAttribute.GetAsString());
    243    return CanonicalAttribute(nameAtom, nullptr);
    244  }
    245 
    246  // Step 3. Assert: name is a dictionary and both name["name"] and
    247  // name["namespace"] exist.
    248  const auto& attr = aAttribute.GetAsSanitizerAttributeNamespace();
    249  MOZ_ASSERT(!attr.mName.IsVoid());
    250 
    251  // Step 4. If name["namespace"] is the empty string, then set it to null.
    252  RefPtr<nsAtom> namespaceAtom;
    253  if (!attr.mNamespace.IsEmpty()) {
    254    namespaceAtom = NS_AtomizeMainThread(attr.mNamespace);
    255  }
    256 
    257  // Step 5. Return «[
    258  //  "name" → name["name"],
    259  //  "namespace" → name["namespace"],
    260  //  )
    261  // ]».
    262  RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(attr.mName);
    263  return CanonicalAttribute(nameAtom, namespaceAtom);
    264 }
    265 
    266 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes
    267 //
    268 // If aErrorMsg is not nullptr, this function will abort for duplicate
    269 // attributes and set an error message, but otherwise they are ignored.
    270 template <typename SanitizerElementWithAttributes>
    271 static CanonicalElementAttributes CanonicalizeElementAttributes(
    272    const SanitizerElementWithAttributes& aElement,
    273    nsACString* aErrorMsg = nullptr) {
    274  // Step 1. Let result be the result of canonicalize a sanitizer element with
    275  // element.
    276  //
    277  // NOTE: CanonicalizeElement is the responsibility of the caller.
    278  CanonicalElementAttributes result{};
    279 
    280  // Step 2. If element is a dictionary:
    281  if (aElement.IsSanitizerElementNamespaceWithAttributes()) {
    282    auto& elem = aElement.GetAsSanitizerElementNamespaceWithAttributes();
    283 
    284    // Step 2.1. If element["attributes"] exists:
    285    if (elem.mAttributes.WasPassed()) {
    286      // Step 2.1.1. Let attributes be « ».
    287      CanonicalAttributeSet attributes;
    288 
    289      // Step 2.1.2. For each attribute of element["attributes"]:
    290      for (const auto& attribute : elem.mAttributes.Value()) {
    291        // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute
    292        // with attribute to attributes.
    293        CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute);
    294        if (!attributes.EnsureInserted(canonicalAttr)) {
    295          if (aErrorMsg) {
    296            aErrorMsg->Assign(nsFmtCString(
    297                FMT_STRING("Duplicate attribute {} in 'attributes' of {}."),
    298                canonicalAttr, CanonicalizeElement(aElement)));
    299            return CanonicalElementAttributes();
    300          }
    301        }
    302      }
    303 
    304      // Step 2.1.3. Set result["attributes"] to attributes.
    305      result.mAttributes = Some(std::move(attributes));
    306    }
    307 
    308    // Step 2.2. If element["attributes"] exists:
    309    if (elem.mRemoveAttributes.WasPassed()) {
    310      // Step 2.2.1. Let attributes be « ».
    311      CanonicalAttributeSet attributes;
    312 
    313      // Step 2.2.2. For each attribute of element["removeAttributes"]:
    314      for (const auto& attribute : elem.mRemoveAttributes.Value()) {
    315        // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute
    316        // with attribute to attributes.
    317        CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute);
    318        if (!attributes.EnsureInserted(canonicalAttr)) {
    319          if (aErrorMsg) {
    320            aErrorMsg->Assign(nsFmtCString(
    321                FMT_STRING(
    322                    "Duplicate attribute {} in 'removeAttributes' of {}."),
    323                canonicalAttr, CanonicalizeElement(aElement)));
    324            return CanonicalElementAttributes();
    325          }
    326        }
    327      }
    328 
    329      // Step 2.2.3. Set result["removeAttributes"] to attributes.
    330      result.mRemoveAttributes = Some(std::move(attributes));
    331    }
    332  }
    333 
    334  // Step 3. If neither result["attributes"] nor
    335  // result["removeAttributes"] exist:
    336  if (!result.mAttributes && !result.mRemoveAttributes) {
    337    // Step 3.1. Set result["removeAttributes"] to « ».
    338    CanonicalAttributeSet set{};
    339    result.mRemoveAttributes = Some(std::move(set));
    340  }
    341 
    342  // Step 4. Return result.
    343  return result;
    344 }
    345 
    346 // https://wicg.github.io/sanitizer-api/#configuration-canonicalize
    347 void Sanitizer::CanonicalizeConfiguration(const SanitizerConfig& aConfig,
    348                                          bool aAllowCommentsAndDataAttributes,
    349                                          ErrorResult& aRv) {
    350  // This function is only called while constructing a new Sanitizer object.
    351  AssertNoLists();
    352 
    353  // Step 1. If neither configuration["elements"] nor
    354  // configuration["removeElements"] exist, then set
    355  // configuration["removeElements"] to « [] ».
    356  if (!aConfig.mElements.WasPassed() && !aConfig.mRemoveElements.WasPassed()) {
    357    mRemoveElements.emplace();
    358  }
    359 
    360  // Step 2. If neither configuration["attributes"] nor
    361  // configuration["removeAttributes"] exist, then set
    362  // configuration["removeAttributes"] to « [] ».
    363  if (!aConfig.mAttributes.WasPassed() &&
    364      !aConfig.mRemoveAttributes.WasPassed()) {
    365    mRemoveAttributes.emplace();
    366  }
    367 
    368  // Step 3. If configuration["elements"] exists:
    369  if (aConfig.mElements.WasPassed()) {
    370    // Step 3.1. Let elements be « [] »
    371    CanonicalElementMap elements;
    372 
    373    nsAutoCString errorMsg;
    374    // Step 3.2. For each element of configuration["elements"] do:
    375    for (const auto& element : aConfig.mElements.Value()) {
    376      // Step 3.3.2.1. Append the result of canonicalize a sanitizer element
    377      // with attributes element to elements.
    378      CanonicalElement elementName = CanonicalizeElement(element);
    379      if (elements.Contains(elementName)) {
    380        aRv.ThrowTypeError(nsFmtCString(
    381            FMT_STRING("Duplicate element {} in 'elements'."), elementName));
    382        return;
    383      }
    384 
    385      CanonicalElementAttributes elementAttributes =
    386          CanonicalizeElementAttributes(element, &errorMsg);
    387      if (!errorMsg.IsEmpty()) {
    388        aRv.ThrowTypeError(errorMsg);
    389        return;
    390      }
    391 
    392      elements.InsertOrUpdate(elementName, std::move(elementAttributes));
    393    }
    394 
    395    // Step 3.3. Set configuration["elements"] to elements.
    396    mElements = Some(std::move(elements));
    397  }
    398 
    399  // Step 4. If configuration["removeElements"] exists:
    400  if (aConfig.mRemoveElements.WasPassed()) {
    401    // Step 4.1. Let elements be « [] »
    402    CanonicalElementSet elements;
    403 
    404    // Step 4.2. For each element of configuration["removeElements"] do:
    405    for (const auto& element : aConfig.mRemoveElements.Value()) {
    406      // Step 4.2.1. Append the result of canonicalize a sanitizer element
    407      // element to elements.
    408      CanonicalElement canonical = CanonicalizeElement(element);
    409      if (!elements.EnsureInserted(canonical)) {
    410        aRv.ThrowTypeError(nsFmtCString(
    411            FMT_STRING("Duplicate element {} in 'removeElements'."),
    412            canonical));
    413        return;
    414      }
    415    }
    416 
    417    // Step 4.3. Set configuration["removeElements"] to elements.
    418    mRemoveElements = Some(std::move(elements));
    419  }
    420 
    421  // Step 5. If configuration["replaceWithChildrenElements"] exists:
    422  if (aConfig.mReplaceWithChildrenElements.WasPassed()) {
    423    // Step 5.1. Let elements be « [] »
    424    CanonicalElementSet elements;
    425 
    426    // Step 5.2. For each element of
    427    // configuration["replaceWithChildrenElements"] do:
    428    for (const auto& element : aConfig.mReplaceWithChildrenElements.Value()) {
    429      // Step 5.2.1. Append the result of canonicalize a sanitizer element
    430      // element to elements.
    431      CanonicalElement canonical = CanonicalizeElement(element);
    432      if (!elements.EnsureInserted(canonical)) {
    433        aRv.ThrowTypeError(nsFmtCString(
    434            FMT_STRING(
    435                "Duplicate element {} in 'replaceWithChildrenElements'."),
    436            canonical));
    437        return;
    438      }
    439    }
    440 
    441    // Step 5.3. Set configuration["replaceWithChildrenElements"] to elements.
    442    mReplaceWithChildrenElements = Some(std::move(elements));
    443  }
    444 
    445  // Step 6. If configuration["attributes"] exists:
    446  if (aConfig.mAttributes.WasPassed()) {
    447    // Step 6.1. Let attributes be « [] »
    448    CanonicalAttributeSet attributes;
    449 
    450    // Step 6.2. For each attribute of configuration["attributes"] do:
    451    for (const auto& attribute : aConfig.mAttributes.Value()) {
    452      // Step 6.2.1. Append the result of canonicalize a sanitizer attribute
    453      // attribute to attributes.
    454      CanonicalAttribute canonical = CanonicalizeAttribute(attribute);
    455      if (!attributes.EnsureInserted(canonical)) {
    456        aRv.ThrowTypeError(nsFmtCString(
    457            FMT_STRING("Duplicate attribute {} in 'attributes'."), canonical));
    458        return;
    459      }
    460    }
    461 
    462    // Step 6.3. Set configuration["attributes"] to attributes.
    463    mAttributes = Some(std::move(attributes));
    464  }
    465 
    466  // Step 7. If configuration["removeAttributes"] exists:
    467  if (aConfig.mRemoveAttributes.WasPassed()) {
    468    // Step 7.1. Let attributes be « [] »
    469    CanonicalAttributeSet attributes;
    470 
    471    // Step 7.2. For each attribute of configuration["removeAttributes"] do:
    472    for (const auto& attribute : aConfig.mRemoveAttributes.Value()) {
    473      // Step 7.2.2. Append the result of canonicalize a sanitizer attribute
    474      // attribute to attributes.
    475      CanonicalAttribute canonical = CanonicalizeAttribute(attribute);
    476      if (!attributes.EnsureInserted(canonical)) {
    477        aRv.ThrowTypeError(nsFmtCString(
    478            FMT_STRING("Duplicate attribute {} in 'removeAttributes'."),
    479            canonical));
    480        return;
    481      }
    482    }
    483 
    484    // Step 7.3. Set configuration["removeAttributes"] to attributes.
    485    mRemoveAttributes = Some(std::move(attributes));
    486  }
    487 
    488  // Step 8. If configuration["comments"] does not exist, then set
    489  // configuration["comments"] to allowCommentsAndDataAttributes.
    490  if (aConfig.mComments.WasPassed()) {
    491    // NOTE: We always need to copy this property if it exists.
    492    mComments = aConfig.mComments.Value();
    493  } else {
    494    mComments = aAllowCommentsAndDataAttributes;
    495  }
    496 
    497  // Step 9. If configuration["attributes"] exists and
    498  // configuration["dataAttributes"] does not exist, then set
    499  // configuration["dataAttributes"] to allowCommentsAndDataAttributes.
    500  if (aConfig.mDataAttributes.WasPassed()) {
    501    // NOTE: We always need to copy this property if it exists.
    502    mDataAttributes = Some(aConfig.mDataAttributes.Value());
    503  } else if (aConfig.mAttributes.WasPassed()) {
    504    mDataAttributes = Some(aAllowCommentsAndDataAttributes);
    505  }
    506 }
    507 
    508 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid
    509 void Sanitizer::IsValid(ErrorResult& aRv) {
    510  // Step 1. The config has either an elements or a removeElements key, but
    511  // not both.
    512  MOZ_ASSERT(mElements || mRemoveElements,
    513             "Must have either due to CanonicalizeConfiguration");
    514  if (mElements && mRemoveElements) {
    515    aRv.ThrowTypeError(
    516        "'elements' and 'removeElements' are not allowed at the same time.");
    517    return;
    518  }
    519 
    520  // Step 2. The config has either an attributes or a removeAttributes key,
    521  // but not both.
    522  MOZ_ASSERT(mAttributes || mRemoveAttributes,
    523             "Must have either due to CanonicalizeConfiguration");
    524  if (mAttributes && mRemoveAttributes) {
    525    aRv.ThrowTypeError(
    526        "'attributes' and 'removeAttributes' are not allowed at the same "
    527        "time.");
    528    return;
    529  }
    530 
    531  // Step 3. Assert: All SanitizerElementNamespaceWithAttributes,
    532  // SanitizerElementNamespace, and SanitizerAttributeNamespace items in
    533  // config are canonical, meaning they have been run through canonicalize a
    534  // sanitizer element or canonicalize a sanitizer attribute, as appropriate.
    535 
    536  // Step 4. None of config[elements], config[removeElements],
    537  // config[replaceWithChildrenElements], config[attributes], or
    538  // config[removeAttributes], if they exist, has duplicates.
    539  //
    540  // NOTE: The Sanitizer::CanonicalizeConfiguration method would have already
    541  // thrown an error for duplicate elements/attributes by this point. The map
    542  // and sets can't have duplicates by definition.
    543 
    544  // Step 5. If both config[elements] and config[replaceWithChildrenElements]
    545  // exist, then the intersection of config[elements] and
    546  // config[replaceWithChildrenElements] is empty.
    547  if (mElements && mReplaceWithChildrenElements) {
    548    for (const CanonicalElement& name : mElements->Keys()) {
    549      if (mReplaceWithChildrenElements->Contains(name)) {
    550        aRv.ThrowTypeError(
    551            nsFmtCString(FMT_STRING("Element {} can't be in both 'elements' "
    552                                    "and 'replaceWithChildrenElements'."),
    553                         name));
    554        return;
    555      }
    556    }
    557  }
    558 
    559  // Step 6. If both config[removeElements] and
    560  // config[replaceWithChildrenElements] exist, then the intersection of
    561  // config[removeElements] and config[replaceWithChildrenElements] is empty.
    562  if (mRemoveElements && mReplaceWithChildrenElements) {
    563    for (const CanonicalElement& name : *mRemoveElements) {
    564      if (mReplaceWithChildrenElements->Contains(name)) {
    565        aRv.ThrowTypeError(nsFmtCString(
    566            FMT_STRING("Element {} can't be in both 'removeElements' and "
    567                       "'replaceWithChildrenElements'."),
    568            name));
    569        return;
    570      }
    571    }
    572  }
    573 
    574  // Step 7. If config[attributes] exists:
    575  if (mAttributes) {
    576    // Step 7.1. If config[elements] exists:
    577    if (mElements) {
    578      // Step 7.1.1 For any element in config[elements]:
    579      for (const auto& entry : *mElements) {
    580        const CanonicalElementAttributes& elemAttributes = entry.GetData();
    581        MOZ_ASSERT(
    582            elemAttributes.mAttributes || elemAttributes.mRemoveAttributes,
    583            "Canonical elements must at least have removeAttributes");
    584 
    585        // Step 7.1.1.1. Neither element[attributes] or
    586        // element[removeAttributes], if they exist, has duplicates.
    587        //
    588        // NOTE: The Sanitizer::CanonicalizeConfiguration (specifically
    589        // CanonicalizeElementAttributes) method would have already thrown an
    590        // error for duplicate attributes by this point. The attribute sets
    591        // can't have duplicates by definition.
    592 
    593        // Step 7.1.1.2. The intersection of config[attributes] and
    594        // element[attributes] with default « [] » is empty.
    595        if (elemAttributes.mAttributes) {
    596          for (const CanonicalAttribute& name : *elemAttributes.mAttributes) {
    597            if (mAttributes->Contains(name)) {
    598              aRv.ThrowTypeError(nsFmtCString(
    599                  FMT_STRING(
    600                      "Attribute {} can't be part of both the 'attributes' of "
    601                      "the element {} and the global 'attributes'."),
    602                  name, entry.GetKey()));
    603              return;
    604            }
    605          }
    606        }
    607 
    608        // Step 7.1.1.3. element[removeAttributes] is a subset of
    609        // config[attributes].
    610        if (elemAttributes.mRemoveAttributes) {
    611          for (const CanonicalAttribute& name :
    612               *elemAttributes.mRemoveAttributes) {
    613            if (!mAttributes->Contains(name)) {
    614              aRv.ThrowTypeError(nsFmtCString(
    615                  FMT_STRING(
    616                      "Attribute {} can't be in 'removeAttributes' of the "
    617                      "element {} but not in the global 'attributes'."),
    618                  name, entry.GetKey()));
    619              return;
    620            }
    621          }
    622        }
    623 
    624        MOZ_ASSERT(mDataAttributes.isSome(),
    625                   "mDataAttributes exists iff mAttributes exists");
    626 
    627        // Step 7.1.1.4. If dataAttributes is true:
    628        if (*mDataAttributes && elemAttributes.mAttributes) {
    629          // TODO: Merge with loop above?
    630          // Step 7.1.1.4.1. element[attributes] does not contain a custom
    631          // data attribute.
    632          for (const CanonicalAttribute& name : *elemAttributes.mAttributes) {
    633            if (name.IsDataAttribute()) {
    634              aRv.ThrowTypeError(nsFmtCString(
    635                  FMT_STRING(
    636                      "Data attribute {} in the 'attributes' of the element {} "
    637                      "is redundant with 'dataAttributes' being true."),
    638                  name, entry.GetKey()));
    639              return;
    640            }
    641          }
    642        }
    643      }
    644    }
    645 
    646    MOZ_ASSERT(mDataAttributes.isSome(),
    647               "mDataAttributes exists iff mAttributes exists");
    648 
    649    // Step 7.2. If dataAttributes is true:
    650    if (*mDataAttributes) {
    651      // Step 7.2.1. config[attributes] does not contain a custom data
    652      // attribute.
    653      for (const CanonicalAttribute& name : *mAttributes) {
    654        if (name.IsDataAttribute()) {
    655          aRv.ThrowTypeError(nsFmtCString(
    656              FMT_STRING("Data attribute {} in the global 'attributes' is "
    657                         "redundant with 'dataAttributes' being true."),
    658              name));
    659          return;
    660        }
    661      }
    662    }
    663  }
    664 
    665  // Step 8. If config[removeAttributes] exists:
    666  if (mRemoveAttributes) {
    667    // Step 8.1. If config[elements] exists, then for any element in
    668    // config[elements]:
    669    if (mElements) {
    670      for (const auto& entry : *mElements) {
    671        const CanonicalElementAttributes& elemAttributes = entry.GetData();
    672 
    673        // Step 8.1.1. Not both element[attributes] and
    674        // element[removeAttributes] exist.
    675        if (elemAttributes.mAttributes && elemAttributes.mRemoveAttributes) {
    676          return aRv.ThrowTypeError(
    677              nsFmtCString(FMT_STRING("Element {} can't have both 'attributes' "
    678                                      "and 'removeAttributes'."),
    679                           entry.GetKey()));
    680        }
    681 
    682        // Step 8.1.2. Neither element[attributes] nor
    683        // element[removeAttributes], if they exist, has duplicates.
    684        //
    685        // NOTE: The Sanitizer::CanonicalizeConfiguration (specifically
    686        // CanonicalizeElementAttributes) method would have already thrown an
    687        // error for duplicate attributes by this point. The attribute sets
    688        // can't have duplicates by definition.
    689 
    690        // Step 8.1.3. The intersection of config[removeAttributes] and
    691        // element[attributes] with default « [] » is empty.
    692        if (elemAttributes.mAttributes) {
    693          for (const CanonicalAttribute& name : *elemAttributes.mAttributes) {
    694            if (mRemoveAttributes->Contains(name)) {
    695              aRv.ThrowTypeError(nsFmtCString(
    696                  FMT_STRING(
    697                      "Attribute {} can't be in 'attributes' of the element {} "
    698                      "while in the global 'removeAttributes'."),
    699                  name, entry.GetKey()));
    700              return;
    701            }
    702          }
    703        }
    704 
    705        // Step 8.1.4. The intersection of config[removeAttributes] and
    706        // element[removeAttributes] with default « [] » is empty.
    707        if (elemAttributes.mRemoveAttributes) {
    708          for (const CanonicalAttribute& name :
    709               *elemAttributes.mRemoveAttributes) {
    710            if (mRemoveAttributes->Contains(name)) {
    711              aRv.ThrowTypeError(nsFmtCString(
    712                  FMT_STRING("Attribute {} can't be part of both the "
    713                             "'removeAttributes' of the element {} and the "
    714                             "global 'removeAttributes'."),
    715                  name, entry.GetKey()));
    716              return;
    717            }
    718          }
    719        }
    720      }
    721    }
    722 
    723    // Step 8.2. config[dataAttributes] does not exist.
    724    if (mDataAttributes) {
    725      aRv.ThrowTypeError(
    726          "'removeAttributes' and 'dataAttributes' aren't allowed at the "
    727          "same time.");
    728    }
    729  }
    730 }
    731 
    732 void Sanitizer::AssertIsValid() {
    733 #ifdef DEBUG
    734  IgnoredErrorResult rv;
    735  IsValid(rv);
    736  MOZ_ASSERT(!rv.Failed());
    737 #endif
    738 }
    739 
    740 // https://wicg.github.io/sanitizer-api/#sanitizer-set-a-configuration
    741 void Sanitizer::SetConfig(const SanitizerConfig& aConfig,
    742                          bool aAllowCommentsAndDataAttributes,
    743                          ErrorResult& aRv) {
    744  // Step 1. Canonicalize configuration with allowCommentsAndDataAttributes.
    745  CanonicalizeConfiguration(aConfig, aAllowCommentsAndDataAttributes, aRv);
    746  if (aRv.Failed()) {
    747    return;
    748  }
    749 
    750  // Step 2. If configuration is not valid, then return false.
    751  IsValid(aRv);
    752  if (aRv.Failed()) {
    753    return;
    754  }
    755 
    756  // Step 3. Set sanitizer’s configuration to configuration.
    757  // Note: This was already done in CanonicalizeConfiguration.
    758  // Step 4. Return true. (implicit)
    759 }
    760 
    761 // Turn the lazy default config into real lists that can be
    762 // modified or queried via get().
    763 void Sanitizer::MaybeMaterializeDefaultConfig() {
    764  if (!mIsDefaultConfig) {
    765    AssertIsValid();
    766    return;
    767  }
    768 
    769  AssertNoLists();
    770 
    771  CanonicalElementMap elements;
    772  auto insertElements = [&elements](
    773                            mozilla::Span<nsStaticAtom* const> aElements,
    774                            nsStaticAtom* aNamespace,
    775                            nsStaticAtom* const* aElementWithAttributes) {
    776    size_t i = 0;
    777    for (nsStaticAtom* name : aElements) {
    778      CanonicalElementAttributes elementAttributes{};
    779 
    780      if (name == aElementWithAttributes[i]) {
    781        CanonicalAttributeSet attributes;
    782        while (aElementWithAttributes[++i]) {
    783          attributes.Insert(
    784              CanonicalAttribute(aElementWithAttributes[i], nullptr));
    785        }
    786        i++;
    787        elementAttributes.mAttributes = Some(std::move(attributes));
    788      } else {
    789        // In the default config all elements have a (maybe empty) `attributes`
    790        // list.
    791        CanonicalAttributeSet set{};
    792        elementAttributes.mAttributes = Some(std::move(set));
    793      }
    794 
    795      CanonicalElement elementName(name, aNamespace);
    796      elements.InsertOrUpdate(elementName, std::move(elementAttributes));
    797    }
    798  };
    799  insertElements(Span(kDefaultHTMLElements), nsGkAtoms::nsuri_xhtml,
    800                 kHTMLElementWithAttributes);
    801  insertElements(Span(kDefaultMathMLElements), nsGkAtoms::nsuri_mathml,
    802                 kMathMLElementWithAttributes);
    803  insertElements(Span(kDefaultSVGElements), nsGkAtoms::nsuri_svg,
    804                 kSVGElementWithAttributes);
    805  mElements = Some(std::move(elements));
    806 
    807  CanonicalAttributeSet attributes;
    808  for (nsStaticAtom* name : kDefaultAttributes) {
    809    attributes.Insert(CanonicalAttribute(name, nullptr));
    810  }
    811  mAttributes = Some(std::move(attributes));
    812 
    813  mIsDefaultConfig = false;
    814 }
    815 
    816 // https://wicg.github.io/sanitizer-api/#dom-sanitizer-get
    817 void Sanitizer::Get(SanitizerConfig& aConfig) {
    818  MaybeMaterializeDefaultConfig();
    819 
    820  // Step 1. Let config be this’s configuration.
    821  // Step 2. If config["elements"] exists:
    822  if (mElements) {
    823    nsTArray<OwningStringOrSanitizerElementNamespaceWithAttributes> elements;
    824    // Step 2.1. For any element of config["elements"]:
    825    for (const auto& entry : *mElements) {
    826      // Step 2.1.1. If element["attributes"] exists:
    827      // Step 2.1.2. If element["removeAttributes"] exists:
    828      // ...
    829      // (The attributes are sorted by the ToSanitizerAttributes call in
    830      // ToSanitizerElementNamespaceWithAttributes)
    831      OwningStringOrSanitizerElementNamespaceWithAttributes owning;
    832      owning.SetAsSanitizerElementNamespaceWithAttributes() =
    833          entry.GetKey().ToSanitizerElementNamespaceWithAttributes(
    834              entry.GetData());
    835 
    836      // Step 2.2. Set config["elements"] to the result of sort in ascending
    837      // order config["elements"], with elementA being less than item elementB.
    838      // (Instead of sorting at the end, we sort during insertion)
    839      elements.InsertElementSorted(owning,
    840                                   SanitizerComparator<decltype(owning)>());
    841    }
    842    aConfig.mElements.Construct(std::move(elements));
    843  } else {
    844    // Step 3. If config["removeElements"] exists:
    845    // Step 3.1. Set config["removeElements"] to the result of sort in ascending
    846    // order config["removeElements"], with elementA being less than item
    847    // elementB.
    848    nsTArray<OwningStringOrSanitizerElementNamespace> removeElements;
    849    for (const CanonicalElement& canonical : *mRemoveElements) {
    850      OwningStringOrSanitizerElementNamespace owning;
    851      owning.SetAsSanitizerElementNamespace() =
    852          canonical.ToSanitizerElementNamespace();
    853      removeElements.InsertElementSorted(
    854          owning, SanitizerComparator<decltype(owning)>());
    855    }
    856    aConfig.mRemoveElements.Construct(std::move(removeElements));
    857  }
    858 
    859  // Step 4. If config["replaceWithChildrenElements"] exists:
    860  if (mReplaceWithChildrenElements) {
    861    // Step 4.1. Set config["replaceWithChildrenElements"] to the result of sort
    862    // in ascending order config["replaceWithChildrenElements"], with elementA
    863    // being less than item elementB.
    864    nsTArray<OwningStringOrSanitizerElementNamespace>
    865        replaceWithChildrenElements;
    866    for (const CanonicalElement& canonical : *mReplaceWithChildrenElements) {
    867      OwningStringOrSanitizerElementNamespace owning;
    868      owning.SetAsSanitizerElementNamespace() =
    869          canonical.ToSanitizerElementNamespace();
    870      replaceWithChildrenElements.InsertElementSorted(
    871          owning, SanitizerComparator<decltype(owning)>());
    872    }
    873    aConfig.mReplaceWithChildrenElements.Construct(
    874        std::move(replaceWithChildrenElements));
    875  }
    876 
    877  // Step 5. If config["attributes"] exists:
    878  if (mAttributes) {
    879    // Step 5.1. Set config["attributes"] to the result of sort in ascending
    880    // order config["attributes"], with attrA being less than item attrB.
    881    // (Sorting is done by ToSanitizerAttributes)
    882    aConfig.mAttributes.Construct(ToSanitizerAttributes(*mAttributes));
    883  } else {
    884    // Step 6. If config["removeAttributes"] exists:
    885    // Step 6.1. Set config["removeAttributes"] to the result of sort in
    886    // ascending order config["removeAttributes"], with attrA being less than
    887    // item attrB.
    888    aConfig.mRemoveAttributes.Construct(
    889        ToSanitizerAttributes(*mRemoveAttributes));
    890  }
    891 
    892  // (In the spec these already exist in the |config| and don't need sorting)
    893  aConfig.mComments.Construct(mComments);
    894  if (mDataAttributes) {
    895    aConfig.mDataAttributes.Construct(*mDataAttributes);
    896  }
    897 
    898  // Step 7. Return config.
    899 }
    900 
    901 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-allow-an-element
    902 bool Sanitizer::AllowElement(
    903    const StringOrSanitizerElementNamespaceWithAttributes& aElement) {
    904  MaybeMaterializeDefaultConfig();
    905 
    906  // Step 1. Set element to the result of canonicalize a sanitizer element
    907  // with attributes with element.
    908  CanonicalElement elementName = CanonicalizeElement(aElement);
    909  // NOTE: Duplicate attributes are removed/ignored.
    910  CanonicalElementAttributes elementAttributes =
    911      CanonicalizeElementAttributes(aElement);
    912 
    913  // Step 2 If configuration["elements"] exists:
    914  if (mElements) {
    915    // Step 2.1. Set modified to the result of remove element from
    916    // configuration["replaceWithChildrenElements"].
    917    bool modified =
    918        mReplaceWithChildrenElements
    919            ? mReplaceWithChildrenElements->EnsureRemoved(elementName)
    920            : false;
    921 
    922    // Step 2.2. Comment: We need to make sure the per-element attributes do
    923    // not overlap with global attributes.
    924 
    925    // Step 2.3. If configuration["attributes"] exists:
    926    if (mAttributes) {
    927      // Step 2.3.1. If element["attributes"] exists:
    928      if (elementAttributes.mAttributes) {
    929        CanonicalAttributeSet attributes;
    930        for (const CanonicalAttribute& attr : *elementAttributes.mAttributes) {
    931          // Step 2.3.1.1. Set element["attributes"] to remove duplicates from
    932          // element["attributes"].
    933          MOZ_ASSERT(!attributes.Contains(attr));
    934 
    935          // Step 2.3.1.2. Set element["attributes"] to the difference of
    936          // element["attributes"] and configuration["attributes"].
    937          if (mAttributes->Contains(attr)) {
    938            continue;
    939          }
    940 
    941          // Step 2.3.1.3. If configuration["dataAttributes"] is true:
    942          MOZ_ASSERT(mDataAttributes.isSome(),
    943                     "mDataAttributes exists iff mAttributes");
    944          if (*mDataAttributes) {
    945            // Step 2.3.1.3.1 Remove all items item from element["attributes"]
    946            // where item is a custom data attribute.
    947            if (attr.IsDataAttribute()) {
    948              continue;
    949            }
    950          }
    951 
    952          attributes.Insert(attr.Clone());
    953        }
    954        elementAttributes.mAttributes = Some(std::move(attributes));
    955      }
    956 
    957      // Step 2.3.2. If element["removeAttributes"] exists:
    958      if (elementAttributes.mRemoveAttributes) {
    959        CanonicalAttributeSet removeAttributes;
    960        for (const CanonicalAttribute& attr :
    961             *elementAttributes.mRemoveAttributes) {
    962          // Step 2.3.2.1. Set element["removeAttributes"] to remove duplicates
    963          // from element["removeAttributes"].
    964          //
    965          // NOTE: CanonicalizeElementAttributes removed all duplicates for us.
    966          MOZ_ASSERT(!removeAttributes.Contains(attr));
    967 
    968          // Step 2.3.2.2. Set element["removeAttributes"] to the intersection
    969          // of element["removeAttributes"] and configuration["attributes"].
    970          if (!mAttributes->Contains(attr)) {
    971            continue;
    972          }
    973 
    974          removeAttributes.Insert(attr.Clone());
    975        }
    976        elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes));
    977      }
    978    } else {
    979      // Step 2.4. Otherwise:
    980 
    981      // Step 2.4.1. If element["attributes"] exists:
    982      if (elementAttributes.mAttributes) {
    983        CanonicalAttributeSet attributes;
    984        for (const CanonicalAttribute& attr : *elementAttributes.mAttributes) {
    985          // Step 2.4.1.1. Set element["attributes"] to remove duplicates from
    986          // element["attributes"].
    987          //
    988          // NOTE: CanonicalizeElementAttributes removed all duplicates for us.
    989          MOZ_ASSERT(!attributes.Contains(attr));
    990 
    991          // Step 2.4.1.2. Set element["attributes"] to the difference of
    992          // element["attributes"] and element["removeAttributes"] with default
    993          // « ».
    994          if (elementAttributes.mRemoveAttributes &&
    995              elementAttributes.mRemoveAttributes->Contains(attr)) {
    996            continue;
    997          }
    998 
    999          // Step 2.4.1.4. Set element["attributes"] to the difference of
   1000          // element["attributes"] and configuration["removeAttributes"].
   1001          if (mRemoveAttributes->Contains(attr)) {
   1002            continue;
   1003          }
   1004 
   1005          attributes.Insert(attr.Clone());
   1006        }
   1007        elementAttributes.mAttributes = Some(std::move(attributes));
   1008 
   1009        // Step 2.4.1.3. Remove element["removeAttributes"].
   1010        elementAttributes.mRemoveAttributes = Nothing();
   1011      }
   1012 
   1013      // Step 2.4.2. If element["removeAttributes"] exists:
   1014      if (elementAttributes.mRemoveAttributes) {
   1015        CanonicalAttributeSet removeAttributes;
   1016        for (const CanonicalAttribute& attr :
   1017             *elementAttributes.mRemoveAttributes) {
   1018          // Step 2.4.2.1. Set element["removeAttributes"] to remove duplicates
   1019          // from element["removeAttributes"].
   1020          MOZ_ASSERT(!removeAttributes.Contains(attr));
   1021 
   1022          // Step 2.4.2.2. Set element["removeAttributes"] to the difference of
   1023          // element["removeAttributes"] and configuration["removeAttributes"].
   1024          if (mRemoveAttributes->Contains(attr)) {
   1025            continue;
   1026          }
   1027 
   1028          removeAttributes.Insert(attr.Clone());
   1029        }
   1030        elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes));
   1031      }
   1032    }
   1033 
   1034    // Step 2.5. If configuration["elements"] does not contain element:
   1035    const CanonicalElementAttributes* existingElementAttributes =
   1036        mElements->Lookup(elementName).DataPtrOrNull();
   1037    if (!existingElementAttributes) {
   1038      // Step 2.5.1. Comment: This is the case with a global allow-list that
   1039      // does not yet contain element.
   1040 
   1041      // Step 2.5.2. Append element to configuration["elements"].
   1042      mElements->InsertOrUpdate(elementName, std::move(elementAttributes));
   1043 
   1044      // Step 2.5.3. Return true.
   1045      return true;
   1046    }
   1047 
   1048    // Step 2.6. Comment: This is the case with a global allow-list that
   1049    // already contains element.
   1050 
   1051    // Step 2.7. Let current element be the item in configuration["elements"]
   1052    // where item[name] equals element[name] and item[namespace] equals
   1053    // element[namespace]. (implict with existingElementAttributes)
   1054 
   1055    // Step 2.8. If element equals current element then return modified.
   1056    if (elementAttributes.Equals(*existingElementAttributes)) {
   1057      return modified;
   1058    }
   1059 
   1060    // Step 2.9. Remove element from configuration["elements"].
   1061    // Step 2.10. Append element to configuration["elements"].
   1062    mElements->InsertOrUpdate(elementName, std::move(elementAttributes));
   1063 
   1064    // Step 2.11. Return true.
   1065    return true;
   1066  }
   1067 
   1068  // Step 3. Otherwise:
   1069  // Step 3.1. If element["attributes"] exists or element["removeAttributes"]
   1070  // with default « » is not empty:
   1071  if (elementAttributes.mAttributes ||
   1072      (elementAttributes.mRemoveAttributes &&
   1073       !elementAttributes.mRemoveAttributes->IsEmpty())) {
   1074    // Step 3.1.1. The user agent may report a warning to the console that this
   1075    // operation is not supported.
   1076    if (auto* win = mGlobal->GetAsInnerWindow()) {
   1077      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
   1078                                      "Sanitizer"_ns, win->GetDoc(),
   1079                                      nsContentUtils::eSECURITY_PROPERTIES,
   1080                                      "SanitizerAllowElementIgnored2");
   1081    }
   1082 
   1083    // Step 3.1.2. Return false.
   1084    return false;
   1085  }
   1086 
   1087  // Step 3.2. Set modified to the result of remove element from
   1088  // configuration["replaceWithChildrenElements"].
   1089  bool modified = mReplaceWithChildrenElements
   1090                      ? mReplaceWithChildrenElements->EnsureRemoved(elementName)
   1091                      : false;
   1092 
   1093  // Step 3.3. If configuration["removeElements"] does not contain element:
   1094  if (!mRemoveElements->Contains(elementName)) {
   1095    // Step 3.3.1. Comment: This is the case with a global remove-list that
   1096    // does not contain element.
   1097 
   1098    // Step 3.3.2. Return modified.
   1099    return modified;
   1100  }
   1101 
   1102  // Step 3.4. Comment: This is the case with a global remove-list that
   1103  // contains element.
   1104 
   1105  // Step 3.5. Remove element from configuration["removeElements"].
   1106  mRemoveElements->Remove(elementName);
   1107 
   1108  // Step 3.6. Return true.
   1109  return true;
   1110 }
   1111 
   1112 // https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element
   1113 bool Sanitizer::RemoveElement(
   1114    const StringOrSanitizerElementNamespace& aElement) {
   1115  MaybeMaterializeDefaultConfig();
   1116 
   1117  // Step 1. Set element to the result of canonicalize a sanitizer element
   1118  // with element.
   1119  CanonicalElement element = CanonicalizeElement(aElement);
   1120 
   1121  return RemoveElementCanonical(std::move(element));
   1122 }
   1123 
   1124 bool Sanitizer::RemoveElementCanonical(CanonicalElement&& aElement) {
   1125  // Step 2. Set modified to the result of remove element from
   1126  // configuration["replaceWithChildrenElements"].
   1127  bool modified = mReplaceWithChildrenElements
   1128                      ? mReplaceWithChildrenElements->EnsureRemoved(aElement)
   1129                      : false;
   1130 
   1131  // Step 3. If configuration["elements"] exists:
   1132  if (mElements) {
   1133    // Step 3.1. If configuration["elements"] contains element:
   1134    if (mElements->Contains(aElement)) {
   1135      // Step 3.1.1. Comment: We have a global allow list and it contains
   1136      // element.
   1137 
   1138      // Step 3.1.2. Remove element from configuration["elements"].
   1139      mElements->Remove(aElement);
   1140 
   1141      // Step 3.1.3. Return true.
   1142      return true;
   1143    }
   1144 
   1145    // Step 3.2. Comment: We have a global allow list and it does not contain
   1146    // element.
   1147 
   1148    // Step 3.3. Return modified.
   1149    return modified;
   1150  }
   1151 
   1152  // Step 4. Otherwise:
   1153  // Step 4.1. If configuration["removeElements"] contains element:
   1154  if (mRemoveElements->Contains(aElement)) {
   1155    // Step 4.1.1. Comment: We have a global remove list and it already
   1156    // contains element.
   1157 
   1158    // Step 4.1.2. Return modified.
   1159    return modified;
   1160  }
   1161 
   1162  // Step 4.2. Comment: We have a global remove list and it does not contain
   1163  // element.
   1164 
   1165  // Step 4.3. Add element to configuration["removeElements"].
   1166  mRemoveElements->Insert(std::move(aElement));
   1167 
   1168  // Step 4.4. Return true.
   1169  return true;
   1170 }
   1171 
   1172 // https://wicg.github.io/sanitizer-api/#sanitizer-replace-an-element-with-its-children
   1173 bool Sanitizer::ReplaceElementWithChildren(
   1174    const StringOrSanitizerElementNamespace& aElement) {
   1175  MaybeMaterializeDefaultConfig();
   1176 
   1177  // Step 1. Set element to the result of canonicalize a sanitizer element
   1178  // with element.
   1179  CanonicalElement element = CanonicalizeElement(aElement);
   1180 
   1181  // Step 2. If configuration["replaceWithChildrenElements"] contains element:
   1182  if (mReplaceWithChildrenElements &&
   1183      mReplaceWithChildrenElements->Contains(element)) {
   1184    // Step 2.1. Return false.
   1185    return false;
   1186  }
   1187 
   1188  // Step 3. Remove element from configuration["removeElements"].
   1189  if (mRemoveElements) {
   1190    mRemoveElements->Remove(element);
   1191  } else {
   1192    // Step 4. Remove element from configuration["elements"] list.
   1193    mElements->Remove(element);
   1194  }
   1195 
   1196  // Step 5. Add element to configuration["replaceWithChildrenElements"].
   1197  if (!mReplaceWithChildrenElements) {
   1198    mReplaceWithChildrenElements.emplace();
   1199  }
   1200  mReplaceWithChildrenElements->Insert(std::move(element));
   1201 
   1202  // Step 6. Return true.
   1203  return true;
   1204 }
   1205 
   1206 // https://wicg.github.io/sanitizer-api/#sanitizer-allow-an-attribute
   1207 bool Sanitizer::AllowAttribute(
   1208    const StringOrSanitizerAttributeNamespace& aAttribute) {
   1209  MaybeMaterializeDefaultConfig();
   1210 
   1211  // Step 1. Set attribute to the result of canonicalize a sanitizer attribute
   1212  // with attribute.
   1213  CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute);
   1214 
   1215  // Step 2. If configuration["attributes"] exists:
   1216  if (mAttributes) {
   1217    // Step 2.1. Comment: If we have a global allow-list, we need to add
   1218    // attribute.
   1219 
   1220    MOZ_ASSERT(mDataAttributes.isSome(),
   1221               "mDataAttributes exists iff mAttributes exists");
   1222 
   1223    // Step 2.2. If configuration["dataAttributes"] is true and attribute is a
   1224    // custom data attribute, then return false.
   1225    if (*mDataAttributes && attribute.IsDataAttribute()) {
   1226      return false;
   1227    }
   1228 
   1229    // Step 2.3. If configuration["attributes"] contains attribute
   1230    // return false.
   1231    if (mAttributes->Contains(attribute)) {
   1232      return false;
   1233    }
   1234 
   1235    // Step 2.3. Comment: Fix-up per-element allow and remove lists.
   1236 
   1237    // Step 2.5. If configuration["elements"] exists:
   1238    if (mElements) {
   1239      // Step 2.5.1. For each element in configuration["elements"]:
   1240      for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
   1241        CanonicalElementAttributes& elemAttributes = iter.Data();
   1242 
   1243        // Step 2.5.1.1. If element["attributes"] with default « [] » contains
   1244        // attribute:
   1245        if (elemAttributes.mAttributes &&
   1246            elemAttributes.mAttributes->Contains(attribute)) {
   1247          // Step 2.5.1.1.1. Remove attribute from element["attributes"].
   1248          elemAttributes.mAttributes->Remove(attribute);
   1249        }
   1250 
   1251        // Step 2.5.1.2. Assert: element["removeAttributes"] with default « []
   1252        // » does not contain attribute.
   1253        MOZ_ASSERT_IF(elemAttributes.mRemoveAttributes,
   1254                      !elemAttributes.mRemoveAttributes->Contains(attribute));
   1255      }
   1256    }
   1257 
   1258    // Step 2.6. Append attribute to configuration["attributes"]
   1259    mAttributes->Insert(std::move(attribute));
   1260 
   1261    // Step 2.7. Return true.
   1262    return true;
   1263  }
   1264 
   1265  // Step 3. Otherwise:
   1266 
   1267  // Step 3.1. Comment: If we have a global remove-list, we need to remove
   1268  // attribute.
   1269 
   1270  // Step 3.2. If configuration["removeAttributes"] does not contain
   1271  // attribute:
   1272  if (!mRemoveAttributes->Contains(attribute)) {
   1273    // Step 3.2.1. Return false.
   1274    return false;
   1275  }
   1276 
   1277  // Step 3.3. Remove attribute from configuration["removeAttributes"].
   1278  mRemoveAttributes->Remove(attribute);
   1279 
   1280  // Step 3.4. Return true.
   1281  return true;
   1282 }
   1283 
   1284 // https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute
   1285 bool Sanitizer::RemoveAttribute(
   1286    const StringOrSanitizerAttributeNamespace& aAttribute) {
   1287  MaybeMaterializeDefaultConfig();
   1288 
   1289  // Step 1. Set attribute to the result of canonicalize a sanitizer attribute
   1290  // with attribute.
   1291  CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute);
   1292 
   1293  return RemoveAttributeCanonical(std::move(attribute));
   1294 }
   1295 
   1296 bool Sanitizer::RemoveAttributeCanonical(CanonicalAttribute&& aAttribute) {
   1297  // Step 2. If configuration["attributes"] exists:
   1298  if (mAttributes) {
   1299    // Step 2.1. Comment: If we have a global allow-list, we need to add
   1300    // attribute.
   1301 
   1302    // Step 2.2. If configuration["attributes"] does not contain attribute:
   1303    if (!mAttributes->Contains(aAttribute)) {
   1304      // Step 2.2.1. Return false.
   1305      return false;
   1306    }
   1307 
   1308    // Step 2.3. Comment: Fix-up per-element allow and remove lists.
   1309 
   1310    // Step 2.4. If configuration["elements"] exists:
   1311    if (mElements) {
   1312      // Step 2.4.1. For each element in configuration["elements"]:
   1313      for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
   1314        CanonicalElementAttributes& elemAttributes = iter.Data();
   1315        // Step 2.4.1.1. If element["removeAttributes"] with default « [] »
   1316        // contains attribute:
   1317        if (elemAttributes.mRemoveAttributes &&
   1318            elemAttributes.mRemoveAttributes->Contains(aAttribute)) {
   1319          // Step 2.4.1.1.1. Remove attribute from
   1320          // element["removeAttributes"].
   1321          elemAttributes.mRemoveAttributes->Remove(aAttribute);
   1322        }
   1323      }
   1324    }
   1325 
   1326    // Step 2.5. Remove attribute from configuration["attributes"].
   1327    mAttributes->Remove(aAttribute);
   1328 
   1329    // Step 2.6. Return true.
   1330    return true;
   1331  }
   1332 
   1333  // Step 3. Otherwise:
   1334  // Step 3.1. Comment: If we have a global remove-list, we need to add
   1335  // attribute.
   1336 
   1337  // Step 3.2. If configuration["removeAttributes"] contains attribute return
   1338  // false.
   1339  if (mRemoveAttributes->Contains(aAttribute)) {
   1340    return false;
   1341  }
   1342 
   1343  // Step 3.3. Comment: Fix-up per-element allow and remove lists.
   1344  // Step 3.4. If configuration["elements"] exists:
   1345  if (mElements) {
   1346    // Step 3.4.1. For each element in configuration["elements"]:
   1347    for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
   1348      CanonicalElementAttributes& elemAttributes = iter.Data();
   1349      // Step 3.4.1.1. If element["attributes"] with default « [] » contains
   1350      // attribute:
   1351      if (elemAttributes.mAttributes &&
   1352          elemAttributes.mAttributes->Contains(aAttribute)) {
   1353        // Step 3.4.1.1.1. Remove attribute from element["attributes"].
   1354        elemAttributes.mAttributes->Remove(aAttribute);
   1355      }
   1356 
   1357      // Step 3.4.1.2. If element["removeAttributes"] with default « [] »
   1358      // contains attribute:
   1359      if (elemAttributes.mRemoveAttributes &&
   1360          elemAttributes.mRemoveAttributes->Contains(aAttribute)) {
   1361        // Step 3.4.1.2.1. Remove attribute from element["removeAttributes"].
   1362        elemAttributes.mRemoveAttributes->Remove(aAttribute);
   1363      }
   1364    }
   1365  }
   1366 
   1367  // Step 3.5. Append attribute to configuration["removeAttributes"].
   1368  mRemoveAttributes->Insert(std::move(aAttribute));
   1369 
   1370  // Step 3.6. Return true.
   1371  return true;
   1372 }
   1373 
   1374 // https://wicg.github.io/sanitizer-api/#sanitizer-set-comments
   1375 bool Sanitizer::SetComments(bool aAllow) {
   1376  // The sanitize algorithm optimized for the default config supports
   1377  // comments both being allowed and disallowed.
   1378 
   1379  // Step 1. If configuration["comments"] exists and configuration["comments"]
   1380  // equals allow, then return false;
   1381  if (mComments == aAllow) {
   1382    return false;
   1383  }
   1384 
   1385  // Step 2. Set configuration["comments"] to allow.
   1386  mComments = aAllow;
   1387 
   1388  // Step 3. Return true.
   1389  return true;
   1390 }
   1391 
   1392 // https://wicg.github.io/sanitizer-api/#sanitizer-set-data-attributes
   1393 bool Sanitizer::SetDataAttributes(bool aAllow) {
   1394  // Same as above for data-attributes.
   1395 
   1396  // Step 1. If configuration["attributes"] does not exist, then return false.
   1397  // Note: The default config has "attributes".
   1398  if (!mIsDefaultConfig && !mAttributes) {
   1399    return false;
   1400  }
   1401 
   1402  MOZ_ASSERT(mDataAttributes.isSome(),
   1403             "mDataAttributes exists iff mAttributes exists (or in the default "
   1404             "config)");
   1405 
   1406  // Step 2. If configuration["dataAttributes"] equals allow, then return false.
   1407  if (*mDataAttributes == aAllow) {
   1408    return false;
   1409  }
   1410 
   1411  // Step 3. If allow is true:
   1412  if (!mIsDefaultConfig && aAllow) {
   1413    // Note: The default config does not contain any data-attributes.
   1414 
   1415    // Step 3.1. Remove any items attr from configuration["attributes"] where
   1416    // attr is a custom data attribute.
   1417    mAttributes->RemoveIf([](const CanonicalAttribute& aAttribute) {
   1418      return aAttribute.IsDataAttribute();
   1419    });
   1420 
   1421    // Step 3.2. If configuration["elements"] exists:
   1422    if (mElements) {
   1423      // Step 3.2.1. For each element in configuration["elements"]:
   1424      for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
   1425        CanonicalElementAttributes& elemAttributes = iter.Data();
   1426        // Step 3.2.1.1. If element[attributes] exists:
   1427        if (elemAttributes.mAttributes) {
   1428          // Step 3.2.1.1.1. Remove any items attr from element[attributes]
   1429          // where attr is a custom data attribute.
   1430          elemAttributes.mAttributes->RemoveIf(
   1431              [](const CanonicalAttribute& aAttribute) {
   1432                return aAttribute.IsDataAttribute();
   1433              });
   1434        }
   1435      }
   1436    }
   1437  }
   1438 
   1439  // Step 4. Set configuration["dataAttributes"] to allow.
   1440  mDataAttributes = Some(aAllow);
   1441 
   1442  // Step 5. Return true.
   1443  return true;
   1444 }
   1445 
   1446 // https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration
   1447 // The built-in safe baseline configuration
   1448 #define FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) \
   1449  ELEMENT(XHTML, xhtml, embed)                    \
   1450  ELEMENT(XHTML, xhtml, frame)                    \
   1451  ELEMENT(XHTML, xhtml, iframe)                   \
   1452  ELEMENT(XHTML, xhtml, object)                   \
   1453  ELEMENT(XHTML, xhtml, script)                   \
   1454  ELEMENT(SVG, svg, script)                       \
   1455  ELEMENT(SVG, svg, use)
   1456 
   1457 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe
   1458 bool Sanitizer::RemoveUnsafe() {
   1459  // XXX this should be a no-op for the default config now.
   1460  MaybeMaterializeDefaultConfig();
   1461 
   1462  // Step 1. Assert: The key set of built-in safe baseline configuration equals
   1463  // «[ "removeElements", "removeAttributes" ] ».
   1464 
   1465  // Step 2. Let result be false.
   1466  bool result = false;
   1467 
   1468  // Step 3. For each element in built-in safe baseline
   1469  // configuration[removeElements]:
   1470 #define ELEMENT(_, NSURI, LOCAL_NAME)                                       \
   1471  /* Step 3.1. Call remove an element element from configuration. */        \
   1472  if (RemoveElementCanonical(CanonicalElement(nsGkAtoms::LOCAL_NAME,        \
   1473                                              nsGkAtoms::nsuri_##NSURI))) { \
   1474    /* Step 3.2. If the call returned true, set result to true. */          \
   1475    result = true;                                                          \
   1476  }
   1477 
   1478  FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT)
   1479 
   1480 #undef ELEMENT
   1481 
   1482  // Step 4. For each attribute in built-in safe baseline
   1483  // configuration[removeAttributes]:
   1484  // (This is an empty list)
   1485 
   1486  // Step 5. For each attribute listed in event handler content attributes:
   1487  // TODO: Consider sorting these.
   1488  nsContentUtils::ForEachEventAttributeName(
   1489      EventNameType_All & ~EventNameType_XUL,
   1490      [self = MOZ_KnownLive(this), &result](nsAtom* aName) {
   1491        // Step 5.1. Call remove an attribute attribute from configuration.
   1492        if (self->RemoveAttributeCanonical(
   1493                CanonicalAttribute(aName, nullptr))) {
   1494          // Step 5.2. If the call returned true, set result to true.
   1495          result = true;
   1496        }
   1497      });
   1498 
   1499  // Step 6. Return result.
   1500  return result;
   1501 }
   1502 
   1503 // https://wicg.github.io/sanitizer-api/#sanitize
   1504 void Sanitizer::Sanitize(nsINode* aNode, bool aSafe, ErrorResult& aRv) {
   1505  MOZ_ASSERT(aNode->OwnerDoc()->IsLoadedAsData(),
   1506             "SanitizeChildren relies on the document being inert to be safe");
   1507 
   1508  // Step 1. Let configuration be the value of sanitizer’s configuration.
   1509 
   1510  // Step 2. If safe is true, then set configuration to the result of calling
   1511  // remove unsafe on configuration.
   1512  //
   1513  // Optimization: We really don't want to make a copy of the configuration
   1514  // here, so we instead explictly remove the handful elements and
   1515  // attributes that are part of "remove unsafe" in the
   1516  // SanitizeChildren() and SanitizeAttributes() methods.
   1517 
   1518  // Step 3. Call sanitize core on node, configuration, and with
   1519  // handleJavascriptNavigationUrls set to safe.
   1520  if (mIsDefaultConfig) {
   1521    AssertNoLists();
   1522    SanitizeChildren<true>(aNode, aSafe);
   1523  } else {
   1524    AssertIsValid();
   1525    SanitizeChildren<false>(aNode, aSafe);
   1526  }
   1527 }
   1528 
   1529 static RefPtr<nsAtom> ToNamespace(int32_t aNamespaceID) {
   1530  if (aNamespaceID == kNameSpaceID_None) {
   1531    return nullptr;
   1532  }
   1533 
   1534  RefPtr<nsAtom> atom =
   1535      nsNameSpaceManager::GetInstance()->NameSpaceURIAtom(aNamespaceID);
   1536  return atom;
   1537 }
   1538 
   1539 static bool IsUnsafeElement(nsAtom* aLocalName, int32_t aNamespaceID) {
   1540 #define ELEMENT(NSID, _, LOCAL_NAME)           \
   1541  if (aNamespaceID == kNameSpaceID_##NSID) {   \
   1542    if (aLocalName == nsGkAtoms::LOCAL_NAME) { \
   1543      return true;                             \
   1544    }                                          \
   1545  }
   1546 
   1547  FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT)
   1548 
   1549 #undef ELEMENT
   1550 
   1551  return false;
   1552 }
   1553 
   1554 // https://wicg.github.io/sanitizer-api/#sanitize-core
   1555 template <bool IsDefaultConfig>
   1556 void Sanitizer::SanitizeChildren(nsINode* aNode, bool aSafe) {
   1557  // Step 1. For each child in current’s children:
   1558  nsCOMPtr<nsIContent> next = nullptr;
   1559  for (nsCOMPtr<nsIContent> child = aNode->GetFirstChild(); child;
   1560       child = next) {
   1561    next = child->GetNextSibling();
   1562 
   1563    // Step 1.1. Assert: child implements Text, Comment, Element, or
   1564    // DocumentType.
   1565    MOZ_ASSERT(child->IsText() || child->IsComment() || child->IsElement() ||
   1566               child->NodeType() == nsINode::DOCUMENT_TYPE_NODE);
   1567 
   1568    // Step 1.2. If child implements DocumentType, then continue.
   1569    if (child->NodeType() == nsINode::DOCUMENT_TYPE_NODE) {
   1570      continue;
   1571    }
   1572 
   1573    // Step 1.3. If child implements Text, then continue.
   1574    if (child->IsText()) {
   1575      continue;
   1576    }
   1577 
   1578    // Step 1.4. If child implements Comment:
   1579    if (child->IsComment()) {
   1580      // Step 1.4.1 If configuration["comments"] is not true, then remove
   1581      // child.
   1582      if (!mComments) {
   1583        child->RemoveFromParent();
   1584      }
   1585      continue;
   1586    }
   1587 
   1588    // Step 1.5. Otherwise:
   1589    MOZ_ASSERT(child->IsElement());
   1590 
   1591    // Step 1.5.1. Let elementName be a SanitizerElementNamespace with child’s
   1592    // local name and namespace.
   1593    nsAtom* nameAtom = child->NodeInfo()->NameAtom();
   1594    int32_t namespaceID = child->NodeInfo()->NamespaceID();
   1595    // Make sure this is optimized away when using the default config.
   1596    Maybe<CanonicalElement> elementName;
   1597    // This is only used for the default config case.
   1598    [[maybe_unused]] StaticAtomSet* elementAttributes = nullptr;
   1599    if constexpr (!IsDefaultConfig) {
   1600      elementName.emplace(nameAtom, ToNamespace(namespaceID));
   1601 
   1602      // Optimization: Remove unsafe elements before doing anything else.
   1603      // https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration
   1604      //
   1605      // We have to do this _before_ handling the
   1606      // "replaceWithChildrenElements" list, because the "remove an element"
   1607      // call in removeUnsafe() would implicitly remove it from the list.
   1608      //
   1609      // The default config's "elements" allow list does not contain any
   1610      // unsafe elements so we can skip this.
   1611      if (aSafe && IsUnsafeElement(nameAtom, namespaceID)) {
   1612        child->RemoveFromParent();
   1613        continue;
   1614      }
   1615 
   1616      // Step 1.5.2. If configuration["replaceWithChildrenElements"] exists
   1617      // and if configuration["replaceWithChildrenElements"] contains
   1618      // elementName:
   1619      if (mReplaceWithChildrenElements &&
   1620          mReplaceWithChildrenElements->Contains(*elementName) &&
   1621          // Temporary fix for Bug 2004112
   1622          // To be specified by https://github.com/WICG/sanitizer-api/issues/365
   1623          !!child->GetParent()) {
   1624        // Note: This follows nsTreeSanitizer by first inserting the
   1625        // child's children in place of the current child and then
   1626        // continueing the sanitization from the first inserted grandchild.
   1627        nsCOMPtr<nsIContent> parent = child->GetParent();
   1628        nsCOMPtr<nsIContent> firstChild = child->GetFirstChild();
   1629        nsCOMPtr<nsIContent> newChild = firstChild;
   1630        for (; newChild; newChild = child->GetFirstChild()) {
   1631          ErrorResult rv;
   1632          parent->InsertBefore(*newChild, child, rv);
   1633          if (rv.Failed()) {
   1634            // TODO: Abort?
   1635            break;
   1636          }
   1637        }
   1638 
   1639        child->RemoveFromParent();
   1640        if (firstChild) {
   1641          next = firstChild;
   1642        }
   1643        continue;
   1644      }
   1645 
   1646      // Step 1.5.3. If configuration["removeElements"] exists and
   1647      // configuration["removeElements"] contains elementName:
   1648      if (mRemoveElements) {
   1649        if (mRemoveElements->Contains(*elementName)) {
   1650          // Step 1.5.3.1. Remove child.
   1651          child->RemoveFromParent();
   1652          // Step 1.5.3.2.Continue.
   1653          continue;
   1654        }
   1655      }
   1656 
   1657      // Step 1.5.4. If configuration["elements"] exists and
   1658      // configuration["elements"] does not contain elementName:
   1659      if (mElements) {
   1660        if (!mElements->Contains(*elementName)) {
   1661          // Step 1.5.4.1. Remove child.
   1662          child->RemoveFromParent();
   1663          // Step 1.5.4.2. Continue.
   1664          continue;
   1665        }
   1666      }
   1667    } else {
   1668      // (The default config has no replaceWithChildrenElements or
   1669      // removeElements)
   1670 
   1671      // Step 1.5.4. If configuration["elements"] exists and
   1672      // configuration["elements"] does not contain elementName:
   1673 
   1674      bool found = false;
   1675      if (nameAtom->IsStatic()) {
   1676        ElementsWithAttributes* elements = nullptr;
   1677        if (namespaceID == kNameSpaceID_XHTML) {
   1678          elements = sDefaultHTMLElements;
   1679        } else if (namespaceID == kNameSpaceID_MathML) {
   1680          elements = sDefaultMathMLElements;
   1681        } else if (namespaceID == kNameSpaceID_SVG) {
   1682          elements = sDefaultSVGElements;
   1683        }
   1684        if (elements) {
   1685          if (auto lookup = elements->Lookup(nameAtom->AsStatic())) {
   1686            found = true;
   1687            // This is the nullptr for elements without specific allowed
   1688            // attributes.
   1689            elementAttributes = lookup->get();
   1690          }
   1691        }
   1692      }
   1693      if (!found) {
   1694        // Step 1.5.4.1. Remove child.
   1695        child->RemoveFromParent();
   1696        // Step 1.5.4.2. Continue.
   1697        continue;
   1698      }
   1699 
   1700      MOZ_ASSERT(!IsUnsafeElement(nameAtom, namespaceID),
   1701                 "The default config has no unsafe elements");
   1702    }
   1703 
   1704    // Step 1.5.5. If elementName equals «[ "name" → "template", "namespace" →
   1705    // HTML namespace ]», then call sanitize core on child’s template contents
   1706    // with configuration and handleJavascriptNavigationUrls.
   1707    if (auto* templateEl = HTMLTemplateElement::FromNode(child)) {
   1708      RefPtr<DocumentFragment> frag = templateEl->Content();
   1709      SanitizeChildren<IsDefaultConfig>(frag, aSafe);
   1710    }
   1711 
   1712    // Step 1.5.6. If child is a shadow host, then call sanitize core on child’s
   1713    // shadow root with configuration and handleJavascriptNavigationUrls.
   1714    if (RefPtr<ShadowRoot> shadow = child->GetShadowRoot()) {
   1715      SanitizeChildren<IsDefaultConfig>(shadow, aSafe);
   1716    }
   1717 
   1718    // Step 1.5.7-9.
   1719    if constexpr (!IsDefaultConfig) {
   1720      SanitizeAttributes(child->AsElement(), *elementName, aSafe);
   1721    } else {
   1722      SanitizeDefaultConfigAttributes(child->AsElement(), elementAttributes,
   1723                                      aSafe);
   1724    }
   1725 
   1726    // Step 1.5.10. Call sanitize core on child with configuration and
   1727    // handleJavascriptNavigationUrls.
   1728    // TODO: Optimization: Remove recusion similar to nsTreeSanitizer
   1729    SanitizeChildren<IsDefaultConfig>(child, aSafe);
   1730  }
   1731 }
   1732 
   1733 static inline bool IsDataAttribute(nsAtom* aName, int32_t aNamespaceID) {
   1734  return StringBeginsWith(nsDependentAtomString(aName), u"data-"_ns) &&
   1735         aNamespaceID == kNameSpaceID_None;
   1736 }
   1737 
   1738 // https://wicg.github.io/sanitizer-api/#sanitize-core
   1739 // Step 2.4.9.5. If handleJavascriptNavigationUrls:
   1740 static bool RemoveJavascriptNavigationURLAttribute(Element* aElement,
   1741                                                   nsAtom* aLocalName,
   1742                                                   int32_t aNamespaceID) {
   1743  // https://wicg.github.io/sanitizer-api/#contains-a-javascript-url
   1744  auto containsJavascriptURL = [&]() {
   1745    nsAutoString value;
   1746    if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) {
   1747      return false;
   1748    }
   1749 
   1750    // Step 1. Let url be the result of running the basic URL parser on
   1751    // attribute’s value.
   1752    nsCOMPtr<nsIURI> uri;
   1753    if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), value))) {
   1754      // Step 2. If url is failure, then return false.
   1755      return false;
   1756    }
   1757 
   1758    // Step 3. Return whether url’s scheme is "javascript".
   1759    return uri->SchemeIs("javascript");
   1760  };
   1761 
   1762  // Step 1. If «[elementName, attrName]» matches an entry in the built-in
   1763  // navigating URL attributes list, and if attribute contains a javascript:
   1764  // URL, then remove attribute from child.
   1765  if ((aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
   1766                                     nsGkAtoms::base) &&
   1767       aLocalName == nsGkAtoms::href && aNamespaceID == kNameSpaceID_None) ||
   1768      (aElement->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input) &&
   1769       aLocalName == nsGkAtoms::formaction &&
   1770       aNamespaceID == kNameSpaceID_None) ||
   1771      (aElement->IsHTMLElement(nsGkAtoms::form) &&
   1772       aLocalName == nsGkAtoms::action && aNamespaceID == kNameSpaceID_None) ||
   1773      (aElement->IsHTMLElement(nsGkAtoms::iframe) &&
   1774       aLocalName == nsGkAtoms::src && aNamespaceID == kNameSpaceID_None) ||
   1775      (aElement->IsSVGElement(nsGkAtoms::a) && aLocalName == nsGkAtoms::href &&
   1776       (aNamespaceID == kNameSpaceID_None ||
   1777        aNamespaceID == kNameSpaceID_XLink))) {
   1778    if (containsJavascriptURL()) {
   1779      return true;
   1780    }
   1781  };
   1782 
   1783  // Step 2. If child’s namespace is the MathML Namespace and attr’s local
   1784  // name is "href" and attr’s namespace is null or the XLink namespace and
   1785  // attr contains a javascript: URL, then remove attr.
   1786  if (aElement->IsMathMLElement() && aLocalName == nsGkAtoms::href &&
   1787      (aNamespaceID == kNameSpaceID_None ||
   1788       aNamespaceID == kNameSpaceID_XLink)) {
   1789    if (containsJavascriptURL()) {
   1790      return true;
   1791    }
   1792  }
   1793 
   1794  // Step 3. If the built-in animating URL attributes list contains
   1795  // «[elementName, attrName]» and attr’s value is "href" or "xlink:href",
   1796  // then remove attr.
   1797  if (aLocalName == nsGkAtoms::attributeName &&
   1798      aNamespaceID == kNameSpaceID_None &&
   1799      aElement->IsAnyOfSVGElements(nsGkAtoms::animate, nsGkAtoms::animateMotion,
   1800                                   nsGkAtoms::animateTransform,
   1801                                   nsGkAtoms::set)) {
   1802    nsAutoString value;
   1803    if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) {
   1804      return false;
   1805    }
   1806 
   1807    return value.EqualsLiteral("href") || value.EqualsLiteral("xlink:href");
   1808  }
   1809 
   1810  return false;
   1811 }
   1812 
   1813 void Sanitizer::SanitizeAttributes(Element* aChild,
   1814                                   const CanonicalElement& aElementName,
   1815                                   bool aSafe) {
   1816  MOZ_ASSERT(!mIsDefaultConfig);
   1817 
   1818  // https://wicg.github.io/sanitizer-api/#sanitize-core
   1819  // Substeps of 1.5. that are relevant to attributes.
   1820 
   1821  // Step 7. Let elementWithLocalAttributes be « [] ».
   1822  // Step 8. If configuration["elements"] exists and configuration["elements"]
   1823  // contains elementName:
   1824  // Step 8.1. Set elementWithLocalAttributes to
   1825  // configuration["elements"][elementName].
   1826  const CanonicalElementAttributes* elementAttributes =
   1827      mElements ? mElements->Lookup(aElementName).DataPtrOrNull() : nullptr;
   1828 
   1829  // Step 9. For each attribute in child’s attribute list:
   1830  int32_t count = int32_t(aChild->GetAttrCount());
   1831  for (int32_t i = count - 1; i >= 0; --i) {
   1832    // Step 9.1. Let attrName be a SanitizerAttributeNamespace with attribute’s
   1833    // local name and namespace.
   1834    const nsAttrName* attr = aChild->GetAttrNameAt(i);
   1835    RefPtr<nsAtom> attrLocalName = attr->LocalName();
   1836    int32_t attrNs = attr->NamespaceID();
   1837    CanonicalAttribute attrName(attrLocalName, ToNamespace(attrNs));
   1838 
   1839    bool remove = false;
   1840    // Optimization: Remove unsafe event handler content attributes.
   1841    // https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe
   1842    if (aSafe && attrNs == kNameSpaceID_None &&
   1843        nsContentUtils::IsEventAttributeName(
   1844            attrLocalName, EventNameType_All & ~EventNameType_XUL)) {
   1845      remove = true;
   1846    }
   1847 
   1848    // Step 9.2. If elementWithLocalAttributes["removeAttributes"] with default
   1849    // « [] » contains attrName:
   1850    else if (elementAttributes && elementAttributes->mRemoveAttributes &&
   1851             elementAttributes->mRemoveAttributes->Contains(attrName)) {
   1852      // Step 9.2.1. Remove attribute.
   1853      remove = true;
   1854    }
   1855 
   1856    // Step 9.3. Otherwise, if configuration["attributes"] exists:
   1857    else if (mAttributes) {
   1858      // Step 9.3.1. If configuration["attributes"] does not contain attrName
   1859      // and elementWithLocalAttributes["attributes"] with default « [] » does
   1860      // not contain attrName, and if "data-" is not a code unit prefix of
   1861      // attribute’s local name and namespace is not null or
   1862      // configuration["dataAttributes"] is not true:
   1863      MOZ_ASSERT(mDataAttributes.isSome(),
   1864                 "mDataAttributes exists iff mAttributes exists");
   1865      if (!mAttributes->Contains(attrName) &&
   1866          !(elementAttributes && elementAttributes->mAttributes &&
   1867            elementAttributes->mAttributes->Contains(attrName)) &&
   1868          !(*mDataAttributes && IsDataAttribute(attrLocalName, attrNs))) {
   1869        // Step 9.3.1.1. Remove attribute.
   1870        remove = true;
   1871      }
   1872    }
   1873 
   1874    // Step 9.4. Otherwise:
   1875    else {
   1876      // Step 9.4.1. If elementWithLocalAttributes["attributes"] exists and
   1877      // elementWithLocalAttributes["attributes"] does not contain attrName:
   1878      if (elementAttributes && elementAttributes->mAttributes &&
   1879          !elementAttributes->mAttributes->Contains(attrName)) {
   1880        // Step 9.4.1.1. Remove attribute.
   1881        remove = true;
   1882      }
   1883 
   1884      // Step 9.4.2. Otherwise, if configuration["removeAttributes"] contains
   1885      // attrName:
   1886      else if (mRemoveAttributes->Contains(attrName)) {
   1887        // Step 9.4.2.1. Remove attribute.
   1888        remove = true;
   1889      }
   1890    }
   1891 
   1892    // Step 5. If handleJavascriptNavigationUrls:
   1893    if (aSafe && !remove) {
   1894      remove =
   1895          RemoveJavascriptNavigationURLAttribute(aChild, attrLocalName, attrNs);
   1896    }
   1897 
   1898    if (remove) {
   1899      aChild->UnsetAttr(attr->NamespaceID(), attr->LocalName(), false);
   1900 
   1901      // XXX Copied from nsTreeSanitizer.
   1902      // In case the attribute removal shuffled the attribute order, start the
   1903      // loop again.
   1904      --count;
   1905      i = count;  // i will be decremented immediately thanks to the for loop
   1906    }
   1907  }
   1908 }
   1909 
   1910 void Sanitizer::SanitizeDefaultConfigAttributes(
   1911    Element* aChild, StaticAtomSet* aElementAttributes, bool aSafe) {
   1912  MOZ_ASSERT(mIsDefaultConfig);
   1913 
   1914  // https://wicg.github.io/sanitizer-api/#sanitize-core
   1915  // Substeps of 1.5. that are relevant to attributes.
   1916 
   1917  // Step 7-8. (aElementAttributes passed as an argument)
   1918 
   1919  // Step 9. For each attribute in child’s attribute list:
   1920  int32_t count = int32_t(aChild->GetAttrCount());
   1921  for (int32_t i = count - 1; i >= 0; --i) {
   1922    // Step 1. Let attrName be a SanitizerAttributeNamespace with attribute’s
   1923    // local name and namespace.
   1924    const nsAttrName* attr = aChild->GetAttrNameAt(i);
   1925    RefPtr<nsAtom> attrLocalName = attr->LocalName();
   1926    int32_t attrNs = attr->NamespaceID();
   1927 
   1928    // Step 2. If elementWithLocalAttributes["removeAttributes"] with default «
   1929    // [] » contains attrName:
   1930    // (No local removeAttributes in the default config)
   1931 
   1932    // Step 3. Otherwise, if configuration["attributes"] exists:
   1933    // Step 3.1. If configuration["attributes"] does not contain attrName and
   1934    // elementWithLocalAttributes["attributes"] with default « [] » does not
   1935    // contain attrName, and if "data-" is not a code unit prefix of attribute’s
   1936    // local name and namespace is not null or configuration["dataAttributes"]
   1937    // is not true:
   1938    bool remove = false;
   1939    // Note: All attributes allowed by the default config are in the "null"
   1940    // namespace.
   1941    MOZ_ASSERT(mDataAttributes.isSome(),
   1942               "mDataAttributes always exists in the default config");
   1943    if (attrNs != kNameSpaceID_None ||
   1944        (!sDefaultAttributes->Contains(attrLocalName) &&
   1945         !(aElementAttributes && aElementAttributes->Contains(attrLocalName)) &&
   1946         !(*mDataAttributes && IsDataAttribute(attrLocalName, attrNs)))) {
   1947      // Step 3.1.1. Remove attribute.
   1948      remove = true;
   1949    }
   1950 
   1951    // Step 4. Otherwise:
   1952    // (not applicable)
   1953 
   1954    // Step 5. If handleJavascriptNavigationUrls:
   1955    else if (aSafe) {
   1956      // TODO: This could be further optimized, because the default config
   1957      // at the moment only allows <a href>.
   1958      remove =
   1959          RemoveJavascriptNavigationURLAttribute(aChild, attrLocalName, attrNs);
   1960    }
   1961 
   1962    // The default config attribute allow lists don't contain event
   1963    // handler attributes.
   1964    MOZ_ASSERT_IF(!remove,
   1965                  !nsContentUtils::IsEventAttributeName(
   1966                      attrLocalName, EventNameType_All & ~EventNameType_XUL));
   1967 
   1968    if (remove) {
   1969      aChild->UnsetAttr(attr->NamespaceID(), attr->LocalName(), false);
   1970 
   1971      // XXX Copied from nsTreeSanitizer.
   1972      // In case the attribute removal shuffled the attribute order, start the
   1973      // loop again.
   1974      --count;
   1975      i = count;  // i will be decremented immediately thanks to the for loop
   1976    }
   1977  }
   1978 }
   1979 
   1980 }  // namespace mozilla::dom