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::cmp::Ordering;
6use std::collections::HashSet;
7
8use dom_struct::dom_struct;
9use html5ever::{Namespace, ns};
10use js::context::JSContext;
11use js::rust::HandleObject;
12
13use crate::dom::bindings::cell::DomRefCell;
14use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
15    SanitizerAttribute, SanitizerAttributeNamespace, SanitizerConfig, SanitizerElement,
16    SanitizerElementNamespace, SanitizerElementNamespaceWithAttributes,
17    SanitizerElementWithAttributes, SanitizerMethods, SanitizerPresets,
18};
19use crate::dom::bindings::codegen::UnionTypes::SanitizerConfigOrSanitizerPresets;
20use crate::dom::bindings::domname::is_valid_attribute_local_name;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto_and_cx};
23use crate::dom::bindings::root::DomRoot;
24use crate::dom::bindings::str::DOMString;
25use crate::dom::types::Console;
26use crate::dom::window::Window;
27
28#[dom_struct]
29pub(crate) struct Sanitizer {
30    reflector_: Reflector,
31    /// <https://wicg.github.io/sanitizer-api/#sanitizer-configuration>
32    configuration: DomRefCell<SanitizerConfig>,
33}
34
35impl Sanitizer {
36    fn new_inherited(configuration: SanitizerConfig) -> Sanitizer {
37        Sanitizer {
38            reflector_: Reflector::new(),
39            configuration: DomRefCell::new(configuration),
40        }
41    }
42
43    pub(crate) fn new_with_proto(
44        cx: &mut JSContext,
45        window: &Window,
46        proto: Option<HandleObject>,
47        configuration: SanitizerConfig,
48    ) -> DomRoot<Sanitizer> {
49        reflect_dom_object_with_proto_and_cx(
50            Box::new(Sanitizer::new_inherited(configuration)),
51            window,
52            proto,
53            cx,
54        )
55    }
56
57    /// <https://wicg.github.io/sanitizer-api/#sanitizer-set-a-configuration>
58    fn set_configuration(
59        &self,
60        mut configuration: SanitizerConfig,
61        allow_comments_pis_and_data_attributes: bool,
62    ) -> bool {
63        // Step 1. Canonicalize configuration with allowCommentsPIsAndDataAttributes.
64        configuration.canonicalize(allow_comments_pis_and_data_attributes);
65
66        // Step 2. If configuration is not valid, then return false.
67        if !configuration.is_valid() {
68            return false;
69        }
70
71        // Step 3. Set sanitizer’s configuration to configuration.
72        let mut sanitizer_configuration = self.configuration.borrow_mut();
73        *sanitizer_configuration = configuration;
74
75        // Step 4. Return true.
76        true
77    }
78}
79
80impl SanitizerMethods<crate::DomTypeHolder> for Sanitizer {
81    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-constructor>
82    fn Constructor(
83        cx: &mut JSContext,
84        window: &Window,
85        proto: Option<HandleObject>,
86        configuration: SanitizerConfigOrSanitizerPresets,
87    ) -> Fallible<DomRoot<Sanitizer>> {
88        let configuration = match configuration {
89            // Step 1. If configuration is a SanitizerPresets string, then:
90            SanitizerConfigOrSanitizerPresets::SanitizerPresets(configuration) => {
91                // Step 1.1. Assert: configuration is default.
92                assert_eq!(configuration, SanitizerPresets::Default);
93
94                // Step 1.2. Set configuration to the built-in safe default configuration.
95                built_in_safe_default_configuration()
96            },
97            SanitizerConfigOrSanitizerPresets::SanitizerConfig(configuration) => configuration,
98        };
99
100        // Step 2. Let valid be the return value of set a configuration with configuration and true
101        // on this.
102        // Step 3. If valid is false, then throw a TypeError.
103        let sanitizer = Sanitizer::new_with_proto(cx, window, proto, SanitizerConfig::default());
104        if !sanitizer.set_configuration(configuration, true) {
105            return Err(Error::Type(c"The configuration is invalid".into()));
106        }
107
108        Ok(sanitizer)
109    }
110
111    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-get>
112    fn Get(&self) -> SanitizerConfig {
113        // Step 1. Let config be this’s configuration.
114        let mut config = self.configuration.borrow_mut();
115
116        // Step 2. Assert: config is valid.
117        assert!(config.is_valid());
118
119        match &mut config.elements {
120            // Step 3. If config["elements"] exists:
121            Some(config_elements) => {
122                // Step 3.1. For any element of config["elements"]:
123                for element in config_elements.iter_mut() {
124                    // Step 3.1.1. If element["attributes"] exists:
125                    if let Some(element_attributes) = &mut element.attributes_mut() {
126                        // Step 3.1.1.1. Set element["attributes"] to the result of sort in
127                        // ascending order element["attributes"], with attrA being less than item
128                        // attrB.
129                        element_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
130                    }
131
132                    // Step 3.1.2. If element["removeAttributes"] exists:
133                    if let Some(element_remove_attributes) = &mut element.remove_attributes_mut() {
134                        // Step 3.1.2.1. Set element["removeAttributes"] to the result of sort in
135                        // ascending order element["removeAttributes"], with attrA being less than
136                        // item attrB.
137                        element_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
138                    }
139                }
140
141                // Step 3.2. Set config["elements"] to the result of sort in ascending order
142                // config["elements"], with elementA being less than item elementB.
143                config_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
144            },
145            // Step 4. Otherwise:
146            None => {
147                // Step 4.1. Set config["removeElements"] to the result of sort in ascending order
148                // config["removeElements"], with elementA being less than item elementB.
149                if let Some(config_remove_elements) = &mut config.removeElements {
150                    config_remove_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
151                }
152            },
153        }
154
155        // Step 5. If config["replaceWithChildrenElements"] exists:
156        if let Some(config_replace_with_children_elements) = &mut config.replaceWithChildrenElements
157        {
158            // Step 5.1.Set config["replaceWithChildrenElements"] to the result of sort in ascending
159            // order config["replaceWithChildrenElements"], with elementA being less than item
160            // elementB.
161            config_replace_with_children_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
162        }
163
164        // TODO:
165        // Step 6. If config["processingInstructions"] exists:
166        // Step 6.1. Set config["processingInstructions"] to the result of sort in ascending order
167        // config["processingInstructions"], with piA["target"] being code unit less than
168        // piB["target"].
169        // Step 7. Otherwise:
170        // Step 7.1. Set config["removeProcessingInstructions"] to the result of sort in ascending
171        // order config["removeProcessingInstructions"], with piA["target"] being code unit less
172        // than piB["target"].
173
174        match &mut config.attributes {
175            // Step 8. If config["attributes"] exists:
176            Some(config_attributes) => {
177                // Step 8.1. Set config["attributes"] to the result of sort in ascending order
178                // config["attributes"], with attrA being less than item attrB.
179                config_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
180            },
181            // Step 9. Otherwise:
182            None => {
183                // Step 9.1. Set config["removeAttributes"] to the result of sort in ascending order
184                // config["removeAttributes"], with attrA being less than item attrB.
185                if let Some(config_remove_attributes) = &mut config.removeAttributes {
186                    config_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
187                }
188            },
189        }
190
191        // Step 10. Return config.
192        (*config).clone()
193    }
194
195    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement>
196    fn AllowElement(&self, element: SanitizerElementWithAttributes) -> bool {
197        // Step 1. Let configuration be this’s configuration.
198        let mut configuration = self.configuration.borrow_mut();
199
200        // Step 2. Assert: configuration is valid.
201        assert!(configuration.is_valid());
202
203        // Step 3. Set element to the result of canonicalize a sanitizer element with attributes
204        // with element.
205        let mut element = element.canonicalize();
206
207        // Step 4. If configuration["elements"] exists:
208        if configuration.elements.is_some() {
209            // Step 4.1. Set modified to the result of remove element from
210            // configuration["replaceWithChildrenElements"].
211            let modified = if let Some(replace_with_children_elements) =
212                &mut configuration.replaceWithChildrenElements
213            {
214                replace_with_children_elements.remove_item(&element)
215            } else {
216                false
217            };
218
219            // Step 4.2. Comment: We need to make sure the per-element attributes do not overlap
220            // with global attributes.
221
222            match &configuration.attributes {
223                // Step 4.3. If configuration["attributes"] exists:
224                Some(configuration_attributes) => {
225                    // Step 4.3.1. If element["attributes"] exists:
226                    if let Some(element_attributes) = element.attributes_mut() {
227                        // Step 4.3.1.1. Set element["attributes"] to remove duplicates from
228                        // element["attributes"].
229                        element_attributes.remove_duplicates();
230
231                        // Step 4.3.1.2. Set element["attributes"] to the difference of
232                        // element["attributes"] and configuration["attributes"].
233                        element_attributes.difference(configuration_attributes);
234
235                        // Step 4.3.1.3. If configuration["dataAttributes"] is true:
236                        if configuration.dataAttributes == Some(true) {
237                            // Step 4.3.1.3.1. Remove all items item from element["attributes"]
238                            // where item is a custom data attribute.
239                            element_attributes.retain(|attribute| {
240                                !is_custom_data_attribute(
241                                    &attribute.name().str(),
242                                    attribute
243                                        .namespace()
244                                        .map(|namespace| namespace.str())
245                                        .as_deref(),
246                                )
247                            });
248                        }
249                    }
250
251                    // Step 4.3.2. If element["removeAttributes"] exists:
252                    if let Some(element_remove_attributes) = element.remove_attributes_mut() {
253                        // Step 4.3.2.1. set element["removeattributes"] to remove duplicates from
254                        // element["removeattributes"].
255                        element_remove_attributes.remove_duplicates();
256
257                        // Step 4.3.2.2. set element["removeattributes"] to the intersection of
258                        // element["removeattributes"] and configuration["attributes"].
259                        element_remove_attributes.intersection(configuration_attributes);
260                    }
261                },
262                // Step 4.4. Otherwise:
263                None => {
264                    // NOTE: To avoid borrowing `element` again at Step 4.4.1.2 and 4.4.1.3 after
265                    // borrowing `element` mutably at the beginning of Step 4.4.1, we clone
266                    // element["attributes"] first, and call `set_attributes` at the end of Step
267                    // 4.4.1 to put it back into `element`.
268
269                    // Step 4.4.1. If element["attributes"] exists:
270                    if let Some(mut element_attributes) = element.attributes_mut().cloned() {
271                        // Step 4.4.1.1. Set element["attributes"] to remove duplicates from
272                        // element["attributes"].
273                        element_attributes.remove_duplicates();
274
275                        // Step 4.4.1.2. Set element["attributes"] to the difference of
276                        // element["attributes"] and element["removeAttributes"] with default « ».
277                        element_attributes
278                            .difference(element.remove_attributes().unwrap_or_default());
279
280                        // Step 4.4.1.3. Remove element["removeAttributes"].
281                        element.set_remove_attributes(None);
282
283                        // Step 4.4.1.4. Set element["attributes"] to the difference of
284                        // element["attributes"] and configuration["removeAttributes"].
285                        element_attributes.difference(
286                            configuration
287                                .removeAttributes
288                                .as_deref()
289                                .unwrap_or_default(),
290                        );
291
292                        element.set_attributes(Some(element_attributes));
293                    }
294
295                    // Step 4.4.2. If element["removeAttributes"] exists:
296                    if let Some(mut element_remove_attributes) = element.remove_attributes_mut() {
297                        // Step 4.4.2.1. Set element["removeAttributes"] to remove duplicates from
298                        // element["removeAttributes"].
299                        element_remove_attributes = element_remove_attributes.remove_duplicates();
300
301                        // Step 4.4.2.2. Set element["removeAttributes"] to the difference of
302                        // element["removeAttributes"] and configuration["removeAttributes"].
303                        element_remove_attributes.difference(
304                            configuration
305                                .removeAttributes
306                                .as_deref()
307                                .unwrap_or_default(),
308                        );
309                    }
310                },
311            }
312
313            // Step 4.5. If configuration["elements"] does not contain element:
314            let configuration_elements = configuration
315                .elements
316                .as_mut()
317                .expect("Guaranteed by Step 4");
318            if !configuration_elements.contains_item(&element) {
319                // Step 4.5.1. Comment: This is the case with a global allow-list that does not yet
320                // contain element.
321
322                // Step 4.5.2. Append element to configuration["elements"].
323                configuration_elements.push(element.clone());
324
325                // Step 4.5.3. Return true.
326                return true;
327            }
328
329            // Step 4.6. Comment: This is the case with a global allow-list that already contains
330            // element.
331
332            // Step 4.7. Let current element be the item in configuration["elements"] where
333            // item["name"] equals element["name"] and item["namespace"] equals
334            // element["namespace"].
335            let current_element = configuration_elements
336                .iter()
337                .find(|item| {
338                    item.name() == element.name() && item.namespace() == element.namespace()
339                })
340                .expect("Guaranteed by Step 4.5 and Step 4.5.2");
341
342            // Step 4.8. If element equals current element then return modified.
343            if element == *current_element {
344                return modified;
345            }
346
347            // Step 4.9. Remove element from configuration["elements"].
348            configuration_elements.remove_item(&element);
349
350            // Step 4.10. Append element to configuration["elements"]
351            configuration_elements.push(element);
352
353            // Step 4.11. Return true.
354            true
355        }
356        // Step 5. Otherwise:
357        else {
358            // Step 5.1. If element["attributes"] exists or element["removeAttributes"] with default
359            // « » is not empty:
360            if element.attributes().is_some() ||
361                !element.remove_attributes().unwrap_or_default().is_empty()
362            {
363                // Step 5.1.1. The user agent may report a warning to the console that this
364                // operation is not supported.
365                Console::internal_warn(
366                    &self.global(),
367                    "Do not support adding an element with attributes to a sanitizer \
368                        whose configuration[\"elements\"] does not exist."
369                        .into(),
370                );
371
372                // Step 5.1.2. Return false.
373                return false;
374            }
375
376            // Step 5.2. Set modified to the result of remove element from
377            // configuration["replaceWithChildrenElements"].
378            let modified = if let Some(replace_with_children_elements) =
379                &mut configuration.replaceWithChildrenElements
380            {
381                replace_with_children_elements.remove_item(&element)
382            } else {
383                false
384            };
385
386            // Step 5.3. If configuration["removeElements"] does not contain element:
387            if !configuration
388                .removeElements
389                .as_ref()
390                .is_some_and(|configuration_remove_elements| {
391                    configuration_remove_elements.contains_item(&element)
392                })
393            {
394                // Step 5.3.1. Comment: This is the case with a global remove-list that does not
395                // contain element.
396
397                // Step 5.3.2. Return modified.
398                return modified;
399            }
400
401            // Step 5.4. Comment: This is the case with a global remove-list that contains element.
402
403            // Step 5.5. Remove element from configuration["removeElements"].
404            if let Some(configuration_remove_elements) = &mut configuration.removeElements {
405                configuration_remove_elements.remove_item(&element);
406            }
407
408            // Step 5.6. Return true.
409            true
410        }
411    }
412
413    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
414    fn RemoveElement(&self, element: SanitizerElement) -> bool {
415        // Remove an element with element and this’s configuration.
416        self.configuration.borrow_mut().remove_element(element)
417    }
418
419    /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
420    fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
421        // Step 1. Let configuration be this’s configuration.
422        let mut configuration = self.configuration.borrow_mut();
423
424        // Step 2. Assert: configuration is valid.
425        assert!(configuration.is_valid());
426
427        // Step 3. Set element to the result of canonicalize a sanitizer element with element.
428        let element = element.canonicalize();
429
430        // Step 4. If the built-in non-replaceable elements list contains element:
431        if built_in_non_replaceable_elements_list().contains_item(&element) {
432            // Step 4.1. Return false.
433            return false;
434        }
435
436        // Step 5. If configuration["replaceWithChildrenElements"] contains element:
437        if configuration
438            .replaceWithChildrenElements
439            .as_ref()
440            .is_some_and(|configuration_replace_with_children_elements| {
441                configuration_replace_with_children_elements.contains_item(&element)
442            })
443        {
444            // Step 5.1. Return false.
445            return false;
446        }
447
448        // Step 6. Remove element from configuration["removeElements"].
449        if let Some(configuration_remove_elements) = &mut configuration.removeElements {
450            configuration_remove_elements.remove_item(&element);
451        }
452
453        // Step 7. Remove element from configuration["elements"] list.
454        if let Some(configuration_elements) = &mut configuration.elements {
455            configuration_elements.remove_item(&element);
456        }
457
458        // Step 8. Add element to configuration["replaceWithChildrenElements"].
459        if let Some(configuration_replace_with_children_elements) =
460            &mut configuration.replaceWithChildrenElements
461        {
462            configuration_replace_with_children_elements.add_item(element);
463        } else {
464            configuration.replaceWithChildrenElements = Some(vec![element]);
465        }
466
467        // Step 9. Return true.
468        true
469    }
470}
471
472trait SanitizerConfigAlgorithm {
473    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
474    fn is_valid(&self) -> bool;
475
476    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
477    fn remove_element(&mut self, element: SanitizerElement) -> bool;
478
479    /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
480    fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
481}
482
483impl SanitizerConfigAlgorithm for SanitizerConfig {
484    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
485    fn is_valid(&self) -> bool {
486        // NOTE: It’s expected that the configuration being passing in has previously been run
487        // through the canonicalize the configuration steps. We will simply assert conditions that
488        // that algorithm should have guaranteed to hold.
489
490        // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
491        assert!(self.elements.is_some() || self.removeElements.is_some());
492
493        // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
494        // false.
495        if self.elements.is_some() && self.removeElements.is_some() {
496            return false;
497        }
498
499        // TODO:
500        // Step 3. Assert: Either config["processingInstructions"] exists or
501        // config["removeProcessingInstructions"] exists.
502        // Step 4. If config["processingInstructions"] exists and
503        // config["removeProcessingInstructions"] exists, then return false.
504
505        // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
506        assert!(self.attributes.is_some() || self.removeAttributes.is_some());
507
508        // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
509        // false.
510        if self.attributes.is_some() && self.removeAttributes.is_some() {
511            return false;
512        }
513
514        // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
515        // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
516        // canonical, meaning they have been run through canonicalize a sanitizer element,
517        // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
518        // as appropriate.
519        //
520        // NOTE: This assertion could be done by running the canonicalization again to see if there
521        // is any changes. Since it is expected to canonicalize the configuration before running
522        // this `is_valid` function, we simply skip this assert for the sake of performace.
523
524        match &self.elements {
525            // Step 8. If config["elements"] exists:
526            Some(config_elements) => {
527                // Step 8.1. If config["elements"] has duplicates, then return false.
528                if config_elements.has_duplicates() {
529                    return false;
530                }
531            },
532            // Step 9. Otherwise:
533            None => {
534                // Step 9.1. If config["removeElements"] has duplicates, then return false.
535                if self
536                    .removeElements
537                    .as_ref()
538                    .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
539                {
540                    return false;
541                }
542            },
543        }
544
545        // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
546        // false.
547        if self
548            .replaceWithChildrenElements
549            .as_ref()
550            .is_some_and(|replace_with_children_elements| {
551                replace_with_children_elements.has_duplicates()
552            })
553        {
554            return false;
555        }
556
557        // TODO:
558        // Step 11. If config["processingInstructions"] exists:
559        // Step 11.1. If config["processingInstructions"] has duplicate targets, then return false.
560        // Step 12. Otherwise:
561        // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then return
562        // false.
563
564        match &self.attributes {
565            // Step 13. If config["attributes"] exists:
566            Some(config_attributes) => {
567                // Step 13.1. If config["attributes"] has duplicates, then return false.
568                if config_attributes.has_duplicates() {
569                    return false;
570                }
571            },
572            // Step 14. Otherwise:
573            None => {
574                // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
575                if self
576                    .removeAttributes
577                    .as_ref()
578                    .is_some_and(|config_remove_attributes| {
579                        config_remove_attributes.has_duplicates()
580                    })
581                {
582                    return false;
583                }
584            },
585        }
586
587        // Step 15. If config["replaceWithChildrenElements"] exists:
588        if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
589            // Step 15.1. For each element of config["replaceWithChildrenElements"]:
590            for element in config_replace_with_children_elements {
591                // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
592                // return false.
593                if built_in_non_replaceable_elements_list().contains_item(element) {
594                    return false;
595                }
596            }
597
598            match &self.elements {
599                // Step 15.2. If config["elements"] exists:
600                Some(config_elements) => {
601                    // Step 15.2.1. If the intersection of config["elements"] and
602                    // config["replaceWithChildrenElements"] is not empty, then return false.
603                    if config_elements
604                        .is_intersection_non_empty(config_replace_with_children_elements)
605                    {
606                        return false;
607                    }
608                },
609                // Step 15.3. Otherwise:
610                None => {
611                    // Step 15.3.1. If the intersection of config["removeElements"] and
612                    // config["replaceWithChildrenElements"] is not empty, then return false.
613                    if self
614                        .removeElements
615                        .as_ref()
616                        .is_some_and(|config_remove_elements| {
617                            config_remove_elements
618                                .is_intersection_non_empty(config_replace_with_children_elements)
619                        })
620                    {
621                        return false;
622                    }
623                },
624            }
625        }
626
627        match &self.attributes {
628            // Step 16. If config["attributes"] exists:
629            Some(config_attributes) => {
630                // Step 16.1. Assert: config["dataAttributes"] exists.
631                assert!(self.dataAttributes.is_some());
632
633                // Step 16.2. If config["elements"] exists:
634                if let Some(config_elements) = &self.elements {
635                    // Step 16.2.1. For each element of config["elements"]:
636                    for element in config_elements {
637                        // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
638                        // has duplicates, then return false.
639                        if element
640                            .attributes()
641                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
642                        {
643                            return false;
644                        }
645
646                        // Step 16.2.1.2. If element["removeAttributes"] exists and
647                        // element["removeAttributes"] has duplicates, then return false.
648                        if element
649                            .remove_attributes()
650                            .is_some_and(|element_remove_attributes| {
651                                element_remove_attributes.has_duplicates()
652                            })
653                        {
654                            return false;
655                        }
656
657                        // Step 16.2.1.3. If the intersection of config["attributes"] and
658                        // element["attributes"] with default « » is not empty, then return false.
659                        if config_attributes
660                            .is_intersection_non_empty(element.attributes().unwrap_or_default())
661                        {
662                            return false;
663                        }
664
665                        // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
666                        // subset of config["attributes"], then return false.
667                        if !element
668                            .remove_attributes()
669                            .unwrap_or_default()
670                            .iter()
671                            .all(|entry| config_attributes.contains_item(entry))
672                        {
673                            return false;
674                        }
675
676                        // Step 16.2.1.5. If config["dataAttributes"] is true and
677                        // element["attributes"] contains a custom data attribute, then return
678                        // false.
679                        if self.dataAttributes == Some(true) &&
680                            element.attributes().is_some_and(|attributes| {
681                                attributes.iter().any(|attribute| {
682                                    is_custom_data_attribute(
683                                        &attribute.name().str(),
684                                        attribute
685                                            .namespace()
686                                            .map(|namespace| namespace.str())
687                                            .as_deref(),
688                                    )
689                                })
690                            })
691                        {
692                            return false;
693                        }
694                    }
695                }
696
697                // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
698                // a custom data attribute, then return false.
699                if self.dataAttributes == Some(true) &&
700                    config_attributes.iter().any(|attribute| {
701                        is_custom_data_attribute(
702                            &attribute.name().str(),
703                            attribute
704                                .namespace()
705                                .map(|namespace| namespace.str())
706                                .as_deref(),
707                        )
708                    })
709                {
710                    return false;
711                }
712            },
713            // Step 17. Otherwise:
714            None => {
715                // Step 17.1. If config["elements"] exists:
716                if let Some(config_elements) = &self.elements {
717                    // Step 17.1.1. For each element of config["elements"]:
718                    for element in config_elements {
719                        // Step 17.1.1.1. If element["attributes"] exists and
720                        // element["removeAttributes"] exists, then return false.
721                        if element.attributes().is_some() && element.remove_attributes().is_some() {
722                            return false;
723                        }
724
725                        // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
726                        // has duplicates, then return false.
727                        if element
728                            .attributes()
729                            .as_ref()
730                            .is_some_and(|element_attributes| element_attributes.has_duplicates())
731                        {
732                            return false;
733                        }
734
735                        // Step 17.1.1.3. If element["removeAttributes"] exist and
736                        // element["removeAttributes"] has duplicates, then return false.
737                        if element.remove_attributes().as_ref().is_some_and(
738                            |element_remove_attributes| element_remove_attributes.has_duplicates(),
739                        ) {
740                            return false;
741                        }
742
743                        // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
744                        // element["attributes"] with default « » is not empty, then return false.
745                        if self
746                            .removeAttributes
747                            .as_ref()
748                            .is_some_and(|config_remove_attributes| {
749                                config_remove_attributes.is_intersection_non_empty(
750                                    element.attributes().unwrap_or_default(),
751                                )
752                            })
753                        {
754                            return false;
755                        }
756
757                        // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
758                        // element["removeAttributes"] with default « » is not empty, then return
759                        // false.
760                        if self
761                            .removeAttributes
762                            .as_ref()
763                            .is_some_and(|config_remove_attributes| {
764                                config_remove_attributes.is_intersection_non_empty(
765                                    element.remove_attributes().unwrap_or_default(),
766                                )
767                            })
768                        {
769                            return false;
770                        }
771                    }
772                }
773
774                // Step 17.2. If config["dataAttributes"] exists, then return false.
775                if self.dataAttributes.is_some() {
776                    return false;
777                }
778            },
779        }
780
781        // Step 18. Return true.
782        true
783    }
784
785    /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
786    fn remove_element(&mut self, element: SanitizerElement) -> bool {
787        // Step 1. Assert: configuration is valid.
788        assert!(self.is_valid());
789
790        // Step 2. Set element to the result of canonicalize a sanitizer element with element.
791        let element = element.canonicalize();
792
793        // Step 3. Set modified to the result of remove element from
794        // configuration["replaceWithChildrenElements"].
795        let modified = if let Some(configuration_replace_with_children_elements) =
796            &mut self.replaceWithChildrenElements
797        {
798            configuration_replace_with_children_elements.remove_item(&element)
799        } else {
800            false
801        };
802
803        // Step 4. If configuration["elements"] exists:
804        if let Some(configuration_elements) = &mut self.elements {
805            // Step 4.1. If configuration["elements"] contains element:
806            if configuration_elements.contains_item(&element) {
807                // Step 4.1.1. Comment: We have a global allow list and it contains element.
808
809                // Step 4.1.2. Remove element from configuration["elements"].
810                configuration_elements.remove_item(&element);
811
812                // Step 4.1.3. Return true.
813                return true;
814            }
815
816            // Step 4.2. Comment: We have a global allow list and it does not contain element.
817
818            // Step 4.3. Return modified.
819            modified
820        }
821        // Step 5. Otherwise:
822        else {
823            // Step 5.1. If configuration["removeElements"] contains element:
824            if self
825                .removeElements
826                .as_mut()
827                .is_some_and(|configuration_remove_elements| {
828                    configuration_remove_elements.contains_item(&element)
829                })
830            {
831                // Step 5.1.1. Comment: We have a global remove list and it already contains element.
832
833                // Step 5.1.2. Return modified.
834                return modified;
835            }
836
837            // Step 5.2. Comment: We have a global remove list and it does not contain element.
838
839            // Step 5.3. Add element to configuration["removeElements"].
840            if let Some(configuration_remove_elements) = &mut self.removeElements {
841                configuration_remove_elements.add_item(element);
842            } else {
843                self.removeElements = Some(vec![element]);
844            }
845
846            // Step 5.4. Return true.
847            true
848        }
849    }
850
851    /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
852    fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool) {
853        // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
854        // then set configuration["removeElements"] to « ».
855        if self.elements.is_none() && self.removeElements.is_none() {
856            self.removeElements = Some(Vec::new());
857        }
858
859        // TODO:
860        // Step 2. If neither configuration["processingInstructions"] nor
861        // configuration["removeProcessingInstructions"] exist:
862        // Step 2.1. If allowCommentsPIsAndDataAttributes is true, then set
863        // configuration["removeProcessingInstructions"] to « ».
864        // Step 2.2. Otherwise, set configuration["processingInstructions"] to « ».
865
866        // Step 3. If neither configuration["attributes"] nor configuration["removeAttributes"]
867        // exist, then set configuration["removeAttributes"] to « ».
868        if self.attributes.is_none() && self.removeAttributes.is_none() {
869            self.removeAttributes = Some(Vec::new());
870        }
871
872        // Step 4. If configuration["elements"] exists:
873        if let Some(elements) = &mut self.elements {
874            // Step 4.1. Let elements be « ».
875            // Step 4.2. For each element of configuration["elements"] do:
876            // Step 4.2.1. Append the result of canonicalize a sanitizer element with attributes
877            // element to elements.
878            // Step 4.3. Set configuration["elements"] to elements.
879            *elements = elements
880                .iter()
881                .cloned()
882                .map(SanitizerElementWithAttributes::canonicalize)
883                .collect();
884        }
885
886        // Step 5. If configuration["removeElements"] exists:
887        if let Some(remove_elements) = &mut self.removeElements {
888            // Step 5.1. Let elements be « ».
889            // Step 5.2. For each element of configuration["removeElements"] do:
890            // Step 5.2.1. Append the result of canonicalize a sanitizer element element to
891            // elements.
892            // Step 5.3. Set configuration["removeElements"] to elements.
893            *remove_elements = remove_elements
894                .iter()
895                .cloned()
896                .map(SanitizerElement::canonicalize)
897                .collect();
898        }
899
900        // Step 6. If configuration["replaceWithChildrenElements"] exists:
901        if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
902            // Step 6.1. Let elements be « ».
903            // Step 6.2. For each element of configuration["replaceWithChildrenElements"] do:
904            // Step 6.2.1. Append the result of canonicalize a sanitizer element element to
905            // elements.
906            // Step 6.3. Set configuration["replaceWithChildrenElements"] to elements.
907            *replace_with_children_elements = replace_with_children_elements
908                .iter()
909                .cloned()
910                .map(SanitizerElement::canonicalize)
911                .collect();
912        }
913
914        // TODO:
915        // Step 7. If configuration["processingInstructions"] exists:
916        // Step 7.1. Let processingInstructions be « ».
917        // Step 7.2. For each pi of configuration["processingInstructions"]:
918        // Step 7.2.1. Append the result of canonicalize a sanitizer processing instruction pi
919        // to processingInstructions.
920        // Step 7.3. Set configuration["processingInstructions"] to processingInstructions.
921
922        // TODO:
923        // Step 8. If configuration["removeProcessingInstructions"] exists:
924        // Step 8.1. Let processingInstructions be « ».
925        // Step 8.2. For each pi of configuration["removeProcessingInstructions"]:
926        // Step 8.2.1. Append the result of canonicalize a sanitizer processing instruction
927        // pi to processingInstructions.
928        // Step 8.3. Set configuration["removeProcessingInstructions"] to processingInstructions.
929
930        // Step 9. If configuration["attributes"] exists:
931        if let Some(attributes) = &mut self.attributes {
932            // Step 9.1. Let attributes be « ».
933            // Step 9.2. For each attribute of configuration["attributes"] do:
934            // Step 9.2.1. Append the result of canonicalize a sanitizer attribute attribute to
935            // attributes.
936            // Step 9.3. Set configuration["attributes"] to attributes.
937            *attributes = attributes
938                .iter()
939                .cloned()
940                .map(SanitizerAttribute::canonicalize)
941                .collect();
942        }
943
944        // Step 10. If configuration["removeAttributes"] exists:
945        if let Some(remove_attributes) = &mut self.removeAttributes {
946            // Step 10.1. Let attributes be « ».
947            // Step 10.2. For each attribute of configuration["removeAttributes"] do:
948            // Step 10.2.1. Append the result of canonicalize a sanitizer attribute attribute to
949            // attributes.
950            // Step 10.3. Set configuration["removeAttributes"] to attributes.
951            *remove_attributes = remove_attributes
952                .iter()
953                .cloned()
954                .map(SanitizerAttribute::canonicalize)
955                .collect();
956        }
957
958        // Step 11. If configuration["comments"] does not exist, then set configuration["comments"]
959        // to allowCommentsPIsAndDataAttributes.
960        if self.comments.is_none() {
961            self.comments = Some(allow_comments_pis_and_data_attributes);
962        }
963
964        // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
965        // not exist, then set configuration["dataAttributes"] to allowCommentsPIsAndDataAttributes.
966        if self.attributes.is_some() && self.dataAttributes.is_none() {
967            self.dataAttributes = Some(allow_comments_pis_and_data_attributes);
968        }
969    }
970}
971
972trait Canonicalization {
973    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
974    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
975    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
976    fn canonicalize(self) -> Self;
977}
978
979impl Canonicalization for SanitizerElementWithAttributes {
980    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
981    fn canonicalize(mut self) -> Self {
982        // Step 1. Let result be the result of canonicalize a sanitizer element with element.
983        let parent = match &mut self {
984            SanitizerElementWithAttributes::String(name) => {
985                SanitizerElement::String(std::mem::take(name))
986            },
987            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
988                SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
989                    name: std::mem::take(&mut dictionary.parent.name),
990                    namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
991                })
992            },
993        };
994        let mut canonicalized_parent = parent.canonicalize();
995        let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
996            SanitizerElementNamespaceWithAttributes {
997                parent: SanitizerElementNamespace {
998                    name: std::mem::take(canonicalized_parent.name_mut()),
999                    namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
1000                },
1001                attributes: None,
1002                removeAttributes: None,
1003            },
1004        );
1005
1006        // Step 2. If element is a dictionary:
1007        if matches!(
1008            self,
1009            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
1010        ) {
1011            // Step 2.1. If element["attributes"] exists:
1012            if let Some(attributes) = self.attributes() {
1013                // Step 2.1.1. Let attributes be « ».
1014                // Step 2.1.2. For each attribute of element["attributes"]:
1015                // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
1016                // attribute to attributes.
1017                let attributes = attributes
1018                    .iter()
1019                    .cloned()
1020                    .map(|attribute| attribute.canonicalize())
1021                    .collect();
1022
1023                // Step 2.1.3. Set result["attributes"] to attributes.
1024                result.set_attributes(Some(attributes));
1025            }
1026
1027            // Step 2.2. If element["removeAttributes"] exists:
1028            if let Some(remove_attributes) = self.remove_attributes() {
1029                // Step 2.2.1. Let attributes be « ».
1030                // Step 2.2.2. For each attribute of element["removeAttributes"]:
1031                // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
1032                // attribute to attributes.
1033                let attributes = remove_attributes
1034                    .iter()
1035                    .cloned()
1036                    .map(|attribute| attribute.canonicalize())
1037                    .collect();
1038
1039                // Step 2.2.3. Set result["removeAttributes"] to attributes.
1040                result.set_remove_attributes(Some(attributes));
1041            }
1042        }
1043
1044        // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
1045        if result.attributes().is_none() && result.remove_attributes().is_none() {
1046            // Step 3.1. Set result["removeAttributes"] to « ».
1047            result.set_remove_attributes(Some(Vec::new()));
1048        }
1049
1050        // Step 4. Return result.
1051        result
1052    }
1053}
1054
1055impl Canonicalization for SanitizerElement {
1056    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
1057    fn canonicalize(self) -> Self {
1058        // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
1059        // the default namespace.
1060        self.canonicalize_name(Some(ns!(html).to_string()))
1061    }
1062}
1063
1064impl Canonicalization for SanitizerAttribute {
1065    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
1066    fn canonicalize(self) -> Self {
1067        // Return the result of canonicalize a sanitizer name with attribute and null as the default
1068        // namespace.
1069        self.canonicalize_name(None)
1070    }
1071}
1072
1073trait NameCanonicalization: NameMember {
1074    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
1075    fn is_string(&self) -> bool;
1076    fn is_dictionary(&self) -> bool;
1077
1078    /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
1079    fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
1080        // Step 1. Assert: name is either a DOMString or a dictionary.
1081        assert!(self.is_string() || self.is_dictionary());
1082
1083        // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
1084        // defaultNamespace]».
1085        if self.is_string() {
1086            return Self::new_dictionary(
1087                std::mem::take(self.name_mut()),
1088                default_namespace.map(DOMString::from),
1089            );
1090        }
1091
1092        // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
1093        // NOTE: The latter is guaranteed by Rust type system.
1094        assert!(self.is_dictionary());
1095
1096        // Step 4. If name["namespace"] is the empty string, then set it to null.
1097        if self
1098            .namespace()
1099            .is_some_and(|namespace| namespace.str() == "")
1100        {
1101            self.set_namespace(None);
1102        }
1103
1104        // Step 5. Return «[
1105        // "name" → name["name"],
1106        // "namespace" → name["namespace"]
1107        // ]».
1108        Self::new_dictionary(
1109            std::mem::take(self.name_mut()),
1110            self.namespace_mut().map(std::mem::take),
1111        )
1112    }
1113}
1114
1115impl NameCanonicalization for SanitizerElement {
1116    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
1117        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
1118    }
1119
1120    fn is_string(&self) -> bool {
1121        matches!(self, SanitizerElement::String(_))
1122    }
1123
1124    fn is_dictionary(&self) -> bool {
1125        matches!(self, SanitizerElement::SanitizerElementNamespace(_))
1126    }
1127}
1128
1129impl NameCanonicalization for SanitizerAttribute {
1130    fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
1131        SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1132            name,
1133            namespace,
1134        })
1135    }
1136
1137    fn is_string(&self) -> bool {
1138        matches!(self, SanitizerAttribute::String(_))
1139    }
1140
1141    fn is_dictionary(&self) -> bool {
1142        matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
1143    }
1144}
1145
1146/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
1147trait NameSlice<T>
1148where
1149    T: NameMember + Canonicalization + Clone,
1150{
1151    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
1152    fn contains_item<S: NameMember>(&self, other: &S) -> bool;
1153
1154    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
1155    fn has_duplicates(&self) -> bool;
1156
1157    /// Custom version of the supporting algorithm
1158    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
1159    /// intersection is non-empty, returning early if it is non-empty for efficiency.
1160    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
1161    where
1162        S: NameMember + Canonicalization + Clone;
1163}
1164
1165impl<T> NameSlice<T> for [T]
1166where
1167    T: NameMember + Canonicalization + Clone,
1168{
1169    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
1170    fn contains_item<S: NameMember>(&self, other: &S) -> bool {
1171        // A Sanitizer name list contains an item if there exists an entry of list that is an
1172        // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
1173        // entry["namespace"].
1174        self.iter()
1175            .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
1176    }
1177
1178    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
1179    fn has_duplicates(&self) -> bool {
1180        // A list list has duplicates, if for any item of list, there is more than one entry in list
1181        // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
1182        let mut used = HashSet::new();
1183        self.iter().any(move |entry| {
1184            !used.insert((
1185                entry.name().to_string(),
1186                entry.namespace().map(DOMString::to_string),
1187            ))
1188        })
1189    }
1190
1191    /// Custom version of the supporting algorithm
1192    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
1193    /// intersection is non-empty, returning early if it is non-empty for efficiency.
1194    fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
1195    where
1196        S: NameMember + Canonicalization + Clone,
1197    {
1198        // Step 1. Let set A be « [] ».
1199        // Step 2. Let set B be « [] ».
1200        // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
1201        // set A.
1202        // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
1203        // set B.
1204        let a = self.iter().map(|entry| entry.clone().canonicalize());
1205        let b = others
1206            .iter()
1207            .map(|entry| entry.clone().canonicalize())
1208            .collect::<Vec<S>>();
1209
1210        // Step 5. Return the intersection of set A and set B.
1211        // NOTE: Instead of returning the intersection itself, return true if the intersection is
1212        // non-empty, and false otherwise.
1213        a.filter(|entry| {
1214            b.iter()
1215                .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
1216        })
1217        .any(|_| true)
1218    }
1219}
1220
1221/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
1222trait NameVec<T>
1223where
1224    T: NameMember + Canonicalization + Clone,
1225{
1226    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
1227    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
1228
1229    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
1230    fn add_item(&mut self, name: T);
1231
1232    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
1233    fn remove_duplicates(&mut self) -> &mut Self;
1234
1235    /// Set itself to the set intersection of itself and another list.
1236    ///
1237    /// <https://infra.spec.whatwg.org/#set-intersection>
1238    fn intersection<S>(&mut self, others: &[S])
1239    where
1240        S: NameMember + Canonicalization + Clone;
1241
1242    /// <https://infra.spec.whatwg.org/#set-difference>
1243    fn difference(&mut self, others: &[T]);
1244}
1245
1246impl<T> NameVec<T> for Vec<T>
1247where
1248    T: NameMember + Canonicalization + Clone,
1249{
1250    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
1251    fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
1252        // Step 1. Set removed to false.
1253        let mut removed = false;
1254
1255        // Step 2. For each entry of list:
1256        // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
1257        // Step 2.1.1. Remove item entry from list.
1258        // Step 2.1.2. Set removed to true.
1259        self.retain(|entry| {
1260            let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
1261            if matched {
1262                removed = true;
1263            }
1264            !matched
1265        });
1266
1267        // Step 3. Return removed.
1268        removed
1269    }
1270
1271    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
1272    fn add_item(&mut self, name: T) {
1273        // Step 1. If list contains name, then return.
1274        if self.contains_item(&name) {
1275            return;
1276        };
1277
1278        // Step 2. Append name to list.
1279        self.push(name);
1280    }
1281
1282    /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
1283    fn remove_duplicates(&mut self) -> &mut Self {
1284        // Step 1. Let result be « ».
1285        // Step 2. For each entry of list, add entry to result.
1286        // Step 3. Return result.
1287        self.sort_by(|item_a, item_b| item_a.compare(item_b));
1288        self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
1289        self
1290    }
1291
1292    /// Set itself to the set intersection of itself and another list.
1293    ///
1294    /// <https://infra.spec.whatwg.org/#set-intersection>
1295    fn intersection<S>(&mut self, others: &[S])
1296    where
1297        S: NameMember + Canonicalization + Clone,
1298    {
1299        // The intersection of ordered sets A and B, is the result of creating a new ordered set set
1300        // and, for each item of A, if B contains item, appending item to set.
1301        self.retain(|item| {
1302            others
1303                .iter()
1304                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
1305        })
1306    }
1307
1308    /// Set itself to the set difference of itself and another list.
1309    ///
1310    /// <https://infra.spec.whatwg.org/#set-difference>
1311    fn difference(&mut self, others: &[T]) {
1312        // The difference of ordered sets A and B, is the result of creating a new ordered set set
1313        // and, for each item of A, if B does not contain item, appending item to set.
1314        self.retain(|item| {
1315            !others
1316                .iter()
1317                .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
1318        })
1319    }
1320}
1321
1322/// Helper functions for accessing the "name" and "namespace" members of
1323/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
1324trait NameMember: Sized {
1325    fn name(&self) -> &DOMString;
1326    fn name_mut(&mut self) -> &mut DOMString;
1327    fn namespace(&self) -> Option<&DOMString>;
1328    fn namespace_mut(&mut self) -> Option<&mut DOMString>;
1329
1330    fn set_namespace(&mut self, namespace: Option<&str>);
1331
1332    // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
1333    fn is_less_than_item(&self, item_b: &Self) -> bool {
1334        let item_a = self;
1335        match item_a.namespace() {
1336            // Step 1. If itemA["namespace"] is null:
1337            None => {
1338                // Step 1.1. If itemB["namespace"] is not null, then return true.
1339                if item_b.namespace().is_some() {
1340                    return true;
1341                }
1342            },
1343            // Step 2. Otherwise:
1344            Some(item_a_namespace) => {
1345                // Step 2.1. If itemB["namespace"] is null, then return false.
1346                if item_b.namespace().is_none() {
1347                    return false;
1348                }
1349
1350                // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
1351                // return true.
1352                if item_b
1353                    .namespace()
1354                    .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
1355                {
1356                    return true;
1357                }
1358
1359                // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
1360                if item_b
1361                    .namespace()
1362                    .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
1363                {
1364                    return false;
1365                }
1366            },
1367        }
1368
1369        // Step 3. Return itemA["name"] is code unit less than itemB["name"].
1370        item_a.name() < item_b.name()
1371    }
1372
1373    /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
1374    fn compare(&self, other: &Self) -> Ordering {
1375        if self.is_less_than_item(other) {
1376            Ordering::Less
1377        } else {
1378            Ordering::Greater
1379        }
1380    }
1381}
1382
1383impl NameMember for SanitizerElementWithAttributes {
1384    fn name(&self) -> &DOMString {
1385        match self {
1386            SanitizerElementWithAttributes::String(name) => name,
1387            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1388                &dictionary.parent.name
1389            },
1390        }
1391    }
1392
1393    fn name_mut(&mut self) -> &mut DOMString {
1394        match self {
1395            SanitizerElementWithAttributes::String(name) => name,
1396            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1397                &mut dictionary.parent.name
1398            },
1399        }
1400    }
1401
1402    fn namespace(&self) -> Option<&DOMString> {
1403        match self {
1404            SanitizerElementWithAttributes::String(_) => None,
1405            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1406                dictionary.parent.namespace.as_ref()
1407            },
1408        }
1409    }
1410
1411    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1412        match self {
1413            SanitizerElementWithAttributes::String(_) => None,
1414            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1415                dictionary.parent.namespace.as_mut()
1416            },
1417        }
1418    }
1419
1420    fn set_namespace(&mut self, namespace: Option<&str>) {
1421        match self {
1422            SanitizerElementWithAttributes::String(name) => {
1423                let new_instance =
1424                    SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1425                        SanitizerElementNamespaceWithAttributes {
1426                            parent: SanitizerElementNamespace {
1427                                name: std::mem::take(name),
1428                                namespace: namespace.map(DOMString::from),
1429                            },
1430                            attributes: None,
1431                            removeAttributes: None,
1432                        },
1433                    );
1434                *self = new_instance;
1435            },
1436            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1437                dictionary.parent.namespace = namespace.map(DOMString::from);
1438            },
1439        }
1440    }
1441}
1442
1443impl NameMember for SanitizerElement {
1444    fn name(&self) -> &DOMString {
1445        match self {
1446            SanitizerElement::String(name) => name,
1447            SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
1448        }
1449    }
1450
1451    fn name_mut(&mut self) -> &mut DOMString {
1452        match self {
1453            SanitizerElement::String(name) => name,
1454            SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
1455        }
1456    }
1457
1458    fn namespace(&self) -> Option<&DOMString> {
1459        match self {
1460            SanitizerElement::String(_) => None,
1461            SanitizerElement::SanitizerElementNamespace(dictionary) => {
1462                dictionary.namespace.as_ref()
1463            },
1464        }
1465    }
1466
1467    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1468        match self {
1469            SanitizerElement::String(_) => None,
1470            SanitizerElement::SanitizerElementNamespace(dictionary) => {
1471                dictionary.namespace.as_mut()
1472            },
1473        }
1474    }
1475
1476    fn set_namespace(&mut self, namespace: Option<&str>) {
1477        match self {
1478            SanitizerElement::String(name) => {
1479                let new_instance =
1480                    SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1481                        name: std::mem::take(name),
1482                        namespace: namespace.map(DOMString::from),
1483                    });
1484                *self = new_instance;
1485            },
1486            SanitizerElement::SanitizerElementNamespace(dictionary) => {
1487                dictionary.namespace = namespace.map(DOMString::from);
1488            },
1489        }
1490    }
1491}
1492
1493impl NameMember for SanitizerAttribute {
1494    fn name(&self) -> &DOMString {
1495        match self {
1496            SanitizerAttribute::String(name) => name,
1497            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
1498        }
1499    }
1500
1501    fn name_mut(&mut self) -> &mut DOMString {
1502        match self {
1503            SanitizerAttribute::String(name) => name,
1504            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
1505        }
1506    }
1507
1508    fn namespace(&self) -> Option<&DOMString> {
1509        match self {
1510            SanitizerAttribute::String(_) => None,
1511            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1512                dictionary.namespace.as_ref()
1513            },
1514        }
1515    }
1516
1517    fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1518        match self {
1519            SanitizerAttribute::String(_) => None,
1520            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1521                dictionary.namespace.as_mut()
1522            },
1523        }
1524    }
1525
1526    fn set_namespace(&mut self, namespace: Option<&str>) {
1527        match self {
1528            SanitizerAttribute::String(name) => {
1529                let new_instance =
1530                    SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1531                        name: std::mem::take(name),
1532                        namespace: namespace.map(DOMString::from),
1533                    });
1534                *self = new_instance;
1535            },
1536            SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1537                dictionary.namespace = namespace.map(DOMString::from);
1538            },
1539        }
1540    }
1541}
1542
1543/// Helper functions for accessing the "attributes" and "removeAttributes" members of
1544/// [`SanitizerElementWithAttributes`].
1545trait AttributeMember {
1546    fn attributes(&self) -> Option<&[SanitizerAttribute]>;
1547    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
1548    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
1549    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
1550
1551    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
1552    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
1553}
1554
1555impl AttributeMember for SanitizerElementWithAttributes {
1556    fn attributes(&self) -> Option<&[SanitizerAttribute]> {
1557        match self {
1558            SanitizerElementWithAttributes::String(_) => None,
1559            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1560                dictionary.attributes.as_deref()
1561            },
1562        }
1563    }
1564
1565    fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
1566        match self {
1567            SanitizerElementWithAttributes::String(_) => None,
1568            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1569                dictionary.attributes.as_mut()
1570            },
1571        }
1572    }
1573
1574    fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
1575        match self {
1576            SanitizerElementWithAttributes::String(_) => None,
1577            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1578                dictionary.removeAttributes.as_deref()
1579            },
1580        }
1581    }
1582
1583    fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
1584        match self {
1585            SanitizerElementWithAttributes::String(_) => None,
1586            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1587                dictionary.removeAttributes.as_mut()
1588            },
1589        }
1590    }
1591
1592    fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
1593        match self {
1594            SanitizerElementWithAttributes::String(name) => {
1595                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1596                    SanitizerElementNamespaceWithAttributes {
1597                        parent: SanitizerElementNamespace {
1598                            name: std::mem::take(name),
1599                            namespace: None,
1600                        },
1601                        attributes,
1602                        removeAttributes: None,
1603                    },
1604                );
1605            },
1606            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1607                dictionary.attributes = attributes;
1608            },
1609        }
1610    }
1611
1612    fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
1613        match self {
1614            SanitizerElementWithAttributes::String(name) => {
1615                *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1616                    SanitizerElementNamespaceWithAttributes {
1617                        parent: SanitizerElementNamespace {
1618                            name: std::mem::take(name),
1619                            namespace: None,
1620                        },
1621                        attributes: None,
1622                        removeAttributes: remove_attributes,
1623                    },
1624                );
1625            },
1626            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1627                dictionary.removeAttributes = remove_attributes;
1628            },
1629        }
1630    }
1631}
1632
1633/// <https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration>
1634fn built_in_safe_default_configuration() -> SanitizerConfig {
1635    const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
1636        ("math", &ns!(mathml), &[]),
1637        ("merror", &ns!(mathml), &[]),
1638        ("mfrac", &ns!(mathml), &[]),
1639        ("mi", &ns!(mathml), &[]),
1640        ("mmultiscripts", &ns!(mathml), &[]),
1641        ("mn", &ns!(mathml), &[]),
1642        (
1643            "mo",
1644            &ns!(mathml),
1645            &[
1646                "fence",
1647                "form",
1648                "largeop",
1649                "lspace",
1650                "maxsize",
1651                "minsize",
1652                "movablelimits",
1653                "rspace",
1654                "separator",
1655                "stretchy",
1656                "symmetric",
1657            ],
1658        ),
1659        ("mover", &ns!(mathml), &["accent"]),
1660        (
1661            "mpadded",
1662            &ns!(mathml),
1663            &["depth", "height", "lspace", "voffset", "width"],
1664        ),
1665        ("mphantom", &ns!(mathml), &[]),
1666        ("mprescripts", &ns!(mathml), &[]),
1667        ("mroot", &ns!(mathml), &[]),
1668        ("mrow", &ns!(mathml), &[]),
1669        ("ms", &ns!(mathml), &[]),
1670        ("mspace", &ns!(mathml), &["depth", "height", "width"]),
1671        ("msqrt", &ns!(mathml), &[]),
1672        ("mstyle", &ns!(mathml), &[]),
1673        ("msub", &ns!(mathml), &[]),
1674        ("msubsup", &ns!(mathml), &[]),
1675        ("msup", &ns!(mathml), &[]),
1676        ("mtable", &ns!(mathml), &[]),
1677        ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
1678        ("mtext", &ns!(mathml), &[]),
1679        ("mtr", &ns!(mathml), &[]),
1680        ("munder", &ns!(mathml), &["accentunder"]),
1681        ("munderover", &ns!(mathml), &["accent", "accentunder"]),
1682        ("semantics", &ns!(mathml), &[]),
1683        ("a", &ns!(html), &["href", "hreflang", "type"]),
1684        ("abbr", &ns!(html), &[]),
1685        ("address", &ns!(html), &[]),
1686        ("article", &ns!(html), &[]),
1687        ("aside", &ns!(html), &[]),
1688        ("b", &ns!(html), &[]),
1689        ("bdi", &ns!(html), &[]),
1690        ("bdo", &ns!(html), &[]),
1691        ("blockquote", &ns!(html), &["cite"]),
1692        ("body", &ns!(html), &[]),
1693        ("br", &ns!(html), &[]),
1694        ("caption", &ns!(html), &[]),
1695        ("cite", &ns!(html), &[]),
1696        ("code", &ns!(html), &[]),
1697        ("col", &ns!(html), &["span"]),
1698        ("colgroup", &ns!(html), &["span"]),
1699        ("data", &ns!(html), &["value"]),
1700        ("dd", &ns!(html), &[]),
1701        ("del", &ns!(html), &["cite", "datetime"]),
1702        ("dfn", &ns!(html), &[]),
1703        ("div", &ns!(html), &[]),
1704        ("dl", &ns!(html), &[]),
1705        ("dt", &ns!(html), &[]),
1706        ("em", &ns!(html), &[]),
1707        ("figcaption", &ns!(html), &[]),
1708        ("figure", &ns!(html), &[]),
1709        ("footer", &ns!(html), &[]),
1710        ("h1", &ns!(html), &[]),
1711        ("h2", &ns!(html), &[]),
1712        ("h3", &ns!(html), &[]),
1713        ("h4", &ns!(html), &[]),
1714        ("h5", &ns!(html), &[]),
1715        ("h6", &ns!(html), &[]),
1716        ("head", &ns!(html), &[]),
1717        ("header", &ns!(html), &[]),
1718        ("hgroup", &ns!(html), &[]),
1719        ("hr", &ns!(html), &[]),
1720        ("html", &ns!(html), &[]),
1721        ("i", &ns!(html), &[]),
1722        ("ins", &ns!(html), &["cite", "datetime"]),
1723        ("kbd", &ns!(html), &[]),
1724        ("li", &ns!(html), &["value"]),
1725        ("main", &ns!(html), &[]),
1726        ("mark", &ns!(html), &[]),
1727        ("menu", &ns!(html), &[]),
1728        ("nav", &ns!(html), &[]),
1729        ("ol", &ns!(html), &["reversed", "start", "type"]),
1730        ("p", &ns!(html), &[]),
1731        ("pre", &ns!(html), &[]),
1732        ("q", &ns!(html), &[]),
1733        ("rp", &ns!(html), &[]),
1734        ("rt", &ns!(html), &[]),
1735        ("ruby", &ns!(html), &[]),
1736        ("s", &ns!(html), &[]),
1737        ("samp", &ns!(html), &[]),
1738        ("search", &ns!(html), &[]),
1739        ("section", &ns!(html), &[]),
1740        ("small", &ns!(html), &[]),
1741        ("span", &ns!(html), &[]),
1742        ("strong", &ns!(html), &[]),
1743        ("sub", &ns!(html), &[]),
1744        ("sup", &ns!(html), &[]),
1745        ("table", &ns!(html), &[]),
1746        ("tbody", &ns!(html), &[]),
1747        ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
1748        ("tfoot", &ns!(html), &[]),
1749        (
1750            "th",
1751            &ns!(html),
1752            &["abbr", "colspan", "headers", "rowspan", "scope"],
1753        ),
1754        ("thead", &ns!(html), &[]),
1755        ("time", &ns!(html), &["datetime"]),
1756        ("title", &ns!(html), &[]),
1757        ("tr", &ns!(html), &[]),
1758        ("u", &ns!(html), &[]),
1759        ("ul", &ns!(html), &[]),
1760        ("var", &ns!(html), &[]),
1761        ("wbr", &ns!(html), &[]),
1762        ("a", &ns!(svg), &["href", "hreflang", "type"]),
1763        ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
1764        ("defs", &ns!(svg), &[]),
1765        ("desc", &ns!(svg), &[]),
1766        (
1767            "ellipse",
1768            &ns!(svg),
1769            &["cx", "cy", "pathLength", "rx", "ry"],
1770        ),
1771        ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
1772        ("g", &ns!(svg), &[]),
1773        ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
1774        (
1775            "marker",
1776            &ns!(svg),
1777            &[
1778                "markerHeight",
1779                "markerUnits",
1780                "markerWidth",
1781                "orient",
1782                "preserveAspectRatio",
1783                "refX",
1784                "refY",
1785                "viewBox",
1786            ],
1787        ),
1788        ("metadata", &ns!(svg), &[]),
1789        ("path", &ns!(svg), &["d", "pathLength"]),
1790        ("polygon", &ns!(svg), &["pathLength", "points"]),
1791        ("polyline", &ns!(svg), &["pathLength", "points"]),
1792        (
1793            "rect",
1794            &ns!(svg),
1795            &["height", "pathLength", "rx", "ry", "width", "x", "y"],
1796        ),
1797        (
1798            "svg",
1799            &ns!(svg),
1800            &[
1801                "height",
1802                "preserveAspectRatio",
1803                "viewBox",
1804                "width",
1805                "x",
1806                "y",
1807            ],
1808        ),
1809        (
1810            "text",
1811            &ns!(svg),
1812            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
1813        ),
1814        (
1815            "textPath",
1816            &ns!(svg),
1817            &[
1818                "lengthAdjust",
1819                "method",
1820                "path",
1821                "side",
1822                "spacing",
1823                "startOffset",
1824                "textLength",
1825            ],
1826        ),
1827        ("title", &ns!(svg), &[]),
1828        (
1829            "tspan",
1830            &ns!(svg),
1831            &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
1832        ),
1833    ];
1834    const ATTRIBUTES: &[&str] = &[
1835        "alignment-baseline",
1836        "baseline-shift",
1837        "clip-path",
1838        "clip-rule",
1839        "color",
1840        "color-interpolation",
1841        "cursor",
1842        "dir",
1843        "direction",
1844        "display",
1845        "displaystyle",
1846        "dominant-baseline",
1847        "fill",
1848        "fill-opacity",
1849        "fill-rule",
1850        "font-family",
1851        "font-size",
1852        "font-size-adjust",
1853        "font-stretch",
1854        "font-style",
1855        "font-variant",
1856        "font-weight",
1857        "lang",
1858        "letter-spacing",
1859        "marker-end",
1860        "marker-mid",
1861        "marker-start",
1862        "mathbackground",
1863        "mathcolor",
1864        "mathsize",
1865        "opacity",
1866        "paint-order",
1867        "pointer-events",
1868        "scriptlevel",
1869        "shape-rendering",
1870        "stop-color",
1871        "stop-opacity",
1872        "stroke",
1873        "stroke-dasharray",
1874        "stroke-dashoffset",
1875        "stroke-linecap",
1876        "stroke-linejoin",
1877        "stroke-miterlimit",
1878        "stroke-opacity",
1879        "stroke-width",
1880        "text-anchor",
1881        "text-decoration",
1882        "text-overflow",
1883        "text-rendering",
1884        "title",
1885        "transform",
1886        "transform-origin",
1887        "unicode-bidi",
1888        "vector-effect",
1889        "visibility",
1890        "white-space",
1891        "word-spacing",
1892        "writing-mode",
1893    ];
1894
1895    let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
1896        attributes
1897            .iter()
1898            .map(|&attribute| {
1899                SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1900                    name: attribute.into(),
1901                    namespace: None,
1902                })
1903            })
1904            .collect()
1905    };
1906
1907    let elements = ELEMENTS
1908        .iter()
1909        .map(|&(name, namespace, attributes)| {
1910            let attributes = create_attribute_vec(attributes);
1911            SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1912                SanitizerElementNamespaceWithAttributes {
1913                    parent: SanitizerElementNamespace {
1914                        name: name.into(),
1915                        namespace: Some(namespace.to_string().into()),
1916                    },
1917                    attributes: Some(attributes),
1918                    removeAttributes: None,
1919                },
1920            )
1921        })
1922        .collect();
1923
1924    let attributes = create_attribute_vec(ATTRIBUTES);
1925
1926    SanitizerConfig {
1927        elements: Some(elements),
1928        removeElements: None,
1929        replaceWithChildrenElements: None,
1930        attributes: Some(attributes),
1931        removeAttributes: None,
1932        comments: Some(false),
1933        dataAttributes: Some(false),
1934    }
1935}
1936
1937/// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
1938fn built_in_non_replaceable_elements_list() -> Vec<SanitizerElement> {
1939    vec![
1940        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1941            name: "html".into(),
1942            namespace: Some(ns!(html).to_string().into()),
1943        }),
1944        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1945            name: "svg".into(),
1946            namespace: Some(ns!(svg).to_string().into()),
1947        }),
1948        SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1949            name: "math".into(),
1950            namespace: Some(ns!(mathml).to_string().into()),
1951        }),
1952    ]
1953}
1954
1955/// <https://html.spec.whatwg.org/multipage/#custom-data-attribute>
1956fn is_custom_data_attribute(name: &str, namespace: Option<&str>) -> bool {
1957    // A custom data attribute is an attribute in no namespace whose name starts with the string
1958    // "data-", has at least one character after the hyphen, is a valid attribute local name,
1959    // and contains no ASCII upper alphas.
1960    namespace.is_none() &&
1961        name.strip_prefix("data-")
1962            .is_some_and(|substring| !substring.is_empty()) &&
1963        is_valid_attribute_local_name(name) &&
1964        name.chars()
1965            .all(|code_point| !code_point.is_ascii_uppercase())
1966}