Skip to main content

script/dom/security/
sanitizer.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::LazyCell;
6use std::cmp::Ordering;
7use std::collections::HashSet;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Namespace, local_name, ns};
11use js::context::JSContext;
12use js::rust::HandleObject;
13use script_bindings::cell::DomRefCell;
14use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
15use style::attr::AttrValue;
16use url::Url;
17
18use crate::dom::Node;
19use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
20use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
21use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
22    SanitizerAttribute, SanitizerAttributeNamespace, SanitizerConfig, SanitizerElement,
23    SanitizerElementNamespace, SanitizerElementNamespaceWithAttributes,
24    SanitizerElementWithAttributes, SanitizerMethods, SanitizerPI, SanitizerPresets,
25    SanitizerProcessingInstruction, SetHTMLOptions, SetHTMLUnsafeOptions,
26};
27use crate::dom::bindings::codegen::UnionTypes::{
28    SanitizerConfigOrSanitizerPresets, SanitizerOrSanitizerConfigOrSanitizerPresets,
29};
30use crate::dom::bindings::domname::is_custom_data_attribute;
31use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
32use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
33use crate::dom::bindings::reflector::DomGlobal;
34use crate::dom::bindings::root::DomRoot;
35use crate::dom::bindings::str::DOMString;
36use crate::dom::console::Console;
37use crate::dom::documentfragment::DocumentFragment;
38use crate::dom::element::Element;
39use crate::dom::eventtarget::CONTENT_EVENT_HANDLER_NAMES;
40use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
41use crate::dom::node::node::NodeTraits;
42use crate::dom::processinginstruction::ProcessingInstruction;
43use crate::dom::servoparser::ServoParser;
44use crate::dom::window::Window;
45
46#[dom_struct]
47pub(crate) struct Sanitizer {
48    reflector_: Reflector,
49    /// <https://wicg.github.io/sanitizer-api/#sanitizer-configuration>
50    configuration: DomRefCell<SanitizerConfig>,
51}
52
53impl Sanitizer {
54    fn new_inherited(configuration: SanitizerConfig) -> Sanitizer {
55        Sanitizer {
56            reflector_: Reflector::new(),
57            configuration: DomRefCell::new(configuration),
58        }
59    }
60
61    pub(crate) fn new_with_proto(
62        cx: &mut JSContext,
63        window: &Window,
64        proto: Option<HandleObject>,
65        configuration: SanitizerConfig,
66    ) -> DomRoot<Sanitizer> {
67        reflect_dom_object_with_proto(
68            cx,
69            Box::new(Sanitizer::new_inherited(configuration)),
70            window,
71            proto,
72        )
73    }
74
75    /// <https://html.spec.whatwg.org/multipage/#get-a-sanitizer-instance-from-options>
76    pub(crate) fn get_sanitizer_instance_from_options(
77        cx: &mut JSContext,
78        window: &Window,
79        options: &impl SanitizerMember,
80        safe: bool,
81    ) -> Fallible<DomRoot<Sanitizer>> {
82        // Step 1. Let sanitizerSpec be "default".
83        // Step 2. If options["sanitizer"] exists, then set sanitizerSpec to options["sanitizer"].
84        //
85        // NOTE: options["sanitizer"] always exists.
86        let mut sanitizer_spec = options.sanitizer().clone();
87
88        // Step 3. Assert: sanitizerSpec is either a Sanitizer instance, a SanitizerPresets member,
89        // or a SanitizerConfig dictionary.
90        assert!(matches!(
91            sanitizer_spec,
92            SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(_) |
93                SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerPresets(_) |
94                SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(_)
95        ));
96
97        // Step 4. If sanitizerSpec is a string:
98        if let SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerPresets(
99            sanitizer_spec_string,
100        ) = sanitizer_spec
101        {
102            // Step 4.1. Assert: sanitizerSpec is "default"
103            assert_eq!(sanitizer_spec_string, SanitizerPresets::Default);
104
105            // Step 4.2. Set sanitizerSpec to the built-in safe default configuration.
106            sanitizer_spec = SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(
107                built_in_safe_default_configuration(),
108            );
109        }
110
111        // Step 5. If sanitizerSpec is a dictionary:
112        if let SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(
113            sanitizer_spec_dictionary,
114        ) = sanitizer_spec
115        {
116            // Step 5.1. Let sanitizer be a new Sanitizer object.
117            let sanitizer = Sanitizer::new_with_proto(cx, window, None, SanitizerConfig::default());
118
119            // Step 5.2. Let permissiveDefaults be true if safe is false; false otherwise.
120            let permissive_defaults = !safe;
121
122            // Step 5.3. Configure sanitizer given sanitizerSpec and permissiveDefaults.
123            sanitizer.configure(sanitizer_spec_dictionary, permissive_defaults)?;
124
125            // Step 5.4. Set sanitizerSpec to sanitizer.
126            sanitizer_spec = SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(sanitizer);
127        }
128
129        // Step 6. Return sanitizerSpec.
130        if let SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(sanitizer) = sanitizer_spec {
131            Ok(sanitizer)
132        } else {
133            unreachable!("Guaranteed by Step 4 and 5")
134        }
135    }
136
137    /// <https://html.spec.whatwg.org/multipage/#configure-a-sanitizer>
138    fn configure(
139        &self,
140        mut configuration: SanitizerConfig,
141        permissive_defaults: bool,
142    ) -> ErrorResult {
143        // Step 1. Canonicalize the configuration configuration with permissiveDefaults.
144        configuration.canonicalize(permissive_defaults);
145
146        // Step 2. If configuration is not valid, then throw a TypeError.
147        if !configuration.is_valid() {
148            return Err(Error::Type(c"The configuration is invalid".into()));
149        }
150
151        // Step 3. Set sanitizer's configuration to configuration.
152        let mut sanitizer_configuration = self.configuration.borrow_mut();
153        *sanitizer_configuration = configuration;
154        Ok(())
155    }
156
157    /// <https://wicg.github.io/sanitizer-api/#set-and-filter-html>
158    pub(crate) fn set_and_filter_html(
159        cx: &mut JSContext,
160        target: &Node,
161        context_element: &Element,
162        html: DOMString,
163        options: &impl SanitizerMember,
164        safe: bool,
165    ) -> ErrorResult {
166        // Step 1. If safe and contextElement’s local name is "script" and contextElement’s
167        // namespace is the HTML namespace or the SVG namespace, then return.
168        if safe &&
169            context_element.local_name() == &local_name!("script") &&
170            (context_element.namespace() == &ns!(html) ||
171                context_element.namespace() == &ns!(svg))
172        {
173            return Ok(());
174        }
175
176        // Step 2. Let sanitizer be the result of calling get a sanitizer instance from options with
177        // options and safe.
178        let sanitizer = Sanitizer::get_sanitizer_instance_from_options(
179            cx,
180            &target.owner_window(),
181            options,
182            safe,
183        )?;
184
185        // Step 3. Let newChildren be the result of the HTML fragment parsing algorithm given
186        // contextElement, html, and true.
187        let new_children = ServoParser::parse_html_fragment(cx, context_element, html, true);
188
189        // Step 4. Let fragment be a new DocumentFragment whose node document is contextElement’s
190        // node document.
191        let context_document = context_element.owner_document();
192        let fragment = DomRoot::upcast::<Node>(DocumentFragment::new(cx, &context_document));
193
194        // Step 5. For each node in newChildren, append node to fragment.
195        for child in new_children {
196            fragment
197                .AppendChild(cx, &child)
198                .expect("Must be able to append child to node");
199        }
200
201        // Step 6. Run sanitize on fragment using sanitizer and safe.
202        sanitizer.sanitize(cx, &fragment, safe)?;
203
204        // Step 7. Replace all with fragment within target.
205        Node::replace_all(cx, Some(&fragment), target);
206
207        Ok(())
208    }
209
210    /// <https://html.spec.whatwg.org/multipage/#sanitize>
211    pub(crate) fn sanitize(&self, cx: &mut JSContext, node: &Node, safe: bool) -> ErrorResult {
212        // Step 1. Let configuration be sanitizer's configuration.
213        {
214            let mut configuration = self.configuration.borrow_mut();
215
216            // Step 2. Assert: configuration is valid.
217            debug_assert!(configuration.is_valid());
218
219            // Step 3. If safe is true, then remove unsafe from configuration.
220            if safe {
221                configuration.remove_unsafe();
222            }
223        }
224
225        let configuration = self.configuration.borrow();
226        // Step 4. Run the inner sanitize steps given node and configuration.
227        inner_sanitize_steps(cx, node, &configuration)
228    }
229}
230
231/// <https://html.spec.whatwg.org/multipage/#inner-sanitize-steps>
232fn inner_sanitize_steps(
233    cx: &mut JSContext,
234    node: &Node,
235    configuration: &SanitizerConfig,
236) -> ErrorResult {
237    // Step 1. For each child of node’s children:
238    for child in node.children() {
239        // Step 1.1. Assert: child is a Text, Comment, Element, ProcessingInstruction, or
240        // DocumentType node.
241        assert!(matches!(
242            child.type_id(),
243            NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) |
244                NodeTypeId::CharacterData(CharacterDataTypeId::Comment) |
245                NodeTypeId::Element(_) |
246                NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) |
247                NodeTypeId::DocumentType
248        ));
249
250        match child.type_id() {
251            // Step 1.2. If child is a DocumentType or Text node, then continue.
252            NodeTypeId::DocumentType | NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
253                continue;
254            },
255
256            // Step 1.3. If child is a Comment node:
257            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
258                // Step 1.3.1. If configuration["comments"] is not true, then remove child.
259                if configuration.comments != Some(true) {
260                    child.remove_self(cx);
261                }
262                // Step 1.3.2. Continue.
263                continue;
264            },
265
266            // Step 1.4. If child is a ProcessingInstruction node:
267            //
268            // FIXME: <https://github.com/whatwg/html/pull/12118>
269            // Currently, processing instructions are parsed as comments, since HTML parsing has not
270            // yet supported processing instructions. This will be resolved once the PR
271            // <https://github.com/whatwg/html/pull/12118> at HTML specification is merged and the
272            // relavent changes are implemented in html5ever.
273            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
274                // Step 1.4.1. Let piTarget be child's target.
275                let pi_target = SanitizerPI::String(
276                    child
277                        .downcast::<ProcessingInstruction>()
278                        .expect("Guaranteed by pattern matching of child.type_id()")
279                        .target()
280                        .clone()
281                        .root(),
282                );
283
284                // Step 1.4.2. If configuration["processingInstructions"] exists:
285                // Step 1.4.2.1. If configuration["processingInstructions"] does not contain
286                // piTarget, then remove child.
287                // Step 1.4.3. Otherwise:
288                // Step 1.4.3.1. If configuration["removeProcessingInstructions"] contains
289                // piTarget, then remove child.
290                if configuration.processingInstructions.as_ref().is_some_and(
291                    |configuration_processing_instructions| {
292                        !configuration_processing_instructions.contains_target(&pi_target)
293                    },
294                ) || configuration
295                    .removeProcessingInstructions
296                    .as_ref()
297                    .is_some_and(|configuration_remove_processing_instructions| {
298                        configuration_remove_processing_instructions.contains_target(&pi_target)
299                    })
300                {
301                    child.remove_self(cx);
302                }
303            },
304
305            // Step 1.5. Otherwise:
306            _ => {
307                // Step 1.5.1. Let elementName be a SanitizerElementNamespace with child’s local
308                // name and namespace.
309                let child = DomRoot::downcast::<Element>(child).expect("Guaranteed by Step 1.1");
310                let element_name =
311                    SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
312                        name: DOMString::from(&**child.local_name()),
313                        namespace: Some(DOMString::from(&**child.namespace())),
314                    });
315
316                // Step 1.5.2. If configuration["replaceWithChildrenElements"] exists and if
317                // configuration["replaceWithChildrenElements"] contains elementName:
318                if configuration
319                    .replaceWithChildrenElements
320                    .as_ref()
321                    .is_some_and(|configuration_replace_with_children_elements| {
322                        configuration_replace_with_children_elements.contains_item(&element_name)
323                    })
324                {
325                    // Step 1.5.2.1. Assert: node does not implement Document.
326                    assert!(!matches!(node.type_id(), NodeTypeId::Document(_)));
327
328                    // Step 1.5.2.2. Call sanitize core on child with configuration and
329                    // handleJavascriptNavigationUrls.
330                    inner_sanitize_steps(cx, child.upcast(), configuration)?;
331
332                    // Step 1.5.2.3. Let fragment be the result of creating a document fragment
333                    // given node's node document.
334                    let fragment = DocumentFragment::new(cx, &node.owner_document());
335
336                    // Step 1.5.2.4. For each innerChild of child’s children, append innerChild to
337                    // fragment.
338                    let child = DomRoot::upcast::<Node>(child);
339                    let fragment = DomRoot::upcast::<Node>(fragment);
340                    for inner_child in child.children() {
341                        fragment.AppendChild(cx, &inner_child)?;
342                    }
343
344                    // Step 1.5.2.5. Replace child with fragment within node.
345                    // Assert that this did not throw.
346                    node.ReplaceChild(cx, &fragment, &child)
347                        .expect("Replacing child with fragment within node should not fail.");
348
349                    // Step 1.5.2.6. Continue.
350                    continue;
351                }
352
353                // Step 1.5.3. If configuration["elements"] exists:
354                if let Some(configuration_elements) = configuration.elements.as_ref() {
355                    // Step 1.5.3.1. If configuration["elements"] does not contain elementName,
356                    // then remove child and continue.
357                    if !configuration_elements.contains_item(&element_name) {
358                        child.upcast::<Node>().remove_self(cx);
359                        continue;
360                    }
361                }
362                // Step 1.5.4. Otherwise:
363                else {
364                    // Step 1.5.4.1. If configuration["removeElements"] contains elementName,
365                    // then remove child and continue.
366                    if configuration.removeElements.as_ref().is_some_and(
367                        |configuration_remove_elements| {
368                            configuration_remove_elements.contains_item(&element_name)
369                        },
370                    ) {
371                        child.upcast::<Node>().remove_self(cx);
372                        continue;
373                    }
374                }
375
376                // Step 1.5.5. If elementName["name"] is "template" and elementName["namespace"]
377                // is the HTML namespace, then run the inner sanitize steps given child's template
378                // contents and configuration.
379                if element_name.name().str() == "template" &&
380                    element_name
381                        .namespace()
382                        .is_some_and(|namespace| *namespace.str() == ns!(html))
383                {
384                    let template_contents = child
385                        .downcast::<HTMLTemplateElement>()
386                        .expect("Guaranteed by elementName's name being \"template\"")
387                        .Content(cx);
388                    inner_sanitize_steps(cx, template_contents.upcast(), configuration)?;
389                }
390
391                // Step 1.5.6. If child is a shadow host, then run the inner sanitize steps given
392                // child's shadow root and configuration.
393                if let Some(shadow_root) = child.shadow_root() {
394                    inner_sanitize_steps(cx, shadow_root.upcast(), configuration)?;
395                }
396
397                // Step 1.5.7. Let elementWithLocalAttributes be « [] ».
398                let mut element_with_local_attributes =
399                    SanitizerElementWithAttributes::String(DOMString::new());
400
401                // Step 1.5.8. If configuration["elements"] exists and configuration["elements"]
402                // contains elementName, then set elementWithLocalAttributes to
403                // configuration["elements"][elementName].
404                if let Some(configuration_elements) = &configuration.elements &&
405                    let Some(found) = configuration_elements.iter().find(|entry| {
406                        entry.name() == element_name.name() &&
407                            entry.namespace() == element_name.namespace()
408                    })
409                {
410                    element_with_local_attributes = found.clone();
411                }
412
413                // Step 1.5.9. For each attribute in child's attribute list:
414                //
415                // NOTE: We will modify the attribute list in the "for" block. So, we clone the
416                // attribute list first to avoid holding an immutable reference to the attribute
417                // list.
418                let attribute_list = child
419                    .attrs()
420                    .borrow()
421                    .iter()
422                    .map(|attribute| {
423                        (
424                            attribute.local_name().clone(),
425                            attribute.namespace().clone(),
426                            attribute.value().clone(),
427                        )
428                    })
429                    .collect::<Vec<_>>();
430                for (attribute_local_name, attribute_namespace, attribute_value) in
431                    attribute_list.iter()
432                {
433                    // Step 1.5.9.1. Let attrName be a SanitizerAttributeNamespace with attribute’s
434                    // local name and namespace.
435                    let attribute_name = SanitizerAttribute::SanitizerAttributeNamespace(
436                        SanitizerAttributeNamespace {
437                            name: DOMString::from(attribute_local_name.as_ref()),
438                            namespace: if attribute_namespace.is_empty() {
439                                None
440                            } else {
441                                Some(DOMString::from(attribute_namespace.as_ref()))
442                            },
443                        },
444                    );
445
446                    // Step 1.5.9.2. If elementWithLocalAttributes["removeAttributes"] exists and
447                    // elementWithLocalAttributes["removeAttributes"] contains attrName,
448                    // then remove attribute.
449                    if element_with_local_attributes
450                        .remove_attributes()
451                        .is_some_and(|remove_attributes| {
452                            remove_attributes.contains_item(&attribute_name)
453                        })
454                    {
455                        child.remove_attribute(cx, attribute_namespace, attribute_local_name);
456                    }
457                    // Step 1.5.9.3. Otherwise, if configuration["attributes"] exists:
458                    else if let Some(configuration_attributes) = configuration.attributes.as_ref()
459                    {
460                        // Step 1.5.9.3.1. If configuration["attributes"] does not contain attrName
461                        // and elementWithLocalAttributes["attributes"] with default « » does not
462                        // contain attrName, and if "data-" is not a code unit prefix of attribute's
463                        // local name or attribute's namespace is not null or
464                        // configuration["dataAttributes"] is not true, then remove attribute.
465                        if (!configuration_attributes.contains_item(&attribute_name) &&
466                            !element_with_local_attributes
467                                .attributes()
468                                .unwrap_or_default()
469                                .contains_item(&attribute_name)) &&
470                            (!attribute_local_name.starts_with("data-") ||
471                                !attribute_namespace.is_empty() ||
472                                configuration.dataAttributes != Some(true))
473                        {
474                            child.remove_attribute(cx, attribute_namespace, attribute_local_name);
475                        }
476                    }
477                    // Step 1.5.9.4. Otherwise:
478                    else {
479                        // Step 1.5.9.4.1. If elementWithLocalAttributes["attributes"] exists and
480                        // elementWithLocalAttributes["attributes"] does not contain attrName, then
481                        // remove attribute.
482                        // Step 1.5.9.4.2. Otherwise, if configuration["removeAttributes"] contains
483                        // attrName, then remove attribute.
484                        if element_with_local_attributes.attributes().is_some_and(
485                            |local_attributes| !local_attributes.contains_item(&attribute_name),
486                        ) || configuration.removeAttributes.as_ref().is_some_and(
487                            |configuration_remove_attributes| {
488                                configuration_remove_attributes.contains_item(&attribute_name)
489                            },
490                        ) {
491                            child.remove_attribute(cx, attribute_namespace, attribute_local_name);
492                        }
493                    }
494
495                    // Step 1.5.9.5. If configuration["javascriptURLs"] is not true:
496                    if configuration.javascriptURLs != Some(true) {
497                        // Step 1.5.9.5.1. If the pair (elementName, attrName) matches an entry in
498                        // the built-in navigating URL attributes list, and if attribute contains a
499                        // javascript: URL, then remove attribute.
500                        if BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST.iter().any(
501                            |(
502                                entry_element_name,
503                                entry_element_namespace,
504                                entry_attribute_name,
505                                entry_attribute_namespace,
506                            )| {
507                                (
508                                    entry_element_name.as_ref(),
509                                    entry_element_namespace.as_deref(),
510                                    entry_attribute_name.as_ref(),
511                                    entry_attribute_namespace.as_deref(),
512                                ) == (
513                                    element_name.name().str().as_ref(),
514                                    element_name.namespace().map(DOMString::str).as_deref(),
515                                    attribute_name.name().str().as_ref(),
516                                    attribute_name.namespace().map(DOMString::str).as_deref(),
517                                )
518                            },
519                        ) && contains_javascript_url(attribute_value)
520                        {
521                            child.remove_attribute(cx, attribute_namespace, attribute_local_name);
522                        }
523
524                        // Step 1.5.9.5.2. If child’s namespace is the MathML Namespace and attr’s
525                        // local name is "href" and attr’s namespace is null or the XLink namespace
526                        // and attr contains a javascript: URL, then remove attribute.
527                        if child.namespace() == &ns!(mathml) &&
528                            attribute_local_name == &local_name!("href") &&
529                            (attribute_namespace.is_empty() ||
530                                attribute_namespace == &ns!(xlink)) &&
531                            contains_javascript_url(attribute_value)
532                        {
533                            child.remove_attribute(cx, attribute_namespace, attribute_local_name);
534                        }
535
536                        // Step 1.5.9.5.3. If the built-in animating URL attributes list contains
537                        // the pair (elementName, attrName) and attr’s value is "href" or
538                        // "xlink:href", then remove attribute.
539                        if BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST.iter().any(
540                            |(
541                                entry_element_name,
542                                entry_element_namespace,
543                                entry_attribute_name,
544                                entry_attribute_namespace,
545                            )| {
546                                (
547                                    entry_element_name.as_ref(),
548                                    entry_element_namespace.as_deref(),
549                                    entry_attribute_name.as_ref(),
550                                    entry_attribute_namespace.as_deref(),
551                                ) == (
552                                    element_name.name().str().as_ref(),
553                                    element_name.namespace().map(DOMString::str).as_deref(),
554                                    attribute_name.name().str().as_ref(),
555                                    attribute_name.namespace().map(DOMString::str).as_deref(),
556                                )
557                            },
558                        ) && matches!(attribute_value.as_ref(), "href" | "xlink:href")
559                        {
560                            child.remove_attribute(cx, attribute_namespace, attribute_local_name);
561                        }
562                    }
563                }
564
565                // Step 1.5.10. Run the inner sanitize steps given child and configuration.
566                inner_sanitize_steps(cx, child.upcast(), configuration)?;
567            },
568        }
569    }
570
571    Ok(())
572}
573
574/// <https://wicg.github.io/sanitizer-api/#contains-a-javascript-url>
575fn contains_javascript_url(attribute_value: &AttrValue) -> bool {
576    // Step 1. Let url be the result of running the basic URL parser on attribute’s value.
577    // Step 2. If url is failure, then return false.
578    let Ok(url) = Url::parse(attribute_value) else {
579        return false;
580    };
581
582    // Step 3. Return whether url’s scheme is "javascript".
583    url.scheme() == "javascript"
584}
585
586impl SanitizerMethods<crate::DomTypeHolder> for Sanitizer {
587    /// <https://html.spec.whatwg.org/multipage/#dom-sanitizer-constructor>
588    fn Constructor(
589        cx: &mut JSContext,
590        window: &Window,
591        proto: Option<HandleObject>,
592        configuration: SanitizerConfigOrSanitizerPresets,
593    ) -> Fallible<DomRoot<Sanitizer>> {
594        let configuration = match configuration {
595            // Step 1. If configuration is a SanitizerPresets string:
596            SanitizerConfigOrSanitizerPresets::SanitizerPresets(configuration) => {
597                // Step 1.1. Assert: configuration is default.
598                assert_eq!(configuration, SanitizerPresets::Default);
599
600                // Step 1.2. Set configuration to the built-in safe default configuration.
601                built_in_safe_default_configuration()
602            },
603            SanitizerConfigOrSanitizerPresets::SanitizerConfig(configuration) => configuration,
604        };
605
606        // Step 2. Configure this given configuration and true.
607        let sanitizer = Sanitizer::new_with_proto(cx, window, proto, SanitizerConfig::default());
608        sanitizer.configure(configuration, true)?;
609
610        Ok(sanitizer)
611    }
612
613    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-get>
614    fn Get(&self) -> SanitizerConfig {
615        // Step 1. Let config be this’s configuration.
616        let mut config = self.configuration.borrow_mut();
617
618        // Step 2. Assert: config is valid.
619        debug_assert!(config.is_valid());
620
621        match &mut config.elements {
622            // Step 3. If config["elements"] exists:
623            Some(config_elements) => {
624                // Step 3.1. For any element of config["elements"]:
625                for element in config_elements.iter_mut() {
626                    // Step 3.1.1. If element["attributes"] exists:
627                    if let Some(element_attributes) = &mut element.attributes_mut() {
628                        // Step 3.1.1.1. Set element["attributes"] to the result of sort in
629                        // ascending order element["attributes"], with attrA being less than item
630                        // attrB.
631                        element_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
632                    }
633
634                    // Step 3.1.2. If element["removeAttributes"] exists:
635                    if let Some(element_remove_attributes) = &mut element.remove_attributes_mut() {
636                        // Step 3.1.2.1. Set element["removeAttributes"] to the result of sort in
637                        // ascending order element["removeAttributes"], with attrA being less than
638                        // item attrB.
639                        element_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
640                    }
641                }
642
643                // Step 3.2. Set config["elements"] to the result of sort in ascending order
644                // config["elements"], with elementA being less than item elementB.
645                config_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
646            },
647            // Step 4. Otherwise:
648            None => {
649                // Step 4.1. Set config["removeElements"] to the result of sort in ascending order
650                // config["removeElements"], with elementA being less than item elementB.
651                if let Some(config_remove_elements) = &mut config.removeElements {
652                    config_remove_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
653                }
654            },
655        }
656
657        // Step 5. If config["replaceWithChildrenElements"] exists:
658        if let Some(config_replace_with_children_elements) = &mut config.replaceWithChildrenElements
659        {
660            // Step 5.1.Set config["replaceWithChildrenElements"] to the result of sort in ascending
661            // order config["replaceWithChildrenElements"], with elementA being less than item
662            // elementB.
663            config_replace_with_children_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
664        }
665
666        match &mut config.processingInstructions {
667            // Step 6. If config["processingInstructions"] exists:
668            Some(config_processing_instructions) => {
669                // Step 6.1. Set config["processingInstructions"] to the result of sort in ascending
670                // order config["processingInstructions"], with piA["target"] being code unit less
671                // than piB["target"].
672                config_processing_instructions.sort_by(
673                    |processing_instruction_a, processing_instruction_b| {
674                        if processing_instruction_a.target() < processing_instruction_b.target() {
675                            Ordering::Less
676                        } else {
677                            Ordering::Greater
678                        }
679                    },
680                )
681            },
682            // Step 7. Otherwise:
683            None => {
684                // Step 7.1. Set config["removeProcessingInstructions"] to the result of sort in
685                // ascending order config["removeProcessingInstructions"], with piA["target"] being
686                // code unit less than piB["target"].
687                if let Some(config_remove_processing_instructions) =
688                    &mut config.removeProcessingInstructions
689                {
690                    config_remove_processing_instructions.sort_by(
691                        |processing_instruction_a, processing_instruction_b| {
692                            if processing_instruction_a.target() < processing_instruction_b.target()
693                            {
694                                Ordering::Less
695                            } else {
696                                Ordering::Greater
697                            }
698                        },
699                    )
700                }
701            },
702        }
703
704        match &mut config.attributes {
705            // Step 8. If config["attributes"] exists:
706            Some(config_attributes) => {
707                // Step 8.1. Set config["attributes"] to the result of sort in ascending order
708                // config["attributes"], with attrA being less than item attrB.
709                config_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
710            },
711            // Step 9. Otherwise:
712            None => {
713                // Step 9.1. Set config["removeAttributes"] to the result of sort in ascending order
714                // config["removeAttributes"], with attrA being less than item attrB.
715                if let Some(config_remove_attributes) = &mut config.removeAttributes {
716                    config_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
717                }
718            },
719        }
720
721        // Step 10. Return config.
722        (*config).clone()
723    }
724
725    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement>
726    fn AllowElement(&self, cx: &mut JSContext, element: SanitizerElementWithAttributes) -> bool {
727        // Step 1. Let configuration be this’s configuration.
728        let mut configuration = self.configuration.safe_borrow_mut(cx);
729
730        // Step 2. Assert: configuration is valid.
731        debug_assert!(configuration.is_valid());
732
733        // Step 3. Set element to the result of canonicalize a sanitizer element with attributes
734        // with element.
735        let mut element = element.canonicalize();
736
737        // Step 4. If configuration["elements"] exists:
738        if configuration.elements.is_some() {
739            // Step 4.1. Set modified to the result of remove element from
740            // configuration["replaceWithChildrenElements"].
741            let modified = if let Some(replace_with_children_elements) =
742                &mut configuration.replaceWithChildrenElements
743            {
744                replace_with_children_elements.remove_item(&element)
745            } else {
746                false
747            };
748
749            // Step 4.2. Comment: We need to make sure the per-element attributes do not overlap
750            // with global attributes.
751
752            match &configuration.attributes {
753                // Step 4.3. If configuration["attributes"] exists:
754                Some(configuration_attributes) => {
755                    // Step 4.3.1. If element["attributes"] exists:
756                    if let Some(element_attributes) = element.attributes_mut() {
757                        // Step 4.3.1.1. Set element["attributes"] to remove duplicates from
758                        // element["attributes"].
759                        element_attributes.remove_duplicates();
760
761                        // Step 4.3.1.2. Set element["attributes"] to the difference of
762                        // element["attributes"] and configuration["attributes"].
763                        element_attributes.difference(configuration_attributes);
764
765                        // Step 4.3.1.3. If configuration["dataAttributes"] is true:
766                        if configuration.dataAttributes == Some(true) {
767                            // Step 4.3.1.3.1. Remove all items item from element["attributes"]
768                            // where item is a custom data attribute.
769                            element_attributes
770                                .retain(|attribute| !attribute.is_custom_data_attribute());
771                        }
772                    }
773
774                    // Step 4.3.2. If element["removeAttributes"] exists:
775                    if let Some(element_remove_attributes) = element.remove_attributes_mut() {
776                        // Step 4.3.2.1. set element["removeattributes"] to remove duplicates from
777                        // element["removeattributes"].
778                        element_remove_attributes.remove_duplicates();
779
780                        // Step 4.3.2.2. set element["removeattributes"] to the intersection of
781                        // element["removeattributes"] and configuration["attributes"].
782                        element_remove_attributes.intersection(configuration_attributes);
783                    }
784                },
785                // Step 4.4. Otherwise:
786                None => {
787                    // NOTE: To avoid borrowing `element` again at Step 4.4.1.2 and 4.4.1.3 after
788                    // borrowing `element` mutably at the beginning of Step 4.4.1, we clone
789                    // element["attributes"] first, and call `set_attributes` at the end of Step
790                    // 4.4.1 to put it back into `element`.
791
792                    // Step 4.4.1. If element["attributes"] exists:
793                    if let Some(mut element_attributes) = element.attributes_mut().cloned() {
794                        // Step 4.4.1.1. Set element["attributes"] to remove duplicates from
795                        // element["attributes"].
796                        element_attributes.remove_duplicates();
797
798                        // Step 4.4.1.2. Set element["attributes"] to the difference of
799                        // element["attributes"] and element["removeAttributes"] with default « ».
800                        element_attributes
801                            .difference(element.remove_attributes().unwrap_or_default());
802
803                        // Step 4.4.1.3. Remove element["removeAttributes"].
804                        element.set_remove_attributes(None);
805
806                        // Step 4.4.1.4. Set element["attributes"] to the difference of
807                        // element["attributes"] and configuration["removeAttributes"].
808                        element_attributes.difference(
809                            configuration
810                                .removeAttributes
811                                .as_deref()
812                                .unwrap_or_default(),
813                        );
814
815                        element.set_attributes(Some(element_attributes));
816                    }
817
818                    // Step 4.4.2. If element["removeAttributes"] exists:
819                    if let Some(mut element_remove_attributes) = element.remove_attributes_mut() {
820                        // Step 4.4.2.1. Set element["removeAttributes"] to remove duplicates from
821                        // element["removeAttributes"].
822                        element_remove_attributes = element_remove_attributes.remove_duplicates();
823
824                        // Step 4.4.2.2. Set element["removeAttributes"] to the difference of
825                        // element["removeAttributes"] and configuration["removeAttributes"].
826                        element_remove_attributes.difference(
827                            configuration
828                                .removeAttributes
829                                .as_deref()
830                                .unwrap_or_default(),
831                        );
832                    }
833                },
834            }
835
836            // Step 4.5. If configuration["elements"] does not contain element:
837            let configuration_elements = configuration
838                .elements
839                .as_mut()
840                .expect("Guaranteed by Step 4");
841            if !configuration_elements.contains_item(&element) {
842                // Step 4.5.1. Comment: This is the case with a global allow-list that does not yet
843                // contain element.
844
845                // Step 4.5.2. Append element to configuration["elements"].
846                configuration_elements.push(element.clone());
847
848                // Step 4.5.3. Return true.
849                return true;
850            }
851
852            // Step 4.6. Comment: This is the case with a global allow-list that already contains
853            // element.
854
855            // Step 4.7. Let current element be the item in configuration["elements"] where
856            // item["name"] equals element["name"] and item["namespace"] equals
857            // element["namespace"].
858            let current_element = configuration_elements
859                .iter()
860                .find(|item| {
861                    item.name() == element.name() && item.namespace() == element.namespace()
862                })
863                .expect("Guaranteed by Step 4.5 and Step 4.5.2");
864
865            // Step 4.8. If element equals current element then return modified.
866            if element == *current_element {
867                return modified;
868            }
869
870            // Step 4.9. Remove element from configuration["elements"].
871            configuration_elements.remove_item(&element);
872
873            // Step 4.10. Append element to configuration["elements"]
874            configuration_elements.push(element);
875
876            // Step 4.11. Return true.
877            true
878        }
879        // Step 5. Otherwise:
880        else {
881            // Step 5.1. If element["attributes"] exists or element["removeAttributes"] with default
882            // « » is not empty:
883            if element.attributes().is_some() ||
884                !element.remove_attributes().unwrap_or_default().is_empty()
885            {
886                std::mem::drop(configuration);
887
888                // Step 5.1.1. The user agent may report a warning to the console that this
889                // operation is not supported.
890                Console::internal_warn(
891                    cx,
892                    &self.global(),
893                    "Do not support adding an element with attributes to a sanitizer \
894                        whose configuration[\"elements\"] does not exist."
895                        .into(),
896                );
897
898                // Step 5.1.2. Return false.
899                return false;
900            }
901
902            // Step 5.2. Set modified to the result of remove element from
903            // configuration["replaceWithChildrenElements"].
904            let modified = if let Some(replace_with_children_elements) =
905                &mut configuration.replaceWithChildrenElements
906            {
907                replace_with_children_elements.remove_item(&element)
908            } else {
909                false
910            };
911
912            // Step 5.3. If configuration["removeElements"] does not contain element:
913            if !configuration
914                .removeElements
915                .as_ref()
916                .is_some_and(|configuration_remove_elements| {
917                    configuration_remove_elements.contains_item(&element)
918                })
919            {
920                // Step 5.3.1. Comment: This is the case with a global remove-list that does not
921                // contain element.
922
923                // Step 5.3.2. Return modified.
924                return modified;
925            }
926
927            // Step 5.4. Comment: This is the case with a global remove-list that contains element.
928
929            // Step 5.5. Remove element from configuration["removeElements"].
930            if let Some(configuration_remove_elements) = &mut configuration.removeElements {
931                configuration_remove_elements.remove_item(&element);
932            }
933
934            // Step 5.6. Return true.
935            true
936        }
937    }
938
939    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
940    fn RemoveElement(&self, element: SanitizerElement) -> bool {
941        // Remove an element with element and this’s configuration.
942        self.configuration.borrow_mut().remove_element(element)
943    }
944
945    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
946    fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
947        // Step 1. Let configuration be this’s configuration.
948        let mut configuration = self.configuration.borrow_mut();
949
950        // Step 2. Assert: configuration is valid.
951        debug_assert!(configuration.is_valid());
952
953        // Step 3. Set element to the result of canonicalize a sanitizer element with element.
954        let element = element.canonicalize();
955
956        // Step 4. If the built-in non-replaceable elements list contains element:
957        if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(&element)) {
958            // Step 4.1. Return false.
959            return false;
960        }
961
962        // Step 5. If configuration["replaceWithChildrenElements"] contains element:
963        if configuration
964            .replaceWithChildrenElements
965            .as_ref()
966            .is_some_and(|configuration_replace_with_children_elements| {
967                configuration_replace_with_children_elements.contains_item(&element)
968            })
969        {
970            // Step 5.1. Return false.
971            return false;
972        }
973
974        // Step 6. Remove element from configuration["removeElements"].
975        if let Some(configuration_remove_elements) = &mut configuration.removeElements {
976            configuration_remove_elements.remove_item(&element);
977        }
978
979        // Step 7. Remove element from configuration["elements"] list.
980        if let Some(configuration_elements) = &mut configuration.elements {
981            configuration_elements.remove_item(&element);
982        }
983
984        // Step 8. Add element to configuration["replaceWithChildrenElements"].
985        if let Some(configuration_replace_with_children_elements) =
986            &mut configuration.replaceWithChildrenElements
987        {
988            configuration_replace_with_children_elements.add_item(element);
989        } else {
990            configuration.replaceWithChildrenElements = Some(vec![element]);
991        }
992
993        // Step 9. Return true.
994        true
995    }
996
997    /// <https://html.spec.whatwg.org/multipage/#dom-sanitizer-setjavascripturls>
998    fn SetJavascriptURLs(&self, allow: bool) -> bool {
999        // Step 1. Let configuration be this's configuration.
1000        let mut configuration = self.configuration.borrow_mut();
1001
1002        // Step 2. Assert: configuration is valid.
1003        debug_assert!(configuration.is_valid());
1004
1005        // Step 3. If configuration["javascriptURLs"] is allow, then return false.
1006        if configuration.javascriptURLs == Some(allow) {
1007            return false;
1008        }
1009
1010        // Step 4. Set configuration["javascriptURLs"] to allow.
1011        configuration.javascriptURLs = Some(allow);
1012
1013        // Step 5. Return true.
1014        true
1015    }
1016
1017    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowprocessinginstruction>
1018    fn AllowProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1019        // Step 1. Let configuration be this’s configuration.
1020        let mut configuration = self.configuration.borrow_mut();
1021
1022        // Step 2. Assert: configuration is valid.
1023        debug_assert!(configuration.is_valid());
1024
1025        // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1026        let processing_instruction = processing_instruction.canonicalize();
1027
1028        match &mut configuration.processingInstructions {
1029            // Step 4. If configuration["processingInstructions"] exists:
1030            Some(configuration_processing_instructions) => {
1031                // Step 4.1. If configuration["processingInstructions"] contains pi:
1032                if configuration_processing_instructions.contains_target(&processing_instruction) {
1033                    // Step 4.1.1. Return false.
1034                    return false;
1035                }
1036
1037                // Step 4.2. Append pi to configuration["processingInstructions"].
1038                configuration_processing_instructions.push(processing_instruction);
1039
1040                // Step 4.3. Return true.
1041                true
1042            },
1043            // Step 5. Otherwise:
1044            None => {
1045                // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1046                if configuration
1047                    .removeProcessingInstructions
1048                    .as_ref()
1049                    .is_some_and(|configuration_remove_processing_instructions| {
1050                        configuration_remove_processing_instructions
1051                            .contains_target(&processing_instruction)
1052                    })
1053                {
1054                    // Step 5.1.1. Remove the item from
1055                    // configuration["removeProcessingInstructions"] whose "target" is pi["target"].
1056                    if let Some(configuration_remove_processing_instructions) =
1057                        &mut configuration.removeProcessingInstructions
1058                    {
1059                        configuration_remove_processing_instructions
1060                            .retain(|item| item.target() != processing_instruction.target())
1061                    }
1062
1063                    // Step 5.1.2. Return true.
1064                    return true;
1065                }
1066
1067                // Step 5.2. Return false.
1068                false
1069            },
1070        }
1071    }
1072
1073    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeprocessinginstruction>
1074    fn RemoveProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1075        // Step 1. Let configuration be this’s configuration.
1076        let mut configuration = self.configuration.borrow_mut();
1077
1078        // Step 2. Assert: configuration is valid.
1079        debug_assert!(configuration.is_valid());
1080
1081        // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1082        let processing_instruction = processing_instruction.canonicalize();
1083
1084        match &mut configuration.processingInstructions {
1085            // Step 4. If configuration["processingInstructions"] exists:
1086            Some(configuration_processing_instructions) => {
1087                // Step 4.1. If configuration["processingInstructions"] contains pi:
1088                if configuration_processing_instructions.contains_target(&processing_instruction) {
1089                    // Step 4.1.1. Remove the item from configuration["processingInstructions"]
1090                    // whose "target" is pi["target"].
1091                    configuration_processing_instructions
1092                        .retain(|item| item.target() != processing_instruction.target());
1093
1094                    // Step 4.1.2. Return true.
1095                    return true;
1096                }
1097
1098                // Step 4.2. Return false.
1099                false
1100            },
1101            // Step 5. Otherwise:
1102            None => {
1103                // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1104                if configuration
1105                    .removeProcessingInstructions
1106                    .as_ref()
1107                    .is_some_and(|configuration_remove_processing_instructions| {
1108                        configuration_remove_processing_instructions
1109                            .contains_target(&processing_instruction)
1110                    })
1111                {
1112                    // Step 5.1.1. Return false.
1113                    return false;
1114                }
1115
1116                // Step 5.2. Append pi to configuration["removeProcessingInstructions"].
1117                if let Some(configuration_remove_processing_instructions) =
1118                    &mut configuration.removeProcessingInstructions
1119                {
1120                    configuration_remove_processing_instructions.push(processing_instruction);
1121                } else {
1122                    configuration.removeProcessingInstructions = Some(vec![processing_instruction]);
1123                }
1124
1125                // Step 5.3. Return true.
1126                true
1127            },
1128        }
1129    }
1130
1131    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowattribute>
1132    fn AllowAttribute(&self, attribute: SanitizerAttribute) -> bool {
1133        // Step 1. Let configuration be this’s configuration.
1134        let mut configuration = self.configuration.borrow_mut();
1135
1136        // Step 2. Assert: configuration is valid.
1137        debug_assert!(configuration.is_valid());
1138
1139        // Step 3. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1140        let attribute = attribute.canonicalize();
1141
1142        // Step 4. If configuration["attributes"] exists:
1143        if configuration.attributes.is_some() {
1144            // Step 4.1. Comment: If we have a global allow-list, we need to add attribute.
1145
1146            // Step 4.2. If configuration["dataAttributes"] is true and attribute is a custom data
1147            // attribute, then return false.
1148            if configuration.dataAttributes == Some(true) && attribute.is_custom_data_attribute() {
1149                return false;
1150            }
1151
1152            // Step 4.3. If configuration["attributes"] contains attribute return false.
1153            if configuration
1154                .attributes
1155                .as_ref()
1156                .is_some_and(|configuration_attributes| {
1157                    configuration_attributes.contains(&attribute)
1158                })
1159            {
1160                return false;
1161            }
1162
1163            // Step 4.4. Comment: Fix-up per-element allow and remove lists.
1164
1165            // Step 4.5. If configuration["elements"] exists:
1166            if let Some(configuration_elements) = &mut configuration.elements {
1167                // Step 4.5.1. For each element in configuration["elements"]:
1168                for element in configuration_elements.iter_mut() {
1169                    // Step 4.5.1.1. If element["attributes"] with default « » contains attribute:
1170                    // Step 4.5.1.1.1. Remove attribute from element["attributes"].
1171                    if let Some(element_attributes) = element.attributes_mut() {
1172                        element_attributes
1173                            .retain(|element_attribute| *element_attribute != attribute);
1174                    }
1175
1176                    // Step 4.5.1.2. Assert: element["removeAttributes"] with default « » does not
1177                    // contain attribute.
1178                    debug_assert!(!element.remove_attributes().is_some_and(
1179                        |element_remove_attributes| element_remove_attributes.contains(&attribute)
1180                    ));
1181                }
1182            }
1183
1184            // Step 4.6. Append attribute to configuration["attributes"]
1185            if let Some(configuration_attributes) = &mut configuration.attributes {
1186                configuration_attributes.push(attribute);
1187            } else {
1188                configuration.attributes = Some(vec![attribute]);
1189            }
1190
1191            // Step 4.7. Return true.
1192            true
1193        }
1194        // Step 5. Otherwise:
1195        else {
1196            // Step 5.1. Comment: If we have a global remove-list, we need to remove attribute.
1197
1198            // Step 5.2. If configuration["removeAttributes"] does not contain attribute:
1199            if !configuration.removeAttributes.as_ref().is_some_and(
1200                |configuration_remove_attributes| {
1201                    configuration_remove_attributes.contains(&attribute)
1202                },
1203            ) {
1204                // Step 5.2.1. Return false.
1205                return false;
1206            }
1207
1208            // Step 5.3. Remove attribute from configuration["removeAttributes"].
1209            if let Some(configuration_remove_attributes) = &mut configuration.removeAttributes {
1210                configuration_remove_attributes.retain(|configuration_remove_attribute| {
1211                    *configuration_remove_attribute != attribute
1212                });
1213            }
1214
1215            // Step 5.4. Return true.
1216            true
1217        }
1218    }
1219
1220    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeattribute>
1221    fn RemoveAttribute(&self, attribute: SanitizerAttribute) -> bool {
1222        // Remove an attribute with attribute and this’s configuration.
1223        self.configuration.borrow_mut().remove_attribute(attribute)
1224    }
1225
1226    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setcomments>
1227    fn SetComments(&self, allow: bool) -> bool {
1228        // Step 1. Let configuration be this’s configuration.
1229        let mut configuration = self.configuration.borrow_mut();
1230
1231        // Step 2. Assert: configuration is valid.
1232        debug_assert!(configuration.is_valid());
1233
1234        // Step 3. If configuration["comments"] exists and configuration["comments"] equals allow,
1235        // then return false;
1236        if configuration
1237            .comments
1238            .is_some_and(|configuration_comments| configuration_comments == allow)
1239        {
1240            return false;
1241        }
1242
1243        // Step 4. Set configuration["comments"] to allow.
1244        configuration.comments = Some(allow);
1245
1246        // Step 5. Return true.
1247        true
1248    }
1249
1250    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setdataattributes>
1251    fn SetDataAttributes(&self, allow: bool) -> bool {
1252        // Step 1. Let configuration be this’s configuration.
1253        let mut configuration = self.configuration.borrow_mut();
1254
1255        // Step 2. Assert: configuration is valid.
1256        debug_assert!(configuration.is_valid());
1257
1258        // Step 3. If configuration["attributes"] does not exist, then return false.
1259        if configuration.attributes.is_none() {
1260            return false;
1261        }
1262
1263        // Step 4. If configuration["dataAttributes"] equals allow, then return false.
1264        if configuration.dataAttributes == Some(allow) {
1265            return false;
1266        }
1267
1268        // Step 5. If allow is true:
1269        if allow {
1270            // Step 5.1. Remove any items attr from configuration["attributes"] where attr is a
1271            // custom data attribute.
1272            if let Some(configuration_attributes) = &mut configuration.attributes {
1273                configuration_attributes.retain(|attribute| !attribute.is_custom_data_attribute());
1274            }
1275
1276            // Step 5.2. If configuration["elements"] exists:
1277            if let Some(configuration_elements) = &mut configuration.elements {
1278                // Step 5.2.1. For each element in configuration["elements"]:
1279                for element in configuration_elements {
1280                    // Step 5.2.1.1. If element["attributes"] exists:
1281                    if let Some(element_attributes) = element.attributes_mut() {
1282                        // Step 5.2.1.1.1. Remove any items attr from element["attributes"] where
1283                        // attr is a custom data attribute.
1284                        element_attributes
1285                            .retain(|attribute| !attribute.is_custom_data_attribute());
1286                    }
1287                }
1288            }
1289        }
1290
1291        // Step 6. Set configuration["dataAttributes"] to allow.
1292        configuration.dataAttributes = Some(allow);
1293
1294        // Step 7. Return true.
1295        true
1296    }
1297
1298    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeunsafe>
1299    fn RemoveUnsafe(&self) -> bool {
1300        // Update this’s configuration with the result of calling remove unsafe on this’s
1301        // configuration.
1302        self.configuration.borrow_mut().remove_unsafe()
1303    }
1304}
1305
1306trait SanitizerConfigAlgorithm {
1307    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1308    fn is_valid(&self) -> bool;
1309
1310    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1311    fn remove_element(&mut self, element: SanitizerElement) -> bool;
1312
1313    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1314    fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool;
1315
1316    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1317    fn remove_unsafe(&mut self) -> bool;
1318
1319    /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1320    fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
1321}
1322
1323impl SanitizerConfigAlgorithm for SanitizerConfig {
1324    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1325    fn is_valid(&self) -> bool {
1326        // NOTE: It’s expected that the configuration being passing in has previously been run
1327        // through the canonicalize the configuration steps. We will simply assert conditions that
1328        // that algorithm should have guaranteed to hold.
1329
1330        // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
1331        assert!(self.elements.is_some() || self.removeElements.is_some());
1332
1333        // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
1334        // false.
1335        if self.elements.is_some() && self.removeElements.is_some() {
1336            return false;
1337        }
1338
1339        // Step 3. Assert: Either config["processingInstructions"] exists or
1340        // config["removeProcessingInstructions"] exists.
1341        assert!(
1342            self.processingInstructions.is_some() || self.removeProcessingInstructions.is_some()
1343        );
1344
1345        // Step 4. If config["processingInstructions"] exists and
1346        // config["removeProcessingInstructions"] exists, then return false.
1347        if self.processingInstructions.is_some() && self.removeProcessingInstructions.is_some() {
1348            return false;
1349        }
1350
1351        // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
1352        assert!(self.attributes.is_some() || self.removeAttributes.is_some());
1353
1354        // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
1355        // false.
1356        if self.attributes.is_some() && self.removeAttributes.is_some() {
1357            return false;
1358        }
1359
1360        // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
1361        // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
1362        // canonical, meaning they have been run through canonicalize a sanitizer element,
1363        // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
1364        // as appropriate.
1365        //
1366        // NOTE: This assertion could be done by running the canonicalization again to see if there
1367        // is any changes. Since it is expected to canonicalize the configuration before running
1368        // this `is_valid` function, we simply skip this assert for the sake of performace.
1369
1370        match &self.elements {
1371            // Step 8. If config["elements"] exists:
1372            Some(config_elements) => {
1373                // Step 8.1. If config["elements"] has duplicates, then return false.
1374                if config_elements.has_duplicates() {
1375                    return false;
1376                }
1377            },
1378            // Step 9. Otherwise:
1379            None => {
1380                // Step 9.1. If config["removeElements"] has duplicates, then return false.
1381                if self
1382                    .removeElements
1383                    .as_ref()
1384                    .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
1385                {
1386                    return false;
1387                }
1388            },
1389        }
1390
1391        // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
1392        // false.
1393        if self
1394            .replaceWithChildrenElements
1395            .as_ref()
1396            .is_some_and(|replace_with_children_elements| {
1397                replace_with_children_elements.has_duplicates()
1398            })
1399        {
1400            return false;
1401        }
1402
1403        match &self.processingInstructions {
1404            // Step 11. If config["processingInstructions"] exists:
1405            Some(config_processing_instructions) => {
1406                // Step 11.1. If config["processingInstructions"] has duplicate targets, then return
1407                // false.
1408                if config_processing_instructions.has_duplicate_targets() {
1409                    return false;
1410                }
1411            },
1412            // Step 12. Otherwise:
1413            None => {
1414                // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then
1415                // return false.
1416                if self.removeProcessingInstructions.as_ref().is_some_and(
1417                    |config_remove_processing_instructions| {
1418                        config_remove_processing_instructions.has_duplicate_targets()
1419                    },
1420                ) {
1421                    return false;
1422                }
1423            },
1424        }
1425
1426        match &self.attributes {
1427            // Step 13. If config["attributes"] exists:
1428            Some(config_attributes) => {
1429                // Step 13.1. If config["attributes"] has duplicates, then return false.
1430                if config_attributes.has_duplicates() {
1431                    return false;
1432                }
1433            },
1434            // Step 14. Otherwise:
1435            None => {
1436                // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
1437                if self
1438                    .removeAttributes
1439                    .as_ref()
1440                    .is_some_and(|config_remove_attributes| {
1441                        config_remove_attributes.has_duplicates()
1442                    })
1443                {
1444                    return false;
1445                }
1446            },
1447        }
1448
1449        // Step 15. If config["replaceWithChildrenElements"] exists:
1450        if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
1451            // Step 15.1. For each element of config["replaceWithChildrenElements"]:
1452            for element in config_replace_with_children_elements {
1453                // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
1454                // return false.
1455                if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(element)) {
1456                    return false;
1457                }
1458            }
1459
1460            match &self.elements {
1461                // Step 15.2. If config["elements"] exists:
1462                Some(config_elements) => {
1463                    // Step 15.2.1. If the intersection of config["elements"] and
1464                    // config["replaceWithChildrenElements"] is not empty, then return false.
1465                    if config_elements
1466                        .is_intersection_non_empty(config_replace_with_children_elements)
1467                    {
1468                        return false;
1469                    }
1470                },
1471                // Step 15.3. Otherwise:
1472                None => {
1473                    // Step 15.3.1. If the intersection of config["removeElements"] and
1474                    // config["replaceWithChildrenElements"] is not empty, then return false.
1475                    if self
1476                        .removeElements
1477                        .as_ref()
1478                        .is_some_and(|config_remove_elements| {
1479                            config_remove_elements
1480                                .is_intersection_non_empty(config_replace_with_children_elements)
1481                        })
1482                    {
1483                        return false;
1484                    }
1485                },
1486            }
1487        }
1488
1489        match &self.attributes {
1490            // Step 16. If config["attributes"] exists:
1491            Some(config_attributes) => {
1492                // Step 16.1. Assert: config["dataAttributes"] exists.
1493                assert!(self.dataAttributes.is_some());
1494
1495                // Step 16.2. If config["elements"] exists:
1496                if let Some(config_elements) = &self.elements {
1497                    // Step 16.2.1. For each element of config["elements"]:
1498                    for element in config_elements {
1499                        // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
1500                        // has duplicates, then return false.
1501                        if element
1502                            .attributes()
1503                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
1504                        {
1505                            return false;
1506                        }
1507
1508                        // Step 16.2.1.2. If element["removeAttributes"] exists and
1509                        // element["removeAttributes"] has duplicates, then return false.
1510                        if element
1511                            .remove_attributes()
1512                            .is_some_and(|element_remove_attributes| {
1513                                element_remove_attributes.has_duplicates()
1514                            })
1515                        {
1516                            return false;
1517                        }
1518
1519                        // Step 16.2.1.3. If the intersection of config["attributes"] and
1520                        // element["attributes"] with default « » is not empty, then return false.
1521                        if config_attributes
1522                            .is_intersection_non_empty(element.attributes().unwrap_or_default())
1523                        {
1524                            return false;
1525                        }
1526
1527                        // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
1528                        // subset of config["attributes"], then return false.
1529                        if !element
1530                            .remove_attributes()
1531                            .unwrap_or_default()
1532                            .iter()
1533                            .all(|entry| config_attributes.contains_item(entry))
1534                        {
1535                            return false;
1536                        }
1537
1538                        // Step 16.2.1.5. If config["dataAttributes"] is true and
1539                        // element["attributes"] contains a custom data attribute, then return
1540                        // false.
1541                        if self.dataAttributes == Some(true) &&
1542                            element.attributes().is_some_and(|attributes| {
1543                                attributes
1544                                    .iter()
1545                                    .any(|attribute| attribute.is_custom_data_attribute())
1546                            })
1547                        {
1548                            return false;
1549                        }
1550                    }
1551                }
1552
1553                // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
1554                // a custom data attribute, then return false.
1555                if self.dataAttributes == Some(true) &&
1556                    config_attributes
1557                        .iter()
1558                        .any(|attribute| attribute.is_custom_data_attribute())
1559                {
1560                    return false;
1561                }
1562            },
1563            // Step 17. Otherwise:
1564            None => {
1565                // Step 17.1. If config["elements"] exists:
1566                if let Some(config_elements) = &self.elements {
1567                    // Step 17.1.1. For each element of config["elements"]:
1568                    for element in config_elements {
1569                        // Step 17.1.1.1. If element["attributes"] exists and
1570                        // element["removeAttributes"] exists, then return false.
1571                        if element.attributes().is_some() && element.remove_attributes().is_some() {
1572                            return false;
1573                        }
1574
1575                        // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
1576                        // has duplicates, then return false.
1577                        if element
1578                            .attributes()
1579                            .as_ref()
1580                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
1581                        {
1582                            return false;
1583                        }
1584
1585                        // Step 17.1.1.3. If element["removeAttributes"] exist and
1586                        // element["removeAttributes"] has duplicates, then return false.
1587                        if element.remove_attributes().as_ref().is_some_and(
1588                            |element_remove_attributes| element_remove_attributes.has_duplicates(),
1589                        ) {
1590                            return false;
1591                        }
1592
1593                        // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
1594                        // element["attributes"] with default « » is not empty, then return false.
1595                        if self
1596                            .removeAttributes
1597                            .as_ref()
1598                            .is_some_and(|config_remove_attributes| {
1599                                config_remove_attributes.is_intersection_non_empty(
1600                                    element.attributes().unwrap_or_default(),
1601                                )
1602                            })
1603                        {
1604                            return false;
1605                        }
1606
1607                        // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
1608                        // element["removeAttributes"] with default « » is not empty, then return
1609                        // false.
1610                        if self
1611                            .removeAttributes
1612                            .as_ref()
1613                            .is_some_and(|config_remove_attributes| {
1614                                config_remove_attributes.is_intersection_non_empty(
1615                                    element.remove_attributes().unwrap_or_default(),
1616                                )
1617                            })
1618                        {
1619                            return false;
1620                        }
1621                    }
1622                }
1623
1624                // Step 17.2. If config["dataAttributes"] exists, then return false.
1625                if self.dataAttributes.is_some() {
1626                    return false;
1627                }
1628            },
1629        }
1630
1631        // Step 18. Return true.
1632        true
1633    }
1634
1635    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1636    fn remove_element(&mut self, element: SanitizerElement) -> bool {
1637        // Step 1. Assert: configuration is valid.
1638        debug_assert!(self.is_valid());
1639
1640        // Step 2. Set element to the result of canonicalize a sanitizer element with element.
1641        let element = element.canonicalize();
1642
1643        // Step 3. Set modified to the result of remove element from
1644        // configuration["replaceWithChildrenElements"].
1645        let modified = if let Some(configuration_replace_with_children_elements) =
1646            &mut self.replaceWithChildrenElements
1647        {
1648            configuration_replace_with_children_elements.remove_item(&element)
1649        } else {
1650            false
1651        };
1652
1653        // Step 4. If configuration["elements"] exists:
1654        if let Some(configuration_elements) = &mut self.elements {
1655            // Step 4.1. If configuration["elements"] contains element:
1656            if configuration_elements.contains_item(&element) {
1657                // Step 4.1.1. Comment: We have a global allow list and it contains element.
1658
1659                // Step 4.1.2. Remove element from configuration["elements"].
1660                configuration_elements.remove_item(&element);
1661
1662                // Step 4.1.3. Return true.
1663                return true;
1664            }
1665
1666            // Step 4.2. Comment: We have a global allow list and it does not contain element.
1667
1668            // Step 4.3. Return modified.
1669            modified
1670        }
1671        // Step 5. Otherwise:
1672        else {
1673            // Step 5.1. If configuration["removeElements"] contains element:
1674            if self
1675                .removeElements
1676                .as_mut()
1677                .is_some_and(|configuration_remove_elements| {
1678                    configuration_remove_elements.contains_item(&element)
1679                })
1680            {
1681                // Step 5.1.1. Comment: We have a global remove list and it already contains element.
1682
1683                // Step 5.1.2. Return modified.
1684                return modified;
1685            }
1686
1687            // Step 5.2. Comment: We have a global remove list and it does not contain element.
1688
1689            // Step 5.3. Add element to configuration["removeElements"].
1690            if let Some(configuration_remove_elements) = &mut self.removeElements {
1691                configuration_remove_elements.add_item(element);
1692            } else {
1693                self.removeElements = Some(vec![element]);
1694            }
1695
1696            // Step 5.4. Return true.
1697            true
1698        }
1699    }
1700
1701    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1702    fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool {
1703        // Step 1. Assert: configuration is valid.
1704        debug_assert!(self.is_valid());
1705
1706        // Step 2. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1707        let attribute = attribute.canonicalize();
1708
1709        // Step 3. If configuration["attributes"] exists:
1710        if self.attributes.is_some() {
1711            // Step 3.1. Comment: If we have a global allow-list, we need to remove attribute.
1712
1713            // Step 3.2. Set modified to the result of remove attribute from
1714            // configuration["attributes"].
1715            let mut modified = self
1716                .attributes
1717                .as_mut()
1718                .is_some_and(|configuration_attributes| {
1719                    configuration_attributes.remove_item(&attribute)
1720                });
1721
1722            // Step 3.3. Comment: Fix-up per-element allow and remove lists.
1723
1724            // Step 3.4. If configuration["elements"] exists:
1725            if let Some(configuration_elements) = &mut self.elements {
1726                // Step 3.4.1. For each element of configuration["elements"]:
1727                for element in configuration_elements {
1728                    // Step 3.4.1.1. If element["attributes"] with default « » contains attribute:
1729                    if element
1730                        .attributes()
1731                        .unwrap_or_default()
1732                        .contains(&attribute)
1733                    {
1734                        // Step 3.4.1.1.1. Set modified to true.
1735                        modified = true;
1736
1737                        // Step 3.4.1.1.2. Remove attribute from element["attributes"].
1738                        if let Some(element_attributes) = element.attributes_mut() {
1739                            element_attributes
1740                                .retain(|element_attribute| *element_attribute != attribute);
1741                        }
1742                    }
1743
1744                    // Step 3.4.1.2. If element["removeAttributes"] with default « » contains
1745                    // attribute:
1746                    if element
1747                        .remove_attributes()
1748                        .unwrap_or_default()
1749                        .contains(&attribute)
1750                    {
1751                        // Step 3.4.1.2.1. Assert: modified is true.
1752                        assert!(modified);
1753
1754                        // Step 3.4.1.2.2. Remove attribute from element["removeAttributes"].
1755                        if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1756                            element_remove_attributes.retain(|element_remove_attribute| {
1757                                *element_remove_attribute != attribute
1758                            });
1759                        }
1760                    }
1761                }
1762            }
1763
1764            // Step 3.5. Return modified.
1765            modified
1766        }
1767        // Step 4. Otherwise:
1768        else {
1769            // Step 4.1. Comment: If we have a global remove-list, we need to add attribute.
1770
1771            // Step 4.2. If configuration["removeAttributes"] contains attribute return false.
1772            if self
1773                .removeAttributes
1774                .as_ref()
1775                .is_some_and(|configuration_remove_attributes| {
1776                    configuration_remove_attributes.contains(&attribute)
1777                })
1778            {
1779                return false;
1780            }
1781
1782            // Step 4.3. Comment: Fix-up per-element allow and remove lists.
1783
1784            // Step 4.4. If configuration["elements"] exists:
1785            if let Some(configuration_elements) = &mut self.elements {
1786                // Step 4.4.1. For each element in configuration["elements"]:
1787                for element in configuration_elements {
1788                    // Step 4.4.1.1. If element["attributes"] with default « » contains attribute:
1789                    // Step 4.4.1.1.1. Remove attribute from element["attributes"].
1790                    if let Some(element_attributes) = element.attributes_mut() {
1791                        element_attributes
1792                            .retain(|element_attribute| *element_attribute != attribute);
1793                    }
1794
1795                    // Step 4.4.1.2. If element["removeAttributes"] with default « » contains
1796                    // attribute:
1797                    // Step 4.4.1.2.1. Remove attribute from element["removeAttributes"].
1798                    if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1799                        element_remove_attributes.retain(|element_remove_attribute| {
1800                            *element_remove_attribute != attribute
1801                        });
1802                    }
1803                }
1804            }
1805
1806            // Step 4.5. Append attribute to configuration["removeAttributes"]
1807            if let Some(configuration_remove_attributes) = &mut self.removeAttributes {
1808                configuration_remove_attributes.push(attribute);
1809            } else {
1810                self.removeAttributes = Some(vec![attribute]);
1811            }
1812
1813            // Step 4.6. Return true.
1814            true
1815        }
1816    }
1817
1818    /// <https://html.spec.whatwg.org/multipage/#remove-unsafe>
1819    fn remove_unsafe(&mut self) -> bool {
1820        let baseline = built_in_safe_baseline_configuration();
1821
1822        // Step 1. Assert: configuration is valid.
1823        debug_assert!(self.is_valid());
1824
1825        // Step 2. Let result be false.
1826        let mut result = false;
1827
1828        // Step 3. For each element in built-in safe baseline configuration["removeElements"]:
1829        for element in baseline.removeElements.unwrap_or_default() {
1830            // Step 3.1. If removing element from configuration is true, then set result to true.
1831            if self.remove_element(element) {
1832                result = true;
1833            }
1834        }
1835
1836        // Step 4. For each attribute in built-in safe baseline configuration["removeAttributes"]:
1837        for attribute in baseline.removeAttributes.unwrap_or_default() {
1838            // Step 4.1. If removing attribute from configuration is true, then set result to true.
1839            if self.remove_attribute(attribute) {
1840                result = true;
1841            }
1842        }
1843
1844        // Step 5. For each attribute listed in event handler content attributes:
1845        for attribute in CONTENT_EVENT_HANDLER_NAMES.iter() {
1846            // Step 5.1. If removing attribute from configuration is true, then set result to true.
1847            let attribute = SanitizerAttribute::String(DOMString::from(*attribute));
1848            if self.remove_attribute(attribute) {
1849                result = true;
1850            }
1851        }
1852
1853        // Step 6. If configuration["javascriptURLs"] is true:
1854        if self.javascriptURLs == Some(true) {
1855            // Step 6.1. Set result to true.
1856            result = true;
1857            // Step 6.2. Set configuration["javascriptURLs"] to false.
1858            self.javascriptURLs = Some(false);
1859        }
1860
1861        // Step 7. Return result.
1862        result
1863    }
1864
1865    /// <https://html.spec.whatwg.org/multipage/#canonicalize-the-configuration>
1866    fn canonicalize(&mut self, permissive_defaults: bool) {
1867        // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
1868        // then set configuration["removeElements"] to an empty list.
1869        if self.elements.is_none() && self.removeElements.is_none() {
1870            self.removeElements = Some(Vec::new());
1871        }
1872
1873        // Step 2. If neither configuration["attributes"] nor configuration["removeAttributes"]
1874        // exist, then set configuration["removeAttributes"] to to an empty list.
1875        if self.attributes.is_none() && self.removeAttributes.is_none() {
1876            self.removeAttributes = Some(Vec::new());
1877        }
1878
1879        // Step 3. If neither configuration["processingInstructions"] nor
1880        // configuration["removeProcessingInstructions"] exist:
1881        if self.processingInstructions.is_none() && self.removeProcessingInstructions.is_none() {
1882            // Step 2.1. If permissiveDefaults is true, then set
1883            // configuration["removeProcessingInstructions"] to an empty list.
1884            if permissive_defaults {
1885                self.removeProcessingInstructions = Some(Vec::new());
1886            }
1887            // Step 2.2. Otherwise, set configuration["processingInstructions"] to an empty list.
1888            else {
1889                self.processingInstructions = Some(Vec::new());
1890            }
1891        }
1892
1893        // Step 4. If configuration["elements"] exists:
1894        if let Some(elements) = &mut self.elements {
1895            // Step 4.1. Let newElements be « ».
1896            // Step 4.2. For each element of configuration["elements"], append the result of
1897            // canonicalizing element to newElements.
1898            // Step 4.3. Set configuration["elements"] to newElements.
1899            *elements = elements
1900                .iter()
1901                .cloned()
1902                .map(SanitizerElementWithAttributes::canonicalize)
1903                .collect();
1904        }
1905
1906        // Step 5. If configuration["removeElements"] exists, then set
1907        // configuration["removeElements"] to the result of canonicalizing
1908        // configuration["removeElements"].
1909        if let Some(remove_elements) = &mut self.removeElements {
1910            *remove_elements = std::mem::take(remove_elements).canonicalize();
1911        }
1912
1913        // Step 6. If configuration["attributes"] exists, then set configuration["attributes"] to
1914        // the result of canonicalizing configuration["attributes"].
1915        if let Some(attributes) = &mut self.attributes {
1916            *attributes = std::mem::take(attributes).canonicalize()
1917        }
1918
1919        // Step 7. If configuration["removeAttributes"] exists, then set
1920        // configuration["removeAttributes"] to the result of canonicalizing
1921        // configuration["removeAttributes"].
1922        if let Some(remove_attributes) = &mut self.removeAttributes {
1923            *remove_attributes = std::mem::take(remove_attributes).canonicalize();
1924        }
1925
1926        // Step 8. If configuration["replaceWithChildrenElements"] exists, then set
1927        // configuration["replaceWithChildrenElements"] to the result of canonicalizing
1928        // configuration["replaceWithChildrenElements"].
1929        if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
1930            *replace_with_children_elements =
1931                std::mem::take(replace_with_children_elements).canonicalize();
1932        }
1933
1934        // Step 9. If configuration["processingInstructions"] exists, then set
1935        // configuration["processingInstructions"] to the result of canonicalizing
1936        // configuration["processingInstructions"].
1937        if let Some(processing_instructions) = &mut self.processingInstructions {
1938            *processing_instructions = std::mem::take(processing_instructions).canonicalize();
1939        }
1940
1941        // Step 10. If configuration["removeProcessingInstructions"] exists, then set
1942        // configuration["removeProcessingInstructions"] to the result of canonicalizing
1943        // configuration["removeProcessingInstructions"].
1944        if let Some(remove_processing_instructions) = &mut self.removeProcessingInstructions {
1945            *remove_processing_instructions =
1946                std::mem::take(remove_processing_instructions).canonicalize();
1947        }
1948
1949        // Step 11. If configuration["comments"] does not exist, then set it to permissiveDefaults.
1950        if self.comments.is_none() {
1951            self.comments = Some(permissive_defaults);
1952        }
1953
1954        // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
1955        // not exist, then set it to permissiveDefaults.
1956        if self.attributes.is_some() && self.dataAttributes.is_none() {
1957            self.dataAttributes = Some(permissive_defaults);
1958        }
1959
1960        // Step 13. If configuration["javascriptURLs"] does not exist, then set it to permissiveDefaults.
1961        if self.javascriptURLs.is_none() {
1962            self.javascriptURLs = Some(permissive_defaults)
1963        }
1964    }
1965}
1966
1967trait Canonicalization {
1968    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
1969    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
1970    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
1971    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
1972    /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-sanitizer-list>
1973    /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-processing-instruction-list>
1974    fn canonicalize(self) -> Self;
1975}
1976
1977impl Canonicalization for SanitizerElementWithAttributes {
1978    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
1979    fn canonicalize(mut self) -> Self {
1980        // Step 1. Let result be the result of canonicalize a sanitizer element with element.
1981        let parent = match &mut self {
1982            SanitizerElementWithAttributes::String(name) => {
1983                SanitizerElement::String(std::mem::take(name))
1984            },
1985            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1986                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1987                    name: std::mem::take(&mut dictionary.parent.name),
1988                    namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
1989                })
1990            },
1991        };
1992        let mut canonicalized_parent = parent.canonicalize();
1993        let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1994            SanitizerElementNamespaceWithAttributes {
1995                parent: SanitizerElementNamespace {
1996                    name: std::mem::take(canonicalized_parent.name_mut()),
1997                    namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
1998                },
1999                attributes: None,
2000                removeAttributes: None,
2001            },
2002        );
2003
2004        // Step 2. If element is a dictionary:
2005        if matches!(
2006            self,
2007            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
2008        ) {
2009            // Step 2.1. If element["attributes"] exists:
2010            if let Some(attributes) = self.attributes() {
2011                // Step 2.1.1. Let attributes be « ».
2012                // Step 2.1.2. For each attribute of element["attributes"]:
2013                // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
2014                // attribute to attributes.
2015                let attributes = attributes
2016                    .iter()
2017                    .cloned()
2018                    .map(|attribute| attribute.canonicalize())
2019                    .collect();
2020
2021                // Step 2.1.3. Set result["attributes"] to attributes.
2022                result.set_attributes(Some(attributes));
2023            }
2024
2025            // Step 2.2. If element["removeAttributes"] exists:
2026            if let Some(remove_attributes) = self.remove_attributes() {
2027                // Step 2.2.1. Let attributes be « ».
2028                // Step 2.2.2. For each attribute of element["removeAttributes"]:
2029                // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
2030                // attribute to attributes.
2031                let attributes = remove_attributes
2032                    .iter()
2033                    .cloned()
2034                    .map(|attribute| attribute.canonicalize())
2035                    .collect();
2036
2037                // Step 2.2.3. Set result["removeAttributes"] to attributes.
2038                result.set_remove_attributes(Some(attributes));
2039            }
2040        }
2041
2042        // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
2043        if result.attributes().is_none() && result.remove_attributes().is_none() {
2044            // Step 3.1. Set result["removeAttributes"] to « ».
2045            result.set_remove_attributes(Some(Vec::new()));
2046        }
2047
2048        // Step 4. Return result.
2049        result
2050    }
2051}
2052
2053impl Canonicalization for SanitizerElement {
2054    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2055    fn canonicalize(self) -> Self {
2056        // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
2057        // the default namespace.
2058        self.canonicalize_name(Some(ns!(html).to_string()))
2059    }
2060}
2061impl Canonicalization for SanitizerPI {
2062    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2063    fn canonicalize(self) -> Self {
2064        // Step 1. Assert: pi is either a DOMString or a dictionary.
2065        assert!(matches!(
2066            self,
2067            SanitizerPI::String(_) | SanitizerPI::SanitizerProcessingInstruction(_)
2068        ));
2069
2070        // Step 2. If pi is a DOMString, then return «[ "target" → pi ]».
2071        if let SanitizerPI::String(target) = self {
2072            return SanitizerPI::SanitizerProcessingInstruction(SanitizerProcessingInstruction {
2073                target,
2074            });
2075        }
2076
2077        // Step 3. Assert: pi is a dictionary and pi["target"] exists.
2078        // NOTE: The latter is guaranteed by Rust type system.
2079        assert!(matches!(
2080            self,
2081            SanitizerPI::SanitizerProcessingInstruction(_)
2082        ));
2083
2084        // Step 4. Return «[ "target" → pi["target"] ]».
2085        self
2086    }
2087}
2088
2089impl Canonicalization for SanitizerAttribute {
2090    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2091    fn canonicalize(self) -> Self {
2092        // Return the result of canonicalize a sanitizer name with attribute and null as the default
2093        // namespace.
2094        self.canonicalize_name(None)
2095    }
2096}
2097
2098impl<T: Canonicalization> Canonicalization for Vec<T> {
2099    /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-sanitizer-list>
2100    /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-processing-instruction-list>
2101    fn canonicalize(self) -> Self {
2102        // Step 1. Let newList be « ».
2103        // Step 2. For each item in list, append the result of canonicalizing item to newList.
2104        // Step 3. Return newList.
2105        self.into_iter()
2106            .map(Canonicalization::canonicalize)
2107            .collect()
2108    }
2109}
2110
2111trait NameCanonicalization: NameMember {
2112    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
2113    fn is_string(&self) -> bool;
2114    fn is_dictionary(&self) -> bool;
2115
2116    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
2117    fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
2118        // Step 1. Assert: name is either a DOMString or a dictionary.
2119        assert!(self.is_string() || self.is_dictionary());
2120
2121        // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
2122        // defaultNamespace]».
2123        if self.is_string() {
2124            return Self::new_dictionary(
2125                std::mem::take(self.name_mut()),
2126                default_namespace.map(DOMString::from),
2127            );
2128        }
2129
2130        // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
2131        // NOTE: The latter is guaranteed by Rust type system.
2132        assert!(self.is_dictionary());
2133
2134        // Step 4. If name["namespace"] is the empty string, then set it to null.
2135        if self
2136            .namespace()
2137            .is_some_and(|namespace| namespace.str() == "")
2138        {
2139            self.set_namespace(None);
2140        }
2141
2142        // Step 5. Return «[
2143        // "name" → name["name"],
2144        // "namespace" → name["namespace"]
2145        // ]».
2146        Self::new_dictionary(
2147            std::mem::take(self.name_mut()),
2148            self.namespace_mut().map(std::mem::take),
2149        )
2150    }
2151}
2152
2153impl NameCanonicalization for SanitizerElement {
2154    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2155        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
2156    }
2157
2158    fn is_string(&self) -> bool {
2159        matches!(self, SanitizerElement::String(_))
2160    }
2161
2162    fn is_dictionary(&self) -> bool {
2163        matches!(self, SanitizerElement::SanitizerElementNamespace(_))
2164    }
2165}
2166
2167impl NameCanonicalization for SanitizerAttribute {
2168    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2169        SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2170            name,
2171            namespace,
2172        })
2173    }
2174
2175    fn is_string(&self) -> bool {
2176        matches!(self, SanitizerAttribute::String(_))
2177    }
2178
2179    fn is_dictionary(&self) -> bool {
2180        matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
2181    }
2182}
2183
2184/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2185trait NameSlice<T>
2186where
2187    T: NameMember + Canonicalization + Clone,
2188{
2189    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2190    fn contains_item<S: NameMember>(&self, other: &S) -> bool;
2191
2192    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2193    fn has_duplicates(&self) -> bool;
2194
2195    /// Custom version of the supporting algorithm
2196    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2197    /// intersection is non-empty, returning early if it is non-empty for efficiency.
2198    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2199    where
2200        S: NameMember + Canonicalization + Clone;
2201}
2202
2203impl<T> NameSlice<T> for [T]
2204where
2205    T: NameMember + Canonicalization + Clone,
2206{
2207    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2208    fn contains_item<S: NameMember>(&self, other: &S) -> bool {
2209        // A Sanitizer name list contains an item if there exists an entry of list that is an
2210        // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
2211        // entry["namespace"].
2212        self.iter()
2213            .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
2214    }
2215
2216    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2217    fn has_duplicates(&self) -> bool {
2218        // A list list has duplicates, if for any item of list, there is more than one entry in list
2219        // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
2220        let mut used = HashSet::new();
2221        self.iter().any(move |entry| {
2222            !used.insert((
2223                entry.name().to_string(),
2224                entry.namespace().map(DOMString::to_string),
2225            ))
2226        })
2227    }
2228
2229    /// Custom version of the supporting algorithm
2230    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2231    /// intersection is non-empty, returning early if it is non-empty for efficiency.
2232    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2233    where
2234        S: NameMember + Canonicalization + Clone,
2235    {
2236        // Step 1. Let set A be « [] ».
2237        // Step 2. Let set B be « [] ».
2238        // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
2239        // set A.
2240        // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
2241        // set B.
2242        let a = self.iter().map(|entry| entry.clone().canonicalize());
2243        let b = others
2244            .iter()
2245            .map(|entry| entry.clone().canonicalize())
2246            .collect::<Vec<S>>();
2247
2248        // Step 5. Return the intersection of set A and set B.
2249        // NOTE: Instead of returning the intersection itself, return true if the intersection is
2250        // non-empty, and false otherwise.
2251        a.filter(|entry| {
2252            b.iter()
2253                .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
2254        })
2255        .any(|_| true)
2256    }
2257}
2258
2259/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2260trait NameVec<T>
2261where
2262    T: NameMember + Canonicalization + Clone,
2263{
2264    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2265    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
2266
2267    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2268    fn add_item(&mut self, name: T);
2269
2270    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2271    fn remove_duplicates(&mut self) -> &mut Self;
2272
2273    /// Set itself to the set intersection of itself and another list.
2274    ///
2275    /// <https://infra.spec.whatwg.org/#set-intersection>
2276    fn intersection<S>(&mut self, others: &[S])
2277    where
2278        S: NameMember + Canonicalization + Clone;
2279
2280    /// <https://infra.spec.whatwg.org/#set-difference>
2281    fn difference(&mut self, others: &[T]);
2282}
2283
2284impl<T> NameVec<T> for Vec<T>
2285where
2286    T: NameMember + Canonicalization + Clone,
2287{
2288    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2289    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
2290        // Step 1. Set removed to false.
2291        let mut removed = false;
2292
2293        // Step 2. For each entry of list:
2294        // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
2295        // Step 2.1.1. Remove item entry from list.
2296        // Step 2.1.2. Set removed to true.
2297        self.retain(|entry| {
2298            let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
2299            if matched {
2300                removed = true;
2301            }
2302            !matched
2303        });
2304
2305        // Step 3. Return removed.
2306        removed
2307    }
2308
2309    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2310    fn add_item(&mut self, name: T) {
2311        // Step 1. If list contains name, then return.
2312        if self.contains_item(&name) {
2313            return;
2314        };
2315
2316        // Step 2. Append name to list.
2317        self.push(name);
2318    }
2319
2320    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2321    fn remove_duplicates(&mut self) -> &mut Self {
2322        // Step 1. Let result be « ».
2323        // Step 2. For each entry of list, add entry to result.
2324        // Step 3. Return result.
2325        self.sort_by(|item_a, item_b| item_a.compare(item_b));
2326        self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
2327        self
2328    }
2329
2330    /// Set itself to the set intersection of itself and another list.
2331    ///
2332    /// <https://infra.spec.whatwg.org/#set-intersection>
2333    fn intersection<S>(&mut self, others: &[S])
2334    where
2335        S: NameMember + Canonicalization + Clone,
2336    {
2337        // The intersection of ordered sets A and B, is the result of creating a new ordered set set
2338        // and, for each item of A, if B contains item, appending item to set.
2339        self.retain(|item| {
2340            others
2341                .iter()
2342                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2343        })
2344    }
2345
2346    /// Set itself to the set difference of itself and another list.
2347    ///
2348    /// <https://infra.spec.whatwg.org/#set-difference>
2349    fn difference(&mut self, others: &[T]) {
2350        // The difference of ordered sets A and B, is the result of creating a new ordered set set
2351        // and, for each item of A, if B does not contain item, appending item to set.
2352        self.retain(|item| {
2353            !others
2354                .iter()
2355                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2356        })
2357    }
2358}
2359
2360/// Helper functions for accessing the "name" and "namespace" members of
2361/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
2362trait NameMember: Sized {
2363    fn name(&self) -> &DOMString;
2364    fn name_mut(&mut self) -> &mut DOMString;
2365    fn namespace(&self) -> Option<&DOMString>;
2366    fn namespace_mut(&mut self) -> Option<&mut DOMString>;
2367
2368    fn set_namespace(&mut self, namespace: Option<&str>);
2369
2370    // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
2371    fn is_less_than_item(&self, item_b: &Self) -> bool {
2372        let item_a = self;
2373        match item_a.namespace() {
2374            // Step 1. If itemA["namespace"] is null:
2375            None => {
2376                // Step 1.1. If itemB["namespace"] is not null, then return true.
2377                if item_b.namespace().is_some() {
2378                    return true;
2379                }
2380            },
2381            // Step 2. Otherwise:
2382            Some(item_a_namespace) => {
2383                // Step 2.1. If itemB["namespace"] is null, then return false.
2384                if item_b.namespace().is_none() {
2385                    return false;
2386                }
2387
2388                // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
2389                // return true.
2390                if item_b
2391                    .namespace()
2392                    .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
2393                {
2394                    return true;
2395                }
2396
2397                // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
2398                if item_b
2399                    .namespace()
2400                    .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
2401                {
2402                    return false;
2403                }
2404            },
2405        }
2406
2407        // Step 3. Return itemA["name"] is code unit less than itemB["name"].
2408        item_a.name() < item_b.name()
2409    }
2410
2411    /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
2412    fn compare(&self, other: &Self) -> Ordering {
2413        if self.is_less_than_item(other) {
2414            Ordering::Less
2415        } else {
2416            Ordering::Greater
2417        }
2418    }
2419
2420    /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2421    /// ['SanitizerAttribute']. For other types such as ['SanitizerElementWithAttributes'] and
2422    /// [`SanitizerElement`], return false by default.
2423    fn is_custom_data_attribute(&self) -> bool {
2424        false
2425    }
2426}
2427
2428impl NameMember for SanitizerElementWithAttributes {
2429    fn name(&self) -> &DOMString {
2430        match self {
2431            SanitizerElementWithAttributes::String(name) => name,
2432            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2433                &dictionary.parent.name
2434            },
2435        }
2436    }
2437
2438    fn name_mut(&mut self) -> &mut DOMString {
2439        match self {
2440            SanitizerElementWithAttributes::String(name) => name,
2441            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2442                &mut dictionary.parent.name
2443            },
2444        }
2445    }
2446
2447    fn namespace(&self) -> Option<&DOMString> {
2448        match self {
2449            SanitizerElementWithAttributes::String(_) => None,
2450            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2451                dictionary.parent.namespace.as_ref()
2452            },
2453        }
2454    }
2455
2456    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2457        match self {
2458            SanitizerElementWithAttributes::String(_) => None,
2459            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2460                dictionary.parent.namespace.as_mut()
2461            },
2462        }
2463    }
2464
2465    fn set_namespace(&mut self, namespace: Option<&str>) {
2466        match self {
2467            SanitizerElementWithAttributes::String(name) => {
2468                let new_instance =
2469                    SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2470                        SanitizerElementNamespaceWithAttributes {
2471                            parent: SanitizerElementNamespace {
2472                                name: std::mem::take(name),
2473                                namespace: namespace.map(DOMString::from),
2474                            },
2475                            attributes: None,
2476                            removeAttributes: None,
2477                        },
2478                    );
2479                *self = new_instance;
2480            },
2481            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2482                dictionary.parent.namespace = namespace.map(DOMString::from);
2483            },
2484        }
2485    }
2486}
2487
2488impl NameMember for SanitizerElement {
2489    fn name(&self) -> &DOMString {
2490        match self {
2491            SanitizerElement::String(name) => name,
2492            SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
2493        }
2494    }
2495
2496    fn name_mut(&mut self) -> &mut DOMString {
2497        match self {
2498            SanitizerElement::String(name) => name,
2499            SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
2500        }
2501    }
2502
2503    fn namespace(&self) -> Option<&DOMString> {
2504        match self {
2505            SanitizerElement::String(_) => None,
2506            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2507                dictionary.namespace.as_ref()
2508            },
2509        }
2510    }
2511
2512    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2513        match self {
2514            SanitizerElement::String(_) => None,
2515            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2516                dictionary.namespace.as_mut()
2517            },
2518        }
2519    }
2520
2521    fn set_namespace(&mut self, namespace: Option<&str>) {
2522        match self {
2523            SanitizerElement::String(name) => {
2524                let new_instance =
2525                    SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2526                        name: std::mem::take(name),
2527                        namespace: namespace.map(DOMString::from),
2528                    });
2529                *self = new_instance;
2530            },
2531            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2532                dictionary.namespace = namespace.map(DOMString::from);
2533            },
2534        }
2535    }
2536}
2537
2538impl NameMember for SanitizerAttribute {
2539    fn name(&self) -> &DOMString {
2540        match self {
2541            SanitizerAttribute::String(name) => name,
2542            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
2543        }
2544    }
2545
2546    fn name_mut(&mut self) -> &mut DOMString {
2547        match self {
2548            SanitizerAttribute::String(name) => name,
2549            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
2550        }
2551    }
2552
2553    fn namespace(&self) -> Option<&DOMString> {
2554        match self {
2555            SanitizerAttribute::String(_) => None,
2556            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2557                dictionary.namespace.as_ref()
2558            },
2559        }
2560    }
2561
2562    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2563        match self {
2564            SanitizerAttribute::String(_) => None,
2565            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2566                dictionary.namespace.as_mut()
2567            },
2568        }
2569    }
2570
2571    fn set_namespace(&mut self, namespace: Option<&str>) {
2572        match self {
2573            SanitizerAttribute::String(name) => {
2574                let new_instance =
2575                    SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2576                        name: std::mem::take(name),
2577                        namespace: namespace.map(DOMString::from),
2578                    });
2579                *self = new_instance;
2580            },
2581            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2582                dictionary.namespace = namespace.map(DOMString::from);
2583            },
2584        }
2585    }
2586
2587    /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2588    /// ['SanitizerAttribute'].
2589    fn is_custom_data_attribute(&self) -> bool {
2590        is_custom_data_attribute(
2591            &self.name().str(),
2592            self.namespace().map(|namespace| namespace.str()).as_deref(),
2593        )
2594    }
2595}
2596
2597/// Helper functions for accessing the "attributes" and "removeAttributes" members of
2598/// [`SanitizerElementWithAttributes`].
2599trait AttributeMember {
2600    fn attributes(&self) -> Option<&[SanitizerAttribute]>;
2601    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2602    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
2603    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2604
2605    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
2606    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
2607}
2608
2609impl AttributeMember for SanitizerElementWithAttributes {
2610    fn attributes(&self) -> Option<&[SanitizerAttribute]> {
2611        match self {
2612            SanitizerElementWithAttributes::String(_) => None,
2613            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2614                dictionary.attributes.as_deref()
2615            },
2616        }
2617    }
2618
2619    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2620        match self {
2621            SanitizerElementWithAttributes::String(_) => None,
2622            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2623                dictionary.attributes.as_mut()
2624            },
2625        }
2626    }
2627
2628    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
2629        match self {
2630            SanitizerElementWithAttributes::String(_) => None,
2631            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2632                dictionary.removeAttributes.as_deref()
2633            },
2634        }
2635    }
2636
2637    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2638        match self {
2639            SanitizerElementWithAttributes::String(_) => None,
2640            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2641                dictionary.removeAttributes.as_mut()
2642            },
2643        }
2644    }
2645
2646    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
2647        match self {
2648            SanitizerElementWithAttributes::String(name) => {
2649                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2650                    SanitizerElementNamespaceWithAttributes {
2651                        parent: SanitizerElementNamespace {
2652                            name: std::mem::take(name),
2653                            namespace: None,
2654                        },
2655                        attributes,
2656                        removeAttributes: None,
2657                    },
2658                );
2659            },
2660            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2661                dictionary.attributes = attributes;
2662            },
2663        }
2664    }
2665
2666    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
2667        match self {
2668            SanitizerElementWithAttributes::String(name) => {
2669                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2670                    SanitizerElementNamespaceWithAttributes {
2671                        parent: SanitizerElementNamespace {
2672                            name: std::mem::take(name),
2673                            namespace: None,
2674                        },
2675                        attributes: None,
2676                        removeAttributes: remove_attributes,
2677                    },
2678                );
2679            },
2680            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2681                dictionary.removeAttributes = remove_attributes;
2682            },
2683        }
2684    }
2685}
2686
2687/// Helper functions for accessing the "target" members of [`SanitizerPI`].
2688trait TargetMember {
2689    fn target(&self) -> &DOMString;
2690}
2691
2692impl TargetMember for SanitizerPI {
2693    fn target(&self) -> &DOMString {
2694        match self {
2695            SanitizerPI::String(string) => string,
2696            SanitizerPI::SanitizerProcessingInstruction(dictionary) => &dictionary.target,
2697        }
2698    }
2699}
2700
2701/// Supporting algorithms on lists of processing instructions, from the specification.
2702trait TargetSlice<T>
2703where
2704    T: TargetMember,
2705{
2706    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2707    fn contains_target(&self, other: &T) -> bool;
2708
2709    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2710    fn has_duplicate_targets(&self) -> bool;
2711}
2712
2713impl<T> TargetSlice<T> for [T]
2714where
2715    T: TargetMember,
2716{
2717    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2718    fn contains_target(&self, other: &T) -> bool {
2719        // A Sanitizer target list contains a target target if there exists an entry of list that is
2720        // an ordered map, and where target equals entry["target"].
2721        self.iter().any(|entry| entry.target() == other.target())
2722    }
2723
2724    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2725    fn has_duplicate_targets(&self) -> bool {
2726        // A list list has duplicate targets, if for any item of list, there is more than one entry
2727        // in list where item["target"] is entry["target"].
2728        let mut used = HashSet::new();
2729        self.iter()
2730            .any(move |entry| !used.insert(entry.target().to_string()))
2731    }
2732}
2733
2734/// Helper functions for accessing the "sanitizer" members of [`SetHTMLOptions`] and
2735/// [`SetHTMLUnsafeOptions`].
2736pub(crate) trait SanitizerMember {
2737    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets;
2738}
2739
2740impl SanitizerMember for SetHTMLOptions {
2741    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2742        &self.sanitizer
2743    }
2744}
2745
2746impl SanitizerMember for SetHTMLUnsafeOptions {
2747    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2748        &self.sanitizer
2749    }
2750}
2751
2752/// <https://html.spec.whatwg.org/multipage/#built-in-safe-default-configuration>
2753fn built_in_safe_default_configuration() -> SanitizerConfig {
2754    const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
2755        ("math", &ns!(mathml), &[]),
2756        ("merror", &ns!(mathml), &[]),
2757        ("mfrac", &ns!(mathml), &[]),
2758        ("mi", &ns!(mathml), &[]),
2759        ("mmultiscripts", &ns!(mathml), &[]),
2760        ("mn", &ns!(mathml), &[]),
2761        (
2762            "mo",
2763            &ns!(mathml),
2764            &[
2765                "fence",
2766                "form",
2767                "largeop",
2768                "lspace",
2769                "maxsize",
2770                "minsize",
2771                "movablelimits",
2772                "rspace",
2773                "separator",
2774                "stretchy",
2775                "symmetric",
2776            ],
2777        ),
2778        ("mover", &ns!(mathml), &["accent"]),
2779        (
2780            "mpadded",
2781            &ns!(mathml),
2782            &["depth", "height", "lspace", "voffset", "width"],
2783        ),
2784        ("mphantom", &ns!(mathml), &[]),
2785        ("mprescripts", &ns!(mathml), &[]),
2786        ("mroot", &ns!(mathml), &[]),
2787        ("mrow", &ns!(mathml), &[]),
2788        ("ms", &ns!(mathml), &[]),
2789        ("mspace", &ns!(mathml), &["depth", "height", "width"]),
2790        ("msqrt", &ns!(mathml), &[]),
2791        ("mstyle", &ns!(mathml), &[]),
2792        ("msub", &ns!(mathml), &[]),
2793        ("msubsup", &ns!(mathml), &[]),
2794        ("msup", &ns!(mathml), &[]),
2795        ("mtable", &ns!(mathml), &[]),
2796        ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
2797        ("mtext", &ns!(mathml), &[]),
2798        ("mtr", &ns!(mathml), &[]),
2799        ("munder", &ns!(mathml), &["accentunder"]),
2800        ("munderover", &ns!(mathml), &["accent", "accentunder"]),
2801        ("semantics", &ns!(mathml), &[]),
2802        ("a", &ns!(html), &["href", "hreflang", "type"]),
2803        ("abbr", &ns!(html), &[]),
2804        ("address", &ns!(html), &[]),
2805        ("article", &ns!(html), &[]),
2806        ("aside", &ns!(html), &[]),
2807        ("b", &ns!(html), &[]),
2808        ("bdi", &ns!(html), &[]),
2809        ("bdo", &ns!(html), &[]),
2810        ("blockquote", &ns!(html), &["cite"]),
2811        ("body", &ns!(html), &[]),
2812        ("br", &ns!(html), &[]),
2813        ("caption", &ns!(html), &[]),
2814        ("cite", &ns!(html), &[]),
2815        ("code", &ns!(html), &[]),
2816        ("col", &ns!(html), &["span"]),
2817        ("colgroup", &ns!(html), &["span"]),
2818        ("data", &ns!(html), &["value"]),
2819        ("dd", &ns!(html), &[]),
2820        ("del", &ns!(html), &["cite", "datetime"]),
2821        ("dfn", &ns!(html), &[]),
2822        ("div", &ns!(html), &[]),
2823        ("dl", &ns!(html), &[]),
2824        ("dt", &ns!(html), &[]),
2825        ("em", &ns!(html), &[]),
2826        ("figcaption", &ns!(html), &[]),
2827        ("figure", &ns!(html), &[]),
2828        ("footer", &ns!(html), &[]),
2829        ("h1", &ns!(html), &[]),
2830        ("h2", &ns!(html), &[]),
2831        ("h3", &ns!(html), &[]),
2832        ("h4", &ns!(html), &[]),
2833        ("h5", &ns!(html), &[]),
2834        ("h6", &ns!(html), &[]),
2835        ("head", &ns!(html), &[]),
2836        ("header", &ns!(html), &[]),
2837        ("hgroup", &ns!(html), &[]),
2838        ("hr", &ns!(html), &[]),
2839        ("html", &ns!(html), &[]),
2840        ("i", &ns!(html), &[]),
2841        ("ins", &ns!(html), &["cite", "datetime"]),
2842        ("kbd", &ns!(html), &[]),
2843        ("li", &ns!(html), &["value"]),
2844        ("main", &ns!(html), &[]),
2845        ("mark", &ns!(html), &[]),
2846        ("menu", &ns!(html), &[]),
2847        ("nav", &ns!(html), &[]),
2848        ("ol", &ns!(html), &["reversed", "start", "type"]),
2849        ("p", &ns!(html), &[]),
2850        ("pre", &ns!(html), &[]),
2851        ("q", &ns!(html), &[]),
2852        ("rp", &ns!(html), &[]),
2853        ("rt", &ns!(html), &[]),
2854        ("ruby", &ns!(html), &[]),
2855        ("s", &ns!(html), &[]),
2856        ("samp", &ns!(html), &[]),
2857        ("search", &ns!(html), &[]),
2858        ("section", &ns!(html), &[]),
2859        ("small", &ns!(html), &[]),
2860        ("span", &ns!(html), &[]),
2861        ("strong", &ns!(html), &[]),
2862        ("sub", &ns!(html), &[]),
2863        ("sup", &ns!(html), &[]),
2864        ("table", &ns!(html), &[]),
2865        ("tbody", &ns!(html), &[]),
2866        ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
2867        ("tfoot", &ns!(html), &[]),
2868        (
2869            "th",
2870            &ns!(html),
2871            &["abbr", "colspan", "headers", "rowspan", "scope"],
2872        ),
2873        ("thead", &ns!(html), &[]),
2874        ("time", &ns!(html), &["datetime"]),
2875        ("title", &ns!(html), &[]),
2876        ("tr", &ns!(html), &[]),
2877        ("u", &ns!(html), &[]),
2878        ("ul", &ns!(html), &[]),
2879        ("var", &ns!(html), &[]),
2880        ("wbr", &ns!(html), &[]),
2881        ("a", &ns!(svg), &["href", "hreflang", "type"]),
2882        ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
2883        ("defs", &ns!(svg), &[]),
2884        ("desc", &ns!(svg), &[]),
2885        (
2886            "ellipse",
2887            &ns!(svg),
2888            &["cx", "cy", "pathLength", "rx", "ry"],
2889        ),
2890        ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
2891        ("g", &ns!(svg), &[]),
2892        ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
2893        (
2894            "marker",
2895            &ns!(svg),
2896            &[
2897                "markerHeight",
2898                "markerUnits",
2899                "markerWidth",
2900                "orient",
2901                "preserveAspectRatio",
2902                "refX",
2903                "refY",
2904                "viewBox",
2905            ],
2906        ),
2907        ("metadata", &ns!(svg), &[]),
2908        ("path", &ns!(svg), &["d", "pathLength"]),
2909        ("polygon", &ns!(svg), &["pathLength", "points"]),
2910        ("polyline", &ns!(svg), &["pathLength", "points"]),
2911        (
2912            "rect",
2913            &ns!(svg),
2914            &["height", "pathLength", "rx", "ry", "width", "x", "y"],
2915        ),
2916        (
2917            "svg",
2918            &ns!(svg),
2919            &[
2920                "height",
2921                "preserveAspectRatio",
2922                "viewBox",
2923                "width",
2924                "x",
2925                "y",
2926            ],
2927        ),
2928        (
2929            "text",
2930            &ns!(svg),
2931            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2932        ),
2933        (
2934            "textPath",
2935            &ns!(svg),
2936            &[
2937                "lengthAdjust",
2938                "method",
2939                "path",
2940                "side",
2941                "spacing",
2942                "startOffset",
2943                "textLength",
2944            ],
2945        ),
2946        ("title", &ns!(svg), &[]),
2947        (
2948            "tspan",
2949            &ns!(svg),
2950            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2951        ),
2952    ];
2953    const ATTRIBUTES: &[&str] = &[
2954        "alignment-baseline",
2955        "baseline-shift",
2956        "clip-path",
2957        "clip-rule",
2958        "color",
2959        "color-interpolation",
2960        "cursor",
2961        "dir",
2962        "direction",
2963        "display",
2964        "displaystyle",
2965        "dominant-baseline",
2966        "fill",
2967        "fill-opacity",
2968        "fill-rule",
2969        "font-family",
2970        "font-size",
2971        "font-size-adjust",
2972        "font-stretch",
2973        "font-style",
2974        "font-variant",
2975        "font-weight",
2976        "lang",
2977        "letter-spacing",
2978        "marker-end",
2979        "marker-mid",
2980        "marker-start",
2981        "mathbackground",
2982        "mathcolor",
2983        "mathsize",
2984        "opacity",
2985        "paint-order",
2986        "pointer-events",
2987        "scriptlevel",
2988        "shape-rendering",
2989        "stop-color",
2990        "stop-opacity",
2991        "stroke",
2992        "stroke-dasharray",
2993        "stroke-dashoffset",
2994        "stroke-linecap",
2995        "stroke-linejoin",
2996        "stroke-miterlimit",
2997        "stroke-opacity",
2998        "stroke-width",
2999        "text-anchor",
3000        "text-decoration",
3001        "text-overflow",
3002        "text-rendering",
3003        "title",
3004        "transform",
3005        "transform-origin",
3006        "unicode-bidi",
3007        "vector-effect",
3008        "visibility",
3009        "white-space",
3010        "word-spacing",
3011        "writing-mode",
3012    ];
3013
3014    let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
3015        attributes
3016            .iter()
3017            .map(|&attribute| {
3018                SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
3019                    name: attribute.into(),
3020                    namespace: None,
3021                })
3022            })
3023            .collect()
3024    };
3025
3026    let elements = ELEMENTS
3027        .iter()
3028        .map(|&(name, namespace, attributes)| {
3029            let attributes = create_attribute_vec(attributes);
3030            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
3031                SanitizerElementNamespaceWithAttributes {
3032                    parent: SanitizerElementNamespace {
3033                        name: name.into(),
3034                        namespace: Some(namespace.to_string().into()),
3035                    },
3036                    attributes: Some(attributes),
3037                    removeAttributes: None,
3038                },
3039            )
3040        })
3041        .collect();
3042
3043    let attributes = create_attribute_vec(ATTRIBUTES);
3044
3045    SanitizerConfig {
3046        elements: Some(elements),
3047        removeElements: None,
3048        replaceWithChildrenElements: None,
3049        processingInstructions: Some(Vec::new()),
3050        removeProcessingInstructions: None,
3051        attributes: Some(attributes),
3052        removeAttributes: None,
3053        comments: Some(false),
3054        dataAttributes: Some(false),
3055        javascriptURLs: Some(false),
3056    }
3057}
3058
3059/// <https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration>
3060fn built_in_safe_baseline_configuration() -> SanitizerConfig {
3061    const REMOVE_ELEMENTS: &[(&str, &Namespace)] = &[
3062        ("embed", &ns!(html)),
3063        ("frame", &ns!(html)),
3064        ("iframe", &ns!(html)),
3065        ("object", &ns!(html)),
3066        ("script", &ns!(html)),
3067        ("script", &ns!(svg)),
3068        ("use", &ns!(svg)),
3069    ];
3070
3071    let remove_elements = REMOVE_ELEMENTS
3072        .iter()
3073        .map(|&(name, namespace)| {
3074            SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3075                name: name.into(),
3076                namespace: Some(namespace.to_string().into()),
3077            })
3078        })
3079        .collect();
3080
3081    SanitizerConfig {
3082        elements: None,
3083        removeElements: Some(remove_elements),
3084        replaceWithChildrenElements: None,
3085        processingInstructions: None,
3086        removeProcessingInstructions: None,
3087        attributes: None,
3088        removeAttributes: Some(Vec::new()),
3089        comments: None,
3090        dataAttributes: None,
3091        javascriptURLs: None,
3092    }
3093}
3094
3095/// <https://wicg.github.io/sanitizer-api/#built-in-navigating-url-attributes-list>
3096const BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST: &[(
3097    LocalName,
3098    Option<Namespace>,
3099    LocalName,
3100    Option<Namespace>,
3101)] = &[
3102    (local_name!("a"), Some(ns!(html)), local_name!("href"), None),
3103    (
3104        local_name!("area"),
3105        Some(ns!(html)),
3106        local_name!("href"),
3107        None,
3108    ),
3109    (
3110        local_name!("base"),
3111        Some(ns!(html)),
3112        local_name!("href"),
3113        None,
3114    ),
3115    (
3116        local_name!("button"),
3117        Some(ns!(html)),
3118        local_name!("formaction"),
3119        None,
3120    ),
3121    (
3122        local_name!("form"),
3123        Some(ns!(html)),
3124        local_name!("action"),
3125        None,
3126    ),
3127    (
3128        local_name!("input"),
3129        Some(ns!(html)),
3130        local_name!("formaction"),
3131        None,
3132    ),
3133    (local_name!("a"), Some(ns!(svg)), local_name!("href"), None),
3134    (
3135        local_name!("a"),
3136        Some(ns!(svg)),
3137        local_name!("href"),
3138        Some(ns!(xlink)),
3139    ),
3140];
3141
3142/// <https://wicg.github.io/sanitizer-api/#built-in-animating-url-attributes-list>
3143const BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST: &[(
3144    LocalName,
3145    Option<Namespace>,
3146    LocalName,
3147    Option<Namespace>,
3148)] = &[
3149    (
3150        local_name!("animate"),
3151        Some(ns!(svg)),
3152        local_name!("attributeName"),
3153        None,
3154    ),
3155    (
3156        local_name!("animateTransform"),
3157        Some(ns!(svg)),
3158        local_name!("attributeName"),
3159        None,
3160    ),
3161    (
3162        local_name!("set"),
3163        Some(ns!(svg)),
3164        local_name!("attributeName"),
3165        None,
3166    ),
3167];
3168
3169thread_local! {
3170    /// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
3171    static BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST: LazyCell<Vec<SanitizerElement>> =
3172        LazyCell::new(|| {
3173            vec![
3174                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3175                    name: DOMString::from_static("html"),
3176                    namespace: Some(ns!(html).as_str().into()),
3177                }),
3178                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3179                    name: DOMString::from_static("svg"),
3180                    namespace: Some(ns!(svg).as_str().into()),
3181                }),
3182                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3183                    name: DOMString::from_static("math"),
3184                    namespace: Some(ns!(mathml).as_str().into()),
3185                }),
3186            ]
3187        });
3188}