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