Skip to main content

script/dom/security/
sanitizer.rs

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