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