Skip to main content

script/dom/security/
sanitizer.rs

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