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