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(cx, context_element, html, true);
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, cx: &mut JSContext, 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                    cx,
943                    &self.global(),
944                    "Do not support adding an element with attributes to a sanitizer \
945                        whose configuration[\"elements\"] does not exist."
946                        .into(),
947                );
948
949                // Step 5.1.2. Return false.
950                return false;
951            }
952
953            // Step 5.2. Set modified to the result of remove element from
954            // configuration["replaceWithChildrenElements"].
955            let modified = if let Some(replace_with_children_elements) =
956                &mut configuration.replaceWithChildrenElements
957            {
958                replace_with_children_elements.remove_item(&element)
959            } else {
960                false
961            };
962
963            // Step 5.3. If configuration["removeElements"] does not contain element:
964            if !configuration
965                .removeElements
966                .as_ref()
967                .is_some_and(|configuration_remove_elements| {
968                    configuration_remove_elements.contains_item(&element)
969                })
970            {
971                // Step 5.3.1. Comment: This is the case with a global remove-list that does not
972                // contain element.
973
974                // Step 5.3.2. Return modified.
975                return modified;
976            }
977
978            // Step 5.4. Comment: This is the case with a global remove-list that contains element.
979
980            // Step 5.5. Remove element from configuration["removeElements"].
981            if let Some(configuration_remove_elements) = &mut configuration.removeElements {
982                configuration_remove_elements.remove_item(&element);
983            }
984
985            // Step 5.6. Return true.
986            true
987        }
988    }
989
990    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
991    fn RemoveElement(&self, element: SanitizerElement) -> bool {
992        // Remove an element with element and this’s configuration.
993        self.configuration.borrow_mut().remove_element(element)
994    }
995
996    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
997    fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
998        // Step 1. Let configuration be this’s configuration.
999        let mut configuration = self.configuration.borrow_mut();
1000
1001        // Step 2. Assert: configuration is valid.
1002        debug_assert!(configuration.is_valid());
1003
1004        // Step 3. Set element to the result of canonicalize a sanitizer element with element.
1005        let element = element.canonicalize();
1006
1007        // Step 4. If the built-in non-replaceable elements list contains element:
1008        if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(&element)) {
1009            // Step 4.1. Return false.
1010            return false;
1011        }
1012
1013        // Step 5. If configuration["replaceWithChildrenElements"] contains element:
1014        if configuration
1015            .replaceWithChildrenElements
1016            .as_ref()
1017            .is_some_and(|configuration_replace_with_children_elements| {
1018                configuration_replace_with_children_elements.contains_item(&element)
1019            })
1020        {
1021            // Step 5.1. Return false.
1022            return false;
1023        }
1024
1025        // Step 6. Remove element from configuration["removeElements"].
1026        if let Some(configuration_remove_elements) = &mut configuration.removeElements {
1027            configuration_remove_elements.remove_item(&element);
1028        }
1029
1030        // Step 7. Remove element from configuration["elements"] list.
1031        if let Some(configuration_elements) = &mut configuration.elements {
1032            configuration_elements.remove_item(&element);
1033        }
1034
1035        // Step 8. Add element to configuration["replaceWithChildrenElements"].
1036        if let Some(configuration_replace_with_children_elements) =
1037            &mut configuration.replaceWithChildrenElements
1038        {
1039            configuration_replace_with_children_elements.add_item(element);
1040        } else {
1041            configuration.replaceWithChildrenElements = Some(vec![element]);
1042        }
1043
1044        // Step 9. Return true.
1045        true
1046    }
1047
1048    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowprocessinginstruction>
1049    fn AllowProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1050        // Step 1. Let configuration be this’s configuration.
1051        let mut configuration = self.configuration.borrow_mut();
1052
1053        // Step 2. Assert: configuration is valid.
1054        debug_assert!(configuration.is_valid());
1055
1056        // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1057        let processing_instruction = processing_instruction.canonicalize();
1058
1059        match &mut configuration.processingInstructions {
1060            // Step 4. If configuration["processingInstructions"] exists:
1061            Some(configuration_processing_instructions) => {
1062                // Step 4.1. If configuration["processingInstructions"] contains pi:
1063                if configuration_processing_instructions.contains_target(&processing_instruction) {
1064                    // Step 4.1.1. Return false.
1065                    return false;
1066                }
1067
1068                // Step 4.2. Append pi to configuration["processingInstructions"].
1069                configuration_processing_instructions.push(processing_instruction);
1070
1071                // Step 4.3. Return true.
1072                true
1073            },
1074            // Step 5. Otherwise:
1075            None => {
1076                // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1077                if configuration
1078                    .removeProcessingInstructions
1079                    .as_ref()
1080                    .is_some_and(|configuration_remove_processing_instructions| {
1081                        configuration_remove_processing_instructions
1082                            .contains_target(&processing_instruction)
1083                    })
1084                {
1085                    // Step 5.1.1. Remove the item from
1086                    // configuration["removeProcessingInstructions"] whose "target" is pi["target"].
1087                    if let Some(configuration_remove_processing_instructions) =
1088                        &mut configuration.removeProcessingInstructions
1089                    {
1090                        configuration_remove_processing_instructions
1091                            .retain(|item| item.target() != processing_instruction.target())
1092                    }
1093
1094                    // Step 5.1.2. Return true.
1095                    return true;
1096                }
1097
1098                // Step 5.2. Return false.
1099                false
1100            },
1101        }
1102    }
1103
1104    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeprocessinginstruction>
1105    fn RemoveProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1106        // Step 1. Let configuration be this’s configuration.
1107        let mut configuration = self.configuration.borrow_mut();
1108
1109        // Step 2. Assert: configuration is valid.
1110        debug_assert!(configuration.is_valid());
1111
1112        // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1113        let processing_instruction = processing_instruction.canonicalize();
1114
1115        match &mut configuration.processingInstructions {
1116            // Step 4. If configuration["processingInstructions"] exists:
1117            Some(configuration_processing_instructions) => {
1118                // Step 4.1. If configuration["processingInstructions"] contains pi:
1119                if configuration_processing_instructions.contains_target(&processing_instruction) {
1120                    // Step 4.1.1. Remove the item from configuration["processingInstructions"]
1121                    // whose "target" is pi["target"].
1122                    configuration_processing_instructions
1123                        .retain(|item| item.target() != processing_instruction.target());
1124
1125                    // Step 4.1.2. Return true.
1126                    return true;
1127                }
1128
1129                // Step 4.2. Return false.
1130                false
1131            },
1132            // Step 5. Otherwise:
1133            None => {
1134                // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1135                if configuration
1136                    .removeProcessingInstructions
1137                    .as_ref()
1138                    .is_some_and(|configuration_remove_processing_instructions| {
1139                        configuration_remove_processing_instructions
1140                            .contains_target(&processing_instruction)
1141                    })
1142                {
1143                    // Step 5.1.1. Return false.
1144                    return false;
1145                }
1146
1147                // Step 5.2. Append pi to configuration["removeProcessingInstructions"].
1148                if let Some(configuration_remove_processing_instructions) =
1149                    &mut configuration.removeProcessingInstructions
1150                {
1151                    configuration_remove_processing_instructions.push(processing_instruction);
1152                } else {
1153                    configuration.removeProcessingInstructions = Some(vec![processing_instruction]);
1154                }
1155
1156                // Step 5.3. Return true.
1157                true
1158            },
1159        }
1160    }
1161
1162    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowattribute>
1163    fn AllowAttribute(&self, attribute: SanitizerAttribute) -> bool {
1164        // Step 1. Let configuration be this’s configuration.
1165        let mut configuration = self.configuration.borrow_mut();
1166
1167        // Step 2. Assert: configuration is valid.
1168        debug_assert!(configuration.is_valid());
1169
1170        // Step 3. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1171        let attribute = attribute.canonicalize();
1172
1173        // Step 4. If configuration["attributes"] exists:
1174        if configuration.attributes.is_some() {
1175            // Step 4.1. Comment: If we have a global allow-list, we need to add attribute.
1176
1177            // Step 4.2. If configuration["dataAttributes"] is true and attribute is a custom data
1178            // attribute, then return false.
1179            if configuration.dataAttributes == Some(true) && attribute.is_custom_data_attribute() {
1180                return false;
1181            }
1182
1183            // Step 4.3. If configuration["attributes"] contains attribute return false.
1184            if configuration
1185                .attributes
1186                .as_ref()
1187                .is_some_and(|configuration_attributes| {
1188                    configuration_attributes.contains(&attribute)
1189                })
1190            {
1191                return false;
1192            }
1193
1194            // Step 4.4. Comment: Fix-up per-element allow and remove lists.
1195
1196            // Step 4.5. If configuration["elements"] exists:
1197            if let Some(configuration_elements) = &mut configuration.elements {
1198                // Step 4.5.1. For each element in configuration["elements"]:
1199                for element in configuration_elements.iter_mut() {
1200                    // Step 4.5.1.1. If element["attributes"] with default « » contains attribute:
1201                    // Step 4.5.1.1.1. Remove attribute from element["attributes"].
1202                    if let Some(element_attributes) = element.attributes_mut() {
1203                        element_attributes
1204                            .retain(|element_attribute| *element_attribute != attribute);
1205                    }
1206
1207                    // Step 4.5.1.2. Assert: element["removeAttributes"] with default « » does not
1208                    // contain attribute.
1209                    debug_assert!(!element.remove_attributes().is_some_and(
1210                        |element_remove_attributes| element_remove_attributes.contains(&attribute)
1211                    ));
1212                }
1213            }
1214
1215            // Step 4.6. Append attribute to configuration["attributes"]
1216            if let Some(configuration_attributes) = &mut configuration.attributes {
1217                configuration_attributes.push(attribute);
1218            } else {
1219                configuration.attributes = Some(vec![attribute]);
1220            }
1221
1222            // Step 4.7. Return true.
1223            true
1224        }
1225        // Step 5. Otherwise:
1226        else {
1227            // Step 5.1. Comment: If we have a global remove-list, we need to remove attribute.
1228
1229            // Step 5.2. If configuration["removeAttributes"] does not contain attribute:
1230            if !configuration.removeAttributes.as_ref().is_some_and(
1231                |configuration_remove_attributes| {
1232                    configuration_remove_attributes.contains(&attribute)
1233                },
1234            ) {
1235                // Step 5.2.1. Return false.
1236                return false;
1237            }
1238
1239            // Step 5.3. Remove attribute from configuration["removeAttributes"].
1240            if let Some(configuration_remove_attributes) = &mut configuration.removeAttributes {
1241                configuration_remove_attributes.retain(|configuration_remove_attribute| {
1242                    *configuration_remove_attribute != attribute
1243                });
1244            }
1245
1246            // Step 5.4. Return true.
1247            true
1248        }
1249    }
1250
1251    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeattribute>
1252    fn RemoveAttribute(&self, attribute: SanitizerAttribute) -> bool {
1253        // Remove an attribute with attribute and this’s configuration.
1254        self.configuration.borrow_mut().remove_attribute(attribute)
1255    }
1256
1257    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setcomments>
1258    fn SetComments(&self, allow: bool) -> bool {
1259        // Step 1. Let configuration be this’s configuration.
1260        let mut configuration = self.configuration.borrow_mut();
1261
1262        // Step 2. Assert: configuration is valid.
1263        debug_assert!(configuration.is_valid());
1264
1265        // Step 3. If configuration["comments"] exists and configuration["comments"] equals allow,
1266        // then return false;
1267        if configuration
1268            .comments
1269            .is_some_and(|configuration_comments| configuration_comments == allow)
1270        {
1271            return false;
1272        }
1273
1274        // Step 4. Set configuration["comments"] to allow.
1275        configuration.comments = Some(allow);
1276
1277        // Step 5. Return true.
1278        true
1279    }
1280
1281    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setdataattributes>
1282    fn SetDataAttributes(&self, allow: bool) -> bool {
1283        // Step 1. Let configuration be this’s configuration.
1284        let mut configuration = self.configuration.borrow_mut();
1285
1286        // Step 2. Assert: configuration is valid.
1287        debug_assert!(configuration.is_valid());
1288
1289        // Step 3. If configuration["attributes"] does not exist, then return false.
1290        if configuration.attributes.is_none() {
1291            return false;
1292        }
1293
1294        // Step 4. If configuration["dataAttributes"] equals allow, then return false.
1295        if configuration.dataAttributes == Some(allow) {
1296            return false;
1297        }
1298
1299        // Step 5. If allow is true:
1300        if allow {
1301            // Step 5.1. Remove any items attr from configuration["attributes"] where attr is a
1302            // custom data attribute.
1303            if let Some(configuration_attributes) = &mut configuration.attributes {
1304                configuration_attributes.retain(|attribute| !attribute.is_custom_data_attribute());
1305            }
1306
1307            // Step 5.2. If configuration["elements"] exists:
1308            if let Some(configuration_elements) = &mut configuration.elements {
1309                // Step 5.2.1. For each element in configuration["elements"]:
1310                for element in configuration_elements {
1311                    // Step 5.2.1.1. If element["attributes"] exists:
1312                    if let Some(element_attributes) = element.attributes_mut() {
1313                        // Step 5.2.1.1.1. Remove any items attr from element["attributes"] where
1314                        // attr is a custom data attribute.
1315                        element_attributes
1316                            .retain(|attribute| !attribute.is_custom_data_attribute());
1317                    }
1318                }
1319            }
1320        }
1321
1322        // Step 6. Set configuration["dataAttributes"] to allow.
1323        configuration.dataAttributes = Some(allow);
1324
1325        // Step 7. Return true.
1326        true
1327    }
1328
1329    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeunsafe>
1330    fn RemoveUnsafe(&self) -> bool {
1331        // Update this’s configuration with the result of calling remove unsafe on this’s
1332        // configuration.
1333        self.configuration.borrow_mut().remove_unsafe()
1334    }
1335}
1336
1337trait SanitizerConfigAlgorithm {
1338    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1339    fn is_valid(&self) -> bool;
1340
1341    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1342    fn remove_element(&mut self, element: SanitizerElement) -> bool;
1343
1344    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1345    fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool;
1346
1347    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1348    fn remove_unsafe(&mut self) -> bool;
1349
1350    /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1351    fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
1352}
1353
1354impl SanitizerConfigAlgorithm for SanitizerConfig {
1355    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1356    fn is_valid(&self) -> bool {
1357        // NOTE: It’s expected that the configuration being passing in has previously been run
1358        // through the canonicalize the configuration steps. We will simply assert conditions that
1359        // that algorithm should have guaranteed to hold.
1360
1361        // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
1362        assert!(self.elements.is_some() || self.removeElements.is_some());
1363
1364        // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
1365        // false.
1366        if self.elements.is_some() && self.removeElements.is_some() {
1367            return false;
1368        }
1369
1370        // Step 3. Assert: Either config["processingInstructions"] exists or
1371        // config["removeProcessingInstructions"] exists.
1372        assert!(
1373            self.processingInstructions.is_some() || self.removeProcessingInstructions.is_some()
1374        );
1375
1376        // Step 4. If config["processingInstructions"] exists and
1377        // config["removeProcessingInstructions"] exists, then return false.
1378        if self.processingInstructions.is_some() && self.removeProcessingInstructions.is_some() {
1379            return false;
1380        }
1381
1382        // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
1383        assert!(self.attributes.is_some() || self.removeAttributes.is_some());
1384
1385        // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
1386        // false.
1387        if self.attributes.is_some() && self.removeAttributes.is_some() {
1388            return false;
1389        }
1390
1391        // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
1392        // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
1393        // canonical, meaning they have been run through canonicalize a sanitizer element,
1394        // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
1395        // as appropriate.
1396        //
1397        // NOTE: This assertion could be done by running the canonicalization again to see if there
1398        // is any changes. Since it is expected to canonicalize the configuration before running
1399        // this `is_valid` function, we simply skip this assert for the sake of performace.
1400
1401        match &self.elements {
1402            // Step 8. If config["elements"] exists:
1403            Some(config_elements) => {
1404                // Step 8.1. If config["elements"] has duplicates, then return false.
1405                if config_elements.has_duplicates() {
1406                    return false;
1407                }
1408            },
1409            // Step 9. Otherwise:
1410            None => {
1411                // Step 9.1. If config["removeElements"] has duplicates, then return false.
1412                if self
1413                    .removeElements
1414                    .as_ref()
1415                    .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
1416                {
1417                    return false;
1418                }
1419            },
1420        }
1421
1422        // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
1423        // false.
1424        if self
1425            .replaceWithChildrenElements
1426            .as_ref()
1427            .is_some_and(|replace_with_children_elements| {
1428                replace_with_children_elements.has_duplicates()
1429            })
1430        {
1431            return false;
1432        }
1433
1434        match &self.processingInstructions {
1435            // Step 11. If config["processingInstructions"] exists:
1436            Some(config_processing_instructions) => {
1437                // Step 11.1. If config["processingInstructions"] has duplicate targets, then return
1438                // false.
1439                if config_processing_instructions.has_duplicate_targets() {
1440                    return false;
1441                }
1442            },
1443            // Step 12. Otherwise:
1444            None => {
1445                // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then
1446                // return false.
1447                if self.removeProcessingInstructions.as_ref().is_some_and(
1448                    |config_remove_processing_instructions| {
1449                        config_remove_processing_instructions.has_duplicate_targets()
1450                    },
1451                ) {
1452                    return false;
1453                }
1454            },
1455        }
1456
1457        match &self.attributes {
1458            // Step 13. If config["attributes"] exists:
1459            Some(config_attributes) => {
1460                // Step 13.1. If config["attributes"] has duplicates, then return false.
1461                if config_attributes.has_duplicates() {
1462                    return false;
1463                }
1464            },
1465            // Step 14. Otherwise:
1466            None => {
1467                // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
1468                if self
1469                    .removeAttributes
1470                    .as_ref()
1471                    .is_some_and(|config_remove_attributes| {
1472                        config_remove_attributes.has_duplicates()
1473                    })
1474                {
1475                    return false;
1476                }
1477            },
1478        }
1479
1480        // Step 15. If config["replaceWithChildrenElements"] exists:
1481        if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
1482            // Step 15.1. For each element of config["replaceWithChildrenElements"]:
1483            for element in config_replace_with_children_elements {
1484                // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
1485                // return false.
1486                if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(element)) {
1487                    return false;
1488                }
1489            }
1490
1491            match &self.elements {
1492                // Step 15.2. If config["elements"] exists:
1493                Some(config_elements) => {
1494                    // Step 15.2.1. If the intersection of config["elements"] and
1495                    // config["replaceWithChildrenElements"] is not empty, then return false.
1496                    if config_elements
1497                        .is_intersection_non_empty(config_replace_with_children_elements)
1498                    {
1499                        return false;
1500                    }
1501                },
1502                // Step 15.3. Otherwise:
1503                None => {
1504                    // Step 15.3.1. If the intersection of config["removeElements"] and
1505                    // config["replaceWithChildrenElements"] is not empty, then return false.
1506                    if self
1507                        .removeElements
1508                        .as_ref()
1509                        .is_some_and(|config_remove_elements| {
1510                            config_remove_elements
1511                                .is_intersection_non_empty(config_replace_with_children_elements)
1512                        })
1513                    {
1514                        return false;
1515                    }
1516                },
1517            }
1518        }
1519
1520        match &self.attributes {
1521            // Step 16. If config["attributes"] exists:
1522            Some(config_attributes) => {
1523                // Step 16.1. Assert: config["dataAttributes"] exists.
1524                assert!(self.dataAttributes.is_some());
1525
1526                // Step 16.2. If config["elements"] exists:
1527                if let Some(config_elements) = &self.elements {
1528                    // Step 16.2.1. For each element of config["elements"]:
1529                    for element in config_elements {
1530                        // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
1531                        // has duplicates, then return false.
1532                        if element
1533                            .attributes()
1534                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
1535                        {
1536                            return false;
1537                        }
1538
1539                        // Step 16.2.1.2. If element["removeAttributes"] exists and
1540                        // element["removeAttributes"] has duplicates, then return false.
1541                        if element
1542                            .remove_attributes()
1543                            .is_some_and(|element_remove_attributes| {
1544                                element_remove_attributes.has_duplicates()
1545                            })
1546                        {
1547                            return false;
1548                        }
1549
1550                        // Step 16.2.1.3. If the intersection of config["attributes"] and
1551                        // element["attributes"] with default « » is not empty, then return false.
1552                        if config_attributes
1553                            .is_intersection_non_empty(element.attributes().unwrap_or_default())
1554                        {
1555                            return false;
1556                        }
1557
1558                        // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
1559                        // subset of config["attributes"], then return false.
1560                        if !element
1561                            .remove_attributes()
1562                            .unwrap_or_default()
1563                            .iter()
1564                            .all(|entry| config_attributes.contains_item(entry))
1565                        {
1566                            return false;
1567                        }
1568
1569                        // Step 16.2.1.5. If config["dataAttributes"] is true and
1570                        // element["attributes"] contains a custom data attribute, then return
1571                        // false.
1572                        if self.dataAttributes == Some(true) &&
1573                            element.attributes().is_some_and(|attributes| {
1574                                attributes
1575                                    .iter()
1576                                    .any(|attribute| attribute.is_custom_data_attribute())
1577                            })
1578                        {
1579                            return false;
1580                        }
1581                    }
1582                }
1583
1584                // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
1585                // a custom data attribute, then return false.
1586                if self.dataAttributes == Some(true) &&
1587                    config_attributes
1588                        .iter()
1589                        .any(|attribute| attribute.is_custom_data_attribute())
1590                {
1591                    return false;
1592                }
1593            },
1594            // Step 17. Otherwise:
1595            None => {
1596                // Step 17.1. If config["elements"] exists:
1597                if let Some(config_elements) = &self.elements {
1598                    // Step 17.1.1. For each element of config["elements"]:
1599                    for element in config_elements {
1600                        // Step 17.1.1.1. If element["attributes"] exists and
1601                        // element["removeAttributes"] exists, then return false.
1602                        if element.attributes().is_some() && element.remove_attributes().is_some() {
1603                            return false;
1604                        }
1605
1606                        // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
1607                        // has duplicates, then return false.
1608                        if element
1609                            .attributes()
1610                            .as_ref()
1611                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
1612                        {
1613                            return false;
1614                        }
1615
1616                        // Step 17.1.1.3. If element["removeAttributes"] exist and
1617                        // element["removeAttributes"] has duplicates, then return false.
1618                        if element.remove_attributes().as_ref().is_some_and(
1619                            |element_remove_attributes| element_remove_attributes.has_duplicates(),
1620                        ) {
1621                            return false;
1622                        }
1623
1624                        // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
1625                        // element["attributes"] with default « » is not empty, then return false.
1626                        if self
1627                            .removeAttributes
1628                            .as_ref()
1629                            .is_some_and(|config_remove_attributes| {
1630                                config_remove_attributes.is_intersection_non_empty(
1631                                    element.attributes().unwrap_or_default(),
1632                                )
1633                            })
1634                        {
1635                            return false;
1636                        }
1637
1638                        // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
1639                        // element["removeAttributes"] with default « » is not empty, then return
1640                        // false.
1641                        if self
1642                            .removeAttributes
1643                            .as_ref()
1644                            .is_some_and(|config_remove_attributes| {
1645                                config_remove_attributes.is_intersection_non_empty(
1646                                    element.remove_attributes().unwrap_or_default(),
1647                                )
1648                            })
1649                        {
1650                            return false;
1651                        }
1652                    }
1653                }
1654
1655                // Step 17.2. If config["dataAttributes"] exists, then return false.
1656                if self.dataAttributes.is_some() {
1657                    return false;
1658                }
1659            },
1660        }
1661
1662        // Step 18. Return true.
1663        true
1664    }
1665
1666    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1667    fn remove_element(&mut self, element: SanitizerElement) -> bool {
1668        // Step 1. Assert: configuration is valid.
1669        debug_assert!(self.is_valid());
1670
1671        // Step 2. Set element to the result of canonicalize a sanitizer element with element.
1672        let element = element.canonicalize();
1673
1674        // Step 3. Set modified to the result of remove element from
1675        // configuration["replaceWithChildrenElements"].
1676        let modified = if let Some(configuration_replace_with_children_elements) =
1677            &mut self.replaceWithChildrenElements
1678        {
1679            configuration_replace_with_children_elements.remove_item(&element)
1680        } else {
1681            false
1682        };
1683
1684        // Step 4. If configuration["elements"] exists:
1685        if let Some(configuration_elements) = &mut self.elements {
1686            // Step 4.1. If configuration["elements"] contains element:
1687            if configuration_elements.contains_item(&element) {
1688                // Step 4.1.1. Comment: We have a global allow list and it contains element.
1689
1690                // Step 4.1.2. Remove element from configuration["elements"].
1691                configuration_elements.remove_item(&element);
1692
1693                // Step 4.1.3. Return true.
1694                return true;
1695            }
1696
1697            // Step 4.2. Comment: We have a global allow list and it does not contain element.
1698
1699            // Step 4.3. Return modified.
1700            modified
1701        }
1702        // Step 5. Otherwise:
1703        else {
1704            // Step 5.1. If configuration["removeElements"] contains element:
1705            if self
1706                .removeElements
1707                .as_mut()
1708                .is_some_and(|configuration_remove_elements| {
1709                    configuration_remove_elements.contains_item(&element)
1710                })
1711            {
1712                // Step 5.1.1. Comment: We have a global remove list and it already contains element.
1713
1714                // Step 5.1.2. Return modified.
1715                return modified;
1716            }
1717
1718            // Step 5.2. Comment: We have a global remove list and it does not contain element.
1719
1720            // Step 5.3. Add element to configuration["removeElements"].
1721            if let Some(configuration_remove_elements) = &mut self.removeElements {
1722                configuration_remove_elements.add_item(element);
1723            } else {
1724                self.removeElements = Some(vec![element]);
1725            }
1726
1727            // Step 5.4. Return true.
1728            true
1729        }
1730    }
1731
1732    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1733    fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool {
1734        // Step 1. Assert: configuration is valid.
1735        debug_assert!(self.is_valid());
1736
1737        // Step 2. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1738        let attribute = attribute.canonicalize();
1739
1740        // Step 3. If configuration["attributes"] exists:
1741        if self.attributes.is_some() {
1742            // Step 3.1. Comment: If we have a global allow-list, we need to remove attribute.
1743
1744            // Step 3.2. Set modified to the result of remove attribute from
1745            // configuration["attributes"].
1746            let mut modified = self
1747                .attributes
1748                .as_mut()
1749                .is_some_and(|configuration_attributes| {
1750                    configuration_attributes.remove_item(&attribute)
1751                });
1752
1753            // Step 3.3. Comment: Fix-up per-element allow and remove lists.
1754
1755            // Step 3.4. If configuration["elements"] exists:
1756            if let Some(configuration_elements) = &mut self.elements {
1757                // Step 3.4.1. For each element of configuration["elements"]:
1758                for element in configuration_elements {
1759                    // Step 3.4.1.1. If element["attributes"] with default « » contains attribute:
1760                    if element
1761                        .attributes()
1762                        .unwrap_or_default()
1763                        .contains(&attribute)
1764                    {
1765                        // Step 3.4.1.1.1. Set modified to true.
1766                        modified = true;
1767
1768                        // Step 3.4.1.1.2. Remove attribute from element["attributes"].
1769                        if let Some(element_attributes) = element.attributes_mut() {
1770                            element_attributes
1771                                .retain(|element_attribute| *element_attribute != attribute);
1772                        }
1773                    }
1774
1775                    // Step 3.4.1.2. If element["removeAttributes"] with default « » contains
1776                    // attribute:
1777                    if element
1778                        .remove_attributes()
1779                        .unwrap_or_default()
1780                        .contains(&attribute)
1781                    {
1782                        // Step 3.4.1.2.1. Assert: modified is true.
1783                        assert!(modified);
1784
1785                        // Step 3.4.1.2.2. Remove attribute from element["removeAttributes"].
1786                        if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1787                            element_remove_attributes.retain(|element_remove_attribute| {
1788                                *element_remove_attribute != attribute
1789                            });
1790                        }
1791                    }
1792                }
1793            }
1794
1795            // Step 3.5. Return modified.
1796            modified
1797        }
1798        // Step 4. Otherwise:
1799        else {
1800            // Step 4.1. Comment: If we have a global remove-list, we need to add attribute.
1801
1802            // Step 4.2. If configuration["removeAttributes"] contains attribute return false.
1803            if self
1804                .removeAttributes
1805                .as_ref()
1806                .is_some_and(|configuration_remove_attributes| {
1807                    configuration_remove_attributes.contains(&attribute)
1808                })
1809            {
1810                return false;
1811            }
1812
1813            // Step 4.3. Comment: Fix-up per-element allow and remove lists.
1814
1815            // Step 4.4. If configuration["elements"] exists:
1816            if let Some(configuration_elements) = &mut self.elements {
1817                // Step 4.4.1. For each element in configuration["elements"]:
1818                for element in configuration_elements {
1819                    // Step 4.4.1.1. If element["attributes"] with default « » contains attribute:
1820                    // Step 4.4.1.1.1. Remove attribute from element["attributes"].
1821                    if let Some(element_attributes) = element.attributes_mut() {
1822                        element_attributes
1823                            .retain(|element_attribute| *element_attribute != attribute);
1824                    }
1825
1826                    // Step 4.4.1.2. If element["removeAttributes"] with default « » contains
1827                    // attribute:
1828                    // Step 4.4.1.2.1. Remove attribute from element["removeAttributes"].
1829                    if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1830                        element_remove_attributes.retain(|element_remove_attribute| {
1831                            *element_remove_attribute != attribute
1832                        });
1833                    }
1834                }
1835            }
1836
1837            // Step 4.5. Append attribute to configuration["removeAttributes"]
1838            if let Some(configuration_remove_attributes) = &mut self.removeAttributes {
1839                configuration_remove_attributes.push(attribute);
1840            } else {
1841                self.removeAttributes = Some(vec![attribute]);
1842            }
1843
1844            // Step 4.6. Return true.
1845            true
1846        }
1847    }
1848
1849    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1850    fn remove_unsafe(&mut self) -> bool {
1851        // Step 1. Assert: The key set of built-in safe baseline configuration equals « [
1852        // "removeElements", "removeAttributes" ] ».
1853        let baseline = built_in_safe_baseline_configuration();
1854        assert!(baseline.removeElements.is_some() && baseline.removeAttributes.is_some());
1855
1856        // Step 2. Assert: configuration is valid.
1857        debug_assert!(self.is_valid());
1858
1859        // Step 3. Let result be false.
1860        let mut result = false;
1861
1862        // Step 4. For each element in built-in safe baseline configuration["removeElements"]:
1863        for element in baseline.removeElements.unwrap_or_default() {
1864            // Step 4.1. Call remove an element element from configuration.
1865            // Step 4.2. If the call returned true, set result to true.
1866            if self.remove_element(element) {
1867                result = true;
1868            }
1869        }
1870
1871        // Step 5. For each attribute in built-in safe baseline configuration["removeAttributes"]:
1872        for attribute in baseline.removeAttributes.unwrap_or_default() {
1873            // Step 5.1. Call remove an attribute attribute from configuration.
1874            // Step 5.2. If the call returned true, set result to true.
1875            if self.remove_attribute(attribute) {
1876                result = true;
1877            }
1878        }
1879
1880        // Step 6. For each attribute listed in event handler content attributes:
1881        for attribute in CONTENT_EVENT_HANDLER_NAMES.iter() {
1882            // Step 6.1. Call remove an attribute attribute from configuration.
1883            // Step 6.2. If the call returned true, set result to true.
1884            let attribute = SanitizerAttribute::String(DOMString::from(*attribute));
1885            if self.remove_attribute(attribute) {
1886                result = true;
1887            }
1888        }
1889
1890        // Step 7. Return result.
1891        result
1892    }
1893
1894    /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1895    fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool) {
1896        // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
1897        // then set configuration["removeElements"] to « ».
1898        if self.elements.is_none() && self.removeElements.is_none() {
1899            self.removeElements = Some(Vec::new());
1900        }
1901
1902        // Step 2. If neither configuration["processingInstructions"] nor
1903        // configuration["removeProcessingInstructions"] exist:
1904        if self.processingInstructions.is_none() && self.removeProcessingInstructions.is_none() {
1905            // Step 2.1. If allowCommentsPIsAndDataAttributes is true, then set
1906            // configuration["removeProcessingInstructions"] to « ».
1907            if allow_comments_pis_and_data_attributes {
1908                self.removeProcessingInstructions = Some(Vec::new());
1909            }
1910            // Step 2.2. Otherwise, set configuration["processingInstructions"] to « ».
1911            else {
1912                self.processingInstructions = Some(Vec::new());
1913            }
1914        }
1915
1916        // Step 3. If neither configuration["attributes"] nor configuration["removeAttributes"]
1917        // exist, then set configuration["removeAttributes"] to « ».
1918        if self.attributes.is_none() && self.removeAttributes.is_none() {
1919            self.removeAttributes = Some(Vec::new());
1920        }
1921
1922        // Step 4. If configuration["elements"] exists:
1923        if let Some(elements) = &mut self.elements {
1924            // Step 4.1. Let elements be « ».
1925            // Step 4.2. For each element of configuration["elements"] do:
1926            // Step 4.2.1. Append the result of canonicalize a sanitizer element with attributes
1927            // element to elements.
1928            // Step 4.3. Set configuration["elements"] to elements.
1929            *elements = elements
1930                .iter()
1931                .cloned()
1932                .map(SanitizerElementWithAttributes::canonicalize)
1933                .collect();
1934        }
1935
1936        // Step 5. If configuration["removeElements"] exists:
1937        if let Some(remove_elements) = &mut self.removeElements {
1938            // Step 5.1. Let elements be « ».
1939            // Step 5.2. For each element of configuration["removeElements"] do:
1940            // Step 5.2.1. Append the result of canonicalize a sanitizer element element to
1941            // elements.
1942            // Step 5.3. Set configuration["removeElements"] to elements.
1943            *remove_elements = remove_elements
1944                .iter()
1945                .cloned()
1946                .map(SanitizerElement::canonicalize)
1947                .collect();
1948        }
1949
1950        // Step 6. If configuration["replaceWithChildrenElements"] exists:
1951        if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
1952            // Step 6.1. Let elements be « ».
1953            // Step 6.2. For each element of configuration["replaceWithChildrenElements"] do:
1954            // Step 6.2.1. Append the result of canonicalize a sanitizer element element to
1955            // elements.
1956            // Step 6.3. Set configuration["replaceWithChildrenElements"] to elements.
1957            *replace_with_children_elements = replace_with_children_elements
1958                .iter()
1959                .cloned()
1960                .map(SanitizerElement::canonicalize)
1961                .collect();
1962        }
1963
1964        // Step 7. If configuration["processingInstructions"] exists:
1965        if let Some(processing_instructions) = &mut self.processingInstructions {
1966            // Step 7.1. Let processingInstructions be « ».
1967            // Step 7.2. For each pi of configuration["processingInstructions"]:
1968            // Step 7.2.1. Append the result of canonicalize a sanitizer processing instruction pi
1969            // to processingInstructions.
1970            // Step 7.3. Set configuration["processingInstructions"] to processingInstructions.
1971            *processing_instructions = processing_instructions
1972                .iter()
1973                .cloned()
1974                .map(SanitizerPI::canonicalize)
1975                .collect();
1976        }
1977
1978        // Step 8. If configuration["removeProcessingInstructions"] exists:
1979        if let Some(remove_processing_instructions) = &mut self.removeProcessingInstructions {
1980            // Step 8.1. Let processingInstructions be « ».
1981            // Step 8.2. For each pi of configuration["removeProcessingInstructions"]:
1982            // Step 8.2.1. Append the result of canonicalize a sanitizer processing instruction
1983            // pi to processingInstructions.
1984            // Step 8.3. Set configuration["removeProcessingInstructions"] to
1985            // processingInstructions.
1986            *remove_processing_instructions = remove_processing_instructions
1987                .iter()
1988                .cloned()
1989                .map(SanitizerPI::canonicalize)
1990                .collect();
1991        }
1992
1993        // Step 9. If configuration["attributes"] exists:
1994        if let Some(attributes) = &mut self.attributes {
1995            // Step 9.1. Let attributes be « ».
1996            // Step 9.2. For each attribute of configuration["attributes"] do:
1997            // Step 9.2.1. Append the result of canonicalize a sanitizer attribute attribute to
1998            // attributes.
1999            // Step 9.3. Set configuration["attributes"] to attributes.
2000            *attributes = attributes
2001                .iter()
2002                .cloned()
2003                .map(SanitizerAttribute::canonicalize)
2004                .collect();
2005        }
2006
2007        // Step 10. If configuration["removeAttributes"] exists:
2008        if let Some(remove_attributes) = &mut self.removeAttributes {
2009            // Step 10.1. Let attributes be « ».
2010            // Step 10.2. For each attribute of configuration["removeAttributes"] do:
2011            // Step 10.2.1. Append the result of canonicalize a sanitizer attribute attribute to
2012            // attributes.
2013            // Step 10.3. Set configuration["removeAttributes"] to attributes.
2014            *remove_attributes = remove_attributes
2015                .iter()
2016                .cloned()
2017                .map(SanitizerAttribute::canonicalize)
2018                .collect();
2019        }
2020
2021        // Step 11. If configuration["comments"] does not exist, then set configuration["comments"]
2022        // to allowCommentsPIsAndDataAttributes.
2023        if self.comments.is_none() {
2024            self.comments = Some(allow_comments_pis_and_data_attributes);
2025        }
2026
2027        // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
2028        // not exist, then set configuration["dataAttributes"] to allowCommentsPIsAndDataAttributes.
2029        if self.attributes.is_some() && self.dataAttributes.is_none() {
2030            self.dataAttributes = Some(allow_comments_pis_and_data_attributes);
2031        }
2032    }
2033}
2034
2035trait Canonicalization {
2036    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
2037    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2038    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2039    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2040    fn canonicalize(self) -> Self;
2041}
2042
2043impl Canonicalization for SanitizerElementWithAttributes {
2044    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
2045    fn canonicalize(mut self) -> Self {
2046        // Step 1. Let result be the result of canonicalize a sanitizer element with element.
2047        let parent = match &mut self {
2048            SanitizerElementWithAttributes::String(name) => {
2049                SanitizerElement::String(std::mem::take(name))
2050            },
2051            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2052                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2053                    name: std::mem::take(&mut dictionary.parent.name),
2054                    namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
2055                })
2056            },
2057        };
2058        let mut canonicalized_parent = parent.canonicalize();
2059        let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2060            SanitizerElementNamespaceWithAttributes {
2061                parent: SanitizerElementNamespace {
2062                    name: std::mem::take(canonicalized_parent.name_mut()),
2063                    namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
2064                },
2065                attributes: None,
2066                removeAttributes: None,
2067            },
2068        );
2069
2070        // Step 2. If element is a dictionary:
2071        if matches!(
2072            self,
2073            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
2074        ) {
2075            // Step 2.1. If element["attributes"] exists:
2076            if let Some(attributes) = self.attributes() {
2077                // Step 2.1.1. Let attributes be « ».
2078                // Step 2.1.2. For each attribute of element["attributes"]:
2079                // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
2080                // attribute to attributes.
2081                let attributes = attributes
2082                    .iter()
2083                    .cloned()
2084                    .map(|attribute| attribute.canonicalize())
2085                    .collect();
2086
2087                // Step 2.1.3. Set result["attributes"] to attributes.
2088                result.set_attributes(Some(attributes));
2089            }
2090
2091            // Step 2.2. If element["removeAttributes"] exists:
2092            if let Some(remove_attributes) = self.remove_attributes() {
2093                // Step 2.2.1. Let attributes be « ».
2094                // Step 2.2.2. For each attribute of element["removeAttributes"]:
2095                // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
2096                // attribute to attributes.
2097                let attributes = remove_attributes
2098                    .iter()
2099                    .cloned()
2100                    .map(|attribute| attribute.canonicalize())
2101                    .collect();
2102
2103                // Step 2.2.3. Set result["removeAttributes"] to attributes.
2104                result.set_remove_attributes(Some(attributes));
2105            }
2106        }
2107
2108        // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
2109        if result.attributes().is_none() && result.remove_attributes().is_none() {
2110            // Step 3.1. Set result["removeAttributes"] to « ».
2111            result.set_remove_attributes(Some(Vec::new()));
2112        }
2113
2114        // Step 4. Return result.
2115        result
2116    }
2117}
2118
2119impl Canonicalization for SanitizerElement {
2120    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2121    fn canonicalize(self) -> Self {
2122        // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
2123        // the default namespace.
2124        self.canonicalize_name(Some(ns!(html).to_string()))
2125    }
2126}
2127impl Canonicalization for SanitizerPI {
2128    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2129    fn canonicalize(self) -> Self {
2130        // Step 1. Assert: pi is either a DOMString or a dictionary.
2131        assert!(matches!(
2132            self,
2133            SanitizerPI::String(_) | SanitizerPI::SanitizerProcessingInstruction(_)
2134        ));
2135
2136        // Step 2. If pi is a DOMString, then return «[ "target" → pi ]».
2137        if let SanitizerPI::String(target) = self {
2138            return SanitizerPI::SanitizerProcessingInstruction(SanitizerProcessingInstruction {
2139                target,
2140            });
2141        }
2142
2143        // Step 3. Assert: pi is a dictionary and pi["target"] exists.
2144        // NOTE: The latter is guaranteed by Rust type system.
2145        assert!(matches!(
2146            self,
2147            SanitizerPI::SanitizerProcessingInstruction(_)
2148        ));
2149
2150        // Step 4. Return «[ "target" → pi["target"] ]».
2151        self
2152    }
2153}
2154
2155impl Canonicalization for SanitizerAttribute {
2156    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2157    fn canonicalize(self) -> Self {
2158        // Return the result of canonicalize a sanitizer name with attribute and null as the default
2159        // namespace.
2160        self.canonicalize_name(None)
2161    }
2162}
2163
2164trait NameCanonicalization: NameMember {
2165    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
2166    fn is_string(&self) -> bool;
2167    fn is_dictionary(&self) -> bool;
2168
2169    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
2170    fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
2171        // Step 1. Assert: name is either a DOMString or a dictionary.
2172        assert!(self.is_string() || self.is_dictionary());
2173
2174        // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
2175        // defaultNamespace]».
2176        if self.is_string() {
2177            return Self::new_dictionary(
2178                std::mem::take(self.name_mut()),
2179                default_namespace.map(DOMString::from),
2180            );
2181        }
2182
2183        // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
2184        // NOTE: The latter is guaranteed by Rust type system.
2185        assert!(self.is_dictionary());
2186
2187        // Step 4. If name["namespace"] is the empty string, then set it to null.
2188        if self
2189            .namespace()
2190            .is_some_and(|namespace| namespace.str() == "")
2191        {
2192            self.set_namespace(None);
2193        }
2194
2195        // Step 5. Return «[
2196        // "name" → name["name"],
2197        // "namespace" → name["namespace"]
2198        // ]».
2199        Self::new_dictionary(
2200            std::mem::take(self.name_mut()),
2201            self.namespace_mut().map(std::mem::take),
2202        )
2203    }
2204}
2205
2206impl NameCanonicalization for SanitizerElement {
2207    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2208        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
2209    }
2210
2211    fn is_string(&self) -> bool {
2212        matches!(self, SanitizerElement::String(_))
2213    }
2214
2215    fn is_dictionary(&self) -> bool {
2216        matches!(self, SanitizerElement::SanitizerElementNamespace(_))
2217    }
2218}
2219
2220impl NameCanonicalization for SanitizerAttribute {
2221    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2222        SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2223            name,
2224            namespace,
2225        })
2226    }
2227
2228    fn is_string(&self) -> bool {
2229        matches!(self, SanitizerAttribute::String(_))
2230    }
2231
2232    fn is_dictionary(&self) -> bool {
2233        matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
2234    }
2235}
2236
2237/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2238trait NameSlice<T>
2239where
2240    T: NameMember + Canonicalization + Clone,
2241{
2242    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2243    fn contains_item<S: NameMember>(&self, other: &S) -> bool;
2244
2245    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2246    fn has_duplicates(&self) -> bool;
2247
2248    /// Custom version of the supporting algorithm
2249    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2250    /// intersection is non-empty, returning early if it is non-empty for efficiency.
2251    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2252    where
2253        S: NameMember + Canonicalization + Clone;
2254}
2255
2256impl<T> NameSlice<T> for [T]
2257where
2258    T: NameMember + Canonicalization + Clone,
2259{
2260    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2261    fn contains_item<S: NameMember>(&self, other: &S) -> bool {
2262        // A Sanitizer name list contains an item if there exists an entry of list that is an
2263        // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
2264        // entry["namespace"].
2265        self.iter()
2266            .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
2267    }
2268
2269    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2270    fn has_duplicates(&self) -> bool {
2271        // A list list has duplicates, if for any item of list, there is more than one entry in list
2272        // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
2273        let mut used = HashSet::new();
2274        self.iter().any(move |entry| {
2275            !used.insert((
2276                entry.name().to_string(),
2277                entry.namespace().map(DOMString::to_string),
2278            ))
2279        })
2280    }
2281
2282    /// Custom version of the supporting algorithm
2283    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2284    /// intersection is non-empty, returning early if it is non-empty for efficiency.
2285    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2286    where
2287        S: NameMember + Canonicalization + Clone,
2288    {
2289        // Step 1. Let set A be « [] ».
2290        // Step 2. Let set B be « [] ».
2291        // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
2292        // set A.
2293        // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
2294        // set B.
2295        let a = self.iter().map(|entry| entry.clone().canonicalize());
2296        let b = others
2297            .iter()
2298            .map(|entry| entry.clone().canonicalize())
2299            .collect::<Vec<S>>();
2300
2301        // Step 5. Return the intersection of set A and set B.
2302        // NOTE: Instead of returning the intersection itself, return true if the intersection is
2303        // non-empty, and false otherwise.
2304        a.filter(|entry| {
2305            b.iter()
2306                .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
2307        })
2308        .any(|_| true)
2309    }
2310}
2311
2312/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2313trait NameVec<T>
2314where
2315    T: NameMember + Canonicalization + Clone,
2316{
2317    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2318    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
2319
2320    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2321    fn add_item(&mut self, name: T);
2322
2323    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2324    fn remove_duplicates(&mut self) -> &mut Self;
2325
2326    /// Set itself to the set intersection of itself and another list.
2327    ///
2328    /// <https://infra.spec.whatwg.org/#set-intersection>
2329    fn intersection<S>(&mut self, others: &[S])
2330    where
2331        S: NameMember + Canonicalization + Clone;
2332
2333    /// <https://infra.spec.whatwg.org/#set-difference>
2334    fn difference(&mut self, others: &[T]);
2335}
2336
2337impl<T> NameVec<T> for Vec<T>
2338where
2339    T: NameMember + Canonicalization + Clone,
2340{
2341    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2342    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
2343        // Step 1. Set removed to false.
2344        let mut removed = false;
2345
2346        // Step 2. For each entry of list:
2347        // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
2348        // Step 2.1.1. Remove item entry from list.
2349        // Step 2.1.2. Set removed to true.
2350        self.retain(|entry| {
2351            let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
2352            if matched {
2353                removed = true;
2354            }
2355            !matched
2356        });
2357
2358        // Step 3. Return removed.
2359        removed
2360    }
2361
2362    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2363    fn add_item(&mut self, name: T) {
2364        // Step 1. If list contains name, then return.
2365        if self.contains_item(&name) {
2366            return;
2367        };
2368
2369        // Step 2. Append name to list.
2370        self.push(name);
2371    }
2372
2373    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2374    fn remove_duplicates(&mut self) -> &mut Self {
2375        // Step 1. Let result be « ».
2376        // Step 2. For each entry of list, add entry to result.
2377        // Step 3. Return result.
2378        self.sort_by(|item_a, item_b| item_a.compare(item_b));
2379        self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
2380        self
2381    }
2382
2383    /// Set itself to the set intersection of itself and another list.
2384    ///
2385    /// <https://infra.spec.whatwg.org/#set-intersection>
2386    fn intersection<S>(&mut self, others: &[S])
2387    where
2388        S: NameMember + Canonicalization + Clone,
2389    {
2390        // The intersection of ordered sets A and B, is the result of creating a new ordered set set
2391        // and, for each item of A, if B contains item, appending item to set.
2392        self.retain(|item| {
2393            others
2394                .iter()
2395                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2396        })
2397    }
2398
2399    /// Set itself to the set difference of itself and another list.
2400    ///
2401    /// <https://infra.spec.whatwg.org/#set-difference>
2402    fn difference(&mut self, others: &[T]) {
2403        // The difference of ordered sets A and B, is the result of creating a new ordered set set
2404        // and, for each item of A, if B does not contain item, appending item to set.
2405        self.retain(|item| {
2406            !others
2407                .iter()
2408                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2409        })
2410    }
2411}
2412
2413/// Helper functions for accessing the "name" and "namespace" members of
2414/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
2415trait NameMember: Sized {
2416    fn name(&self) -> &DOMString;
2417    fn name_mut(&mut self) -> &mut DOMString;
2418    fn namespace(&self) -> Option<&DOMString>;
2419    fn namespace_mut(&mut self) -> Option<&mut DOMString>;
2420
2421    fn set_namespace(&mut self, namespace: Option<&str>);
2422
2423    // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
2424    fn is_less_than_item(&self, item_b: &Self) -> bool {
2425        let item_a = self;
2426        match item_a.namespace() {
2427            // Step 1. If itemA["namespace"] is null:
2428            None => {
2429                // Step 1.1. If itemB["namespace"] is not null, then return true.
2430                if item_b.namespace().is_some() {
2431                    return true;
2432                }
2433            },
2434            // Step 2. Otherwise:
2435            Some(item_a_namespace) => {
2436                // Step 2.1. If itemB["namespace"] is null, then return false.
2437                if item_b.namespace().is_none() {
2438                    return false;
2439                }
2440
2441                // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
2442                // return true.
2443                if item_b
2444                    .namespace()
2445                    .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
2446                {
2447                    return true;
2448                }
2449
2450                // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
2451                if item_b
2452                    .namespace()
2453                    .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
2454                {
2455                    return false;
2456                }
2457            },
2458        }
2459
2460        // Step 3. Return itemA["name"] is code unit less than itemB["name"].
2461        item_a.name() < item_b.name()
2462    }
2463
2464    /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
2465    fn compare(&self, other: &Self) -> Ordering {
2466        if self.is_less_than_item(other) {
2467            Ordering::Less
2468        } else {
2469            Ordering::Greater
2470        }
2471    }
2472
2473    /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2474    /// ['SanitizerAttribute']. For other types such as ['SanitizerElementWithAttributes'] and
2475    /// [`SanitizerElement`], return false by default.
2476    fn is_custom_data_attribute(&self) -> bool {
2477        false
2478    }
2479}
2480
2481impl NameMember for SanitizerElementWithAttributes {
2482    fn name(&self) -> &DOMString {
2483        match self {
2484            SanitizerElementWithAttributes::String(name) => name,
2485            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2486                &dictionary.parent.name
2487            },
2488        }
2489    }
2490
2491    fn name_mut(&mut self) -> &mut DOMString {
2492        match self {
2493            SanitizerElementWithAttributes::String(name) => name,
2494            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2495                &mut dictionary.parent.name
2496            },
2497        }
2498    }
2499
2500    fn namespace(&self) -> Option<&DOMString> {
2501        match self {
2502            SanitizerElementWithAttributes::String(_) => None,
2503            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2504                dictionary.parent.namespace.as_ref()
2505            },
2506        }
2507    }
2508
2509    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2510        match self {
2511            SanitizerElementWithAttributes::String(_) => None,
2512            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2513                dictionary.parent.namespace.as_mut()
2514            },
2515        }
2516    }
2517
2518    fn set_namespace(&mut self, namespace: Option<&str>) {
2519        match self {
2520            SanitizerElementWithAttributes::String(name) => {
2521                let new_instance =
2522                    SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2523                        SanitizerElementNamespaceWithAttributes {
2524                            parent: SanitizerElementNamespace {
2525                                name: std::mem::take(name),
2526                                namespace: namespace.map(DOMString::from),
2527                            },
2528                            attributes: None,
2529                            removeAttributes: None,
2530                        },
2531                    );
2532                *self = new_instance;
2533            },
2534            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2535                dictionary.parent.namespace = namespace.map(DOMString::from);
2536            },
2537        }
2538    }
2539}
2540
2541impl NameMember for SanitizerElement {
2542    fn name(&self) -> &DOMString {
2543        match self {
2544            SanitizerElement::String(name) => name,
2545            SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
2546        }
2547    }
2548
2549    fn name_mut(&mut self) -> &mut DOMString {
2550        match self {
2551            SanitizerElement::String(name) => name,
2552            SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
2553        }
2554    }
2555
2556    fn namespace(&self) -> Option<&DOMString> {
2557        match self {
2558            SanitizerElement::String(_) => None,
2559            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2560                dictionary.namespace.as_ref()
2561            },
2562        }
2563    }
2564
2565    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2566        match self {
2567            SanitizerElement::String(_) => None,
2568            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2569                dictionary.namespace.as_mut()
2570            },
2571        }
2572    }
2573
2574    fn set_namespace(&mut self, namespace: Option<&str>) {
2575        match self {
2576            SanitizerElement::String(name) => {
2577                let new_instance =
2578                    SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2579                        name: std::mem::take(name),
2580                        namespace: namespace.map(DOMString::from),
2581                    });
2582                *self = new_instance;
2583            },
2584            SanitizerElement::SanitizerElementNamespace(dictionary) => {
2585                dictionary.namespace = namespace.map(DOMString::from);
2586            },
2587        }
2588    }
2589}
2590
2591impl NameMember for SanitizerAttribute {
2592    fn name(&self) -> &DOMString {
2593        match self {
2594            SanitizerAttribute::String(name) => name,
2595            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
2596        }
2597    }
2598
2599    fn name_mut(&mut self) -> &mut DOMString {
2600        match self {
2601            SanitizerAttribute::String(name) => name,
2602            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
2603        }
2604    }
2605
2606    fn namespace(&self) -> Option<&DOMString> {
2607        match self {
2608            SanitizerAttribute::String(_) => None,
2609            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2610                dictionary.namespace.as_ref()
2611            },
2612        }
2613    }
2614
2615    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2616        match self {
2617            SanitizerAttribute::String(_) => None,
2618            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2619                dictionary.namespace.as_mut()
2620            },
2621        }
2622    }
2623
2624    fn set_namespace(&mut self, namespace: Option<&str>) {
2625        match self {
2626            SanitizerAttribute::String(name) => {
2627                let new_instance =
2628                    SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2629                        name: std::mem::take(name),
2630                        namespace: namespace.map(DOMString::from),
2631                    });
2632                *self = new_instance;
2633            },
2634            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2635                dictionary.namespace = namespace.map(DOMString::from);
2636            },
2637        }
2638    }
2639
2640    /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2641    /// ['SanitizerAttribute'].
2642    fn is_custom_data_attribute(&self) -> bool {
2643        is_custom_data_attribute(
2644            &self.name().str(),
2645            self.namespace().map(|namespace| namespace.str()).as_deref(),
2646        )
2647    }
2648}
2649
2650/// Helper functions for accessing the "attributes" and "removeAttributes" members of
2651/// [`SanitizerElementWithAttributes`].
2652trait AttributeMember {
2653    fn attributes(&self) -> Option<&[SanitizerAttribute]>;
2654    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2655    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
2656    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2657
2658    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
2659    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
2660}
2661
2662impl AttributeMember for SanitizerElementWithAttributes {
2663    fn attributes(&self) -> Option<&[SanitizerAttribute]> {
2664        match self {
2665            SanitizerElementWithAttributes::String(_) => None,
2666            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2667                dictionary.attributes.as_deref()
2668            },
2669        }
2670    }
2671
2672    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2673        match self {
2674            SanitizerElementWithAttributes::String(_) => None,
2675            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2676                dictionary.attributes.as_mut()
2677            },
2678        }
2679    }
2680
2681    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
2682        match self {
2683            SanitizerElementWithAttributes::String(_) => None,
2684            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2685                dictionary.removeAttributes.as_deref()
2686            },
2687        }
2688    }
2689
2690    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2691        match self {
2692            SanitizerElementWithAttributes::String(_) => None,
2693            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2694                dictionary.removeAttributes.as_mut()
2695            },
2696        }
2697    }
2698
2699    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
2700        match self {
2701            SanitizerElementWithAttributes::String(name) => {
2702                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2703                    SanitizerElementNamespaceWithAttributes {
2704                        parent: SanitizerElementNamespace {
2705                            name: std::mem::take(name),
2706                            namespace: None,
2707                        },
2708                        attributes,
2709                        removeAttributes: None,
2710                    },
2711                );
2712            },
2713            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2714                dictionary.attributes = attributes;
2715            },
2716        }
2717    }
2718
2719    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
2720        match self {
2721            SanitizerElementWithAttributes::String(name) => {
2722                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2723                    SanitizerElementNamespaceWithAttributes {
2724                        parent: SanitizerElementNamespace {
2725                            name: std::mem::take(name),
2726                            namespace: None,
2727                        },
2728                        attributes: None,
2729                        removeAttributes: remove_attributes,
2730                    },
2731                );
2732            },
2733            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2734                dictionary.removeAttributes = remove_attributes;
2735            },
2736        }
2737    }
2738}
2739
2740/// Helper functions for accessing the "target" members of [`SanitizerPI`].
2741trait TargetMember {
2742    fn target(&self) -> &DOMString;
2743}
2744
2745impl TargetMember for SanitizerPI {
2746    fn target(&self) -> &DOMString {
2747        match self {
2748            SanitizerPI::String(string) => string,
2749            SanitizerPI::SanitizerProcessingInstruction(dictionary) => &dictionary.target,
2750        }
2751    }
2752}
2753
2754/// Supporting algorithms on lists of processing instructions, from the specification.
2755trait TargetSlice<T>
2756where
2757    T: TargetMember,
2758{
2759    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2760    fn contains_target(&self, other: &T) -> bool;
2761
2762    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2763    fn has_duplicate_targets(&self) -> bool;
2764}
2765
2766impl<T> TargetSlice<T> for [T]
2767where
2768    T: TargetMember,
2769{
2770    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2771    fn contains_target(&self, other: &T) -> bool {
2772        // A Sanitizer target list contains a target target if there exists an entry of list that is
2773        // an ordered map, and where target equals entry["target"].
2774        self.iter().any(|entry| entry.target() == other.target())
2775    }
2776
2777    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2778    fn has_duplicate_targets(&self) -> bool {
2779        // A list list has duplicate targets, if for any item of list, there is more than one entry
2780        // in list where item["target"] is entry["target"].
2781        let mut used = HashSet::new();
2782        self.iter()
2783            .any(move |entry| !used.insert(entry.target().to_string()))
2784    }
2785}
2786
2787/// Helper functions for accessing the "sanitizer" members of [`SetHTMLOptions`] and
2788/// [`SetHTMLUnsafeOptions`].
2789pub(crate) trait SanitizerMember {
2790    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets;
2791}
2792
2793impl SanitizerMember for SetHTMLOptions {
2794    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2795        &self.sanitizer
2796    }
2797}
2798
2799impl SanitizerMember for SetHTMLUnsafeOptions {
2800    fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2801        &self.sanitizer
2802    }
2803}
2804
2805/// <https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration>
2806fn built_in_safe_default_configuration() -> SanitizerConfig {
2807    const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
2808        ("math", &ns!(mathml), &[]),
2809        ("merror", &ns!(mathml), &[]),
2810        ("mfrac", &ns!(mathml), &[]),
2811        ("mi", &ns!(mathml), &[]),
2812        ("mmultiscripts", &ns!(mathml), &[]),
2813        ("mn", &ns!(mathml), &[]),
2814        (
2815            "mo",
2816            &ns!(mathml),
2817            &[
2818                "fence",
2819                "form",
2820                "largeop",
2821                "lspace",
2822                "maxsize",
2823                "minsize",
2824                "movablelimits",
2825                "rspace",
2826                "separator",
2827                "stretchy",
2828                "symmetric",
2829            ],
2830        ),
2831        ("mover", &ns!(mathml), &["accent"]),
2832        (
2833            "mpadded",
2834            &ns!(mathml),
2835            &["depth", "height", "lspace", "voffset", "width"],
2836        ),
2837        ("mphantom", &ns!(mathml), &[]),
2838        ("mprescripts", &ns!(mathml), &[]),
2839        ("mroot", &ns!(mathml), &[]),
2840        ("mrow", &ns!(mathml), &[]),
2841        ("ms", &ns!(mathml), &[]),
2842        ("mspace", &ns!(mathml), &["depth", "height", "width"]),
2843        ("msqrt", &ns!(mathml), &[]),
2844        ("mstyle", &ns!(mathml), &[]),
2845        ("msub", &ns!(mathml), &[]),
2846        ("msubsup", &ns!(mathml), &[]),
2847        ("msup", &ns!(mathml), &[]),
2848        ("mtable", &ns!(mathml), &[]),
2849        ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
2850        ("mtext", &ns!(mathml), &[]),
2851        ("mtr", &ns!(mathml), &[]),
2852        ("munder", &ns!(mathml), &["accentunder"]),
2853        ("munderover", &ns!(mathml), &["accent", "accentunder"]),
2854        ("semantics", &ns!(mathml), &[]),
2855        ("a", &ns!(html), &["href", "hreflang", "type"]),
2856        ("abbr", &ns!(html), &[]),
2857        ("address", &ns!(html), &[]),
2858        ("article", &ns!(html), &[]),
2859        ("aside", &ns!(html), &[]),
2860        ("b", &ns!(html), &[]),
2861        ("bdi", &ns!(html), &[]),
2862        ("bdo", &ns!(html), &[]),
2863        ("blockquote", &ns!(html), &["cite"]),
2864        ("body", &ns!(html), &[]),
2865        ("br", &ns!(html), &[]),
2866        ("caption", &ns!(html), &[]),
2867        ("cite", &ns!(html), &[]),
2868        ("code", &ns!(html), &[]),
2869        ("col", &ns!(html), &["span"]),
2870        ("colgroup", &ns!(html), &["span"]),
2871        ("data", &ns!(html), &["value"]),
2872        ("dd", &ns!(html), &[]),
2873        ("del", &ns!(html), &["cite", "datetime"]),
2874        ("dfn", &ns!(html), &[]),
2875        ("div", &ns!(html), &[]),
2876        ("dl", &ns!(html), &[]),
2877        ("dt", &ns!(html), &[]),
2878        ("em", &ns!(html), &[]),
2879        ("figcaption", &ns!(html), &[]),
2880        ("figure", &ns!(html), &[]),
2881        ("footer", &ns!(html), &[]),
2882        ("h1", &ns!(html), &[]),
2883        ("h2", &ns!(html), &[]),
2884        ("h3", &ns!(html), &[]),
2885        ("h4", &ns!(html), &[]),
2886        ("h5", &ns!(html), &[]),
2887        ("h6", &ns!(html), &[]),
2888        ("head", &ns!(html), &[]),
2889        ("header", &ns!(html), &[]),
2890        ("hgroup", &ns!(html), &[]),
2891        ("hr", &ns!(html), &[]),
2892        ("html", &ns!(html), &[]),
2893        ("i", &ns!(html), &[]),
2894        ("ins", &ns!(html), &["cite", "datetime"]),
2895        ("kbd", &ns!(html), &[]),
2896        ("li", &ns!(html), &["value"]),
2897        ("main", &ns!(html), &[]),
2898        ("mark", &ns!(html), &[]),
2899        ("menu", &ns!(html), &[]),
2900        ("nav", &ns!(html), &[]),
2901        ("ol", &ns!(html), &["reversed", "start", "type"]),
2902        ("p", &ns!(html), &[]),
2903        ("pre", &ns!(html), &[]),
2904        ("q", &ns!(html), &[]),
2905        ("rp", &ns!(html), &[]),
2906        ("rt", &ns!(html), &[]),
2907        ("ruby", &ns!(html), &[]),
2908        ("s", &ns!(html), &[]),
2909        ("samp", &ns!(html), &[]),
2910        ("search", &ns!(html), &[]),
2911        ("section", &ns!(html), &[]),
2912        ("small", &ns!(html), &[]),
2913        ("span", &ns!(html), &[]),
2914        ("strong", &ns!(html), &[]),
2915        ("sub", &ns!(html), &[]),
2916        ("sup", &ns!(html), &[]),
2917        ("table", &ns!(html), &[]),
2918        ("tbody", &ns!(html), &[]),
2919        ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
2920        ("tfoot", &ns!(html), &[]),
2921        (
2922            "th",
2923            &ns!(html),
2924            &["abbr", "colspan", "headers", "rowspan", "scope"],
2925        ),
2926        ("thead", &ns!(html), &[]),
2927        ("time", &ns!(html), &["datetime"]),
2928        ("title", &ns!(html), &[]),
2929        ("tr", &ns!(html), &[]),
2930        ("u", &ns!(html), &[]),
2931        ("ul", &ns!(html), &[]),
2932        ("var", &ns!(html), &[]),
2933        ("wbr", &ns!(html), &[]),
2934        ("a", &ns!(svg), &["href", "hreflang", "type"]),
2935        ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
2936        ("defs", &ns!(svg), &[]),
2937        ("desc", &ns!(svg), &[]),
2938        (
2939            "ellipse",
2940            &ns!(svg),
2941            &["cx", "cy", "pathLength", "rx", "ry"],
2942        ),
2943        ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
2944        ("g", &ns!(svg), &[]),
2945        ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
2946        (
2947            "marker",
2948            &ns!(svg),
2949            &[
2950                "markerHeight",
2951                "markerUnits",
2952                "markerWidth",
2953                "orient",
2954                "preserveAspectRatio",
2955                "refX",
2956                "refY",
2957                "viewBox",
2958            ],
2959        ),
2960        ("metadata", &ns!(svg), &[]),
2961        ("path", &ns!(svg), &["d", "pathLength"]),
2962        ("polygon", &ns!(svg), &["pathLength", "points"]),
2963        ("polyline", &ns!(svg), &["pathLength", "points"]),
2964        (
2965            "rect",
2966            &ns!(svg),
2967            &["height", "pathLength", "rx", "ry", "width", "x", "y"],
2968        ),
2969        (
2970            "svg",
2971            &ns!(svg),
2972            &[
2973                "height",
2974                "preserveAspectRatio",
2975                "viewBox",
2976                "width",
2977                "x",
2978                "y",
2979            ],
2980        ),
2981        (
2982            "text",
2983            &ns!(svg),
2984            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2985        ),
2986        (
2987            "textPath",
2988            &ns!(svg),
2989            &[
2990                "lengthAdjust",
2991                "method",
2992                "path",
2993                "side",
2994                "spacing",
2995                "startOffset",
2996                "textLength",
2997            ],
2998        ),
2999        ("title", &ns!(svg), &[]),
3000        (
3001            "tspan",
3002            &ns!(svg),
3003            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
3004        ),
3005    ];
3006    const ATTRIBUTES: &[&str] = &[
3007        "alignment-baseline",
3008        "baseline-shift",
3009        "clip-path",
3010        "clip-rule",
3011        "color",
3012        "color-interpolation",
3013        "cursor",
3014        "dir",
3015        "direction",
3016        "display",
3017        "displaystyle",
3018        "dominant-baseline",
3019        "fill",
3020        "fill-opacity",
3021        "fill-rule",
3022        "font-family",
3023        "font-size",
3024        "font-size-adjust",
3025        "font-stretch",
3026        "font-style",
3027        "font-variant",
3028        "font-weight",
3029        "lang",
3030        "letter-spacing",
3031        "marker-end",
3032        "marker-mid",
3033        "marker-start",
3034        "mathbackground",
3035        "mathcolor",
3036        "mathsize",
3037        "opacity",
3038        "paint-order",
3039        "pointer-events",
3040        "scriptlevel",
3041        "shape-rendering",
3042        "stop-color",
3043        "stop-opacity",
3044        "stroke",
3045        "stroke-dasharray",
3046        "stroke-dashoffset",
3047        "stroke-linecap",
3048        "stroke-linejoin",
3049        "stroke-miterlimit",
3050        "stroke-opacity",
3051        "stroke-width",
3052        "text-anchor",
3053        "text-decoration",
3054        "text-overflow",
3055        "text-rendering",
3056        "title",
3057        "transform",
3058        "transform-origin",
3059        "unicode-bidi",
3060        "vector-effect",
3061        "visibility",
3062        "white-space",
3063        "word-spacing",
3064        "writing-mode",
3065    ];
3066
3067    let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
3068        attributes
3069            .iter()
3070            .map(|&attribute| {
3071                SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
3072                    name: attribute.into(),
3073                    namespace: None,
3074                })
3075            })
3076            .collect()
3077    };
3078
3079    let elements = ELEMENTS
3080        .iter()
3081        .map(|&(name, namespace, attributes)| {
3082            let attributes = create_attribute_vec(attributes);
3083            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
3084                SanitizerElementNamespaceWithAttributes {
3085                    parent: SanitizerElementNamespace {
3086                        name: name.into(),
3087                        namespace: Some(namespace.to_string().into()),
3088                    },
3089                    attributes: Some(attributes),
3090                    removeAttributes: None,
3091                },
3092            )
3093        })
3094        .collect();
3095
3096    let attributes = create_attribute_vec(ATTRIBUTES);
3097
3098    SanitizerConfig {
3099        elements: Some(elements),
3100        removeElements: None,
3101        replaceWithChildrenElements: None,
3102        processingInstructions: Some(Vec::new()),
3103        removeProcessingInstructions: None,
3104        attributes: Some(attributes),
3105        removeAttributes: None,
3106        comments: Some(false),
3107        dataAttributes: Some(false),
3108    }
3109}
3110
3111/// <https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration>
3112fn built_in_safe_baseline_configuration() -> SanitizerConfig {
3113    const REMOVE_ELEMENTS: &[(&str, &Namespace)] = &[
3114        ("embed", &ns!(html)),
3115        ("frame", &ns!(html)),
3116        ("iframe", &ns!(html)),
3117        ("object", &ns!(html)),
3118        ("script", &ns!(html)),
3119        ("script", &ns!(svg)),
3120        ("use", &ns!(svg)),
3121    ];
3122
3123    let remove_elements = REMOVE_ELEMENTS
3124        .iter()
3125        .map(|&(name, namespace)| {
3126            SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3127                name: name.into(),
3128                namespace: Some(namespace.to_string().into()),
3129            })
3130        })
3131        .collect();
3132
3133    SanitizerConfig {
3134        elements: None,
3135        removeElements: Some(remove_elements),
3136        replaceWithChildrenElements: None,
3137        processingInstructions: None,
3138        removeProcessingInstructions: None,
3139        attributes: None,
3140        removeAttributes: Some(Vec::new()),
3141        comments: None,
3142        dataAttributes: None,
3143    }
3144}
3145
3146/// <https://wicg.github.io/sanitizer-api/#built-in-navigating-url-attributes-list>
3147const BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST: &[(
3148    LocalName,
3149    Option<Namespace>,
3150    LocalName,
3151    Option<Namespace>,
3152)] = &[
3153    (local_name!("a"), Some(ns!(html)), local_name!("href"), None),
3154    (
3155        local_name!("area"),
3156        Some(ns!(html)),
3157        local_name!("href"),
3158        None,
3159    ),
3160    (
3161        local_name!("base"),
3162        Some(ns!(html)),
3163        local_name!("href"),
3164        None,
3165    ),
3166    (
3167        local_name!("button"),
3168        Some(ns!(html)),
3169        local_name!("formaction"),
3170        None,
3171    ),
3172    (
3173        local_name!("form"),
3174        Some(ns!(html)),
3175        local_name!("action"),
3176        None,
3177    ),
3178    (
3179        local_name!("input"),
3180        Some(ns!(html)),
3181        local_name!("formaction"),
3182        None,
3183    ),
3184    (local_name!("a"), Some(ns!(svg)), local_name!("href"), None),
3185    (
3186        local_name!("a"),
3187        Some(ns!(svg)),
3188        local_name!("href"),
3189        Some(ns!(xlink)),
3190    ),
3191];
3192
3193/// <https://wicg.github.io/sanitizer-api/#built-in-animating-url-attributes-list>
3194const BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST: &[(
3195    LocalName,
3196    Option<Namespace>,
3197    LocalName,
3198    Option<Namespace>,
3199)] = &[
3200    (
3201        local_name!("animate"),
3202        Some(ns!(svg)),
3203        local_name!("attributeName"),
3204        None,
3205    ),
3206    (
3207        local_name!("animateTransform"),
3208        Some(ns!(svg)),
3209        local_name!("attributeName"),
3210        None,
3211    ),
3212    (
3213        local_name!("set"),
3214        Some(ns!(svg)),
3215        local_name!("attributeName"),
3216        None,
3217    ),
3218];
3219
3220thread_local! {
3221    /// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
3222    static BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST: LazyCell<Vec<SanitizerElement>> =
3223        LazyCell::new(|| {
3224            vec![
3225                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3226                    name: local_name!("html").as_ref().into(),
3227                    namespace: Some(ns!(html).as_ref().into()),
3228                }),
3229                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3230                    name: local_name!("svg").as_ref().into(),
3231                    namespace: Some(ns!(svg).as_ref().into()),
3232                }),
3233                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3234                    name: local_name!("math").as_ref().into(),
3235                    namespace: Some(ns!(mathml).as_ref().into()),
3236                }),
3237            ]
3238        });
3239}