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