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