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