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(cx, context_element, html, true);
207
208 // Step 4. Let fragment be a new DocumentFragment whose node document is contextElement’s
209 // node document.
210 let context_document = context_element.owner_document();
211 let fragment = DomRoot::upcast::<Node>(DocumentFragment::new(cx, &context_document));
212
213 // Step 5. For each node in newChildren, append node to fragment.
214 for child in new_children {
215 fragment
216 .AppendChild(cx, &child)
217 .expect("Must be able to append child to node");
218 }
219
220 // Step 6. Run sanitize on fragment using sanitizer and safe.
221 sanitizer.sanitize(cx, &fragment, safe)?;
222
223 // Step 7. Replace all with fragment within target.
224 Node::replace_all(cx, Some(&fragment), target);
225
226 Ok(())
227 }
228
229 /// <https://wicg.github.io/sanitizer-api/#sanitize>
230 pub(crate) fn sanitize(&self, cx: &mut JSContext, node: &Node, safe: bool) -> ErrorResult {
231 // Step 1. Let configuration be the value of sanitizer’s configuration.
232 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, cx: &mut JSContext, 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 cx,
943 &self.global(),
944 "Do not support adding an element with attributes to a sanitizer \
945 whose configuration[\"elements\"] does not exist."
946 .into(),
947 );
948
949 // Step 5.1.2. Return false.
950 return false;
951 }
952
953 // Step 5.2. Set modified to the result of remove element from
954 // configuration["replaceWithChildrenElements"].
955 let modified = if let Some(replace_with_children_elements) =
956 &mut configuration.replaceWithChildrenElements
957 {
958 replace_with_children_elements.remove_item(&element)
959 } else {
960 false
961 };
962
963 // Step 5.3. If configuration["removeElements"] does not contain element:
964 if !configuration
965 .removeElements
966 .as_ref()
967 .is_some_and(|configuration_remove_elements| {
968 configuration_remove_elements.contains_item(&element)
969 })
970 {
971 // Step 5.3.1. Comment: This is the case with a global remove-list that does not
972 // contain element.
973
974 // Step 5.3.2. Return modified.
975 return modified;
976 }
977
978 // Step 5.4. Comment: This is the case with a global remove-list that contains element.
979
980 // Step 5.5. Remove element from configuration["removeElements"].
981 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
982 configuration_remove_elements.remove_item(&element);
983 }
984
985 // Step 5.6. Return true.
986 true
987 }
988 }
989
990 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
991 fn RemoveElement(&self, element: SanitizerElement) -> bool {
992 // Remove an element with element and this’s configuration.
993 self.configuration.borrow_mut().remove_element(element)
994 }
995
996 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
997 fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
998 // Step 1. Let configuration be this’s configuration.
999 let mut configuration = self.configuration.borrow_mut();
1000
1001 // Step 2. Assert: configuration is valid.
1002 debug_assert!(configuration.is_valid());
1003
1004 // Step 3. Set element to the result of canonicalize a sanitizer element with element.
1005 let element = element.canonicalize();
1006
1007 // Step 4. If the built-in non-replaceable elements list contains element:
1008 if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(&element)) {
1009 // Step 4.1. Return false.
1010 return false;
1011 }
1012
1013 // Step 5. If configuration["replaceWithChildrenElements"] contains element:
1014 if configuration
1015 .replaceWithChildrenElements
1016 .as_ref()
1017 .is_some_and(|configuration_replace_with_children_elements| {
1018 configuration_replace_with_children_elements.contains_item(&element)
1019 })
1020 {
1021 // Step 5.1. Return false.
1022 return false;
1023 }
1024
1025 // Step 6. Remove element from configuration["removeElements"].
1026 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
1027 configuration_remove_elements.remove_item(&element);
1028 }
1029
1030 // Step 7. Remove element from configuration["elements"] list.
1031 if let Some(configuration_elements) = &mut configuration.elements {
1032 configuration_elements.remove_item(&element);
1033 }
1034
1035 // Step 8. Add element to configuration["replaceWithChildrenElements"].
1036 if let Some(configuration_replace_with_children_elements) =
1037 &mut configuration.replaceWithChildrenElements
1038 {
1039 configuration_replace_with_children_elements.add_item(element);
1040 } else {
1041 configuration.replaceWithChildrenElements = Some(vec![element]);
1042 }
1043
1044 // Step 9. Return true.
1045 true
1046 }
1047
1048 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowprocessinginstruction>
1049 fn AllowProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1050 // Step 1. Let configuration be this’s configuration.
1051 let mut configuration = self.configuration.borrow_mut();
1052
1053 // Step 2. Assert: configuration is valid.
1054 debug_assert!(configuration.is_valid());
1055
1056 // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1057 let processing_instruction = processing_instruction.canonicalize();
1058
1059 match &mut configuration.processingInstructions {
1060 // Step 4. If configuration["processingInstructions"] exists:
1061 Some(configuration_processing_instructions) => {
1062 // Step 4.1. If configuration["processingInstructions"] contains pi:
1063 if configuration_processing_instructions.contains_target(&processing_instruction) {
1064 // Step 4.1.1. Return false.
1065 return false;
1066 }
1067
1068 // Step 4.2. Append pi to configuration["processingInstructions"].
1069 configuration_processing_instructions.push(processing_instruction);
1070
1071 // Step 4.3. Return true.
1072 true
1073 },
1074 // Step 5. Otherwise:
1075 None => {
1076 // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1077 if configuration
1078 .removeProcessingInstructions
1079 .as_ref()
1080 .is_some_and(|configuration_remove_processing_instructions| {
1081 configuration_remove_processing_instructions
1082 .contains_target(&processing_instruction)
1083 })
1084 {
1085 // Step 5.1.1. Remove the item from
1086 // configuration["removeProcessingInstructions"] whose "target" is pi["target"].
1087 if let Some(configuration_remove_processing_instructions) =
1088 &mut configuration.removeProcessingInstructions
1089 {
1090 configuration_remove_processing_instructions
1091 .retain(|item| item.target() != processing_instruction.target())
1092 }
1093
1094 // Step 5.1.2. Return true.
1095 return true;
1096 }
1097
1098 // Step 5.2. Return false.
1099 false
1100 },
1101 }
1102 }
1103
1104 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeprocessinginstruction>
1105 fn RemoveProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1106 // Step 1. Let configuration be this’s configuration.
1107 let mut configuration = self.configuration.borrow_mut();
1108
1109 // Step 2. Assert: configuration is valid.
1110 debug_assert!(configuration.is_valid());
1111
1112 // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1113 let processing_instruction = processing_instruction.canonicalize();
1114
1115 match &mut configuration.processingInstructions {
1116 // Step 4. If configuration["processingInstructions"] exists:
1117 Some(configuration_processing_instructions) => {
1118 // Step 4.1. If configuration["processingInstructions"] contains pi:
1119 if configuration_processing_instructions.contains_target(&processing_instruction) {
1120 // Step 4.1.1. Remove the item from configuration["processingInstructions"]
1121 // whose "target" is pi["target"].
1122 configuration_processing_instructions
1123 .retain(|item| item.target() != processing_instruction.target());
1124
1125 // Step 4.1.2. Return true.
1126 return true;
1127 }
1128
1129 // Step 4.2. Return false.
1130 false
1131 },
1132 // Step 5. Otherwise:
1133 None => {
1134 // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1135 if configuration
1136 .removeProcessingInstructions
1137 .as_ref()
1138 .is_some_and(|configuration_remove_processing_instructions| {
1139 configuration_remove_processing_instructions
1140 .contains_target(&processing_instruction)
1141 })
1142 {
1143 // Step 5.1.1. Return false.
1144 return false;
1145 }
1146
1147 // Step 5.2. Append pi to configuration["removeProcessingInstructions"].
1148 if let Some(configuration_remove_processing_instructions) =
1149 &mut configuration.removeProcessingInstructions
1150 {
1151 configuration_remove_processing_instructions.push(processing_instruction);
1152 } else {
1153 configuration.removeProcessingInstructions = Some(vec![processing_instruction]);
1154 }
1155
1156 // Step 5.3. Return true.
1157 true
1158 },
1159 }
1160 }
1161
1162 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowattribute>
1163 fn AllowAttribute(&self, attribute: SanitizerAttribute) -> bool {
1164 // Step 1. Let configuration be this’s configuration.
1165 let mut configuration = self.configuration.borrow_mut();
1166
1167 // Step 2. Assert: configuration is valid.
1168 debug_assert!(configuration.is_valid());
1169
1170 // Step 3. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1171 let attribute = attribute.canonicalize();
1172
1173 // Step 4. If configuration["attributes"] exists:
1174 if configuration.attributes.is_some() {
1175 // Step 4.1. Comment: If we have a global allow-list, we need to add attribute.
1176
1177 // Step 4.2. If configuration["dataAttributes"] is true and attribute is a custom data
1178 // attribute, then return false.
1179 if configuration.dataAttributes == Some(true) && attribute.is_custom_data_attribute() {
1180 return false;
1181 }
1182
1183 // Step 4.3. If configuration["attributes"] contains attribute return false.
1184 if configuration
1185 .attributes
1186 .as_ref()
1187 .is_some_and(|configuration_attributes| {
1188 configuration_attributes.contains(&attribute)
1189 })
1190 {
1191 return false;
1192 }
1193
1194 // Step 4.4. Comment: Fix-up per-element allow and remove lists.
1195
1196 // Step 4.5. If configuration["elements"] exists:
1197 if let Some(configuration_elements) = &mut configuration.elements {
1198 // Step 4.5.1. For each element in configuration["elements"]:
1199 for element in configuration_elements.iter_mut() {
1200 // Step 4.5.1.1. If element["attributes"] with default « » contains attribute:
1201 // Step 4.5.1.1.1. Remove attribute from element["attributes"].
1202 if let Some(element_attributes) = element.attributes_mut() {
1203 element_attributes
1204 .retain(|element_attribute| *element_attribute != attribute);
1205 }
1206
1207 // Step 4.5.1.2. Assert: element["removeAttributes"] with default « » does not
1208 // contain attribute.
1209 debug_assert!(!element.remove_attributes().is_some_and(
1210 |element_remove_attributes| element_remove_attributes.contains(&attribute)
1211 ));
1212 }
1213 }
1214
1215 // Step 4.6. Append attribute to configuration["attributes"]
1216 if let Some(configuration_attributes) = &mut configuration.attributes {
1217 configuration_attributes.push(attribute);
1218 } else {
1219 configuration.attributes = Some(vec![attribute]);
1220 }
1221
1222 // Step 4.7. Return true.
1223 true
1224 }
1225 // Step 5. Otherwise:
1226 else {
1227 // Step 5.1. Comment: If we have a global remove-list, we need to remove attribute.
1228
1229 // Step 5.2. If configuration["removeAttributes"] does not contain attribute:
1230 if !configuration.removeAttributes.as_ref().is_some_and(
1231 |configuration_remove_attributes| {
1232 configuration_remove_attributes.contains(&attribute)
1233 },
1234 ) {
1235 // Step 5.2.1. Return false.
1236 return false;
1237 }
1238
1239 // Step 5.3. Remove attribute from configuration["removeAttributes"].
1240 if let Some(configuration_remove_attributes) = &mut configuration.removeAttributes {
1241 configuration_remove_attributes.retain(|configuration_remove_attribute| {
1242 *configuration_remove_attribute != attribute
1243 });
1244 }
1245
1246 // Step 5.4. Return true.
1247 true
1248 }
1249 }
1250
1251 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeattribute>
1252 fn RemoveAttribute(&self, attribute: SanitizerAttribute) -> bool {
1253 // Remove an attribute with attribute and this’s configuration.
1254 self.configuration.borrow_mut().remove_attribute(attribute)
1255 }
1256
1257 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setcomments>
1258 fn SetComments(&self, allow: bool) -> bool {
1259 // Step 1. Let configuration be this’s configuration.
1260 let mut configuration = self.configuration.borrow_mut();
1261
1262 // Step 2. Assert: configuration is valid.
1263 debug_assert!(configuration.is_valid());
1264
1265 // Step 3. If configuration["comments"] exists and configuration["comments"] equals allow,
1266 // then return false;
1267 if configuration
1268 .comments
1269 .is_some_and(|configuration_comments| configuration_comments == allow)
1270 {
1271 return false;
1272 }
1273
1274 // Step 4. Set configuration["comments"] to allow.
1275 configuration.comments = Some(allow);
1276
1277 // Step 5. Return true.
1278 true
1279 }
1280
1281 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setdataattributes>
1282 fn SetDataAttributes(&self, allow: bool) -> bool {
1283 // Step 1. Let configuration be this’s configuration.
1284 let mut configuration = self.configuration.borrow_mut();
1285
1286 // Step 2. Assert: configuration is valid.
1287 debug_assert!(configuration.is_valid());
1288
1289 // Step 3. If configuration["attributes"] does not exist, then return false.
1290 if configuration.attributes.is_none() {
1291 return false;
1292 }
1293
1294 // Step 4. If configuration["dataAttributes"] equals allow, then return false.
1295 if configuration.dataAttributes == Some(allow) {
1296 return false;
1297 }
1298
1299 // Step 5. If allow is true:
1300 if allow {
1301 // Step 5.1. Remove any items attr from configuration["attributes"] where attr is a
1302 // custom data attribute.
1303 if let Some(configuration_attributes) = &mut configuration.attributes {
1304 configuration_attributes.retain(|attribute| !attribute.is_custom_data_attribute());
1305 }
1306
1307 // Step 5.2. If configuration["elements"] exists:
1308 if let Some(configuration_elements) = &mut configuration.elements {
1309 // Step 5.2.1. For each element in configuration["elements"]:
1310 for element in configuration_elements {
1311 // Step 5.2.1.1. If element["attributes"] exists:
1312 if let Some(element_attributes) = element.attributes_mut() {
1313 // Step 5.2.1.1.1. Remove any items attr from element["attributes"] where
1314 // attr is a custom data attribute.
1315 element_attributes
1316 .retain(|attribute| !attribute.is_custom_data_attribute());
1317 }
1318 }
1319 }
1320 }
1321
1322 // Step 6. Set configuration["dataAttributes"] to allow.
1323 configuration.dataAttributes = Some(allow);
1324
1325 // Step 7. Return true.
1326 true
1327 }
1328
1329 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeunsafe>
1330 fn RemoveUnsafe(&self) -> bool {
1331 // Update this’s configuration with the result of calling remove unsafe on this’s
1332 // configuration.
1333 self.configuration.borrow_mut().remove_unsafe()
1334 }
1335}
1336
1337trait SanitizerConfigAlgorithm {
1338 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1339 fn is_valid(&self) -> bool;
1340
1341 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1342 fn remove_element(&mut self, element: SanitizerElement) -> bool;
1343
1344 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1345 fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool;
1346
1347 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1348 fn remove_unsafe(&mut self) -> bool;
1349
1350 /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1351 fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
1352}
1353
1354impl SanitizerConfigAlgorithm for SanitizerConfig {
1355 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1356 fn is_valid(&self) -> bool {
1357 // NOTE: It’s expected that the configuration being passing in has previously been run
1358 // through the canonicalize the configuration steps. We will simply assert conditions that
1359 // that algorithm should have guaranteed to hold.
1360
1361 // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
1362 assert!(self.elements.is_some() || self.removeElements.is_some());
1363
1364 // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
1365 // false.
1366 if self.elements.is_some() && self.removeElements.is_some() {
1367 return false;
1368 }
1369
1370 // Step 3. Assert: Either config["processingInstructions"] exists or
1371 // config["removeProcessingInstructions"] exists.
1372 assert!(
1373 self.processingInstructions.is_some() || self.removeProcessingInstructions.is_some()
1374 );
1375
1376 // Step 4. If config["processingInstructions"] exists and
1377 // config["removeProcessingInstructions"] exists, then return false.
1378 if self.processingInstructions.is_some() && self.removeProcessingInstructions.is_some() {
1379 return false;
1380 }
1381
1382 // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
1383 assert!(self.attributes.is_some() || self.removeAttributes.is_some());
1384
1385 // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
1386 // false.
1387 if self.attributes.is_some() && self.removeAttributes.is_some() {
1388 return false;
1389 }
1390
1391 // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
1392 // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
1393 // canonical, meaning they have been run through canonicalize a sanitizer element,
1394 // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
1395 // as appropriate.
1396 //
1397 // NOTE: This assertion could be done by running the canonicalization again to see if there
1398 // is any changes. Since it is expected to canonicalize the configuration before running
1399 // this `is_valid` function, we simply skip this assert for the sake of performace.
1400
1401 match &self.elements {
1402 // Step 8. If config["elements"] exists:
1403 Some(config_elements) => {
1404 // Step 8.1. If config["elements"] has duplicates, then return false.
1405 if config_elements.has_duplicates() {
1406 return false;
1407 }
1408 },
1409 // Step 9. Otherwise:
1410 None => {
1411 // Step 9.1. If config["removeElements"] has duplicates, then return false.
1412 if self
1413 .removeElements
1414 .as_ref()
1415 .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
1416 {
1417 return false;
1418 }
1419 },
1420 }
1421
1422 // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
1423 // false.
1424 if self
1425 .replaceWithChildrenElements
1426 .as_ref()
1427 .is_some_and(|replace_with_children_elements| {
1428 replace_with_children_elements.has_duplicates()
1429 })
1430 {
1431 return false;
1432 }
1433
1434 match &self.processingInstructions {
1435 // Step 11. If config["processingInstructions"] exists:
1436 Some(config_processing_instructions) => {
1437 // Step 11.1. If config["processingInstructions"] has duplicate targets, then return
1438 // false.
1439 if config_processing_instructions.has_duplicate_targets() {
1440 return false;
1441 }
1442 },
1443 // Step 12. Otherwise:
1444 None => {
1445 // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then
1446 // return false.
1447 if self.removeProcessingInstructions.as_ref().is_some_and(
1448 |config_remove_processing_instructions| {
1449 config_remove_processing_instructions.has_duplicate_targets()
1450 },
1451 ) {
1452 return false;
1453 }
1454 },
1455 }
1456
1457 match &self.attributes {
1458 // Step 13. If config["attributes"] exists:
1459 Some(config_attributes) => {
1460 // Step 13.1. If config["attributes"] has duplicates, then return false.
1461 if config_attributes.has_duplicates() {
1462 return false;
1463 }
1464 },
1465 // Step 14. Otherwise:
1466 None => {
1467 // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
1468 if self
1469 .removeAttributes
1470 .as_ref()
1471 .is_some_and(|config_remove_attributes| {
1472 config_remove_attributes.has_duplicates()
1473 })
1474 {
1475 return false;
1476 }
1477 },
1478 }
1479
1480 // Step 15. If config["replaceWithChildrenElements"] exists:
1481 if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
1482 // Step 15.1. For each element of config["replaceWithChildrenElements"]:
1483 for element in config_replace_with_children_elements {
1484 // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
1485 // return false.
1486 if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(element)) {
1487 return false;
1488 }
1489 }
1490
1491 match &self.elements {
1492 // Step 15.2. If config["elements"] exists:
1493 Some(config_elements) => {
1494 // Step 15.2.1. If the intersection of config["elements"] and
1495 // config["replaceWithChildrenElements"] is not empty, then return false.
1496 if config_elements
1497 .is_intersection_non_empty(config_replace_with_children_elements)
1498 {
1499 return false;
1500 }
1501 },
1502 // Step 15.3. Otherwise:
1503 None => {
1504 // Step 15.3.1. If the intersection of config["removeElements"] and
1505 // config["replaceWithChildrenElements"] is not empty, then return false.
1506 if self
1507 .removeElements
1508 .as_ref()
1509 .is_some_and(|config_remove_elements| {
1510 config_remove_elements
1511 .is_intersection_non_empty(config_replace_with_children_elements)
1512 })
1513 {
1514 return false;
1515 }
1516 },
1517 }
1518 }
1519
1520 match &self.attributes {
1521 // Step 16. If config["attributes"] exists:
1522 Some(config_attributes) => {
1523 // Step 16.1. Assert: config["dataAttributes"] exists.
1524 assert!(self.dataAttributes.is_some());
1525
1526 // Step 16.2. If config["elements"] exists:
1527 if let Some(config_elements) = &self.elements {
1528 // Step 16.2.1. For each element of config["elements"]:
1529 for element in config_elements {
1530 // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
1531 // has duplicates, then return false.
1532 if element
1533 .attributes()
1534 .is_some_and(|element_attributes| element_attributes.has_duplicates())
1535 {
1536 return false;
1537 }
1538
1539 // Step 16.2.1.2. If element["removeAttributes"] exists and
1540 // element["removeAttributes"] has duplicates, then return false.
1541 if element
1542 .remove_attributes()
1543 .is_some_and(|element_remove_attributes| {
1544 element_remove_attributes.has_duplicates()
1545 })
1546 {
1547 return false;
1548 }
1549
1550 // Step 16.2.1.3. If the intersection of config["attributes"] and
1551 // element["attributes"] with default « » is not empty, then return false.
1552 if config_attributes
1553 .is_intersection_non_empty(element.attributes().unwrap_or_default())
1554 {
1555 return false;
1556 }
1557
1558 // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
1559 // subset of config["attributes"], then return false.
1560 if !element
1561 .remove_attributes()
1562 .unwrap_or_default()
1563 .iter()
1564 .all(|entry| config_attributes.contains_item(entry))
1565 {
1566 return false;
1567 }
1568
1569 // Step 16.2.1.5. If config["dataAttributes"] is true and
1570 // element["attributes"] contains a custom data attribute, then return
1571 // false.
1572 if self.dataAttributes == Some(true) &&
1573 element.attributes().is_some_and(|attributes| {
1574 attributes
1575 .iter()
1576 .any(|attribute| attribute.is_custom_data_attribute())
1577 })
1578 {
1579 return false;
1580 }
1581 }
1582 }
1583
1584 // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
1585 // a custom data attribute, then return false.
1586 if self.dataAttributes == Some(true) &&
1587 config_attributes
1588 .iter()
1589 .any(|attribute| attribute.is_custom_data_attribute())
1590 {
1591 return false;
1592 }
1593 },
1594 // Step 17. Otherwise:
1595 None => {
1596 // Step 17.1. If config["elements"] exists:
1597 if let Some(config_elements) = &self.elements {
1598 // Step 17.1.1. For each element of config["elements"]:
1599 for element in config_elements {
1600 // Step 17.1.1.1. If element["attributes"] exists and
1601 // element["removeAttributes"] exists, then return false.
1602 if element.attributes().is_some() && element.remove_attributes().is_some() {
1603 return false;
1604 }
1605
1606 // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
1607 // has duplicates, then return false.
1608 if element
1609 .attributes()
1610 .as_ref()
1611 .is_some_and(|element_attributes| element_attributes.has_duplicates())
1612 {
1613 return false;
1614 }
1615
1616 // Step 17.1.1.3. If element["removeAttributes"] exist and
1617 // element["removeAttributes"] has duplicates, then return false.
1618 if element.remove_attributes().as_ref().is_some_and(
1619 |element_remove_attributes| element_remove_attributes.has_duplicates(),
1620 ) {
1621 return false;
1622 }
1623
1624 // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
1625 // element["attributes"] with default « » is not empty, then return false.
1626 if self
1627 .removeAttributes
1628 .as_ref()
1629 .is_some_and(|config_remove_attributes| {
1630 config_remove_attributes.is_intersection_non_empty(
1631 element.attributes().unwrap_or_default(),
1632 )
1633 })
1634 {
1635 return false;
1636 }
1637
1638 // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
1639 // element["removeAttributes"] with default « » is not empty, then return
1640 // false.
1641 if self
1642 .removeAttributes
1643 .as_ref()
1644 .is_some_and(|config_remove_attributes| {
1645 config_remove_attributes.is_intersection_non_empty(
1646 element.remove_attributes().unwrap_or_default(),
1647 )
1648 })
1649 {
1650 return false;
1651 }
1652 }
1653 }
1654
1655 // Step 17.2. If config["dataAttributes"] exists, then return false.
1656 if self.dataAttributes.is_some() {
1657 return false;
1658 }
1659 },
1660 }
1661
1662 // Step 18. Return true.
1663 true
1664 }
1665
1666 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1667 fn remove_element(&mut self, element: SanitizerElement) -> bool {
1668 // Step 1. Assert: configuration is valid.
1669 debug_assert!(self.is_valid());
1670
1671 // Step 2. Set element to the result of canonicalize a sanitizer element with element.
1672 let element = element.canonicalize();
1673
1674 // Step 3. Set modified to the result of remove element from
1675 // configuration["replaceWithChildrenElements"].
1676 let modified = if let Some(configuration_replace_with_children_elements) =
1677 &mut self.replaceWithChildrenElements
1678 {
1679 configuration_replace_with_children_elements.remove_item(&element)
1680 } else {
1681 false
1682 };
1683
1684 // Step 4. If configuration["elements"] exists:
1685 if let Some(configuration_elements) = &mut self.elements {
1686 // Step 4.1. If configuration["elements"] contains element:
1687 if configuration_elements.contains_item(&element) {
1688 // Step 4.1.1. Comment: We have a global allow list and it contains element.
1689
1690 // Step 4.1.2. Remove element from configuration["elements"].
1691 configuration_elements.remove_item(&element);
1692
1693 // Step 4.1.3. Return true.
1694 return true;
1695 }
1696
1697 // Step 4.2. Comment: We have a global allow list and it does not contain element.
1698
1699 // Step 4.3. Return modified.
1700 modified
1701 }
1702 // Step 5. Otherwise:
1703 else {
1704 // Step 5.1. If configuration["removeElements"] contains element:
1705 if self
1706 .removeElements
1707 .as_mut()
1708 .is_some_and(|configuration_remove_elements| {
1709 configuration_remove_elements.contains_item(&element)
1710 })
1711 {
1712 // Step 5.1.1. Comment: We have a global remove list and it already contains element.
1713
1714 // Step 5.1.2. Return modified.
1715 return modified;
1716 }
1717
1718 // Step 5.2. Comment: We have a global remove list and it does not contain element.
1719
1720 // Step 5.3. Add element to configuration["removeElements"].
1721 if let Some(configuration_remove_elements) = &mut self.removeElements {
1722 configuration_remove_elements.add_item(element);
1723 } else {
1724 self.removeElements = Some(vec![element]);
1725 }
1726
1727 // Step 5.4. Return true.
1728 true
1729 }
1730 }
1731
1732 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1733 fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool {
1734 // Step 1. Assert: configuration is valid.
1735 debug_assert!(self.is_valid());
1736
1737 // Step 2. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1738 let attribute = attribute.canonicalize();
1739
1740 // Step 3. If configuration["attributes"] exists:
1741 if self.attributes.is_some() {
1742 // Step 3.1. Comment: If we have a global allow-list, we need to remove attribute.
1743
1744 // Step 3.2. Set modified to the result of remove attribute from
1745 // configuration["attributes"].
1746 let mut modified = self
1747 .attributes
1748 .as_mut()
1749 .is_some_and(|configuration_attributes| {
1750 configuration_attributes.remove_item(&attribute)
1751 });
1752
1753 // Step 3.3. Comment: Fix-up per-element allow and remove lists.
1754
1755 // Step 3.4. If configuration["elements"] exists:
1756 if let Some(configuration_elements) = &mut self.elements {
1757 // Step 3.4.1. For each element of configuration["elements"]:
1758 for element in configuration_elements {
1759 // Step 3.4.1.1. If element["attributes"] with default « » contains attribute:
1760 if element
1761 .attributes()
1762 .unwrap_or_default()
1763 .contains(&attribute)
1764 {
1765 // Step 3.4.1.1.1. Set modified to true.
1766 modified = true;
1767
1768 // Step 3.4.1.1.2. Remove attribute from element["attributes"].
1769 if let Some(element_attributes) = element.attributes_mut() {
1770 element_attributes
1771 .retain(|element_attribute| *element_attribute != attribute);
1772 }
1773 }
1774
1775 // Step 3.4.1.2. If element["removeAttributes"] with default « » contains
1776 // attribute:
1777 if element
1778 .remove_attributes()
1779 .unwrap_or_default()
1780 .contains(&attribute)
1781 {
1782 // Step 3.4.1.2.1. Assert: modified is true.
1783 assert!(modified);
1784
1785 // Step 3.4.1.2.2. Remove attribute from element["removeAttributes"].
1786 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1787 element_remove_attributes.retain(|element_remove_attribute| {
1788 *element_remove_attribute != attribute
1789 });
1790 }
1791 }
1792 }
1793 }
1794
1795 // Step 3.5. Return modified.
1796 modified
1797 }
1798 // Step 4. Otherwise:
1799 else {
1800 // Step 4.1. Comment: If we have a global remove-list, we need to add attribute.
1801
1802 // Step 4.2. If configuration["removeAttributes"] contains attribute return false.
1803 if self
1804 .removeAttributes
1805 .as_ref()
1806 .is_some_and(|configuration_remove_attributes| {
1807 configuration_remove_attributes.contains(&attribute)
1808 })
1809 {
1810 return false;
1811 }
1812
1813 // Step 4.3. Comment: Fix-up per-element allow and remove lists.
1814
1815 // Step 4.4. If configuration["elements"] exists:
1816 if let Some(configuration_elements) = &mut self.elements {
1817 // Step 4.4.1. For each element in configuration["elements"]:
1818 for element in configuration_elements {
1819 // Step 4.4.1.1. If element["attributes"] with default « » contains attribute:
1820 // Step 4.4.1.1.1. Remove attribute from element["attributes"].
1821 if let Some(element_attributes) = element.attributes_mut() {
1822 element_attributes
1823 .retain(|element_attribute| *element_attribute != attribute);
1824 }
1825
1826 // Step 4.4.1.2. If element["removeAttributes"] with default « » contains
1827 // attribute:
1828 // Step 4.4.1.2.1. Remove attribute from element["removeAttributes"].
1829 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1830 element_remove_attributes.retain(|element_remove_attribute| {
1831 *element_remove_attribute != attribute
1832 });
1833 }
1834 }
1835 }
1836
1837 // Step 4.5. Append attribute to configuration["removeAttributes"]
1838 if let Some(configuration_remove_attributes) = &mut self.removeAttributes {
1839 configuration_remove_attributes.push(attribute);
1840 } else {
1841 self.removeAttributes = Some(vec![attribute]);
1842 }
1843
1844 // Step 4.6. Return true.
1845 true
1846 }
1847 }
1848
1849 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1850 fn remove_unsafe(&mut self) -> bool {
1851 // Step 1. Assert: The key set of built-in safe baseline configuration equals « [
1852 // "removeElements", "removeAttributes" ] ».
1853 let baseline = built_in_safe_baseline_configuration();
1854 assert!(baseline.removeElements.is_some() && baseline.removeAttributes.is_some());
1855
1856 // Step 2. Assert: configuration is valid.
1857 debug_assert!(self.is_valid());
1858
1859 // Step 3. Let result be false.
1860 let mut result = false;
1861
1862 // Step 4. For each element in built-in safe baseline configuration["removeElements"]:
1863 for element in baseline.removeElements.unwrap_or_default() {
1864 // Step 4.1. Call remove an element element from configuration.
1865 // Step 4.2. If the call returned true, set result to true.
1866 if self.remove_element(element) {
1867 result = true;
1868 }
1869 }
1870
1871 // Step 5. For each attribute in built-in safe baseline configuration["removeAttributes"]:
1872 for attribute in baseline.removeAttributes.unwrap_or_default() {
1873 // Step 5.1. Call remove an attribute attribute from configuration.
1874 // Step 5.2. If the call returned true, set result to true.
1875 if self.remove_attribute(attribute) {
1876 result = true;
1877 }
1878 }
1879
1880 // Step 6. For each attribute listed in event handler content attributes:
1881 for attribute in CONTENT_EVENT_HANDLER_NAMES.iter() {
1882 // Step 6.1. Call remove an attribute attribute from configuration.
1883 // Step 6.2. If the call returned true, set result to true.
1884 let attribute = SanitizerAttribute::String(DOMString::from(*attribute));
1885 if self.remove_attribute(attribute) {
1886 result = true;
1887 }
1888 }
1889
1890 // Step 7. Return result.
1891 result
1892 }
1893
1894 /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1895 fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool) {
1896 // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
1897 // then set configuration["removeElements"] to « ».
1898 if self.elements.is_none() && self.removeElements.is_none() {
1899 self.removeElements = Some(Vec::new());
1900 }
1901
1902 // Step 2. If neither configuration["processingInstructions"] nor
1903 // configuration["removeProcessingInstructions"] exist:
1904 if self.processingInstructions.is_none() && self.removeProcessingInstructions.is_none() {
1905 // Step 2.1. If allowCommentsPIsAndDataAttributes is true, then set
1906 // configuration["removeProcessingInstructions"] to « ».
1907 if allow_comments_pis_and_data_attributes {
1908 self.removeProcessingInstructions = Some(Vec::new());
1909 }
1910 // Step 2.2. Otherwise, set configuration["processingInstructions"] to « ».
1911 else {
1912 self.processingInstructions = Some(Vec::new());
1913 }
1914 }
1915
1916 // Step 3. If neither configuration["attributes"] nor configuration["removeAttributes"]
1917 // exist, then set configuration["removeAttributes"] to « ».
1918 if self.attributes.is_none() && self.removeAttributes.is_none() {
1919 self.removeAttributes = Some(Vec::new());
1920 }
1921
1922 // Step 4. If configuration["elements"] exists:
1923 if let Some(elements) = &mut self.elements {
1924 // Step 4.1. Let elements be « ».
1925 // Step 4.2. For each element of configuration["elements"] do:
1926 // Step 4.2.1. Append the result of canonicalize a sanitizer element with attributes
1927 // element to elements.
1928 // Step 4.3. Set configuration["elements"] to elements.
1929 *elements = elements
1930 .iter()
1931 .cloned()
1932 .map(SanitizerElementWithAttributes::canonicalize)
1933 .collect();
1934 }
1935
1936 // Step 5. If configuration["removeElements"] exists:
1937 if let Some(remove_elements) = &mut self.removeElements {
1938 // Step 5.1. Let elements be « ».
1939 // Step 5.2. For each element of configuration["removeElements"] do:
1940 // Step 5.2.1. Append the result of canonicalize a sanitizer element element to
1941 // elements.
1942 // Step 5.3. Set configuration["removeElements"] to elements.
1943 *remove_elements = remove_elements
1944 .iter()
1945 .cloned()
1946 .map(SanitizerElement::canonicalize)
1947 .collect();
1948 }
1949
1950 // Step 6. If configuration["replaceWithChildrenElements"] exists:
1951 if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
1952 // Step 6.1. Let elements be « ».
1953 // Step 6.2. For each element of configuration["replaceWithChildrenElements"] do:
1954 // Step 6.2.1. Append the result of canonicalize a sanitizer element element to
1955 // elements.
1956 // Step 6.3. Set configuration["replaceWithChildrenElements"] to elements.
1957 *replace_with_children_elements = replace_with_children_elements
1958 .iter()
1959 .cloned()
1960 .map(SanitizerElement::canonicalize)
1961 .collect();
1962 }
1963
1964 // Step 7. If configuration["processingInstructions"] exists:
1965 if let Some(processing_instructions) = &mut self.processingInstructions {
1966 // Step 7.1. Let processingInstructions be « ».
1967 // Step 7.2. For each pi of configuration["processingInstructions"]:
1968 // Step 7.2.1. Append the result of canonicalize a sanitizer processing instruction pi
1969 // to processingInstructions.
1970 // Step 7.3. Set configuration["processingInstructions"] to processingInstructions.
1971 *processing_instructions = processing_instructions
1972 .iter()
1973 .cloned()
1974 .map(SanitizerPI::canonicalize)
1975 .collect();
1976 }
1977
1978 // Step 8. If configuration["removeProcessingInstructions"] exists:
1979 if let Some(remove_processing_instructions) = &mut self.removeProcessingInstructions {
1980 // Step 8.1. Let processingInstructions be « ».
1981 // Step 8.2. For each pi of configuration["removeProcessingInstructions"]:
1982 // Step 8.2.1. Append the result of canonicalize a sanitizer processing instruction
1983 // pi to processingInstructions.
1984 // Step 8.3. Set configuration["removeProcessingInstructions"] to
1985 // processingInstructions.
1986 *remove_processing_instructions = remove_processing_instructions
1987 .iter()
1988 .cloned()
1989 .map(SanitizerPI::canonicalize)
1990 .collect();
1991 }
1992
1993 // Step 9. If configuration["attributes"] exists:
1994 if let Some(attributes) = &mut self.attributes {
1995 // Step 9.1. Let attributes be « ».
1996 // Step 9.2. For each attribute of configuration["attributes"] do:
1997 // Step 9.2.1. Append the result of canonicalize a sanitizer attribute attribute to
1998 // attributes.
1999 // Step 9.3. Set configuration["attributes"] to attributes.
2000 *attributes = attributes
2001 .iter()
2002 .cloned()
2003 .map(SanitizerAttribute::canonicalize)
2004 .collect();
2005 }
2006
2007 // Step 10. If configuration["removeAttributes"] exists:
2008 if let Some(remove_attributes) = &mut self.removeAttributes {
2009 // Step 10.1. Let attributes be « ».
2010 // Step 10.2. For each attribute of configuration["removeAttributes"] do:
2011 // Step 10.2.1. Append the result of canonicalize a sanitizer attribute attribute to
2012 // attributes.
2013 // Step 10.3. Set configuration["removeAttributes"] to attributes.
2014 *remove_attributes = remove_attributes
2015 .iter()
2016 .cloned()
2017 .map(SanitizerAttribute::canonicalize)
2018 .collect();
2019 }
2020
2021 // Step 11. If configuration["comments"] does not exist, then set configuration["comments"]
2022 // to allowCommentsPIsAndDataAttributes.
2023 if self.comments.is_none() {
2024 self.comments = Some(allow_comments_pis_and_data_attributes);
2025 }
2026
2027 // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
2028 // not exist, then set configuration["dataAttributes"] to allowCommentsPIsAndDataAttributes.
2029 if self.attributes.is_some() && self.dataAttributes.is_none() {
2030 self.dataAttributes = Some(allow_comments_pis_and_data_attributes);
2031 }
2032 }
2033}
2034
2035trait Canonicalization {
2036 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
2037 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2038 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2039 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2040 fn canonicalize(self) -> Self;
2041}
2042
2043impl Canonicalization for SanitizerElementWithAttributes {
2044 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
2045 fn canonicalize(mut self) -> Self {
2046 // Step 1. Let result be the result of canonicalize a sanitizer element with element.
2047 let parent = match &mut self {
2048 SanitizerElementWithAttributes::String(name) => {
2049 SanitizerElement::String(std::mem::take(name))
2050 },
2051 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2052 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2053 name: std::mem::take(&mut dictionary.parent.name),
2054 namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
2055 })
2056 },
2057 };
2058 let mut canonicalized_parent = parent.canonicalize();
2059 let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2060 SanitizerElementNamespaceWithAttributes {
2061 parent: SanitizerElementNamespace {
2062 name: std::mem::take(canonicalized_parent.name_mut()),
2063 namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
2064 },
2065 attributes: None,
2066 removeAttributes: None,
2067 },
2068 );
2069
2070 // Step 2. If element is a dictionary:
2071 if matches!(
2072 self,
2073 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
2074 ) {
2075 // Step 2.1. If element["attributes"] exists:
2076 if let Some(attributes) = self.attributes() {
2077 // Step 2.1.1. Let attributes be « ».
2078 // Step 2.1.2. For each attribute of element["attributes"]:
2079 // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
2080 // attribute to attributes.
2081 let attributes = attributes
2082 .iter()
2083 .cloned()
2084 .map(|attribute| attribute.canonicalize())
2085 .collect();
2086
2087 // Step 2.1.3. Set result["attributes"] to attributes.
2088 result.set_attributes(Some(attributes));
2089 }
2090
2091 // Step 2.2. If element["removeAttributes"] exists:
2092 if let Some(remove_attributes) = self.remove_attributes() {
2093 // Step 2.2.1. Let attributes be « ».
2094 // Step 2.2.2. For each attribute of element["removeAttributes"]:
2095 // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
2096 // attribute to attributes.
2097 let attributes = remove_attributes
2098 .iter()
2099 .cloned()
2100 .map(|attribute| attribute.canonicalize())
2101 .collect();
2102
2103 // Step 2.2.3. Set result["removeAttributes"] to attributes.
2104 result.set_remove_attributes(Some(attributes));
2105 }
2106 }
2107
2108 // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
2109 if result.attributes().is_none() && result.remove_attributes().is_none() {
2110 // Step 3.1. Set result["removeAttributes"] to « ».
2111 result.set_remove_attributes(Some(Vec::new()));
2112 }
2113
2114 // Step 4. Return result.
2115 result
2116 }
2117}
2118
2119impl Canonicalization for SanitizerElement {
2120 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2121 fn canonicalize(self) -> Self {
2122 // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
2123 // the default namespace.
2124 self.canonicalize_name(Some(ns!(html).to_string()))
2125 }
2126}
2127impl Canonicalization for SanitizerPI {
2128 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2129 fn canonicalize(self) -> Self {
2130 // Step 1. Assert: pi is either a DOMString or a dictionary.
2131 assert!(matches!(
2132 self,
2133 SanitizerPI::String(_) | SanitizerPI::SanitizerProcessingInstruction(_)
2134 ));
2135
2136 // Step 2. If pi is a DOMString, then return «[ "target" → pi ]».
2137 if let SanitizerPI::String(target) = self {
2138 return SanitizerPI::SanitizerProcessingInstruction(SanitizerProcessingInstruction {
2139 target,
2140 });
2141 }
2142
2143 // Step 3. Assert: pi is a dictionary and pi["target"] exists.
2144 // NOTE: The latter is guaranteed by Rust type system.
2145 assert!(matches!(
2146 self,
2147 SanitizerPI::SanitizerProcessingInstruction(_)
2148 ));
2149
2150 // Step 4. Return «[ "target" → pi["target"] ]».
2151 self
2152 }
2153}
2154
2155impl Canonicalization for SanitizerAttribute {
2156 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2157 fn canonicalize(self) -> Self {
2158 // Return the result of canonicalize a sanitizer name with attribute and null as the default
2159 // namespace.
2160 self.canonicalize_name(None)
2161 }
2162}
2163
2164trait NameCanonicalization: NameMember {
2165 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
2166 fn is_string(&self) -> bool;
2167 fn is_dictionary(&self) -> bool;
2168
2169 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
2170 fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
2171 // Step 1. Assert: name is either a DOMString or a dictionary.
2172 assert!(self.is_string() || self.is_dictionary());
2173
2174 // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
2175 // defaultNamespace]».
2176 if self.is_string() {
2177 return Self::new_dictionary(
2178 std::mem::take(self.name_mut()),
2179 default_namespace.map(DOMString::from),
2180 );
2181 }
2182
2183 // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
2184 // NOTE: The latter is guaranteed by Rust type system.
2185 assert!(self.is_dictionary());
2186
2187 // Step 4. If name["namespace"] is the empty string, then set it to null.
2188 if self
2189 .namespace()
2190 .is_some_and(|namespace| namespace.str() == "")
2191 {
2192 self.set_namespace(None);
2193 }
2194
2195 // Step 5. Return «[
2196 // "name" → name["name"],
2197 // "namespace" → name["namespace"]
2198 // ]».
2199 Self::new_dictionary(
2200 std::mem::take(self.name_mut()),
2201 self.namespace_mut().map(std::mem::take),
2202 )
2203 }
2204}
2205
2206impl NameCanonicalization for SanitizerElement {
2207 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2208 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
2209 }
2210
2211 fn is_string(&self) -> bool {
2212 matches!(self, SanitizerElement::String(_))
2213 }
2214
2215 fn is_dictionary(&self) -> bool {
2216 matches!(self, SanitizerElement::SanitizerElementNamespace(_))
2217 }
2218}
2219
2220impl NameCanonicalization for SanitizerAttribute {
2221 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2222 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2223 name,
2224 namespace,
2225 })
2226 }
2227
2228 fn is_string(&self) -> bool {
2229 matches!(self, SanitizerAttribute::String(_))
2230 }
2231
2232 fn is_dictionary(&self) -> bool {
2233 matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
2234 }
2235}
2236
2237/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2238trait NameSlice<T>
2239where
2240 T: NameMember + Canonicalization + Clone,
2241{
2242 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2243 fn contains_item<S: NameMember>(&self, other: &S) -> bool;
2244
2245 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2246 fn has_duplicates(&self) -> bool;
2247
2248 /// Custom version of the supporting algorithm
2249 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2250 /// intersection is non-empty, returning early if it is non-empty for efficiency.
2251 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2252 where
2253 S: NameMember + Canonicalization + Clone;
2254}
2255
2256impl<T> NameSlice<T> for [T]
2257where
2258 T: NameMember + Canonicalization + Clone,
2259{
2260 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2261 fn contains_item<S: NameMember>(&self, other: &S) -> bool {
2262 // A Sanitizer name list contains an item if there exists an entry of list that is an
2263 // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
2264 // entry["namespace"].
2265 self.iter()
2266 .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
2267 }
2268
2269 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2270 fn has_duplicates(&self) -> bool {
2271 // A list list has duplicates, if for any item of list, there is more than one entry in list
2272 // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
2273 let mut used = HashSet::new();
2274 self.iter().any(move |entry| {
2275 !used.insert((
2276 entry.name().to_string(),
2277 entry.namespace().map(DOMString::to_string),
2278 ))
2279 })
2280 }
2281
2282 /// Custom version of the supporting algorithm
2283 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2284 /// intersection is non-empty, returning early if it is non-empty for efficiency.
2285 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2286 where
2287 S: NameMember + Canonicalization + Clone,
2288 {
2289 // Step 1. Let set A be « [] ».
2290 // Step 2. Let set B be « [] ».
2291 // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
2292 // set A.
2293 // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
2294 // set B.
2295 let a = self.iter().map(|entry| entry.clone().canonicalize());
2296 let b = others
2297 .iter()
2298 .map(|entry| entry.clone().canonicalize())
2299 .collect::<Vec<S>>();
2300
2301 // Step 5. Return the intersection of set A and set B.
2302 // NOTE: Instead of returning the intersection itself, return true if the intersection is
2303 // non-empty, and false otherwise.
2304 a.filter(|entry| {
2305 b.iter()
2306 .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
2307 })
2308 .any(|_| true)
2309 }
2310}
2311
2312/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2313trait NameVec<T>
2314where
2315 T: NameMember + Canonicalization + Clone,
2316{
2317 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2318 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
2319
2320 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2321 fn add_item(&mut self, name: T);
2322
2323 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2324 fn remove_duplicates(&mut self) -> &mut Self;
2325
2326 /// Set itself to the set intersection of itself and another list.
2327 ///
2328 /// <https://infra.spec.whatwg.org/#set-intersection>
2329 fn intersection<S>(&mut self, others: &[S])
2330 where
2331 S: NameMember + Canonicalization + Clone;
2332
2333 /// <https://infra.spec.whatwg.org/#set-difference>
2334 fn difference(&mut self, others: &[T]);
2335}
2336
2337impl<T> NameVec<T> for Vec<T>
2338where
2339 T: NameMember + Canonicalization + Clone,
2340{
2341 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2342 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
2343 // Step 1. Set removed to false.
2344 let mut removed = false;
2345
2346 // Step 2. For each entry of list:
2347 // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
2348 // Step 2.1.1. Remove item entry from list.
2349 // Step 2.1.2. Set removed to true.
2350 self.retain(|entry| {
2351 let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
2352 if matched {
2353 removed = true;
2354 }
2355 !matched
2356 });
2357
2358 // Step 3. Return removed.
2359 removed
2360 }
2361
2362 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2363 fn add_item(&mut self, name: T) {
2364 // Step 1. If list contains name, then return.
2365 if self.contains_item(&name) {
2366 return;
2367 };
2368
2369 // Step 2. Append name to list.
2370 self.push(name);
2371 }
2372
2373 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2374 fn remove_duplicates(&mut self) -> &mut Self {
2375 // Step 1. Let result be « ».
2376 // Step 2. For each entry of list, add entry to result.
2377 // Step 3. Return result.
2378 self.sort_by(|item_a, item_b| item_a.compare(item_b));
2379 self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
2380 self
2381 }
2382
2383 /// Set itself to the set intersection of itself and another list.
2384 ///
2385 /// <https://infra.spec.whatwg.org/#set-intersection>
2386 fn intersection<S>(&mut self, others: &[S])
2387 where
2388 S: NameMember + Canonicalization + Clone,
2389 {
2390 // The intersection of ordered sets A and B, is the result of creating a new ordered set set
2391 // and, for each item of A, if B contains item, appending item to set.
2392 self.retain(|item| {
2393 others
2394 .iter()
2395 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2396 })
2397 }
2398
2399 /// Set itself to the set difference of itself and another list.
2400 ///
2401 /// <https://infra.spec.whatwg.org/#set-difference>
2402 fn difference(&mut self, others: &[T]) {
2403 // The difference of ordered sets A and B, is the result of creating a new ordered set set
2404 // and, for each item of A, if B does not contain item, appending item to set.
2405 self.retain(|item| {
2406 !others
2407 .iter()
2408 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2409 })
2410 }
2411}
2412
2413/// Helper functions for accessing the "name" and "namespace" members of
2414/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
2415trait NameMember: Sized {
2416 fn name(&self) -> &DOMString;
2417 fn name_mut(&mut self) -> &mut DOMString;
2418 fn namespace(&self) -> Option<&DOMString>;
2419 fn namespace_mut(&mut self) -> Option<&mut DOMString>;
2420
2421 fn set_namespace(&mut self, namespace: Option<&str>);
2422
2423 // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
2424 fn is_less_than_item(&self, item_b: &Self) -> bool {
2425 let item_a = self;
2426 match item_a.namespace() {
2427 // Step 1. If itemA["namespace"] is null:
2428 None => {
2429 // Step 1.1. If itemB["namespace"] is not null, then return true.
2430 if item_b.namespace().is_some() {
2431 return true;
2432 }
2433 },
2434 // Step 2. Otherwise:
2435 Some(item_a_namespace) => {
2436 // Step 2.1. If itemB["namespace"] is null, then return false.
2437 if item_b.namespace().is_none() {
2438 return false;
2439 }
2440
2441 // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
2442 // return true.
2443 if item_b
2444 .namespace()
2445 .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
2446 {
2447 return true;
2448 }
2449
2450 // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
2451 if item_b
2452 .namespace()
2453 .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
2454 {
2455 return false;
2456 }
2457 },
2458 }
2459
2460 // Step 3. Return itemA["name"] is code unit less than itemB["name"].
2461 item_a.name() < item_b.name()
2462 }
2463
2464 /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
2465 fn compare(&self, other: &Self) -> Ordering {
2466 if self.is_less_than_item(other) {
2467 Ordering::Less
2468 } else {
2469 Ordering::Greater
2470 }
2471 }
2472
2473 /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2474 /// ['SanitizerAttribute']. For other types such as ['SanitizerElementWithAttributes'] and
2475 /// [`SanitizerElement`], return false by default.
2476 fn is_custom_data_attribute(&self) -> bool {
2477 false
2478 }
2479}
2480
2481impl NameMember for SanitizerElementWithAttributes {
2482 fn name(&self) -> &DOMString {
2483 match self {
2484 SanitizerElementWithAttributes::String(name) => name,
2485 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2486 &dictionary.parent.name
2487 },
2488 }
2489 }
2490
2491 fn name_mut(&mut self) -> &mut DOMString {
2492 match self {
2493 SanitizerElementWithAttributes::String(name) => name,
2494 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2495 &mut dictionary.parent.name
2496 },
2497 }
2498 }
2499
2500 fn namespace(&self) -> Option<&DOMString> {
2501 match self {
2502 SanitizerElementWithAttributes::String(_) => None,
2503 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2504 dictionary.parent.namespace.as_ref()
2505 },
2506 }
2507 }
2508
2509 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2510 match self {
2511 SanitizerElementWithAttributes::String(_) => None,
2512 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2513 dictionary.parent.namespace.as_mut()
2514 },
2515 }
2516 }
2517
2518 fn set_namespace(&mut self, namespace: Option<&str>) {
2519 match self {
2520 SanitizerElementWithAttributes::String(name) => {
2521 let new_instance =
2522 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2523 SanitizerElementNamespaceWithAttributes {
2524 parent: SanitizerElementNamespace {
2525 name: std::mem::take(name),
2526 namespace: namespace.map(DOMString::from),
2527 },
2528 attributes: None,
2529 removeAttributes: None,
2530 },
2531 );
2532 *self = new_instance;
2533 },
2534 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2535 dictionary.parent.namespace = namespace.map(DOMString::from);
2536 },
2537 }
2538 }
2539}
2540
2541impl NameMember for SanitizerElement {
2542 fn name(&self) -> &DOMString {
2543 match self {
2544 SanitizerElement::String(name) => name,
2545 SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
2546 }
2547 }
2548
2549 fn name_mut(&mut self) -> &mut DOMString {
2550 match self {
2551 SanitizerElement::String(name) => name,
2552 SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
2553 }
2554 }
2555
2556 fn namespace(&self) -> Option<&DOMString> {
2557 match self {
2558 SanitizerElement::String(_) => None,
2559 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2560 dictionary.namespace.as_ref()
2561 },
2562 }
2563 }
2564
2565 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2566 match self {
2567 SanitizerElement::String(_) => None,
2568 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2569 dictionary.namespace.as_mut()
2570 },
2571 }
2572 }
2573
2574 fn set_namespace(&mut self, namespace: Option<&str>) {
2575 match self {
2576 SanitizerElement::String(name) => {
2577 let new_instance =
2578 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2579 name: std::mem::take(name),
2580 namespace: namespace.map(DOMString::from),
2581 });
2582 *self = new_instance;
2583 },
2584 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2585 dictionary.namespace = namespace.map(DOMString::from);
2586 },
2587 }
2588 }
2589}
2590
2591impl NameMember for SanitizerAttribute {
2592 fn name(&self) -> &DOMString {
2593 match self {
2594 SanitizerAttribute::String(name) => name,
2595 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
2596 }
2597 }
2598
2599 fn name_mut(&mut self) -> &mut DOMString {
2600 match self {
2601 SanitizerAttribute::String(name) => name,
2602 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
2603 }
2604 }
2605
2606 fn namespace(&self) -> Option<&DOMString> {
2607 match self {
2608 SanitizerAttribute::String(_) => None,
2609 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2610 dictionary.namespace.as_ref()
2611 },
2612 }
2613 }
2614
2615 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2616 match self {
2617 SanitizerAttribute::String(_) => None,
2618 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2619 dictionary.namespace.as_mut()
2620 },
2621 }
2622 }
2623
2624 fn set_namespace(&mut self, namespace: Option<&str>) {
2625 match self {
2626 SanitizerAttribute::String(name) => {
2627 let new_instance =
2628 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2629 name: std::mem::take(name),
2630 namespace: namespace.map(DOMString::from),
2631 });
2632 *self = new_instance;
2633 },
2634 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2635 dictionary.namespace = namespace.map(DOMString::from);
2636 },
2637 }
2638 }
2639
2640 /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2641 /// ['SanitizerAttribute'].
2642 fn is_custom_data_attribute(&self) -> bool {
2643 is_custom_data_attribute(
2644 &self.name().str(),
2645 self.namespace().map(|namespace| namespace.str()).as_deref(),
2646 )
2647 }
2648}
2649
2650/// Helper functions for accessing the "attributes" and "removeAttributes" members of
2651/// [`SanitizerElementWithAttributes`].
2652trait AttributeMember {
2653 fn attributes(&self) -> Option<&[SanitizerAttribute]>;
2654 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2655 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
2656 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2657
2658 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
2659 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
2660}
2661
2662impl AttributeMember for SanitizerElementWithAttributes {
2663 fn attributes(&self) -> Option<&[SanitizerAttribute]> {
2664 match self {
2665 SanitizerElementWithAttributes::String(_) => None,
2666 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2667 dictionary.attributes.as_deref()
2668 },
2669 }
2670 }
2671
2672 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2673 match self {
2674 SanitizerElementWithAttributes::String(_) => None,
2675 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2676 dictionary.attributes.as_mut()
2677 },
2678 }
2679 }
2680
2681 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
2682 match self {
2683 SanitizerElementWithAttributes::String(_) => None,
2684 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2685 dictionary.removeAttributes.as_deref()
2686 },
2687 }
2688 }
2689
2690 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2691 match self {
2692 SanitizerElementWithAttributes::String(_) => None,
2693 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2694 dictionary.removeAttributes.as_mut()
2695 },
2696 }
2697 }
2698
2699 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
2700 match self {
2701 SanitizerElementWithAttributes::String(name) => {
2702 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2703 SanitizerElementNamespaceWithAttributes {
2704 parent: SanitizerElementNamespace {
2705 name: std::mem::take(name),
2706 namespace: None,
2707 },
2708 attributes,
2709 removeAttributes: None,
2710 },
2711 );
2712 },
2713 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2714 dictionary.attributes = attributes;
2715 },
2716 }
2717 }
2718
2719 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
2720 match self {
2721 SanitizerElementWithAttributes::String(name) => {
2722 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2723 SanitizerElementNamespaceWithAttributes {
2724 parent: SanitizerElementNamespace {
2725 name: std::mem::take(name),
2726 namespace: None,
2727 },
2728 attributes: None,
2729 removeAttributes: remove_attributes,
2730 },
2731 );
2732 },
2733 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2734 dictionary.removeAttributes = remove_attributes;
2735 },
2736 }
2737 }
2738}
2739
2740/// Helper functions for accessing the "target" members of [`SanitizerPI`].
2741trait TargetMember {
2742 fn target(&self) -> &DOMString;
2743}
2744
2745impl TargetMember for SanitizerPI {
2746 fn target(&self) -> &DOMString {
2747 match self {
2748 SanitizerPI::String(string) => string,
2749 SanitizerPI::SanitizerProcessingInstruction(dictionary) => &dictionary.target,
2750 }
2751 }
2752}
2753
2754/// Supporting algorithms on lists of processing instructions, from the specification.
2755trait TargetSlice<T>
2756where
2757 T: TargetMember,
2758{
2759 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2760 fn contains_target(&self, other: &T) -> bool;
2761
2762 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2763 fn has_duplicate_targets(&self) -> bool;
2764}
2765
2766impl<T> TargetSlice<T> for [T]
2767where
2768 T: TargetMember,
2769{
2770 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2771 fn contains_target(&self, other: &T) -> bool {
2772 // A Sanitizer target list contains a target target if there exists an entry of list that is
2773 // an ordered map, and where target equals entry["target"].
2774 self.iter().any(|entry| entry.target() == other.target())
2775 }
2776
2777 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2778 fn has_duplicate_targets(&self) -> bool {
2779 // A list list has duplicate targets, if for any item of list, there is more than one entry
2780 // in list where item["target"] is entry["target"].
2781 let mut used = HashSet::new();
2782 self.iter()
2783 .any(move |entry| !used.insert(entry.target().to_string()))
2784 }
2785}
2786
2787/// Helper functions for accessing the "sanitizer" members of [`SetHTMLOptions`] and
2788/// [`SetHTMLUnsafeOptions`].
2789pub(crate) trait SanitizerMember {
2790 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets;
2791}
2792
2793impl SanitizerMember for SetHTMLOptions {
2794 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2795 &self.sanitizer
2796 }
2797}
2798
2799impl SanitizerMember for SetHTMLUnsafeOptions {
2800 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2801 &self.sanitizer
2802 }
2803}
2804
2805/// <https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration>
2806fn built_in_safe_default_configuration() -> SanitizerConfig {
2807 const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
2808 ("math", &ns!(mathml), &[]),
2809 ("merror", &ns!(mathml), &[]),
2810 ("mfrac", &ns!(mathml), &[]),
2811 ("mi", &ns!(mathml), &[]),
2812 ("mmultiscripts", &ns!(mathml), &[]),
2813 ("mn", &ns!(mathml), &[]),
2814 (
2815 "mo",
2816 &ns!(mathml),
2817 &[
2818 "fence",
2819 "form",
2820 "largeop",
2821 "lspace",
2822 "maxsize",
2823 "minsize",
2824 "movablelimits",
2825 "rspace",
2826 "separator",
2827 "stretchy",
2828 "symmetric",
2829 ],
2830 ),
2831 ("mover", &ns!(mathml), &["accent"]),
2832 (
2833 "mpadded",
2834 &ns!(mathml),
2835 &["depth", "height", "lspace", "voffset", "width"],
2836 ),
2837 ("mphantom", &ns!(mathml), &[]),
2838 ("mprescripts", &ns!(mathml), &[]),
2839 ("mroot", &ns!(mathml), &[]),
2840 ("mrow", &ns!(mathml), &[]),
2841 ("ms", &ns!(mathml), &[]),
2842 ("mspace", &ns!(mathml), &["depth", "height", "width"]),
2843 ("msqrt", &ns!(mathml), &[]),
2844 ("mstyle", &ns!(mathml), &[]),
2845 ("msub", &ns!(mathml), &[]),
2846 ("msubsup", &ns!(mathml), &[]),
2847 ("msup", &ns!(mathml), &[]),
2848 ("mtable", &ns!(mathml), &[]),
2849 ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
2850 ("mtext", &ns!(mathml), &[]),
2851 ("mtr", &ns!(mathml), &[]),
2852 ("munder", &ns!(mathml), &["accentunder"]),
2853 ("munderover", &ns!(mathml), &["accent", "accentunder"]),
2854 ("semantics", &ns!(mathml), &[]),
2855 ("a", &ns!(html), &["href", "hreflang", "type"]),
2856 ("abbr", &ns!(html), &[]),
2857 ("address", &ns!(html), &[]),
2858 ("article", &ns!(html), &[]),
2859 ("aside", &ns!(html), &[]),
2860 ("b", &ns!(html), &[]),
2861 ("bdi", &ns!(html), &[]),
2862 ("bdo", &ns!(html), &[]),
2863 ("blockquote", &ns!(html), &["cite"]),
2864 ("body", &ns!(html), &[]),
2865 ("br", &ns!(html), &[]),
2866 ("caption", &ns!(html), &[]),
2867 ("cite", &ns!(html), &[]),
2868 ("code", &ns!(html), &[]),
2869 ("col", &ns!(html), &["span"]),
2870 ("colgroup", &ns!(html), &["span"]),
2871 ("data", &ns!(html), &["value"]),
2872 ("dd", &ns!(html), &[]),
2873 ("del", &ns!(html), &["cite", "datetime"]),
2874 ("dfn", &ns!(html), &[]),
2875 ("div", &ns!(html), &[]),
2876 ("dl", &ns!(html), &[]),
2877 ("dt", &ns!(html), &[]),
2878 ("em", &ns!(html), &[]),
2879 ("figcaption", &ns!(html), &[]),
2880 ("figure", &ns!(html), &[]),
2881 ("footer", &ns!(html), &[]),
2882 ("h1", &ns!(html), &[]),
2883 ("h2", &ns!(html), &[]),
2884 ("h3", &ns!(html), &[]),
2885 ("h4", &ns!(html), &[]),
2886 ("h5", &ns!(html), &[]),
2887 ("h6", &ns!(html), &[]),
2888 ("head", &ns!(html), &[]),
2889 ("header", &ns!(html), &[]),
2890 ("hgroup", &ns!(html), &[]),
2891 ("hr", &ns!(html), &[]),
2892 ("html", &ns!(html), &[]),
2893 ("i", &ns!(html), &[]),
2894 ("ins", &ns!(html), &["cite", "datetime"]),
2895 ("kbd", &ns!(html), &[]),
2896 ("li", &ns!(html), &["value"]),
2897 ("main", &ns!(html), &[]),
2898 ("mark", &ns!(html), &[]),
2899 ("menu", &ns!(html), &[]),
2900 ("nav", &ns!(html), &[]),
2901 ("ol", &ns!(html), &["reversed", "start", "type"]),
2902 ("p", &ns!(html), &[]),
2903 ("pre", &ns!(html), &[]),
2904 ("q", &ns!(html), &[]),
2905 ("rp", &ns!(html), &[]),
2906 ("rt", &ns!(html), &[]),
2907 ("ruby", &ns!(html), &[]),
2908 ("s", &ns!(html), &[]),
2909 ("samp", &ns!(html), &[]),
2910 ("search", &ns!(html), &[]),
2911 ("section", &ns!(html), &[]),
2912 ("small", &ns!(html), &[]),
2913 ("span", &ns!(html), &[]),
2914 ("strong", &ns!(html), &[]),
2915 ("sub", &ns!(html), &[]),
2916 ("sup", &ns!(html), &[]),
2917 ("table", &ns!(html), &[]),
2918 ("tbody", &ns!(html), &[]),
2919 ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
2920 ("tfoot", &ns!(html), &[]),
2921 (
2922 "th",
2923 &ns!(html),
2924 &["abbr", "colspan", "headers", "rowspan", "scope"],
2925 ),
2926 ("thead", &ns!(html), &[]),
2927 ("time", &ns!(html), &["datetime"]),
2928 ("title", &ns!(html), &[]),
2929 ("tr", &ns!(html), &[]),
2930 ("u", &ns!(html), &[]),
2931 ("ul", &ns!(html), &[]),
2932 ("var", &ns!(html), &[]),
2933 ("wbr", &ns!(html), &[]),
2934 ("a", &ns!(svg), &["href", "hreflang", "type"]),
2935 ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
2936 ("defs", &ns!(svg), &[]),
2937 ("desc", &ns!(svg), &[]),
2938 (
2939 "ellipse",
2940 &ns!(svg),
2941 &["cx", "cy", "pathLength", "rx", "ry"],
2942 ),
2943 ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
2944 ("g", &ns!(svg), &[]),
2945 ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
2946 (
2947 "marker",
2948 &ns!(svg),
2949 &[
2950 "markerHeight",
2951 "markerUnits",
2952 "markerWidth",
2953 "orient",
2954 "preserveAspectRatio",
2955 "refX",
2956 "refY",
2957 "viewBox",
2958 ],
2959 ),
2960 ("metadata", &ns!(svg), &[]),
2961 ("path", &ns!(svg), &["d", "pathLength"]),
2962 ("polygon", &ns!(svg), &["pathLength", "points"]),
2963 ("polyline", &ns!(svg), &["pathLength", "points"]),
2964 (
2965 "rect",
2966 &ns!(svg),
2967 &["height", "pathLength", "rx", "ry", "width", "x", "y"],
2968 ),
2969 (
2970 "svg",
2971 &ns!(svg),
2972 &[
2973 "height",
2974 "preserveAspectRatio",
2975 "viewBox",
2976 "width",
2977 "x",
2978 "y",
2979 ],
2980 ),
2981 (
2982 "text",
2983 &ns!(svg),
2984 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2985 ),
2986 (
2987 "textPath",
2988 &ns!(svg),
2989 &[
2990 "lengthAdjust",
2991 "method",
2992 "path",
2993 "side",
2994 "spacing",
2995 "startOffset",
2996 "textLength",
2997 ],
2998 ),
2999 ("title", &ns!(svg), &[]),
3000 (
3001 "tspan",
3002 &ns!(svg),
3003 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
3004 ),
3005 ];
3006 const ATTRIBUTES: &[&str] = &[
3007 "alignment-baseline",
3008 "baseline-shift",
3009 "clip-path",
3010 "clip-rule",
3011 "color",
3012 "color-interpolation",
3013 "cursor",
3014 "dir",
3015 "direction",
3016 "display",
3017 "displaystyle",
3018 "dominant-baseline",
3019 "fill",
3020 "fill-opacity",
3021 "fill-rule",
3022 "font-family",
3023 "font-size",
3024 "font-size-adjust",
3025 "font-stretch",
3026 "font-style",
3027 "font-variant",
3028 "font-weight",
3029 "lang",
3030 "letter-spacing",
3031 "marker-end",
3032 "marker-mid",
3033 "marker-start",
3034 "mathbackground",
3035 "mathcolor",
3036 "mathsize",
3037 "opacity",
3038 "paint-order",
3039 "pointer-events",
3040 "scriptlevel",
3041 "shape-rendering",
3042 "stop-color",
3043 "stop-opacity",
3044 "stroke",
3045 "stroke-dasharray",
3046 "stroke-dashoffset",
3047 "stroke-linecap",
3048 "stroke-linejoin",
3049 "stroke-miterlimit",
3050 "stroke-opacity",
3051 "stroke-width",
3052 "text-anchor",
3053 "text-decoration",
3054 "text-overflow",
3055 "text-rendering",
3056 "title",
3057 "transform",
3058 "transform-origin",
3059 "unicode-bidi",
3060 "vector-effect",
3061 "visibility",
3062 "white-space",
3063 "word-spacing",
3064 "writing-mode",
3065 ];
3066
3067 let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
3068 attributes
3069 .iter()
3070 .map(|&attribute| {
3071 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
3072 name: attribute.into(),
3073 namespace: None,
3074 })
3075 })
3076 .collect()
3077 };
3078
3079 let elements = ELEMENTS
3080 .iter()
3081 .map(|&(name, namespace, attributes)| {
3082 let attributes = create_attribute_vec(attributes);
3083 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
3084 SanitizerElementNamespaceWithAttributes {
3085 parent: SanitizerElementNamespace {
3086 name: name.into(),
3087 namespace: Some(namespace.to_string().into()),
3088 },
3089 attributes: Some(attributes),
3090 removeAttributes: None,
3091 },
3092 )
3093 })
3094 .collect();
3095
3096 let attributes = create_attribute_vec(ATTRIBUTES);
3097
3098 SanitizerConfig {
3099 elements: Some(elements),
3100 removeElements: None,
3101 replaceWithChildrenElements: None,
3102 processingInstructions: Some(Vec::new()),
3103 removeProcessingInstructions: None,
3104 attributes: Some(attributes),
3105 removeAttributes: None,
3106 comments: Some(false),
3107 dataAttributes: Some(false),
3108 }
3109}
3110
3111/// <https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration>
3112fn built_in_safe_baseline_configuration() -> SanitizerConfig {
3113 const REMOVE_ELEMENTS: &[(&str, &Namespace)] = &[
3114 ("embed", &ns!(html)),
3115 ("frame", &ns!(html)),
3116 ("iframe", &ns!(html)),
3117 ("object", &ns!(html)),
3118 ("script", &ns!(html)),
3119 ("script", &ns!(svg)),
3120 ("use", &ns!(svg)),
3121 ];
3122
3123 let remove_elements = REMOVE_ELEMENTS
3124 .iter()
3125 .map(|&(name, namespace)| {
3126 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3127 name: name.into(),
3128 namespace: Some(namespace.to_string().into()),
3129 })
3130 })
3131 .collect();
3132
3133 SanitizerConfig {
3134 elements: None,
3135 removeElements: Some(remove_elements),
3136 replaceWithChildrenElements: None,
3137 processingInstructions: None,
3138 removeProcessingInstructions: None,
3139 attributes: None,
3140 removeAttributes: Some(Vec::new()),
3141 comments: None,
3142 dataAttributes: None,
3143 }
3144}
3145
3146/// <https://wicg.github.io/sanitizer-api/#built-in-navigating-url-attributes-list>
3147const BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST: &[(
3148 LocalName,
3149 Option<Namespace>,
3150 LocalName,
3151 Option<Namespace>,
3152)] = &[
3153 (local_name!("a"), Some(ns!(html)), local_name!("href"), None),
3154 (
3155 local_name!("area"),
3156 Some(ns!(html)),
3157 local_name!("href"),
3158 None,
3159 ),
3160 (
3161 local_name!("base"),
3162 Some(ns!(html)),
3163 local_name!("href"),
3164 None,
3165 ),
3166 (
3167 local_name!("button"),
3168 Some(ns!(html)),
3169 local_name!("formaction"),
3170 None,
3171 ),
3172 (
3173 local_name!("form"),
3174 Some(ns!(html)),
3175 local_name!("action"),
3176 None,
3177 ),
3178 (
3179 local_name!("input"),
3180 Some(ns!(html)),
3181 local_name!("formaction"),
3182 None,
3183 ),
3184 (local_name!("a"), Some(ns!(svg)), local_name!("href"), None),
3185 (
3186 local_name!("a"),
3187 Some(ns!(svg)),
3188 local_name!("href"),
3189 Some(ns!(xlink)),
3190 ),
3191];
3192
3193/// <https://wicg.github.io/sanitizer-api/#built-in-animating-url-attributes-list>
3194const BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST: &[(
3195 LocalName,
3196 Option<Namespace>,
3197 LocalName,
3198 Option<Namespace>,
3199)] = &[
3200 (
3201 local_name!("animate"),
3202 Some(ns!(svg)),
3203 local_name!("attributeName"),
3204 None,
3205 ),
3206 (
3207 local_name!("animateTransform"),
3208 Some(ns!(svg)),
3209 local_name!("attributeName"),
3210 None,
3211 ),
3212 (
3213 local_name!("set"),
3214 Some(ns!(svg)),
3215 local_name!("attributeName"),
3216 None,
3217 ),
3218];
3219
3220thread_local! {
3221 /// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
3222 static BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST: LazyCell<Vec<SanitizerElement>> =
3223 LazyCell::new(|| {
3224 vec![
3225 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3226 name: local_name!("html").as_ref().into(),
3227 namespace: Some(ns!(html).as_ref().into()),
3228 }),
3229 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3230 name: local_name!("svg").as_ref().into(),
3231 namespace: Some(ns!(svg).as_ref().into()),
3232 }),
3233 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3234 name: local_name!("math").as_ref().into(),
3235 namespace: Some(ns!(mathml).as_ref().into()),
3236 }),
3237 ]
3238 });
3239}