1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::collections::BTreeSet;
4use std::fmt::Write as _;
5
6use regex::Regex;
7
8use super::DomIndexes;
9use super::DomStore;
10use super::ElementData;
11use super::HTML_NAMESPACE_URI;
12use super::MATHML_NAMESPACE_URI;
13use super::NodeId;
14use super::NodeKind;
15use super::NodeRecord;
16use super::SVG_NAMESPACE_URI;
17use super::TextData;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20struct SelectorQuery {
21 tag: Option<String>,
22 id: Option<String>,
23 classes: Vec<String>,
24 attributes: Vec<SelectorAttribute>,
25 pseudo_classes: Vec<SelectorPseudoClass>,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29struct SelectorAttribute {
30 name: String,
31 operator: SelectorAttributeOperator,
32 value: Option<String>,
33 case_sensitivity: SelectorAttributeCaseSensitivity,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37enum SelectorAttributeOperator {
38 Exists,
39 Exact,
40 Prefix,
41 Suffix,
42 Contains,
43 Includes,
44 DashMatch,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48enum SelectorAttributeCaseSensitivity {
49 CaseSensitive,
50 AsciiInsensitive,
51}
52
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54struct SelectorChain {
55 parts: Vec<SelectorQuery>,
56 relations: Vec<SelectorCombinator>,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
60struct SelectorRelativeSelector {
61 combinator: Option<SelectorCombinator>,
62 chain: SelectorChain,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66enum SelectorCombinator {
67 Descendant,
68 Child,
69 AdjacentSibling,
70 GeneralSibling,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
74enum SelectorPseudoClass {
75 Scope,
76 Root,
77 Empty,
78 Target,
79 Lang(Vec<String>),
80 AnyLink,
81 Defined,
82 Dir(SelectorDirValue),
83 PlaceholderShown,
84 Blank,
85 Indeterminate,
86 Default,
87 Focus,
88 FocusVisible,
89 FocusWithin,
90 Required,
91 Optional,
92 Valid,
93 Invalid,
94 InRange,
95 OutOfRange,
96 ReadOnly,
97 ReadWrite,
98 OnlyChild,
99 OnlyOfType,
100 FirstChild,
101 LastChild,
102 FirstOfType,
103 LastOfType,
104 NthChild(SelectorNthChildPattern),
105 NthLastChild(SelectorNthChildPattern),
106 NthOfType(SelectorNthChildPattern),
107 NthLastOfType(SelectorNthChildPattern),
108 Is(Vec<SelectorChain>),
109 Where(Vec<SelectorChain>),
110 Not(Vec<SelectorChain>),
111 Has(Vec<SelectorRelativeSelector>),
112 Checked,
113 Disabled,
114 Enabled,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118enum SelectorDirValue {
119 Ltr,
120 Rtl,
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124struct SelectorNthChildPattern {
125 step: isize,
126 offset: isize,
127 of_selectors: Option<Vec<SelectorChain>>,
128}
129
130impl DomStore {
131 pub fn bootstrap_html(&mut self, html: impl Into<String>) -> Result<(), String> {
132 let html = html.into();
133 let mut parsed = Self::new_empty();
134 parsed.source_html = Some(html.clone());
135 let mut parser = HtmlParser::new(&html);
136 parser.parse_into(&mut parsed)?;
137 parsed.rebuild_form_controls();
138 parsed.document.title = parsed.document_title();
139 *self = parsed;
140 Ok(())
141 }
142
143 pub fn select(&self, selector: &str) -> Result<Vec<NodeId>, String> {
144 self.select_with_scope(selector, self.root_element_id())
145 }
146
147 pub fn select_with_scope(
148 &self,
149 selector: &str,
150 scope_root: Option<NodeId>,
151 ) -> Result<Vec<NodeId>, String> {
152 let selector = selector.trim();
153 if selector.is_empty() {
154 return Err("selector must not be empty".to_string());
155 }
156
157 let chains = Self::parse_selector_list(selector)?;
158 Ok(self.select_by_selector_chains(&chains, scope_root))
159 }
160
161 pub fn dump_dom(&self) -> String {
162 let mut output = String::new();
163 self.dump_node(self.document_id, 0, &mut output);
164 output
165 }
166
167 pub fn document_title(&self) -> String {
168 self.html_title_element_id()
169 .map(|title_id| self.text_content_for_node(title_id))
170 .unwrap_or_else(|| self.document.title.clone())
171 }
172
173 pub fn set_document_title(&mut self, value: impl Into<String>) -> Result<(), String> {
174 let value = value.into();
175 if let Some(title_id) = self.html_title_element_id() {
176 self.set_text_content(title_id, &value)?;
177 }
178 self.document.title = value;
179 Ok(())
180 }
181
182 pub fn get_attribute(&self, node_id: NodeId, name: &str) -> Result<Option<String>, String> {
183 let name = normalize_attribute_name(name)?;
184 let Some(node) = self.nodes.get(node_id.index() as usize) else {
185 return Err(format!("invalid node id: {:?}", node_id));
186 };
187
188 let NodeKind::Element(element) = &node.kind else {
189 return Err(format!("node {:?} is not an element", node_id));
190 };
191
192 Ok(element.attributes.get(&name).cloned())
193 }
194
195 pub fn has_attribute(&self, node_id: NodeId, name: &str) -> Result<bool, String> {
196 let name = normalize_attribute_name(name)?;
197 let Some(node) = self.nodes.get(node_id.index() as usize) else {
198 return Err(format!("invalid node id: {:?}", node_id));
199 };
200
201 let NodeKind::Element(element) = &node.kind else {
202 return Err(format!("node {:?} is not an element", node_id));
203 };
204
205 Ok(element.attributes.contains_key(&name))
206 }
207
208 pub fn set_attribute(
209 &mut self,
210 node_id: NodeId,
211 name: &str,
212 value: impl Into<String>,
213 ) -> Result<(), String> {
214 let name = normalize_attribute_name(name)?;
215 let rebuild_indexes = attribute_affects_indexes(&name);
216 let rebuild_form_controls = attribute_affects_form_controls(&name);
217 let value = value.into();
218 {
219 let Some(node) = self.nodes.get_mut(node_id.index() as usize) else {
220 return Err(format!("invalid node id: {:?}", node_id));
221 };
222 let NodeKind::Element(element) = &mut node.kind else {
223 return Err(format!("node {:?} is not an element", node_id));
224 };
225 element.attributes.insert(name, value);
226 }
227
228 if rebuild_indexes {
229 self.rebuild_indexes();
230 }
231 if rebuild_form_controls {
232 self.rebuild_form_controls();
233 }
234 Ok(())
235 }
236
237 pub fn remove_attribute(&mut self, node_id: NodeId, name: &str) -> Result<bool, String> {
238 let name = normalize_attribute_name(name)?;
239 let rebuild_indexes = attribute_affects_indexes(&name);
240 let rebuild_form_controls = attribute_affects_form_controls(&name);
241 let removed = {
242 let Some(node) = self.nodes.get_mut(node_id.index() as usize) else {
243 return Err(format!("invalid node id: {:?}", node_id));
244 };
245 let NodeKind::Element(element) = &mut node.kind else {
246 return Err(format!("node {:?} is not an element", node_id));
247 };
248 element.attributes.remove(&name).is_some()
249 };
250
251 if removed {
252 if rebuild_indexes {
253 self.rebuild_indexes();
254 }
255 if rebuild_form_controls {
256 self.rebuild_form_controls();
257 }
258 }
259 Ok(removed)
260 }
261
262 pub fn toggle_attribute(
263 &mut self,
264 node_id: NodeId,
265 name: &str,
266 force: Option<bool>,
267 ) -> Result<bool, String> {
268 let name = normalize_attribute_name(name)?;
269 let rebuild_indexes = attribute_affects_indexes(&name);
270 let rebuild_form_controls = attribute_affects_form_controls(&name);
271 let (changed, now_present) = {
272 let Some(node) = self.nodes.get_mut(node_id.index() as usize) else {
273 return Err(format!("invalid node id: {:?}", node_id));
274 };
275 let NodeKind::Element(element) = &mut node.kind else {
276 return Err(format!("node {:?} is not an element", node_id));
277 };
278
279 let has_attr = element.attributes.contains_key(&name);
280 match force {
281 Some(true) => {
282 if has_attr {
283 (false, true)
284 } else {
285 element.attributes.insert(name, String::new());
286 (true, true)
287 }
288 }
289 Some(false) => {
290 if has_attr {
291 element.attributes.remove(&name);
292 (true, false)
293 } else {
294 (false, false)
295 }
296 }
297 None => {
298 if has_attr {
299 element.attributes.remove(&name);
300 (true, false)
301 } else {
302 element.attributes.insert(name, String::new());
303 (true, true)
304 }
305 }
306 }
307 };
308
309 if changed {
310 if rebuild_indexes {
311 self.rebuild_indexes();
312 }
313 if rebuild_form_controls {
314 self.rebuild_form_controls();
315 }
316 }
317
318 Ok(now_present)
319 }
320
321 pub fn set_text_content(&mut self, node_id: NodeId, value: &str) -> Result<(), String> {
322 let node_index = node_id.index() as usize;
323 let old_children = {
324 let Some(node) = self.nodes.get_mut(node_index) else {
325 return Err(format!("invalid node id: {:?}", node_id));
326 };
327
328 match &mut node.kind {
329 NodeKind::Document => return Ok(()),
330 NodeKind::Text(text) => {
331 text.value = value.to_string();
332 return Ok(());
333 }
334 NodeKind::Comment(comment) => {
335 comment.clear();
336 comment.push_str(value);
337 return Ok(());
338 }
339 NodeKind::Element(_) => std::mem::take(&mut node.children),
340 }
341 };
342
343 let removed_nodes = self.collect_subtree_nodes(old_children.iter().copied());
344 for removed_id in &removed_nodes {
345 if let Some(record) = self.nodes.get_mut(removed_id.index() as usize) {
346 record.parent = None;
347 }
348 }
349 for removed_id in removed_nodes {
350 self.remove_subtree_side_tables(removed_id);
351 }
352
353 if !value.is_empty() {
354 self.add_text(node_id, value.to_string());
355 }
356
357 self.rebuild_indexes();
358 self.rebuild_form_controls();
359 self.document.title = self.document_title();
360 Ok(())
361 }
362
363 pub fn create_element(&mut self, tag_name: impl Into<String>) -> Result<NodeId, String> {
364 self.create_element_ns(HTML_NAMESPACE_URI, tag_name)
365 }
366
367 pub fn create_element_ns(
368 &mut self,
369 namespace_uri: impl Into<String>,
370 tag_name: impl Into<String>,
371 ) -> Result<NodeId, String> {
372 let tag_name = tag_name.into().trim().to_ascii_lowercase();
373 if tag_name.is_empty() || !tag_name.bytes().all(is_simple_name_byte) {
374 return Err(format!("invalid tag name: `{tag_name}`"));
375 }
376
377 let node_id = NodeId::new(self.nodes.len() as u32, 0);
378 self.nodes.push(NodeRecord {
379 id: node_id,
380 parent: None,
381 children: Vec::new(),
382 kind: NodeKind::Element(ElementData {
383 tag_name: tag_name.clone(),
384 local_name: tag_name,
385 namespace_uri: namespace_uri.into(),
386 attributes: BTreeMap::new(),
387 }),
388 });
389 Ok(node_id)
390 }
391
392 pub fn create_text_node(&mut self, value: impl Into<String>) -> Result<NodeId, String> {
393 let node_id = NodeId::new(self.nodes.len() as u32, 0);
394 self.nodes.push(NodeRecord {
395 id: node_id,
396 parent: None,
397 children: Vec::new(),
398 kind: NodeKind::Text(TextData {
399 value: value.into(),
400 }),
401 });
402 Ok(node_id)
403 }
404
405 pub fn create_comment(&mut self, value: impl Into<String>) -> Result<NodeId, String> {
406 let node_id = NodeId::new(self.nodes.len() as u32, 0);
407 self.nodes.push(NodeRecord {
408 id: node_id,
409 parent: None,
410 children: Vec::new(),
411 kind: NodeKind::Comment(value.into()),
412 });
413 Ok(node_id)
414 }
415
416 pub fn clone_node(&mut self, node_id: NodeId, deep: bool) -> Result<NodeId, String> {
417 let Some(source) = self.nodes.get(node_id.index() as usize).cloned() else {
418 return Err(format!("invalid node id: {:?}", node_id));
419 };
420
421 let cloned_kind = match &source.kind {
422 NodeKind::Document => NodeKind::Document,
423 NodeKind::Element(element) => NodeKind::Element(element.clone()),
424 NodeKind::Text(text) => NodeKind::Text(text.clone()),
425 NodeKind::Comment(comment) => NodeKind::Comment(comment.clone()),
426 };
427
428 let cloned_id = NodeId::new(self.nodes.len() as u32, 0);
429 self.nodes.push(NodeRecord {
430 id: cloned_id,
431 parent: None,
432 children: Vec::new(),
433 kind: cloned_kind,
434 });
435
436 if deep {
437 let snapshot = self.clone();
438 for (index, child) in source.children.iter().copied().enumerate() {
439 self.clone_subtree_at(&snapshot, child, cloned_id, index)?;
440 }
441 }
442
443 Ok(cloned_id)
444 }
445
446 pub fn text_content_for_node(&self, node_id: NodeId) -> String {
447 let Some(node) = self.nodes.get(node_id.index() as usize) else {
448 return String::new();
449 };
450
451 match &node.kind {
452 NodeKind::Document | NodeKind::Element(_) => {
453 let mut out = String::new();
454 for child in &node.children {
455 out.push_str(&self.text_content_for_node(*child));
456 }
457 out
458 }
459 NodeKind::Text(text) => text.value.clone(),
460 NodeKind::Comment(_) => String::new(),
461 }
462 }
463
464 pub fn value_for_node(&self, node_id: NodeId) -> String {
465 if let Some(state) = self.side_tables.form_controls.get(&node_id) {
466 return state.value.clone();
467 }
468
469 let Some(node) = self.nodes.get(node_id.index() as usize) else {
470 return String::new();
471 };
472
473 match &node.kind {
474 NodeKind::Element(element) if element.tag_name == "select" => {
475 self.select_value_for_node(node_id)
476 }
477 NodeKind::Element(element) if element.tag_name == "option" => {
478 self.option_value_for_node(node_id)
479 }
480 NodeKind::Element(element)
481 if element.tag_name == "input"
482 && is_file_input_type(element.attributes.get("type").map(String::as_str)) =>
483 {
484 self.file_input_value_for_node(node_id)
485 }
486 NodeKind::Element(element) => element
487 .attributes
488 .get("value")
489 .cloned()
490 .unwrap_or_else(|| self.text_content_for_node(node_id)),
491 NodeKind::Document => self.text_content_for_node(node_id),
492 NodeKind::Text(text) => text.value.clone(),
493 NodeKind::Comment(_) => String::new(),
494 }
495 }
496
497 pub fn checked_for_node(&self, node_id: NodeId) -> Option<bool> {
498 let Some(node) = self.nodes.get(node_id.index() as usize) else {
499 return None;
500 };
501
502 let NodeKind::Element(element) = &node.kind else {
503 return None;
504 };
505
506 if element.tag_name == "input"
507 && is_checkable_input_type(element.attributes.get("type").map(String::as_str))
508 {
509 self.side_tables
510 .form_controls
511 .get(&node_id)
512 .map(|state| state.checked)
513 .or_else(|| Some(element.attributes.contains_key("checked")))
514 } else {
515 None
516 }
517 }
518
519 pub fn indeterminate_for_node(&self, node_id: NodeId) -> Option<bool> {
520 let Some(node) = self.nodes.get(node_id.index() as usize) else {
521 return None;
522 };
523
524 let NodeKind::Element(element) = &node.kind else {
525 return None;
526 };
527
528 if element.tag_name == "input"
529 && is_checkable_input_type(element.attributes.get("type").map(String::as_str))
530 {
531 Some(
532 self.side_tables
533 .form_controls
534 .get(&node_id)
535 .map(|state| state.indeterminate)
536 .unwrap_or(false),
537 )
538 } else {
539 None
540 }
541 }
542
543 pub fn is_content_editable(&self, node_id: NodeId) -> bool {
544 let mut current = Some(node_id);
545
546 while let Some(current_id) = current {
547 let Some(node) = self.nodes.get(current_id.index() as usize) else {
548 return false;
549 };
550 let NodeKind::Element(element) = &node.kind else {
551 return false;
552 };
553
554 if let Some(value) = element.attributes.get("contenteditable") {
555 match value.trim().to_ascii_lowercase().as_str() {
556 "" | "true" | "plaintext-only" => return true,
557 "false" => return false,
558 _ => current = node.parent,
559 }
560 } else {
561 current = node.parent;
562 }
563 }
564
565 false
566 }
567
568 pub fn set_form_control_value(
569 &mut self,
570 node_id: NodeId,
571 value: impl Into<String>,
572 ) -> Result<(), String> {
573 let value = value.into();
574 let node_index = node_id.index() as usize;
575 let Some(node) = self.nodes.get(node_index) else {
576 return Err(format!("invalid node id: {:?}", node_id));
577 };
578
579 let NodeKind::Element(element) = &node.kind else {
580 return Err(format!(
581 "node {:?} is not a supported form control",
582 node_id
583 ));
584 };
585
586 match element.tag_name.as_str() {
587 "textarea" => self.set_text_content(node_id, &value),
588 "input" if is_text_input_type(element.attributes.get("type").map(String::as_str)) => {
589 {
590 let Some(node) = self.nodes.get_mut(node_index) else {
591 return Err(format!("invalid node id: {:?}", node_id));
592 };
593 let NodeKind::Element(element) = &mut node.kind else {
594 return Err(format!(
595 "node {:?} is not a supported form control",
596 node_id
597 ));
598 };
599 element
600 .attributes
601 .insert("value".to_string(), value.clone());
602 }
603 self.rebuild_form_controls();
604 Ok(())
605 }
606 "input" => Err(format!(
607 "set_value is only supported on text-like inputs and textareas, not <input type=\"{}\">",
608 element
609 .attributes
610 .get("type")
611 .map(String::as_str)
612 .unwrap_or("text")
613 )),
614 _ => Err(format!(
615 "node {:?} is not a supported form control",
616 node_id
617 )),
618 }
619 }
620
621 pub fn set_select_value(
622 &mut self,
623 node_id: NodeId,
624 value: impl Into<String>,
625 ) -> Result<(), String> {
626 let value = value.into();
627 let node_index = node_id.index() as usize;
628 let Some(node) = self.nodes.get(node_index) else {
629 return Err(format!("invalid node id: {:?}", node_id));
630 };
631
632 let NodeKind::Element(element) = &node.kind else {
633 return Err(format!("node {:?} is not a select control", node_id));
634 };
635
636 if element.tag_name != "select" {
637 return Err(format!("node {:?} is not a select control", node_id));
638 }
639
640 let option_ids = self.collect_subtree_nodes(node.children.iter().copied());
641 let options: Vec<NodeId> = option_ids
642 .into_iter()
643 .filter(|option_id| self.is_option_node(*option_id))
644 .collect();
645
646 if options.is_empty() {
647 return Err(format!(
648 "select node {:?} does not contain any options",
649 node_id
650 ));
651 }
652
653 let mut first_matching_option = None;
654 for option_id in options {
655 self.set_option_selected(option_id, false)?;
656 if first_matching_option.is_none() && self.option_value_for_node(option_id) == value {
657 first_matching_option = Some(option_id);
658 }
659 }
660
661 if let Some(option_id) = first_matching_option {
662 self.set_option_selected(option_id, true)?;
663 }
664
665 Ok(())
666 }
667
668 pub fn set_form_control_checked(
669 &mut self,
670 node_id: NodeId,
671 checked: bool,
672 ) -> Result<(), String> {
673 let node_index = node_id.index() as usize;
674 let Some(node) = self.nodes.get(node_index) else {
675 return Err(format!("invalid node id: {:?}", node_id));
676 };
677
678 let NodeKind::Element(element) = &node.kind else {
679 return Err(format!(
680 "node {:?} is not a supported form control",
681 node_id
682 ));
683 };
684
685 match element.tag_name.as_str() {
686 "input"
687 if is_checkable_input_type(element.attributes.get("type").map(String::as_str)) =>
688 {
689 {
690 let Some(node) = self.nodes.get_mut(node_index) else {
691 return Err(format!("invalid node id: {:?}", node_id));
692 };
693 let NodeKind::Element(element) = &mut node.kind else {
694 return Err(format!(
695 "node {:?} is not a supported form control",
696 node_id
697 ));
698 };
699 if checked {
700 element
701 .attributes
702 .insert("checked".to_string(), String::new());
703 } else {
704 element.attributes.remove("checked");
705 }
706 }
707 self.rebuild_form_controls();
708 Ok(())
709 }
710 "input" => Err(format!(
711 "set_checked is only supported on checkbox and radio inputs, not <input type=\"{}\">",
712 element
713 .attributes
714 .get("type")
715 .map(String::as_str)
716 .unwrap_or("text")
717 )),
718 _ => Err(format!(
719 "node {:?} is not a supported form control",
720 node_id
721 )),
722 }
723 }
724
725 pub fn set_form_control_indeterminate(
726 &mut self,
727 node_id: NodeId,
728 indeterminate: bool,
729 ) -> Result<(), String> {
730 let node_index = node_id.index() as usize;
731 let Some(node) = self.nodes.get(node_index) else {
732 return Err(format!("invalid node id: {:?}", node_id));
733 };
734
735 let NodeKind::Element(element) = &node.kind else {
736 return Err(format!(
737 "node {:?} is not a supported form control",
738 node_id
739 ));
740 };
741
742 if element.tag_name != "input"
743 || !matches!(
744 element.attributes.get("type").map(String::as_str),
745 Some("checkbox") | Some("radio")
746 )
747 {
748 return Err(format!(
749 "indeterminate is only supported on checkbox and radio inputs, not <input type=\"{}\">",
750 element
751 .attributes
752 .get("type")
753 .map(String::as_str)
754 .unwrap_or("text")
755 ));
756 }
757
758 let state = self.side_tables.form_controls.entry(node_id).or_default();
759 state.indeterminate = indeterminate;
760 Ok(())
761 }
762
763 pub fn set_file_input_files(
764 &mut self,
765 node_id: NodeId,
766 files: impl IntoIterator<Item = impl Into<String>>,
767 ) -> Result<(), String> {
768 let node_index = node_id.index() as usize;
769 let Some(node) = self.nodes.get(node_index) else {
770 return Err(format!("invalid node id: {:?}", node_id));
771 };
772
773 let NodeKind::Element(element) = &node.kind else {
774 return Err(format!("node {:?} is not a file input control", node_id));
775 };
776
777 if element.tag_name != "input"
778 || !is_file_input_type(element.attributes.get("type").map(String::as_str))
779 {
780 return Err(format!("node {:?} is not a file input control", node_id));
781 }
782
783 self.side_tables.file_inputs.insert(
784 node_id,
785 super::FileInputState {
786 files: files.into_iter().map(Into::into).collect(),
787 },
788 );
789 Ok(())
790 }
791
792 pub fn inner_html_for_node(&self, node_id: NodeId) -> Result<String, String> {
793 let Some(node) = self.nodes.get(node_id.index() as usize) else {
794 return Err(format!("invalid node id: {:?}", node_id));
795 };
796
797 match &node.kind {
798 NodeKind::Document | NodeKind::Element(_) => {
799 let mut output = String::new();
800 let raw_text_context = matches!(
801 &node.kind,
802 NodeKind::Element(element) if is_raw_text_element(element.tag_name.as_str())
803 );
804 for child in &node.children {
805 self.serialize_html_node_with_context(*child, &mut output, raw_text_context)?;
806 }
807 Ok(output)
808 }
809 _ => Err(format!("node {:?} does not support innerHTML", node_id)),
810 }
811 }
812
813 pub fn outer_html_for_node(&self, node_id: NodeId) -> Result<String, String> {
814 let Some(node) = self.nodes.get(node_id.index() as usize) else {
815 return Err(format!("invalid node id: {:?}", node_id));
816 };
817
818 match &node.kind {
819 NodeKind::Element(_) => {
820 let mut output = String::new();
821 self.serialize_html_node(node_id, &mut output)?;
822 Ok(output)
823 }
824 _ => Err(format!("node {:?} does not support outerHTML", node_id)),
825 }
826 }
827
828 pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) -> Result<(), String> {
829 let Some(node) = self.nodes.get(node_id.index() as usize) else {
830 return Err(format!("invalid node id: {:?}", node_id));
831 };
832
833 let NodeKind::Element(element) = &node.kind else {
834 return Err(format!("node {:?} does not support innerHTML", node_id));
835 };
836 if is_void_element(element.tag_name.as_str()) {
837 return Err(format!(
838 "innerHTML is not supported on void elements like <{}>",
839 element.tag_name
840 ));
841 }
842
843 let (fragment_store, fragment_children) = self.fragment_children_for_html(node_id, html)?;
844
845 self.set_text_content(node_id, "")?;
846
847 self.clone_fragment_children_into(&fragment_store, &fragment_children, node_id, 0)?;
848
849 self.rebuild_indexes();
850 self.rebuild_form_controls();
851 self.document.title = self.document_title();
852 Ok(())
853 }
854
855 pub fn set_outer_html(&mut self, node_id: NodeId, html: &str) -> Result<(), String> {
856 if node_id == self.document_id {
857 return Err("document node does not support outerHTML".to_string());
858 }
859
860 let Some(parent_id) = self.parent_of(node_id) else {
861 return Ok(());
862 };
863 let insertion_index = self
864 .child_index(parent_id, node_id)
865 .ok_or_else(|| format!("node {:?} is not present in its parent", node_id))?;
866
867 let (fragment_store, fragment_children) =
868 self.fragment_children_for_html(parent_id, html)?;
869
870 self.remove_node(node_id)?;
871
872 self.clone_fragment_children_into(
873 &fragment_store,
874 &fragment_children,
875 parent_id,
876 insertion_index,
877 )?;
878
879 self.rebuild_indexes();
880 self.rebuild_form_controls();
881 self.document.title = self.document_title();
882 Ok(())
883 }
884
885 pub fn append_html_to_document(&mut self, html: &str) -> Result<(), String> {
886 let target_parent = self
887 .body_element_id()
888 .or(self.root_element_id())
889 .or(Some(self.document_id))
890 .ok_or_else(|| "document.write() requires a document element".to_string())?;
891 let insertion_index = self.child_count(target_parent)?;
892 let (fragment_store, fragment_children) =
893 self.fragment_children_for_html(target_parent, html)?;
894
895 self.clone_fragment_children_into(
896 &fragment_store,
897 &fragment_children,
898 target_parent,
899 insertion_index,
900 )?;
901
902 self.rebuild_indexes();
903 self.rebuild_form_controls();
904 self.document.title = self.document_title();
905 Ok(())
906 }
907
908 pub fn document_open(&mut self) -> Result<(), String> {
909 let target = self.document_id;
910 let removed_nodes = self
911 .nodes
912 .get(target.index() as usize)
913 .map(|node| self.collect_subtree_nodes(node.children.clone()))
914 .unwrap_or_default();
915 let focused_node = self.focused_node;
916
917 self.replace_children(target, std::iter::empty::<NodeId>())?;
918 if focused_node.is_some_and(|focused| removed_nodes.contains(&focused)) {
919 self.focused_node = None;
920 }
921 self.rebuild_indexes();
922 self.rebuild_form_controls();
923 self.document.title = String::new();
924 Ok(())
925 }
926
927 pub fn insert_adjacent_html(
928 &mut self,
929 node_id: NodeId,
930 position: &str,
931 html: &str,
932 ) -> Result<(), String> {
933 let Some(node) = self.nodes.get(node_id.index() as usize) else {
934 return Err(format!("invalid node id: {:?}", node_id));
935 };
936
937 let NodeKind::Element(element) = &node.kind else {
938 return Err(format!(
939 "node {:?} does not support insertAdjacentHTML",
940 node_id
941 ));
942 };
943
944 match position {
945 "beforebegin" => {
946 let Some(parent_id) = self.parent_of(node_id) else {
947 return Err(format!(
948 "node {:?} has no parent for insertAdjacentHTML(beforebegin)",
949 node_id
950 ));
951 };
952 let insertion_index = self
953 .child_index(parent_id, node_id)
954 .ok_or_else(|| format!("node {:?} is not present in its parent", node_id))?;
955 let (fragment_store, fragment_children) =
956 self.fragment_children_for_html(parent_id, html)?;
957 self.clone_fragment_children_into(
958 &fragment_store,
959 &fragment_children,
960 parent_id,
961 insertion_index,
962 )?;
963 }
964 "afterbegin" => {
965 if is_void_element(element.tag_name.as_str()) {
966 return Err(format!(
967 "insertAdjacentHTML is not supported on void elements like <{}>",
968 element.tag_name
969 ));
970 }
971
972 let (fragment_store, fragment_children) =
973 self.fragment_children_for_html(node_id, html)?;
974 self.clone_fragment_children_into(&fragment_store, &fragment_children, node_id, 0)?;
975 }
976 "beforeend" => {
977 if is_void_element(element.tag_name.as_str()) {
978 return Err(format!(
979 "insertAdjacentHTML is not supported on void elements like <{}>",
980 element.tag_name
981 ));
982 }
983
984 let insertion_index = self.child_count(node_id)?;
985 let (fragment_store, fragment_children) =
986 self.fragment_children_for_html(node_id, html)?;
987 self.clone_fragment_children_into(
988 &fragment_store,
989 &fragment_children,
990 node_id,
991 insertion_index,
992 )?;
993 }
994 "afterend" => {
995 let Some(parent_id) = self.parent_of(node_id) else {
996 return Err(format!(
997 "node {:?} has no parent for insertAdjacentHTML(afterend)",
998 node_id
999 ));
1000 };
1001 let insertion_index = self
1002 .child_index(parent_id, node_id)
1003 .ok_or_else(|| format!("node {:?} is not present in its parent", node_id))?
1004 + 1;
1005 let (fragment_store, fragment_children) =
1006 self.fragment_children_for_html(parent_id, html)?;
1007 self.clone_fragment_children_into(
1008 &fragment_store,
1009 &fragment_children,
1010 parent_id,
1011 insertion_index,
1012 )?;
1013 }
1014 _ => {
1015 return Err(format!(
1016 "unsupported insertAdjacentHTML position `{position}`"
1017 ));
1018 }
1019 }
1020
1021 self.rebuild_indexes();
1022 self.rebuild_form_controls();
1023 self.document.title = self.document_title();
1024 Ok(())
1025 }
1026
1027 pub fn append_child(&mut self, parent: NodeId, child: NodeId) -> Result<(), String> {
1028 self.append_children(parent, [child])
1029 }
1030
1031 pub fn append_children<I>(&mut self, parent: NodeId, children: I) -> Result<(), String>
1032 where
1033 I: IntoIterator<Item = NodeId>,
1034 {
1035 let children = children.into_iter().collect::<Vec<_>>();
1036 let insertion_index = self.child_count(parent)?;
1037 self.insert_children_at(parent, insertion_index, &children)?;
1038 self.document.title = self.document_title();
1039 Ok(())
1040 }
1041
1042 pub fn prepend_children<I>(&mut self, parent: NodeId, children: I) -> Result<(), String>
1043 where
1044 I: IntoIterator<Item = NodeId>,
1045 {
1046 let children = children.into_iter().collect::<Vec<_>>();
1047 self.insert_children_at(parent, 0, &children)?;
1048 self.document.title = self.document_title();
1049 Ok(())
1050 }
1051
1052 pub fn insert_before(
1053 &mut self,
1054 parent: NodeId,
1055 child: NodeId,
1056 reference: NodeId,
1057 ) -> Result<(), String> {
1058 self.insert_children_before(parent, reference, [child])
1059 }
1060
1061 pub fn insert_children_before<I>(
1062 &mut self,
1063 parent: NodeId,
1064 reference: NodeId,
1065 children: I,
1066 ) -> Result<(), String>
1067 where
1068 I: IntoIterator<Item = NodeId>,
1069 {
1070 let children = children.into_iter().collect::<Vec<_>>();
1071 if children.iter().any(|child| *child == reference) {
1072 return Err("a node cannot be inserted relative to itself".to_string());
1073 }
1074
1075 let reference_parent = self.parent_of(reference);
1076 if reference_parent != Some(parent) {
1077 return Err(format!(
1078 "reference node {:?} is not a child of {:?}",
1079 reference, parent
1080 ));
1081 }
1082
1083 let reference_index = self.child_index(parent, reference).ok_or_else(|| {
1084 format!(
1085 "reference node {:?} is not a child of {:?}",
1086 reference, parent
1087 )
1088 })?;
1089 self.insert_children_at(parent, reference_index, &children)?;
1090 self.document.title = self.document_title();
1091 Ok(())
1092 }
1093
1094 pub fn insert_children_after<I>(
1095 &mut self,
1096 parent: NodeId,
1097 reference: NodeId,
1098 children: I,
1099 ) -> Result<(), String>
1100 where
1101 I: IntoIterator<Item = NodeId>,
1102 {
1103 let children = children.into_iter().collect::<Vec<_>>();
1104 if children.iter().any(|child| *child == reference) {
1105 return Err("a node cannot be inserted relative to itself".to_string());
1106 }
1107
1108 let reference_parent = self.parent_of(reference);
1109 if reference_parent != Some(parent) {
1110 return Err(format!(
1111 "reference node {:?} is not a child of {:?}",
1112 reference, parent
1113 ));
1114 }
1115
1116 let reference_index = self.child_index(parent, reference).ok_or_else(|| {
1117 format!(
1118 "reference node {:?} is not a child of {:?}",
1119 reference, parent
1120 )
1121 })?;
1122 self.insert_children_at(parent, reference_index + 1, &children)?;
1123 self.document.title = self.document_title();
1124 Ok(())
1125 }
1126
1127 pub fn replace_child(
1128 &mut self,
1129 parent: NodeId,
1130 new_child: NodeId,
1131 old_child: NodeId,
1132 ) -> Result<(), String> {
1133 if new_child == old_child {
1134 return Ok(());
1135 }
1136
1137 self.insert_children_before(parent, old_child, [new_child])?;
1138 self.remove_node(old_child)?;
1139 Ok(())
1140 }
1141
1142 pub fn replace_children<I>(&mut self, parent: NodeId, children: I) -> Result<(), String>
1143 where
1144 I: IntoIterator<Item = NodeId>,
1145 {
1146 self.ensure_mutation_parent(parent)?;
1147
1148 let children = children.into_iter().collect::<Vec<_>>();
1149 self.validate_mutation_children(parent, &children)?;
1150
1151 let old_children = self
1152 .nodes
1153 .get(parent.index() as usize)
1154 .map(|node| node.children.clone())
1155 .ok_or_else(|| format!("invalid node id: {:?}", parent))?;
1156
1157 {
1158 let Some(parent_node) = self.nodes.get_mut(parent.index() as usize) else {
1159 return Err(format!("invalid node id: {:?}", parent));
1160 };
1161 parent_node.children.clear();
1162 }
1163
1164 for old_child in &old_children {
1165 if let Some(record) = self.nodes.get_mut(old_child.index() as usize) {
1166 record.parent = None;
1167 }
1168 }
1169 for old_child in old_children {
1170 self.remove_subtree_side_tables(old_child);
1171 }
1172
1173 self.insert_children_at(parent, 0, &children)?;
1174 self.document.title = self.document_title();
1175 Ok(())
1176 }
1177
1178 pub fn remove_node(&mut self, node_id: NodeId) -> Result<(), String> {
1179 if node_id == self.document_id {
1180 return Err("document node cannot be removed".to_string());
1181 }
1182
1183 let Some(parent_id) = self.parent_of(node_id) else {
1184 return Ok(());
1185 };
1186 let Some(parent_index) = self.child_index(parent_id, node_id) else {
1187 return Err(format!("node {:?} is not present in its parent", node_id));
1188 };
1189 {
1190 let Some(parent_node) = self.nodes.get_mut(parent_id.index() as usize) else {
1191 return Err(format!("invalid node id: {:?}", parent_id));
1192 };
1193 parent_node.children.remove(parent_index);
1194 }
1195 {
1196 let Some(node) = self.nodes.get_mut(node_id.index() as usize) else {
1197 return Err(format!("invalid node id: {:?}", node_id));
1198 };
1199 node.parent = None;
1200 }
1201 self.remove_subtree_side_tables(node_id);
1202 self.rebuild_indexes();
1203 self.rebuild_form_controls();
1204 self.document.title = self.document_title();
1205 Ok(())
1206 }
1207
1208 pub fn normalize_node(&mut self, node_id: NodeId) -> Result<(), String> {
1209 let Some(node) = self.nodes.get(node_id.index() as usize).cloned() else {
1210 return Err(format!("invalid node id: {:?}", node_id));
1211 };
1212
1213 match node.kind {
1214 NodeKind::Document | NodeKind::Element(_) => {}
1215 _ => return Ok(()),
1216 }
1217
1218 let children = node.children.clone();
1219 for child in children {
1220 self.normalize_node(child)?;
1221 }
1222
1223 let mut index = 0;
1224 let mut changed = false;
1225 loop {
1226 let Some(parent_node) = self.nodes.get(node_id.index() as usize) else {
1227 return Err(format!("invalid node id: {:?}", node_id));
1228 };
1229 if index >= parent_node.children.len() {
1230 break;
1231 }
1232
1233 let child = parent_node.children[index];
1234 let child_kind = self
1235 .nodes
1236 .get(child.index() as usize)
1237 .map(|node| &node.kind);
1238
1239 let Some(NodeKind::Text(text)) = child_kind else {
1240 index += 1;
1241 continue;
1242 };
1243
1244 if text.value.is_empty() {
1245 self.remove_node(child)?;
1246 changed = true;
1247 continue;
1248 }
1249
1250 loop {
1251 let Some(parent_node) = self.nodes.get(node_id.index() as usize) else {
1252 return Err(format!("invalid node id: {:?}", node_id));
1253 };
1254 let Some(next_child) = parent_node.children.get(index + 1).copied() else {
1255 break;
1256 };
1257
1258 let next_value = match self
1259 .nodes
1260 .get(next_child.index() as usize)
1261 .map(|node| &node.kind)
1262 {
1263 Some(NodeKind::Text(text)) => text.value.clone(),
1264 _ => break,
1265 };
1266
1267 if next_value.is_empty() {
1268 self.remove_node(next_child)?;
1269 changed = true;
1270 continue;
1271 }
1272
1273 if let Some(node) = self.nodes.get_mut(child.index() as usize) {
1274 if let NodeKind::Text(text) = &mut node.kind {
1275 text.value.push_str(&next_value);
1276 }
1277 }
1278 self.remove_node(next_child)?;
1279 changed = true;
1280 }
1281
1282 index += 1;
1283 }
1284
1285 if changed {
1286 self.document.title = self.document_title();
1287 self.rebuild_form_controls();
1288 }
1289
1290 Ok(())
1291 }
1292
1293 fn add_node(&mut self, parent: NodeId, kind: NodeKind) -> NodeId {
1294 let id = NodeId::new(self.nodes.len() as u32, 0);
1295 self.nodes.push(NodeRecord {
1296 id,
1297 parent: Some(parent),
1298 children: Vec::new(),
1299 kind,
1300 });
1301 self.nodes[parent.index() as usize].children.push(id);
1302 id
1303 }
1304
1305 fn add_element(
1306 &mut self,
1307 parent: NodeId,
1308 tag_name: String,
1309 attributes: BTreeMap<String, String>,
1310 ) -> NodeId {
1311 let namespace_uri = self.namespace_uri_for_child(parent, &tag_name).to_string();
1312 let node_id = self.add_node(
1313 parent,
1314 NodeKind::Element(ElementData {
1315 tag_name: tag_name.clone(),
1316 local_name: tag_name.clone(),
1317 namespace_uri,
1318 attributes: attributes.clone(),
1319 }),
1320 );
1321
1322 self.indexes
1323 .tag_index
1324 .entry(tag_name.clone())
1325 .or_default()
1326 .push(node_id);
1327
1328 if let Some(value) = attributes.get("id") {
1329 self.indexes
1330 .id_index
1331 .entry(value.clone())
1332 .or_insert(node_id);
1333 }
1334
1335 if let Some(value) = attributes.get("name") {
1336 self.indexes
1337 .name_index
1338 .entry(value.clone())
1339 .or_default()
1340 .push(node_id);
1341 }
1342
1343 if let Some(value) = attributes.get("class") {
1344 for class_name in value.split_ascii_whitespace() {
1345 if !class_name.is_empty() {
1346 self.indexes
1347 .class_index
1348 .entry(class_name.to_string())
1349 .or_default()
1350 .push(node_id);
1351 }
1352 }
1353 }
1354
1355 node_id
1356 }
1357
1358 fn namespace_uri_for_child(&self, parent: NodeId, tag_name: &str) -> &'static str {
1359 let parent_kind = self
1360 .nodes
1361 .get(parent.index() as usize)
1362 .map(|node| &node.kind);
1363 let parent_element = match parent_kind {
1364 Some(NodeKind::Element(element)) => Some(element),
1365 _ => None,
1366 };
1367
1368 match parent_element {
1369 None => element_namespace_for_root(tag_name),
1370 Some(element)
1371 if element.namespace_uri == SVG_NAMESPACE_URI
1372 && element.local_name == "foreignobject" =>
1373 {
1374 element_namespace_for_root(tag_name)
1375 }
1376 Some(element) if element.namespace_uri == SVG_NAMESPACE_URI => SVG_NAMESPACE_URI,
1377 Some(element) if element.namespace_uri == MATHML_NAMESPACE_URI => MATHML_NAMESPACE_URI,
1378 Some(_) => element_namespace_for_root(tag_name),
1379 }
1380 }
1381
1382 fn add_text(&mut self, parent: NodeId, value: String) -> NodeId {
1383 self.add_node(parent, NodeKind::Text(TextData { value }))
1384 }
1385
1386 fn add_comment(&mut self, parent: NodeId, value: String) -> NodeId {
1387 self.add_node(parent, NodeKind::Comment(value))
1388 }
1389
1390 fn html_title_element_id(&self) -> Option<NodeId> {
1391 self.indexes.tag_index.get("title").and_then(|ids| {
1392 ids.iter().copied().find(|node_id| {
1393 matches!(
1394 self.nodes.get(node_id.index() as usize).map(|node| &node.kind),
1395 Some(NodeKind::Element(element))
1396 if element.tag_name == "title"
1397 && element.namespace_uri == HTML_NAMESPACE_URI
1398 )
1399 })
1400 })
1401 }
1402
1403 fn insert_node_at(
1404 &mut self,
1405 parent: NodeId,
1406 insertion_index: usize,
1407 kind: NodeKind,
1408 ) -> Result<NodeId, String> {
1409 self.ensure_mutation_parent(parent)?;
1410 let id = NodeId::new(self.nodes.len() as u32, 0);
1411 self.nodes.push(NodeRecord {
1412 id,
1413 parent: Some(parent),
1414 children: Vec::new(),
1415 kind,
1416 });
1417
1418 let Some(parent_node) = self.nodes.get_mut(parent.index() as usize) else {
1419 return Err(format!("invalid node id: {:?}", parent));
1420 };
1421 let insertion_index = insertion_index.min(parent_node.children.len());
1422 parent_node.children.insert(insertion_index, id);
1423 Ok(id)
1424 }
1425
1426 fn fragment_context_for_parent(&self, parent: NodeId) -> Result<ElementData, String> {
1427 let Some(node) = self.nodes.get(parent.index() as usize) else {
1428 return Err(format!("invalid node id: {:?}", parent));
1429 };
1430
1431 match &node.kind {
1432 NodeKind::Document => Ok(ElementData {
1433 tag_name: "div".to_string(),
1434 local_name: "div".to_string(),
1435 namespace_uri: HTML_NAMESPACE_URI.to_string(),
1436 attributes: BTreeMap::new(),
1437 }),
1438 NodeKind::Element(element) => Ok(ElementData {
1439 tag_name: element.tag_name.clone(),
1440 local_name: element.local_name.clone(),
1441 namespace_uri: element.namespace_uri.clone(),
1442 attributes: BTreeMap::new(),
1443 }),
1444 _ => Err(format!(
1445 "node {:?} cannot act as an HTML fragment context",
1446 parent
1447 )),
1448 }
1449 }
1450
1451 fn parse_html_fragment_for_context(
1452 &self,
1453 context: ElementData,
1454 html: &str,
1455 ) -> Result<(DomStore, NodeId), String> {
1456 let mut fragment_store = DomStore::new_empty();
1457 let fragment_document_id = fragment_store.document_id;
1458 let fragment_root =
1459 fragment_store.add_node(fragment_document_id, NodeKind::Element(context));
1460 let mut parser = HtmlParser::new(html);
1461 parser.parse_fragment_into(&mut fragment_store, fragment_root)?;
1462 Ok((fragment_store, fragment_root))
1463 }
1464
1465 fn fragment_children_for_html(
1466 &self,
1467 context_parent: NodeId,
1468 html: &str,
1469 ) -> Result<(DomStore, Vec<NodeId>), String> {
1470 let (fragment_store, fragment_root) = self.parse_html_fragment_for_context(
1471 self.fragment_context_for_parent(context_parent)?,
1472 html,
1473 )?;
1474 let fragment_children = fragment_store.nodes()[fragment_root.index() as usize]
1475 .children
1476 .clone();
1477 Ok((fragment_store, fragment_children))
1478 }
1479
1480 fn clone_fragment_children_into(
1481 &mut self,
1482 fragment_store: &DomStore,
1483 fragment_children: &[NodeId],
1484 parent: NodeId,
1485 insertion_index: usize,
1486 ) -> Result<(), String> {
1487 let mut insertion_index = insertion_index;
1488 for child in fragment_children {
1489 self.clone_subtree_at(fragment_store, *child, parent, insertion_index)?;
1490 insertion_index += 1;
1491 }
1492
1493 Ok(())
1494 }
1495
1496 fn clone_subtree_at(
1497 &mut self,
1498 source: &DomStore,
1499 source_node_id: NodeId,
1500 parent: NodeId,
1501 insertion_index: usize,
1502 ) -> Result<NodeId, String> {
1503 let Some(source_node) = source.nodes.get(source_node_id.index() as usize) else {
1504 return Err(format!("invalid node id: {:?}", source_node_id));
1505 };
1506
1507 match &source_node.kind {
1508 NodeKind::Document => Err("document node cannot be cloned".to_string()),
1509 NodeKind::Text(text) => self.insert_node_at(
1510 parent,
1511 insertion_index,
1512 NodeKind::Text(TextData {
1513 value: text.value.clone(),
1514 }),
1515 ),
1516 NodeKind::Comment(comment) => {
1517 self.insert_node_at(parent, insertion_index, NodeKind::Comment(comment.clone()))
1518 }
1519 NodeKind::Element(element) => {
1520 let node_id = self.insert_node_at(
1521 parent,
1522 insertion_index,
1523 NodeKind::Element(ElementData {
1524 tag_name: element.tag_name.clone(),
1525 local_name: element.local_name.clone(),
1526 namespace_uri: element.namespace_uri.clone(),
1527 attributes: element.attributes.clone(),
1528 }),
1529 )?;
1530
1531 let mut child_index = 0usize;
1532 for child in &source_node.children {
1533 self.clone_subtree_at(source, *child, node_id, child_index)?;
1534 child_index += 1;
1535 }
1536
1537 Ok(node_id)
1538 }
1539 }
1540 }
1541
1542 fn remove_subtree_side_tables(&mut self, root: NodeId) {
1543 for removed_id in self.collect_subtree_nodes([root]) {
1544 self.side_tables.form_controls.remove(&removed_id);
1545 self.side_tables.selection.remove(&removed_id);
1546 self.side_tables.file_inputs.remove(&removed_id);
1547 self.side_tables.dialogs.remove(&removed_id);
1548 self.side_tables.layout_stub.remove(&removed_id);
1549 }
1550 }
1551
1552 fn serialize_html_node(&self, node_id: NodeId, output: &mut String) -> Result<(), String> {
1553 self.serialize_html_node_with_context(node_id, output, false)
1554 }
1555
1556 fn serialize_html_node_with_context(
1557 &self,
1558 node_id: NodeId,
1559 output: &mut String,
1560 raw_text_context: bool,
1561 ) -> Result<(), String> {
1562 let Some(node) = self.nodes.get(node_id.index() as usize) else {
1563 return Err(format!("invalid node id: {:?}", node_id));
1564 };
1565
1566 match &node.kind {
1567 NodeKind::Document => {
1568 for child in &node.children {
1569 self.serialize_html_node_with_context(*child, output, raw_text_context)?;
1570 }
1571 Ok(())
1572 }
1573 NodeKind::Element(element) => {
1574 let tag_name = self.serialized_element_name(element);
1575 output.push('<');
1576 output.push_str(tag_name.as_ref());
1577
1578 let attributes = self.serialize_html_attributes(element)?;
1579 if !attributes.is_empty() {
1580 output.push(' ');
1581 output.push_str(&attributes);
1582 }
1583
1584 if is_void_element(element.tag_name.as_str()) {
1585 if !node.children.is_empty() {
1586 return Err(format!(
1587 "cannot serialize void element <{}> with children",
1588 element.tag_name
1589 ));
1590 }
1591 output.push('>');
1592 return Ok(());
1593 }
1594
1595 output.push('>');
1596 let child_raw_text_context = is_raw_text_element(element.tag_name.as_str());
1597 for child in &node.children {
1598 self.serialize_html_node_with_context(*child, output, child_raw_text_context)?;
1599 }
1600 output.push_str("</");
1601 output.push_str(tag_name.as_ref());
1602 output.push('>');
1603 Ok(())
1604 }
1605 NodeKind::Text(text) => {
1606 if raw_text_context {
1607 output.push_str(&text.value);
1608 } else {
1609 output.push_str(&escape_html_text(&text.value));
1610 }
1611 Ok(())
1612 }
1613 NodeKind::Comment(comment) => {
1614 output.push_str("<!--");
1615 output.push_str(comment);
1616 output.push_str("-->");
1617 Ok(())
1618 }
1619 }
1620 }
1621
1622 fn serialize_html_attributes(&self, element: &ElementData) -> Result<String, String> {
1623 let mut parts = Vec::new();
1624 for (name, value) in &element.attributes {
1625 let name = self.serialized_attribute_name(element, name);
1626 if value.is_empty() {
1627 parts.push(name.into_owned());
1628 continue;
1629 }
1630
1631 parts.push(format!(r#"{name}="{}""#, escape_html_attribute(value)));
1632 }
1633
1634 Ok(parts.join(" "))
1635 }
1636
1637 fn serialized_element_name<'a>(&self, element: &'a ElementData) -> Cow<'a, str> {
1638 if element.namespace_uri == SVG_NAMESPACE_URI {
1639 Cow::Borrowed(adjust_svg_element_name(element.local_name.as_str()))
1640 } else {
1641 Cow::Borrowed(element.local_name.as_str())
1642 }
1643 }
1644
1645 fn serialized_attribute_name<'a>(&self, element: &ElementData, name: &'a str) -> Cow<'a, str> {
1646 if element.namespace_uri == SVG_NAMESPACE_URI {
1647 Cow::Borrowed(adjust_svg_attribute_name(name))
1648 } else if element.namespace_uri == MATHML_NAMESPACE_URI {
1649 match name {
1650 "definitionurl" => Cow::Borrowed("definitionURL"),
1651 _ => Cow::Borrowed(name),
1652 }
1653 } else {
1654 Cow::Borrowed(name)
1655 }
1656 }
1657
1658 fn ensure_mutation_parent(&self, parent: NodeId) -> Result<(), String> {
1659 let Some(node) = self.nodes.get(parent.index() as usize) else {
1660 return Err(format!("invalid node id: {:?}", parent));
1661 };
1662
1663 match &node.kind {
1664 NodeKind::Document | NodeKind::Element(_) => Ok(()),
1665 _ => Err(format!("node {:?} cannot contain children", parent)),
1666 }
1667 }
1668
1669 fn child_index(&self, parent: NodeId, child: NodeId) -> Option<usize> {
1670 let parent = self.nodes.get(parent.index() as usize)?;
1671 parent
1672 .children
1673 .iter()
1674 .position(|candidate| *candidate == child)
1675 }
1676
1677 fn validate_mutation_children(
1678 &self,
1679 parent: NodeId,
1680 children: &[NodeId],
1681 ) -> Result<(), String> {
1682 let mut seen = BTreeSet::new();
1683
1684 for child in children {
1685 if !seen.insert(*child) {
1686 return Err("duplicate child in mutation arguments".to_string());
1687 }
1688
1689 let Some(node) = self.nodes.get(child.index() as usize) else {
1690 return Err(format!("invalid node id: {:?}", child));
1691 };
1692
1693 if *child == self.document_id {
1694 return Err("document node cannot be inserted".to_string());
1695 }
1696
1697 if *child == parent {
1698 return Err(format!("node {:?} cannot be inserted into itself", parent));
1699 }
1700
1701 if matches!(node.kind, NodeKind::Document) {
1702 return Err("document node cannot be inserted".to_string());
1703 }
1704
1705 if self
1706 .collect_subtree_nodes([*child])
1707 .into_iter()
1708 .any(|descendant| descendant == parent)
1709 {
1710 return Err(format!(
1711 "cannot insert node {:?} into its descendant {:?}",
1712 child, parent
1713 ));
1714 }
1715 }
1716
1717 Ok(())
1718 }
1719
1720 fn insert_children_at(
1721 &mut self,
1722 parent: NodeId,
1723 insertion_index: usize,
1724 children: &[NodeId],
1725 ) -> Result<(), String> {
1726 self.ensure_mutation_parent(parent)?;
1727
1728 if children.is_empty() {
1729 return Ok(());
1730 }
1731
1732 self.validate_mutation_children(parent, children)?;
1733
1734 let parent_len = self.child_count(parent)?;
1735 let insertion_index = insertion_index.min(parent_len);
1736 let mut adjusted_insertion_index = insertion_index;
1737 let mut removals_by_parent: BTreeMap<NodeId, Vec<(usize, NodeId)>> = BTreeMap::new();
1738
1739 for child in children {
1740 let Some(old_parent) = self.parent_of(*child) else {
1741 continue;
1742 };
1743 let Some(old_index) = self.child_index(old_parent, *child) else {
1744 continue;
1745 };
1746
1747 if old_parent == parent && old_index < insertion_index {
1748 adjusted_insertion_index -= 1;
1749 }
1750
1751 removals_by_parent
1752 .entry(old_parent)
1753 .or_default()
1754 .push((old_index, *child));
1755 }
1756
1757 for (old_parent, mut removals) in removals_by_parent {
1758 removals.sort_by_key(|(index, _)| std::cmp::Reverse(*index));
1759 for (old_index, child) in removals {
1760 {
1761 let Some(parent_node) = self.nodes.get_mut(old_parent.index() as usize) else {
1762 return Err(format!("invalid node id: {:?}", old_parent));
1763 };
1764 if parent_node.children.get(old_index) != Some(&child) {
1765 return Err(format!(
1766 "node {:?} is not present in its parent {:?}",
1767 child, old_parent
1768 ));
1769 }
1770 parent_node.children.remove(old_index);
1771 }
1772 let Some(record) = self.nodes.get_mut(child.index() as usize) else {
1773 return Err(format!("invalid node id: {:?}", child));
1774 };
1775 record.parent = None;
1776 }
1777 }
1778
1779 let mut insertion_index = adjusted_insertion_index;
1780 for child in children {
1781 {
1782 let Some(parent_node) = self.nodes.get_mut(parent.index() as usize) else {
1783 return Err(format!("invalid node id: {:?}", parent));
1784 };
1785 parent_node.children.insert(insertion_index, *child);
1786 }
1787 let Some(record) = self.nodes.get_mut(child.index() as usize) else {
1788 return Err(format!("invalid node id: {:?}", child));
1789 };
1790 record.parent = Some(parent);
1791 insertion_index += 1;
1792 }
1793
1794 self.rebuild_indexes();
1795 self.rebuild_form_controls();
1796 Ok(())
1797 }
1798
1799 fn child_count(&self, parent: NodeId) -> Result<usize, String> {
1800 let Some(node) = self.nodes.get(parent.index() as usize) else {
1801 return Err(format!("invalid node id: {:?}", parent));
1802 };
1803
1804 match &node.kind {
1805 NodeKind::Document | NodeKind::Element(_) => Ok(node.children.len()),
1806 _ => Err(format!("node {:?} cannot contain children", parent)),
1807 }
1808 }
1809
1810 fn is_option_node(&self, node_id: NodeId) -> bool {
1811 matches!(
1812 self.nodes.get(node_id.index() as usize).map(|node| &node.kind),
1813 Some(NodeKind::Element(element)) if element.tag_name == "option"
1814 )
1815 }
1816
1817 fn option_value_for_node(&self, node_id: NodeId) -> String {
1818 let Some(node) = self.nodes.get(node_id.index() as usize) else {
1819 return String::new();
1820 };
1821
1822 let NodeKind::Element(element) = &node.kind else {
1823 return String::new();
1824 };
1825
1826 element
1827 .attributes
1828 .get("value")
1829 .cloned()
1830 .unwrap_or_else(|| self.text_content_for_node(node_id))
1831 }
1832
1833 fn file_input_value_for_node(&self, node_id: NodeId) -> String {
1834 self.side_tables
1835 .file_inputs
1836 .get(&node_id)
1837 .map(|state| state.files.join(", "))
1838 .unwrap_or_default()
1839 }
1840
1841 fn set_option_selected(&mut self, node_id: NodeId, selected: bool) -> Result<(), String> {
1842 let node_index = node_id.index() as usize;
1843 let Some(node) = self.nodes.get_mut(node_index) else {
1844 return Err(format!("invalid node id: {:?}", node_id));
1845 };
1846
1847 let NodeKind::Element(element) = &mut node.kind else {
1848 return Err(format!("node {:?} is not an option element", node_id));
1849 };
1850
1851 if element.tag_name != "option" {
1852 return Err(format!("node {:?} is not an option element", node_id));
1853 }
1854
1855 if selected {
1856 element
1857 .attributes
1858 .insert("selected".to_string(), String::new());
1859 } else {
1860 element.attributes.remove("selected");
1861 }
1862
1863 Ok(())
1864 }
1865
1866 fn select_value_for_node(&self, node_id: NodeId) -> String {
1867 let Some(node) = self.nodes.get(node_id.index() as usize) else {
1868 return String::new();
1869 };
1870
1871 let NodeKind::Element(element) = &node.kind else {
1872 return String::new();
1873 };
1874
1875 if element.tag_name != "select" {
1876 return String::new();
1877 }
1878
1879 let descendants = self.collect_subtree_nodes(node.children.iter().copied());
1880 for descendant_id in descendants {
1881 if !self.is_option_node(descendant_id) {
1882 continue;
1883 }
1884
1885 if self.is_option_selected(descendant_id) {
1886 return self.option_value_for_node(descendant_id);
1887 }
1888 }
1889
1890 String::new()
1891 }
1892
1893 fn is_option_selected(&self, node_id: NodeId) -> bool {
1894 let Some(node) = self.nodes.get(node_id.index() as usize) else {
1895 return false;
1896 };
1897
1898 let NodeKind::Element(element) = &node.kind else {
1899 return false;
1900 };
1901
1902 element.attributes.contains_key("selected")
1903 }
1904
1905 fn collect_subtree_nodes<I>(&self, roots: I) -> Vec<NodeId>
1906 where
1907 I: IntoIterator<Item = NodeId>,
1908 {
1909 let mut collected = Vec::new();
1910 for root in roots {
1911 self.collect_subtree_nodes_inner(root, &mut collected);
1912 }
1913 collected
1914 }
1915
1916 fn collect_subtree_nodes_inner(&self, node_id: NodeId, collected: &mut Vec<NodeId>) {
1917 collected.push(node_id);
1918 let Some(node) = self.nodes.get(node_id.index() as usize) else {
1919 return;
1920 };
1921 for child in &node.children {
1922 self.collect_subtree_nodes_inner(*child, collected);
1923 }
1924 }
1925
1926 fn rebuild_form_controls(&mut self) {
1927 let previous_form_controls = std::mem::take(&mut self.side_tables.form_controls);
1928 self.index_form_controls(self.document_id, &previous_form_controls);
1929 }
1930
1931 fn index_form_controls(
1932 &mut self,
1933 node_id: NodeId,
1934 previous_form_controls: &BTreeMap<NodeId, super::FormControlState>,
1935 ) {
1936 let Some(node) = self.nodes.get(node_id.index() as usize).cloned() else {
1937 return;
1938 };
1939
1940 if let NodeKind::Element(element) = &node.kind {
1941 match element.tag_name.as_str() {
1942 "textarea" => {
1943 let indeterminate = previous_form_controls
1944 .get(&node_id)
1945 .map(|state| state.indeterminate)
1946 .unwrap_or(false);
1947 self.side_tables.form_controls.insert(
1948 node_id,
1949 super::FormControlState {
1950 value: self.text_content_for_node(node_id),
1951 checked: false,
1952 indeterminate,
1953 },
1954 );
1955 }
1956 "input"
1957 if is_text_input_type(element.attributes.get("type").map(String::as_str)) =>
1958 {
1959 let indeterminate = previous_form_controls
1960 .get(&node_id)
1961 .map(|state| state.indeterminate)
1962 .unwrap_or(false);
1963 self.side_tables.form_controls.insert(
1964 node_id,
1965 super::FormControlState {
1966 value: element.attributes.get("value").cloned().unwrap_or_default(),
1967 checked: false,
1968 indeterminate,
1969 },
1970 );
1971 }
1972 "input"
1973 if is_checkable_input_type(
1974 element.attributes.get("type").map(String::as_str),
1975 ) =>
1976 {
1977 let indeterminate = previous_form_controls
1978 .get(&node_id)
1979 .map(|state| state.indeterminate)
1980 .unwrap_or(false);
1981 self.side_tables.form_controls.insert(
1982 node_id,
1983 super::FormControlState {
1984 value: element
1985 .attributes
1986 .get("value")
1987 .cloned()
1988 .unwrap_or_else(|| "on".to_string()),
1989 checked: element.attributes.contains_key("checked"),
1990 indeterminate,
1991 },
1992 );
1993 }
1994 _ => {}
1995 }
1996 }
1997
1998 for child in node.children {
1999 self.index_form_controls(child, previous_form_controls);
2000 }
2001 }
2002
2003 fn rebuild_indexes(&mut self) {
2004 self.indexes = DomIndexes::default();
2005 self.index_node(self.document_id);
2006 }
2007
2008 fn index_node(&mut self, node_id: NodeId) {
2009 let Some(node) = self.nodes.get(node_id.index() as usize).cloned() else {
2010 return;
2011 };
2012
2013 if let NodeKind::Element(element) = node.kind {
2014 self.indexes
2015 .tag_index
2016 .entry(element.tag_name.clone())
2017 .or_default()
2018 .push(node_id);
2019
2020 if let Some(value) = element.attributes.get("id") {
2021 self.indexes
2022 .id_index
2023 .entry(value.clone())
2024 .or_insert(node_id);
2025 }
2026
2027 if let Some(value) = element.attributes.get("name") {
2028 self.indexes
2029 .name_index
2030 .entry(value.clone())
2031 .or_default()
2032 .push(node_id);
2033 }
2034
2035 if let Some(value) = element.attributes.get("class") {
2036 for class_name in value.split_ascii_whitespace() {
2037 if !class_name.is_empty() {
2038 self.indexes
2039 .class_index
2040 .entry(class_name.to_string())
2041 .or_default()
2042 .push(node_id);
2043 }
2044 }
2045 }
2046 }
2047
2048 for child in node.children {
2049 self.index_node(child);
2050 }
2051 }
2052
2053 fn select_by_chain(&self, chain: &SelectorChain, scope_root: Option<NodeId>) -> Vec<NodeId> {
2054 let Some(last) = chain.parts.last() else {
2055 return Vec::new();
2056 };
2057
2058 let candidates = self.selector_candidates(last);
2059 let mut results: Vec<NodeId> = candidates
2060 .into_iter()
2061 .filter(|node_id| self.matches_selector_chain(*node_id, chain, scope_root))
2062 .collect();
2063 results.dedup();
2064 results
2065 }
2066
2067 fn select_by_selector_chains(
2068 &self,
2069 chains: &[SelectorChain],
2070 scope_root: Option<NodeId>,
2071 ) -> Vec<NodeId> {
2072 match chains {
2073 [] => Vec::new(),
2074 [single] => self.select_by_chain(single, scope_root),
2075 _ => {
2076 let mut matched = BTreeSet::new();
2077 for chain in chains {
2078 matched.extend(self.select_by_chain(chain, scope_root));
2079 }
2080
2081 self.nodes
2082 .iter()
2083 .filter_map(|node| match &node.kind {
2084 NodeKind::Element(_) if matched.contains(&node.id) => Some(node.id),
2085 _ => None,
2086 })
2087 .collect()
2088 }
2089 }
2090 }
2091
2092 fn selector_candidates(&self, query: &SelectorQuery) -> Vec<NodeId> {
2093 if let Some(id) = query.id.as_ref() {
2094 return self.indexes.id_index.get(id).copied().into_iter().collect();
2095 }
2096
2097 let mut candidate_lists: Vec<&[NodeId]> = Vec::new();
2098
2099 if let Some(tag) = query.tag.as_ref() {
2100 match self.indexes.tag_index.get(tag) {
2101 Some(nodes) => candidate_lists.push(nodes),
2102 None => return Vec::new(),
2103 }
2104 }
2105
2106 for class_name in &query.classes {
2107 match self.indexes.class_index.get(class_name) {
2108 Some(nodes) => candidate_lists.push(nodes),
2109 None => return Vec::new(),
2110 }
2111 }
2112
2113 if candidate_lists.is_empty() {
2114 return self
2115 .nodes
2116 .iter()
2117 .filter_map(|node| match node.kind {
2118 NodeKind::Element(_) => Some(node.id),
2119 _ => None,
2120 })
2121 .collect();
2122 }
2123
2124 candidate_lists
2125 .into_iter()
2126 .min_by_key(|nodes| nodes.len())
2127 .map(|nodes| nodes.to_vec())
2128 .unwrap_or_default()
2129 }
2130
2131 fn matches_selector_chain(
2132 &self,
2133 node_id: NodeId,
2134 chain: &SelectorChain,
2135 scope_root: Option<NodeId>,
2136 ) -> bool {
2137 let Some(last_index) = chain.parts.len().checked_sub(1) else {
2138 return false;
2139 };
2140 self.matches_selector_chain_part(
2141 node_id,
2142 &chain.parts,
2143 &chain.relations,
2144 last_index,
2145 scope_root,
2146 )
2147 }
2148
2149 fn matches_selector_chain_part(
2150 &self,
2151 node_id: NodeId,
2152 parts: &[SelectorQuery],
2153 relations: &[SelectorCombinator],
2154 index: usize,
2155 scope_root: Option<NodeId>,
2156 ) -> bool {
2157 if !self.matches_selector_query(node_id, &parts[index], scope_root) {
2158 return false;
2159 }
2160
2161 if index == 0 {
2162 return true;
2163 }
2164
2165 match relations[index - 1] {
2166 SelectorCombinator::Child => {
2167 let Some(parent_id) = self.parent_of(node_id) else {
2168 return false;
2169 };
2170 self.matches_selector_chain_part(parent_id, parts, relations, index - 1, scope_root)
2171 }
2172 SelectorCombinator::AdjacentSibling => {
2173 let Some(previous_sibling) = self.previous_element_sibling_of(node_id) else {
2174 return false;
2175 };
2176 self.matches_selector_chain_part(
2177 previous_sibling,
2178 parts,
2179 relations,
2180 index - 1,
2181 scope_root,
2182 )
2183 }
2184 SelectorCombinator::GeneralSibling => {
2185 let mut sibling = self.previous_element_sibling_of(node_id);
2186 while let Some(previous_sibling) = sibling {
2187 if self.matches_selector_chain_part(
2188 previous_sibling,
2189 parts,
2190 relations,
2191 index - 1,
2192 scope_root,
2193 ) {
2194 return true;
2195 }
2196 sibling = self.previous_element_sibling_of(previous_sibling);
2197 }
2198 false
2199 }
2200 SelectorCombinator::Descendant => {
2201 let mut ancestor = self.parent_of(node_id);
2202 while let Some(ancestor_id) = ancestor {
2203 if self.matches_selector_chain_part(
2204 ancestor_id,
2205 parts,
2206 relations,
2207 index - 1,
2208 scope_root,
2209 ) {
2210 return true;
2211 }
2212 ancestor = self.parent_of(ancestor_id);
2213 }
2214 false
2215 }
2216 }
2217 }
2218
2219 fn matches_selector_query(
2220 &self,
2221 node_id: NodeId,
2222 query: &SelectorQuery,
2223 scope_root: Option<NodeId>,
2224 ) -> bool {
2225 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2226 return false;
2227 };
2228
2229 let NodeKind::Element(element) = &node.kind else {
2230 return false;
2231 };
2232
2233 if let Some(tag) = query.tag.as_ref() {
2234 if element.tag_name != *tag {
2235 return false;
2236 }
2237 }
2238
2239 if let Some(id) = query.id.as_ref() {
2240 if element.attributes.get("id") != Some(id) {
2241 return false;
2242 }
2243 }
2244
2245 if !query.classes.is_empty() {
2246 let Some(value) = element.attributes.get("class") else {
2247 return false;
2248 };
2249
2250 let element_classes: Vec<&str> = value.split_ascii_whitespace().collect();
2251 if !query.classes.iter().all(|class_name| {
2252 element_classes
2253 .iter()
2254 .any(|candidate| candidate == class_name)
2255 }) {
2256 return false;
2257 }
2258 }
2259
2260 for attribute in &query.attributes {
2261 let Some(element_value) = element.attributes.get(&attribute.name) else {
2262 return false;
2263 };
2264
2265 match (
2266 attribute.operator,
2267 attribute.value.as_ref(),
2268 attribute.case_sensitivity,
2269 ) {
2270 (SelectorAttributeOperator::Exists, None, _) => {}
2271 (
2272 SelectorAttributeOperator::Exact,
2273 Some(value),
2274 SelectorAttributeCaseSensitivity::CaseSensitive,
2275 ) if element_value == value => {}
2276 (
2277 SelectorAttributeOperator::Exact,
2278 Some(value),
2279 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2280 ) if element_value.eq_ignore_ascii_case(value) => {}
2281 (
2282 SelectorAttributeOperator::Prefix,
2283 Some(value),
2284 SelectorAttributeCaseSensitivity::CaseSensitive,
2285 ) if element_value.starts_with(value) => {}
2286 (
2287 SelectorAttributeOperator::Prefix,
2288 Some(value),
2289 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2290 ) if starts_with_ignore_ascii_case(element_value, value) => {}
2291 (
2292 SelectorAttributeOperator::Suffix,
2293 Some(value),
2294 SelectorAttributeCaseSensitivity::CaseSensitive,
2295 ) if element_value.ends_with(value) => {}
2296 (
2297 SelectorAttributeOperator::Suffix,
2298 Some(value),
2299 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2300 ) if ends_with_ignore_ascii_case(element_value, value) => {}
2301 (
2302 SelectorAttributeOperator::Contains,
2303 Some(value),
2304 SelectorAttributeCaseSensitivity::CaseSensitive,
2305 ) if element_value.contains(value) => {}
2306 (
2307 SelectorAttributeOperator::Contains,
2308 Some(value),
2309 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2310 ) if contains_ignore_ascii_case(element_value, value) => {}
2311 (
2312 SelectorAttributeOperator::Includes,
2313 Some(value),
2314 SelectorAttributeCaseSensitivity::CaseSensitive,
2315 ) if element_value
2316 .split_ascii_whitespace()
2317 .any(|candidate| candidate == value) => {}
2318 (
2319 SelectorAttributeOperator::Includes,
2320 Some(value),
2321 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2322 ) if element_value
2323 .split_ascii_whitespace()
2324 .any(|candidate| candidate.eq_ignore_ascii_case(value)) => {}
2325 (
2326 SelectorAttributeOperator::DashMatch,
2327 Some(value),
2328 SelectorAttributeCaseSensitivity::CaseSensitive,
2329 ) if element_value == value
2330 || (!value.is_empty()
2331 && element_value
2332 .strip_prefix(value)
2333 .is_some_and(|rest| rest.starts_with('-'))) => {}
2334 (
2335 SelectorAttributeOperator::DashMatch,
2336 Some(value),
2337 SelectorAttributeCaseSensitivity::AsciiInsensitive,
2338 ) if element_value.eq_ignore_ascii_case(value)
2339 || (!value.is_empty()
2340 && starts_with_ignore_ascii_case(element_value, value)
2341 && element_value
2342 .get(value.len()..)
2343 .is_some_and(|rest| rest.starts_with('-'))) => {}
2344 _ => {
2345 return false;
2346 }
2347 }
2348 }
2349
2350 for pseudo_class in &query.pseudo_classes {
2351 if !self.matches_selector_pseudo_class(node_id, pseudo_class, scope_root) {
2352 return false;
2353 }
2354 }
2355
2356 true
2357 }
2358
2359 fn matches_selector_pseudo_class(
2360 &self,
2361 node_id: NodeId,
2362 pseudo_class: &SelectorPseudoClass,
2363 scope_root: Option<NodeId>,
2364 ) -> bool {
2365 match pseudo_class {
2366 SelectorPseudoClass::Scope => scope_root == Some(node_id),
2367 SelectorPseudoClass::Root => self.is_root_pseudo_class(node_id),
2368 SelectorPseudoClass::Empty => self.is_empty_pseudo_class(node_id),
2369 SelectorPseudoClass::Target => self.is_target_pseudo_class(node_id),
2370 SelectorPseudoClass::Lang(langs) => self.is_lang_pseudo_class(node_id, langs),
2371 SelectorPseudoClass::AnyLink => self.is_any_link_pseudo_class(node_id),
2372 SelectorPseudoClass::Defined => self.is_defined_pseudo_class(node_id),
2373 SelectorPseudoClass::Dir(dir) => self.is_dir_pseudo_class(node_id, *dir),
2374 SelectorPseudoClass::PlaceholderShown => {
2375 self.is_placeholder_shown_pseudo_class(node_id)
2376 }
2377 SelectorPseudoClass::Blank => self.is_blank_pseudo_class(node_id),
2378 SelectorPseudoClass::Indeterminate => self.is_indeterminate_pseudo_class(node_id),
2379 SelectorPseudoClass::Default => self.is_default_pseudo_class(node_id),
2380 SelectorPseudoClass::Focus => self.is_focus_pseudo_class(node_id),
2381 SelectorPseudoClass::FocusVisible => self.is_focus_visible_pseudo_class(node_id),
2382 SelectorPseudoClass::FocusWithin => self.is_focus_within_pseudo_class(node_id),
2383 SelectorPseudoClass::Required => self.is_required_pseudo_class(node_id),
2384 SelectorPseudoClass::Optional => self.is_optional_pseudo_class(node_id),
2385 SelectorPseudoClass::Valid => self.is_valid_pseudo_class(node_id),
2386 SelectorPseudoClass::Invalid => self.is_invalid_pseudo_class(node_id),
2387 SelectorPseudoClass::InRange => self.is_in_range_pseudo_class(node_id),
2388 SelectorPseudoClass::OutOfRange => self.is_out_of_range_pseudo_class(node_id),
2389 SelectorPseudoClass::ReadOnly => self.is_read_only_pseudo_class(node_id),
2390 SelectorPseudoClass::ReadWrite => self.is_read_write_pseudo_class(node_id),
2391 SelectorPseudoClass::OnlyChild => self.is_only_child_pseudo_class(node_id),
2392 SelectorPseudoClass::OnlyOfType => self.is_only_of_type_pseudo_class(node_id),
2393 SelectorPseudoClass::FirstChild => self.is_first_child(node_id),
2394 SelectorPseudoClass::LastChild => self.is_last_child(node_id),
2395 SelectorPseudoClass::FirstOfType => self.is_first_of_type(node_id),
2396 SelectorPseudoClass::LastOfType => self.is_last_of_type(node_id),
2397 SelectorPseudoClass::NthChild(pattern) => self.is_nth_child(node_id, pattern),
2398 SelectorPseudoClass::NthLastChild(pattern) => self.is_nth_last_child(node_id, pattern),
2399 SelectorPseudoClass::NthOfType(pattern) => self.is_nth_of_type(node_id, pattern),
2400 SelectorPseudoClass::NthLastOfType(pattern) => {
2401 self.is_nth_last_of_type(node_id, pattern)
2402 }
2403 SelectorPseudoClass::Is(chains) => chains
2404 .iter()
2405 .any(|chain| self.matches_selector_chain(node_id, chain, scope_root)),
2406 SelectorPseudoClass::Where(chains) => chains
2407 .iter()
2408 .any(|chain| self.matches_selector_chain(node_id, chain, scope_root)),
2409 SelectorPseudoClass::Not(chains) => !chains
2410 .iter()
2411 .any(|chain| self.matches_selector_chain(node_id, chain, scope_root)),
2412 SelectorPseudoClass::Has(chains) => chains.iter().any(|relative| {
2413 self.matches_selector_relative_selector(node_id, relative, scope_root)
2414 }),
2415 SelectorPseudoClass::Checked => self.is_checked_pseudo_class(node_id),
2416 SelectorPseudoClass::Disabled => self.is_disabled_pseudo_class(node_id),
2417 SelectorPseudoClass::Enabled => self.is_enabled_pseudo_class(node_id),
2418 }
2419 }
2420
2421 fn matches_selector_relative_selector(
2422 &self,
2423 node_id: NodeId,
2424 relative_selector: &SelectorRelativeSelector,
2425 _scope_root: Option<NodeId>,
2426 ) -> bool {
2427 match relative_selector.combinator {
2428 Some(SelectorCombinator::Child) => {
2429 self.has_child_matching_chain(node_id, &relative_selector.chain, Some(node_id))
2430 }
2431 Some(SelectorCombinator::AdjacentSibling) => self.has_adjacent_sibling_matching_chain(
2432 node_id,
2433 &relative_selector.chain,
2434 Some(node_id),
2435 ),
2436 Some(SelectorCombinator::GeneralSibling) => self.has_general_sibling_matching_chain(
2437 node_id,
2438 &relative_selector.chain,
2439 Some(node_id),
2440 ),
2441 Some(SelectorCombinator::Descendant) | None => {
2442 if self.chain_starts_with_scope(&relative_selector.chain) {
2443 self.matches_selector_chain(node_id, &relative_selector.chain, Some(node_id))
2444 } else {
2445 self.has_descendant_matching_chain(
2446 node_id,
2447 &relative_selector.chain,
2448 Some(node_id),
2449 )
2450 }
2451 }
2452 }
2453 }
2454
2455 fn chain_starts_with_scope(&self, chain: &SelectorChain) -> bool {
2456 chain.parts.first().is_some_and(|query| {
2457 query
2458 .pseudo_classes
2459 .iter()
2460 .any(|pseudo| matches!(pseudo, SelectorPseudoClass::Scope))
2461 })
2462 }
2463
2464 fn has_descendant_matching_chain(
2465 &self,
2466 node_id: NodeId,
2467 chain: &SelectorChain,
2468 scope_root: Option<NodeId>,
2469 ) -> bool {
2470 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2471 return false;
2472 };
2473
2474 node.children.iter().copied().any(|child_id| {
2475 self.matches_selector_chain(child_id, chain, scope_root)
2476 || self.has_descendant_matching_chain(child_id, chain, scope_root)
2477 })
2478 }
2479
2480 fn has_child_matching_chain(
2481 &self,
2482 node_id: NodeId,
2483 chain: &SelectorChain,
2484 scope_root: Option<NodeId>,
2485 ) -> bool {
2486 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2487 return false;
2488 };
2489
2490 node.children
2491 .iter()
2492 .copied()
2493 .any(|child_id| self.matches_selector_chain(child_id, chain, scope_root))
2494 }
2495
2496 fn has_adjacent_sibling_matching_chain(
2497 &self,
2498 node_id: NodeId,
2499 chain: &SelectorChain,
2500 scope_root: Option<NodeId>,
2501 ) -> bool {
2502 self.next_element_sibling_of(node_id)
2503 .is_some_and(|sibling_id| self.matches_selector_chain(sibling_id, chain, scope_root))
2504 }
2505
2506 fn has_general_sibling_matching_chain(
2507 &self,
2508 node_id: NodeId,
2509 chain: &SelectorChain,
2510 scope_root: Option<NodeId>,
2511 ) -> bool {
2512 let mut sibling = self.next_element_sibling_of(node_id);
2513 while let Some(next_sibling) = sibling {
2514 if self.matches_selector_chain(next_sibling, chain, scope_root) {
2515 return true;
2516 }
2517 sibling = self.next_element_sibling_of(next_sibling);
2518 }
2519
2520 false
2521 }
2522
2523 fn parse_selector_chain(selector: &str) -> Result<SelectorChain, String> {
2524 let mut pos = 0;
2525 let chain = Self::parse_selector_chain_from_pos(selector, &mut pos)?;
2526 let bytes = selector.as_bytes();
2527 skip_selector_whitespace(bytes, &mut pos);
2528 if pos != bytes.len() {
2529 return Err(selector_not_supported(selector));
2530 }
2531
2532 Ok(chain)
2533 }
2534
2535 fn parse_selector_chain_from_pos(
2536 selector: &str,
2537 pos: &mut usize,
2538 ) -> Result<SelectorChain, String> {
2539 let mut parts = Vec::new();
2540 let mut relations = Vec::new();
2541 let bytes = selector.as_bytes();
2542
2543 parts.push(Self::parse_selector_compound(selector, pos)?);
2544
2545 while *pos < bytes.len() {
2546 let had_whitespace = skip_selector_whitespace(bytes, pos);
2547 if *pos >= bytes.len() {
2548 break;
2549 }
2550
2551 let relation = match bytes[*pos] {
2552 b'>' => {
2553 *pos += 1;
2554 SelectorCombinator::Child
2555 }
2556 b'+' => {
2557 *pos += 1;
2558 SelectorCombinator::AdjacentSibling
2559 }
2560 b'~' => {
2561 *pos += 1;
2562 SelectorCombinator::GeneralSibling
2563 }
2564 byte if is_selector_combinator_byte(byte) => {
2565 return Err(selector_not_supported(selector));
2566 }
2567 _ if had_whitespace => SelectorCombinator::Descendant,
2568 _ => return Err(selector_not_supported(selector)),
2569 };
2570
2571 skip_selector_whitespace(bytes, pos);
2572 if *pos >= bytes.len() {
2573 return Err(selector_not_supported(selector));
2574 }
2575
2576 let part = Self::parse_selector_compound(selector, pos)?;
2577 relations.push(relation);
2578 parts.push(part);
2579 }
2580
2581 if parts.is_empty() {
2582 return Err(selector_not_supported(selector));
2583 }
2584
2585 Ok(SelectorChain { parts, relations })
2586 }
2587
2588 fn parse_selector_list(selector: &str) -> Result<Vec<SelectorChain>, String> {
2589 let mut chains = Vec::new();
2590
2591 for item in split_selector_list_items(selector)? {
2592 chains.push(Self::parse_selector_chain(item)?);
2593 }
2594
2595 if chains.is_empty() {
2596 return Err(selector_not_supported(selector));
2597 }
2598
2599 Ok(chains)
2600 }
2601
2602 fn parse_selector_compound(selector: &str, pos: &mut usize) -> Result<SelectorQuery, String> {
2603 let mut query = SelectorQuery::default();
2604 let bytes = selector.as_bytes();
2605 let mut saw_token = false;
2606
2607 while *pos < bytes.len() {
2608 if bytes[*pos].is_ascii_whitespace() || is_selector_combinator_byte(bytes[*pos]) {
2609 break;
2610 }
2611
2612 match bytes[*pos] {
2613 b'#' => {
2614 *pos += 1;
2615 let token = parse_selector_token(selector, pos)?;
2616 if query.id.is_some() {
2617 return Err(selector_not_supported(selector));
2618 }
2619 query.id = Some(token);
2620 saw_token = true;
2621 }
2622 b'.' => {
2623 *pos += 1;
2624 let token = parse_selector_token(selector, pos)?;
2625 query.classes.push(token);
2626 saw_token = true;
2627 }
2628 b'[' => {
2629 *pos += 1;
2630 skip_selector_whitespace(bytes, pos);
2631 let name = parse_selector_token(selector, pos)?.to_ascii_lowercase();
2632 skip_selector_whitespace(bytes, pos);
2633
2634 let (operator, value, case_sensitivity) =
2635 if *pos < bytes.len() && bytes[*pos] != b']' {
2636 parse_selector_attribute_operator_and_value(selector, pos)?
2637 } else {
2638 (
2639 SelectorAttributeOperator::Exists,
2640 None,
2641 SelectorAttributeCaseSensitivity::CaseSensitive,
2642 )
2643 };
2644
2645 skip_selector_whitespace(bytes, pos);
2646 if *pos >= bytes.len() || bytes[*pos] != b']' {
2647 return Err(selector_not_supported(selector));
2648 }
2649 *pos += 1;
2650 query.attributes.push(SelectorAttribute {
2651 name,
2652 operator,
2653 value,
2654 case_sensitivity,
2655 });
2656 saw_token = true;
2657 }
2658 b':' => {
2659 *pos += 1;
2660 let token = parse_selector_token(selector, pos)?;
2661 let pseudo_class = match token.as_str() {
2662 "root" => SelectorPseudoClass::Root,
2663 "scope" => SelectorPseudoClass::Scope,
2664 "empty" => SelectorPseudoClass::Empty,
2665 "target" => SelectorPseudoClass::Target,
2666 "link" | "any-link" => SelectorPseudoClass::AnyLink,
2667 "defined" => SelectorPseudoClass::Defined,
2668 "lang" => SelectorPseudoClass::Lang(parse_lang_argument(selector, pos)?),
2669 "dir" => SelectorPseudoClass::Dir(parse_dir_argument(selector, pos)?),
2670 "placeholder-shown" => SelectorPseudoClass::PlaceholderShown,
2671 "blank" => SelectorPseudoClass::Blank,
2672 "indeterminate" => SelectorPseudoClass::Indeterminate,
2673 "default" => SelectorPseudoClass::Default,
2674 "focus" => SelectorPseudoClass::Focus,
2675 "focus-visible" => SelectorPseudoClass::FocusVisible,
2676 "focus-within" => SelectorPseudoClass::FocusWithin,
2677 "required" => SelectorPseudoClass::Required,
2678 "optional" => SelectorPseudoClass::Optional,
2679 "valid" => SelectorPseudoClass::Valid,
2680 "invalid" => SelectorPseudoClass::Invalid,
2681 "in-range" => SelectorPseudoClass::InRange,
2682 "out-of-range" => SelectorPseudoClass::OutOfRange,
2683 "read-only" => SelectorPseudoClass::ReadOnly,
2684 "read-write" => SelectorPseudoClass::ReadWrite,
2685 "only-child" => SelectorPseudoClass::OnlyChild,
2686 "only-of-type" => SelectorPseudoClass::OnlyOfType,
2687 "first-child" => SelectorPseudoClass::FirstChild,
2688 "last-child" => SelectorPseudoClass::LastChild,
2689 "first-of-type" => SelectorPseudoClass::FirstOfType,
2690 "last-of-type" => SelectorPseudoClass::LastOfType,
2691 "nth-child" => {
2692 SelectorPseudoClass::NthChild(parse_nth_child_argument(selector, pos)?)
2693 }
2694 "nth-last-child" => SelectorPseudoClass::NthLastChild(
2695 parse_nth_child_argument(selector, pos)?,
2696 ),
2697 "nth-of-type" => {
2698 SelectorPseudoClass::NthOfType(parse_nth_child_argument(selector, pos)?)
2699 }
2700 "nth-last-of-type" => SelectorPseudoClass::NthLastOfType(
2701 parse_nth_child_argument(selector, pos)?,
2702 ),
2703 "is" => {
2704 SelectorPseudoClass::Is(parse_logical_pseudo_argument(selector, pos)?)
2705 }
2706 "where" => SelectorPseudoClass::Where(parse_logical_pseudo_argument(
2707 selector, pos,
2708 )?),
2709 "not" => {
2710 SelectorPseudoClass::Not(parse_logical_pseudo_argument(selector, pos)?)
2711 }
2712 "has" => SelectorPseudoClass::Has(parse_relative_selector_argument(
2713 selector, pos,
2714 )?),
2715 "checked" => SelectorPseudoClass::Checked,
2716 "disabled" => SelectorPseudoClass::Disabled,
2717 "enabled" => SelectorPseudoClass::Enabled,
2718 _ => return Err(selector_not_supported(selector)),
2719 };
2720 query.pseudo_classes.push(pseudo_class);
2721 saw_token = true;
2722 }
2723 byte if is_simple_name_byte(byte) => {
2724 let token = parse_selector_token(selector, pos)?;
2725 if query.tag.is_some() {
2726 return Err(selector_not_supported(selector));
2727 }
2728 query.tag = Some(token.to_ascii_lowercase());
2729 saw_token = true;
2730 }
2731 _ => return Err(selector_not_supported(selector)),
2732 }
2733 }
2734
2735 if !saw_token {
2736 return Err(selector_not_supported(selector));
2737 }
2738
2739 Ok(query)
2740 }
2741
2742 fn parent_of(&self, node_id: NodeId) -> Option<NodeId> {
2743 self.nodes
2744 .get(node_id.index() as usize)
2745 .and_then(|node| node.parent)
2746 }
2747
2748 pub fn root_element_id(&self) -> Option<NodeId> {
2749 let document = self.nodes.get(self.document_id.index() as usize)?;
2750 document.children.iter().find_map(|child| {
2751 matches!(
2752 self.nodes
2753 .get(child.index() as usize)
2754 .map(|node| &node.kind),
2755 Some(NodeKind::Element(_))
2756 )
2757 .then_some(*child)
2758 })
2759 }
2760
2761 pub fn document_element_id(&self) -> Option<NodeId> {
2762 self.root_element_id()
2763 }
2764
2765 pub fn head_element_id(&self) -> Option<NodeId> {
2766 let root = self.root_element_id()?;
2767 if self.tag_name_for(root) == Some("head") {
2768 return Some(root);
2769 }
2770 if self.tag_name_for(root) != Some("html") {
2771 return None;
2772 }
2773
2774 self.child_element_with_tag_name(root, "head")
2775 }
2776
2777 pub fn body_element_id(&self) -> Option<NodeId> {
2778 let root = self.root_element_id()?;
2779 if self.tag_name_for(root) == Some("body") {
2780 return Some(root);
2781 }
2782 if self.tag_name_for(root) != Some("html") {
2783 return None;
2784 }
2785
2786 self.child_element_with_tag_name(root, "body")
2787 }
2788
2789 fn is_root_pseudo_class(&self, node_id: NodeId) -> bool {
2790 self.root_element_id() == Some(node_id)
2791 }
2792
2793 fn is_empty_pseudo_class(&self, node_id: NodeId) -> bool {
2794 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2795 return false;
2796 };
2797 let NodeKind::Element(_) = node.kind else {
2798 return false;
2799 };
2800
2801 !node.children.iter().any(|child| {
2802 matches!(
2803 self.nodes
2804 .get(child.index() as usize)
2805 .map(|child_node| &child_node.kind),
2806 Some(NodeKind::Element(_)) | Some(NodeKind::Text(_))
2807 )
2808 })
2809 }
2810
2811 fn is_target_pseudo_class(&self, node_id: NodeId) -> bool {
2812 self.target_fragment()
2813 .and_then(|fragment| self.target_node_for_fragment(fragment))
2814 == Some(node_id)
2815 }
2816
2817 fn is_lang_pseudo_class(&self, node_id: NodeId, langs: &[String]) -> bool {
2818 let mut current = Some(node_id);
2819
2820 while let Some(current_id) = current {
2821 let Some(node) = self.nodes.get(current_id.index() as usize) else {
2822 return false;
2823 };
2824 let NodeKind::Element(element) = &node.kind else {
2825 return false;
2826 };
2827
2828 if let Some(value) = element
2829 .attributes
2830 .get("lang")
2831 .or_else(|| element.attributes.get("xml:lang"))
2832 {
2833 let value = value.trim();
2834 if !value.is_empty() {
2835 let value = value.to_ascii_lowercase();
2836 return langs.iter().any(|lang| lang_matches_range(&value, lang));
2837 }
2838 }
2839
2840 current = node.parent;
2841 }
2842
2843 false
2844 }
2845
2846 fn is_any_link_pseudo_class(&self, node_id: NodeId) -> bool {
2847 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2848 return false;
2849 };
2850 let NodeKind::Element(element) = &node.kind else {
2851 return false;
2852 };
2853
2854 matches!(element.tag_name.as_str(), "a" | "area") && element.attributes.contains_key("href")
2855 }
2856
2857 fn is_defined_pseudo_class(&self, node_id: NodeId) -> bool {
2858 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2859 return false;
2860 };
2861 let NodeKind::Element(element) = &node.kind else {
2862 return false;
2863 };
2864
2865 if element.namespace_uri == HTML_NAMESPACE_URI {
2866 !element.tag_name.contains('-')
2867 } else {
2868 true
2869 }
2870 }
2871
2872 fn target_node_for_fragment(&self, fragment: &str) -> Option<NodeId> {
2873 if fragment.is_empty() {
2874 return None;
2875 }
2876
2877 if let Some(node_id) = self.indexes.id_index.get(fragment) {
2878 return Some(*node_id);
2879 }
2880
2881 self.indexes
2882 .name_index
2883 .get(fragment)
2884 .and_then(|nodes| nodes.first().copied())
2885 }
2886
2887 fn is_dir_pseudo_class(&self, node_id: NodeId, dir: SelectorDirValue) -> bool {
2888 self.inherited_directionality(node_id) == Some(dir)
2889 }
2890
2891 fn is_placeholder_shown_pseudo_class(&self, node_id: NodeId) -> bool {
2892 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2893 return false;
2894 };
2895 let NodeKind::Element(element) = &node.kind else {
2896 return false;
2897 };
2898
2899 match element.tag_name.as_str() {
2900 "textarea" => {
2901 element.attributes.contains_key("placeholder")
2902 && self.value_for_node(node_id).is_empty()
2903 }
2904 "input" if is_text_input_type(element.attributes.get("type").map(String::as_str)) => {
2905 element.attributes.contains_key("placeholder")
2906 && self.value_for_node(node_id).is_empty()
2907 }
2908 _ => false,
2909 }
2910 }
2911
2912 fn is_blank_pseudo_class(&self, node_id: NodeId) -> bool {
2913 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2914 return false;
2915 };
2916 let NodeKind::Element(element) = &node.kind else {
2917 return false;
2918 };
2919
2920 match element.tag_name.as_str() {
2921 "textarea" => self.value_for_node(node_id).trim().is_empty(),
2922 "input" if is_blank_input_type(element.attributes.get("type").map(String::as_str)) => {
2923 self.value_for_node(node_id).trim().is_empty()
2924 }
2925 _ => false,
2926 }
2927 }
2928
2929 fn is_focus_pseudo_class(&self, node_id: NodeId) -> bool {
2930 self.focused_node() == Some(node_id)
2931 }
2932
2933 fn is_focus_visible_pseudo_class(&self, node_id: NodeId) -> bool {
2934 self.is_focus_pseudo_class(node_id)
2935 }
2936
2937 fn is_focus_within_pseudo_class(&self, node_id: NodeId) -> bool {
2938 let mut current = self.focused_node();
2939
2940 while let Some(current_id) = current {
2941 if current_id == node_id {
2942 return true;
2943 }
2944 current = self.parent_of(current_id);
2945 }
2946
2947 false
2948 }
2949
2950 fn is_required_pseudo_class(&self, node_id: NodeId) -> bool {
2951 self.is_required_form_control_element(node_id)
2952 }
2953
2954 fn is_optional_pseudo_class(&self, node_id: NodeId) -> bool {
2955 self.is_optional_form_control_element(node_id)
2956 }
2957
2958 fn is_valid_pseudo_class(&self, node_id: NodeId) -> bool {
2959 self.is_validity_form_control_element(node_id) && !self.is_invalid_pseudo_class(node_id)
2960 }
2961
2962 fn is_invalid_pseudo_class(&self, node_id: NodeId) -> bool {
2963 let Some(node) = self.nodes.get(node_id.index() as usize) else {
2964 return false;
2965 };
2966
2967 let NodeKind::Element(element) = &node.kind else {
2968 return false;
2969 };
2970
2971 match element.tag_name.as_str() {
2972 "textarea" => {
2973 element.attributes.contains_key("required")
2974 && self.value_for_node(node_id).is_empty()
2975 || self.is_text_length_invalid(node_id)
2976 }
2977 "select" => {
2978 element.attributes.contains_key("required")
2979 && self.value_for_node(node_id).is_empty()
2980 }
2981 "input" => {
2982 let input_type = element.attributes.get("type").map(String::as_str);
2983
2984 if matches!(input_type, Some("hidden")) {
2985 return false;
2986 }
2987
2988 if is_checkable_input_type(input_type) {
2989 element.attributes.contains_key("required")
2990 && self.checked_for_node(node_id) != Some(true)
2991 } else if self.is_range_input_type(input_type) {
2992 self.is_out_of_range_pseudo_class(node_id)
2993 || (element.attributes.contains_key("required")
2994 && self.value_for_node(node_id).is_empty())
2995 } else if is_file_input_type(input_type) || is_text_input_type(input_type) {
2996 element.attributes.contains_key("required")
2997 && self.value_for_node(node_id).is_empty()
2998 || self.is_text_length_invalid(node_id)
2999 || self.is_pattern_mismatch(node_id)
3000 } else {
3001 false
3002 }
3003 }
3004 _ => false,
3005 }
3006 }
3007
3008 fn is_in_range_pseudo_class(&self, node_id: NodeId) -> bool {
3009 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3010 return false;
3011 };
3012 let NodeKind::Element(element) = &node.kind else {
3013 return false;
3014 };
3015
3016 if !self.is_range_input_type(element.attributes.get("type").map(String::as_str)) {
3017 return false;
3018 }
3019
3020 let Some(current_value) = self.numeric_range_value(node_id) else {
3021 return false;
3022 };
3023 let Some((min, max)) = self.numeric_range_limits(node_id) else {
3024 return false;
3025 };
3026
3027 if let Some(min) = min {
3028 if current_value < min {
3029 return false;
3030 }
3031 }
3032 if let Some(max) = max {
3033 if current_value > max {
3034 return false;
3035 }
3036 }
3037
3038 true
3039 }
3040
3041 fn is_out_of_range_pseudo_class(&self, node_id: NodeId) -> bool {
3042 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3043 return false;
3044 };
3045 let NodeKind::Element(element) = &node.kind else {
3046 return false;
3047 };
3048
3049 if !self.is_range_input_type(element.attributes.get("type").map(String::as_str)) {
3050 return false;
3051 }
3052
3053 let Some(current_value) = self.numeric_range_value(node_id) else {
3054 return false;
3055 };
3056 let Some((min, max)) = self.numeric_range_limits(node_id) else {
3057 return false;
3058 };
3059
3060 if let Some(min) = min {
3061 if current_value < min {
3062 return true;
3063 }
3064 }
3065 if let Some(max) = max {
3066 if current_value > max {
3067 return true;
3068 }
3069 }
3070
3071 false
3072 }
3073
3074 fn is_validity_form_control_element(&self, node_id: NodeId) -> bool {
3075 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3076 return false;
3077 };
3078 let NodeKind::Element(element) = &node.kind else {
3079 return false;
3080 };
3081
3082 match element.tag_name.as_str() {
3083 "textarea" | "select" => true,
3084 "input" => {
3085 let input_type = element.attributes.get("type").map(String::as_str);
3086 !matches!(input_type, Some("hidden"))
3087 && (is_text_input_type(input_type)
3088 || self.is_range_input_type(input_type)
3089 || is_checkable_input_type(input_type)
3090 || is_file_input_type(input_type))
3091 }
3092 _ => false,
3093 }
3094 }
3095
3096 fn is_range_input_type(&self, input_type: Option<&str>) -> bool {
3097 matches!(input_type.unwrap_or("text"), "number" | "range")
3098 }
3099
3100 fn numeric_range_value(&self, node_id: NodeId) -> Option<f64> {
3101 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3102 return None;
3103 };
3104 let NodeKind::Element(element) = &node.kind else {
3105 return None;
3106 };
3107 let input_type = element.attributes.get("type").map(String::as_str);
3108 if !self.is_range_input_type(input_type) {
3109 return None;
3110 }
3111
3112 let value = self.value_for_node(node_id);
3113 let value = value.trim();
3114 if value.is_empty() {
3115 return if matches!(input_type, Some("range")) {
3116 Some(50.0)
3117 } else {
3118 None
3119 };
3120 }
3121
3122 value.parse::<f64>().ok()
3123 }
3124
3125 fn numeric_range_limits(&self, node_id: NodeId) -> Option<(Option<f64>, Option<f64>)> {
3126 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3127 return None;
3128 };
3129 let NodeKind::Element(element) = &node.kind else {
3130 return None;
3131 };
3132 let input_type = element.attributes.get("type").map(String::as_str);
3133 if !self.is_range_input_type(input_type) {
3134 return None;
3135 }
3136
3137 let min = element
3138 .attributes
3139 .get("min")
3140 .and_then(|value| value.trim().parse::<f64>().ok());
3141 let max = element
3142 .attributes
3143 .get("max")
3144 .and_then(|value| value.trim().parse::<f64>().ok());
3145
3146 if matches!(input_type, Some("number")) && min.is_none() && max.is_none() {
3147 return None;
3148 }
3149
3150 if matches!(input_type, Some("range")) {
3151 Some((Some(min.unwrap_or(0.0)), Some(max.unwrap_or(100.0))))
3152 } else {
3153 Some((min, max))
3154 }
3155 }
3156
3157 fn is_read_only_pseudo_class(&self, node_id: NodeId) -> bool {
3158 !self.is_read_write_pseudo_class(node_id)
3159 }
3160
3161 fn is_read_write_pseudo_class(&self, node_id: NodeId) -> bool {
3162 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3163 return false;
3164 };
3165
3166 let NodeKind::Element(element) = &node.kind else {
3167 return false;
3168 };
3169
3170 if self.is_content_editable(node_id) {
3171 return true;
3172 }
3173
3174 match element.tag_name.as_str() {
3175 "textarea" => {
3176 !element.attributes.contains_key("disabled")
3177 && !element.attributes.contains_key("readonly")
3178 }
3179 "input" => {
3180 let input_type = element.attributes.get("type").map(String::as_str);
3181 is_text_input_type(input_type)
3182 && !element.attributes.contains_key("disabled")
3183 && !element.attributes.contains_key("readonly")
3184 }
3185 _ => false,
3186 }
3187 }
3188
3189 fn inherited_directionality(&self, node_id: NodeId) -> Option<SelectorDirValue> {
3190 let mut current = Some(node_id);
3191
3192 while let Some(current_id) = current {
3193 let Some(node) = self.nodes.get(current_id.index() as usize) else {
3194 return None;
3195 };
3196 let NodeKind::Element(element) = &node.kind else {
3197 return None;
3198 };
3199
3200 if let Some(value) = element.attributes.get("dir") {
3201 match value.trim().to_ascii_lowercase().as_str() {
3202 "ltr" => return Some(SelectorDirValue::Ltr),
3203 "rtl" => return Some(SelectorDirValue::Rtl),
3204 "auto" => {
3205 current = node.parent;
3206 continue;
3207 }
3208 _ => {
3209 current = node.parent;
3210 continue;
3211 }
3212 }
3213 }
3214
3215 current = node.parent;
3216 }
3217
3218 Some(SelectorDirValue::Ltr)
3219 }
3220
3221 fn is_required_form_control_element(&self, node_id: NodeId) -> bool {
3222 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3223 return false;
3224 };
3225 let NodeKind::Element(element) = &node.kind else {
3226 return false;
3227 };
3228
3229 match element.tag_name.as_str() {
3230 "textarea" | "select" => element.attributes.contains_key("required"),
3231 "input" => {
3232 let input_type = element.attributes.get("type").map(String::as_str);
3233 !matches!(input_type, Some("hidden"))
3234 && (is_text_input_type(input_type)
3235 || is_checkable_input_type(input_type)
3236 || is_file_input_type(input_type))
3237 && element.attributes.contains_key("required")
3238 }
3239 _ => false,
3240 }
3241 }
3242
3243 fn is_text_length_invalid(&self, node_id: NodeId) -> bool {
3244 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3245 return false;
3246 };
3247 let NodeKind::Element(element) = &node.kind else {
3248 return false;
3249 };
3250
3251 let input_type = element.attributes.get("type").map(String::as_str);
3252 match element.tag_name.as_str() {
3253 "textarea" => {}
3254 "input" if is_text_input_type(input_type) => {}
3255 _ => return false,
3256 }
3257
3258 let value = self.value_for_node(node_id);
3259 if value.is_empty() {
3260 return false;
3261 }
3262
3263 let value_length = value.encode_utf16().count();
3264 let min_length = element
3265 .attributes
3266 .get("minlength")
3267 .and_then(|value| value.trim().parse::<usize>().ok());
3268 let max_length = element
3269 .attributes
3270 .get("maxlength")
3271 .and_then(|value| value.trim().parse::<usize>().ok());
3272
3273 if let Some(min_length) = min_length {
3274 if value_length < min_length {
3275 return true;
3276 }
3277 }
3278 if let Some(max_length) = max_length {
3279 if value_length > max_length {
3280 return true;
3281 }
3282 }
3283
3284 false
3285 }
3286
3287 fn is_pattern_mismatch(&self, node_id: NodeId) -> bool {
3288 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3289 return false;
3290 };
3291 let NodeKind::Element(element) = &node.kind else {
3292 return false;
3293 };
3294
3295 let input_type = element.attributes.get("type").map(String::as_str);
3296 match element.tag_name.as_str() {
3297 "input" if is_pattern_input_type(input_type) => {}
3298 _ => return false,
3299 }
3300
3301 let Some(pattern) = element.attributes.get("pattern") else {
3302 return false;
3303 };
3304 let value = self.value_for_node(node_id);
3305 if value.is_empty() {
3306 return false;
3307 }
3308
3309 let Ok(pattern) = Regex::new(&format!("^(?:{})$", pattern)) else {
3310 return false;
3311 };
3312
3313 !pattern.is_match(&value)
3314 }
3315
3316 fn is_optional_form_control_element(&self, node_id: NodeId) -> bool {
3317 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3318 return false;
3319 };
3320 let NodeKind::Element(element) = &node.kind else {
3321 return false;
3322 };
3323
3324 match element.tag_name.as_str() {
3325 "textarea" | "select" => !element.attributes.contains_key("required"),
3326 "input" => {
3327 let input_type = element.attributes.get("type").map(String::as_str);
3328 !matches!(input_type, Some("hidden"))
3329 && (is_text_input_type(input_type)
3330 || is_checkable_input_type(input_type)
3331 || is_file_input_type(input_type))
3332 && !element.attributes.contains_key("required")
3333 }
3334 _ => false,
3335 }
3336 }
3337
3338 fn is_only_child_pseudo_class(&self, node_id: NodeId) -> bool {
3339 self.is_first_child(node_id) && self.is_last_child(node_id)
3340 }
3341
3342 fn is_only_of_type_pseudo_class(&self, node_id: NodeId) -> bool {
3343 self.element_sibling_position_of_type(node_id) == Some(1)
3344 && self.element_sibling_position_from_end_of_type(node_id) == Some(1)
3345 }
3346
3347 fn is_first_of_type(&self, node_id: NodeId) -> bool {
3348 self.element_sibling_position_of_type(node_id) == Some(1)
3349 }
3350
3351 fn is_last_of_type(&self, node_id: NodeId) -> bool {
3352 self.element_sibling_position_from_end_of_type(node_id) == Some(1)
3353 }
3354
3355 fn is_nth_of_type(&self, node_id: NodeId, pattern: &SelectorNthChildPattern) -> bool {
3356 let Some(position) = self
3357 .element_sibling_position_of_type_filtered(node_id, pattern.of_selectors.as_deref())
3358 else {
3359 return false;
3360 };
3361
3362 self.matches_nth_pattern(position as isize, pattern)
3363 }
3364
3365 fn is_nth_last_of_type(&self, node_id: NodeId, pattern: &SelectorNthChildPattern) -> bool {
3366 let Some(position) = self.element_sibling_position_from_end_of_type_filtered(
3367 node_id,
3368 pattern.of_selectors.as_deref(),
3369 ) else {
3370 return false;
3371 };
3372
3373 self.matches_nth_pattern(position as isize, pattern)
3374 }
3375
3376 fn element_sibling_position_of_type(&self, node_id: NodeId) -> Option<usize> {
3377 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3378 return None;
3379 };
3380 let NodeKind::Element(element) = &node.kind else {
3381 return None;
3382 };
3383
3384 let Some(parent_id) = node.parent else {
3385 return None;
3386 };
3387 let Some(parent) = self.nodes.get(parent_id.index() as usize) else {
3388 return None;
3389 };
3390
3391 let mut matching_sibling_count = 0usize;
3392 for child in &parent.children {
3393 let Some(child_node) = self.nodes.get(child.index() as usize) else {
3394 continue;
3395 };
3396 let NodeKind::Element(child_element) = &child_node.kind else {
3397 continue;
3398 };
3399
3400 if child_element.local_name == element.local_name
3401 && child_element.namespace_uri == element.namespace_uri
3402 {
3403 matching_sibling_count += 1;
3404 if *child == node_id {
3405 return Some(matching_sibling_count);
3406 }
3407 } else if *child == node_id {
3408 return None;
3409 }
3410 }
3411
3412 None
3413 }
3414
3415 fn element_sibling_position_from_end_of_type(&self, node_id: NodeId) -> Option<usize> {
3416 self.element_sibling_position_from_end_of_type_filtered(node_id, None)
3417 }
3418
3419 fn element_sibling_position_of_type_filtered(
3420 &self,
3421 node_id: NodeId,
3422 of_selectors: Option<&[SelectorChain]>,
3423 ) -> Option<usize> {
3424 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3425 return None;
3426 };
3427 let NodeKind::Element(element) = &node.kind else {
3428 return None;
3429 };
3430
3431 let Some(parent_id) = node.parent else {
3432 return None;
3433 };
3434 let Some(parent) = self.nodes.get(parent_id.index() as usize) else {
3435 return None;
3436 };
3437
3438 let mut matching_sibling_count = 0usize;
3439 for child in &parent.children {
3440 let Some(child_node) = self.nodes.get(child.index() as usize) else {
3441 continue;
3442 };
3443 let NodeKind::Element(child_element) = &child_node.kind else {
3444 continue;
3445 };
3446
3447 if child_element.local_name == element.local_name
3448 && child_element.namespace_uri == element.namespace_uri
3449 && self.matches_nth_of_type_filters(*child, of_selectors)
3450 {
3451 matching_sibling_count += 1;
3452 if *child == node_id {
3453 return Some(matching_sibling_count);
3454 }
3455 } else if *child == node_id {
3456 return None;
3457 }
3458 }
3459
3460 None
3461 }
3462
3463 fn element_sibling_position_from_end_of_type_filtered(
3464 &self,
3465 node_id: NodeId,
3466 of_selectors: Option<&[SelectorChain]>,
3467 ) -> Option<usize> {
3468 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3469 return None;
3470 };
3471 let NodeKind::Element(element) = &node.kind else {
3472 return None;
3473 };
3474
3475 let Some(parent_id) = node.parent else {
3476 return None;
3477 };
3478 let Some(parent) = self.nodes.get(parent_id.index() as usize) else {
3479 return None;
3480 };
3481
3482 let mut matching_sibling_count = 0usize;
3483 for child in parent.children.iter().rev() {
3484 let Some(child_node) = self.nodes.get(child.index() as usize) else {
3485 continue;
3486 };
3487 let NodeKind::Element(child_element) = &child_node.kind else {
3488 continue;
3489 };
3490
3491 if child_element.local_name == element.local_name
3492 && child_element.namespace_uri == element.namespace_uri
3493 && self.matches_nth_of_type_filters(*child, of_selectors)
3494 {
3495 matching_sibling_count += 1;
3496 if *child == node_id {
3497 return Some(matching_sibling_count);
3498 }
3499 } else if *child == node_id {
3500 return None;
3501 }
3502 }
3503
3504 None
3505 }
3506
3507 fn is_first_child(&self, node_id: NodeId) -> bool {
3508 self.element_child_position(node_id) == Some(1)
3509 }
3510
3511 fn is_last_child(&self, node_id: NodeId) -> bool {
3512 let Some(parent_id) = self.parent_of(node_id) else {
3513 return false;
3514 };
3515 let Some(parent) = self.nodes.get(parent_id.index() as usize) else {
3516 return false;
3517 };
3518
3519 parent.children.iter().rev().find_map(|child| {
3520 matches!(
3521 self.nodes
3522 .get(child.index() as usize)
3523 .map(|node| &node.kind),
3524 Some(NodeKind::Element(_))
3525 )
3526 .then_some(*child)
3527 }) == Some(node_id)
3528 }
3529
3530 fn is_nth_child(&self, node_id: NodeId, pattern: &SelectorNthChildPattern) -> bool {
3531 let Some(position) =
3532 self.element_child_position_filtered(node_id, pattern.of_selectors.as_deref())
3533 else {
3534 return false;
3535 };
3536
3537 self.matches_nth_pattern(position as isize, pattern)
3538 }
3539
3540 fn is_nth_last_child(&self, node_id: NodeId, pattern: &SelectorNthChildPattern) -> bool {
3541 let Some(position) =
3542 self.element_child_position_from_end_filtered(node_id, pattern.of_selectors.as_deref())
3543 else {
3544 return false;
3545 };
3546
3547 self.matches_nth_pattern(position as isize, pattern)
3548 }
3549
3550 fn element_child_position_filtered(
3551 &self,
3552 node_id: NodeId,
3553 of_selectors: Option<&[SelectorChain]>,
3554 ) -> Option<usize> {
3555 let parent_id = self.parent_of(node_id)?;
3556 let parent = self.nodes.get(parent_id.index() as usize)?;
3557 let mut position = 0;
3558
3559 for child in &parent.children {
3560 if !matches!(
3561 self.nodes
3562 .get(child.index() as usize)
3563 .map(|node| &node.kind),
3564 Some(NodeKind::Element(_))
3565 ) {
3566 if *child == node_id {
3567 return None;
3568 }
3569 continue;
3570 }
3571
3572 if !self.matches_nth_child_of_filters(*child, of_selectors) {
3573 if *child == node_id {
3574 return None;
3575 }
3576 continue;
3577 }
3578
3579 position += 1;
3580 if *child == node_id {
3581 return Some(position);
3582 }
3583 }
3584
3585 None
3586 }
3587
3588 fn element_child_position_from_end_filtered(
3589 &self,
3590 node_id: NodeId,
3591 of_selectors: Option<&[SelectorChain]>,
3592 ) -> Option<usize> {
3593 let parent_id = self.parent_of(node_id)?;
3594 let parent = self.nodes.get(parent_id.index() as usize)?;
3595 let mut position = 0;
3596
3597 for child in parent.children.iter().rev() {
3598 if !matches!(
3599 self.nodes
3600 .get(child.index() as usize)
3601 .map(|node| &node.kind),
3602 Some(NodeKind::Element(_))
3603 ) {
3604 if *child == node_id {
3605 return None;
3606 }
3607 continue;
3608 }
3609
3610 if !self.matches_nth_child_of_filters(*child, of_selectors) {
3611 if *child == node_id {
3612 return None;
3613 }
3614 continue;
3615 }
3616
3617 position += 1;
3618 if *child == node_id {
3619 return Some(position);
3620 }
3621 }
3622
3623 None
3624 }
3625
3626 fn matches_nth_child_of_filters(
3627 &self,
3628 node_id: NodeId,
3629 of_selectors: Option<&[SelectorChain]>,
3630 ) -> bool {
3631 match of_selectors {
3632 None => true,
3633 Some(selectors) => selectors
3634 .iter()
3635 .any(|chain| self.matches_selector_chain(node_id, chain, None)),
3636 }
3637 }
3638
3639 fn matches_nth_of_type_filters(
3640 &self,
3641 node_id: NodeId,
3642 of_selectors: Option<&[SelectorChain]>,
3643 ) -> bool {
3644 self.matches_nth_child_of_filters(node_id, of_selectors)
3645 }
3646
3647 fn matches_nth_pattern(&self, position: isize, pattern: &SelectorNthChildPattern) -> bool {
3648 match pattern.step.cmp(&0) {
3649 std::cmp::Ordering::Equal => position == pattern.offset && position > 0,
3650 std::cmp::Ordering::Greater => {
3651 let diff = position - pattern.offset;
3652 diff >= 0 && diff % pattern.step == 0
3653 }
3654 std::cmp::Ordering::Less => {
3655 let step = -pattern.step;
3656 let diff = pattern.offset - position;
3657 diff >= 0 && diff % step == 0
3658 }
3659 }
3660 }
3661
3662 fn is_checked_pseudo_class(&self, node_id: NodeId) -> bool {
3663 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3664 return false;
3665 };
3666
3667 let NodeKind::Element(element) = &node.kind else {
3668 return false;
3669 };
3670
3671 if element.tag_name == "option" {
3672 return self.is_option_selected(node_id);
3673 }
3674
3675 self.checked_for_node(node_id) == Some(true)
3676 }
3677
3678 fn is_indeterminate_pseudo_class(&self, node_id: NodeId) -> bool {
3679 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3680 return false;
3681 };
3682
3683 let NodeKind::Element(element) = &node.kind else {
3684 return false;
3685 };
3686
3687 match element.tag_name.as_str() {
3688 "progress" => !element.attributes.contains_key("value"),
3689 "input"
3690 if matches!(
3691 element.attributes.get("type").map(String::as_str),
3692 Some("checkbox")
3693 ) =>
3694 {
3695 self.indeterminate_for_node(node_id).unwrap_or(false)
3696 }
3697 "input"
3698 if matches!(
3699 element.attributes.get("type").map(String::as_str),
3700 Some("radio")
3701 ) =>
3702 {
3703 let Some(name) = element.attributes.get("name") else {
3704 return false;
3705 };
3706
3707 self.radio_group_is_indeterminate(node_id, name)
3708 }
3709 _ => false,
3710 }
3711 }
3712
3713 fn is_default_pseudo_class(&self, node_id: NodeId) -> bool {
3714 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3715 return false;
3716 };
3717
3718 let NodeKind::Element(element) = &node.kind else {
3719 return false;
3720 };
3721
3722 match element.tag_name.as_str() {
3723 "option" => self.is_option_selected(node_id),
3724 "input"
3725 if matches!(
3726 element.attributes.get("type").map(String::as_str),
3727 Some("checkbox") | Some("radio")
3728 ) =>
3729 {
3730 self.checked_for_node(node_id) == Some(true)
3731 }
3732 "input"
3733 if matches!(
3734 element.attributes.get("type").map(String::as_str),
3735 Some("submit") | Some("image")
3736 ) =>
3737 {
3738 self.is_default_submit_button(node_id)
3739 }
3740 "button" => self.is_default_submit_button(node_id),
3741 _ => false,
3742 }
3743 }
3744
3745 fn is_default_submit_button(&self, node_id: NodeId) -> bool {
3746 let Some(form_id) = self.form_ancestor_of(node_id) else {
3747 return false;
3748 };
3749
3750 self.collect_subtree_nodes([form_id])
3751 .into_iter()
3752 .find(|candidate_id| self.is_submit_button_candidate(*candidate_id))
3753 == Some(node_id)
3754 }
3755
3756 fn is_submit_button_candidate(&self, node_id: NodeId) -> bool {
3757 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3758 return false;
3759 };
3760
3761 let NodeKind::Element(element) = &node.kind else {
3762 return false;
3763 };
3764
3765 if element.attributes.contains_key("disabled") {
3766 return false;
3767 }
3768
3769 match element.tag_name.as_str() {
3770 "button" => !matches!(
3771 element.attributes.get("type").map(String::as_str),
3772 Some("button")
3773 ),
3774 "input" => matches!(
3775 element.attributes.get("type").map(String::as_str),
3776 Some("submit") | Some("image")
3777 ),
3778 _ => false,
3779 }
3780 }
3781
3782 fn radio_group_is_indeterminate(&self, node_id: NodeId, name: &str) -> bool {
3783 let Some(scope_root) = self.radio_group_scope_root(node_id) else {
3784 return false;
3785 };
3786
3787 self.collect_subtree_nodes([scope_root])
3788 .into_iter()
3789 .all(|descendant_id| {
3790 if descendant_id == node_id {
3791 return self.checked_for_node(descendant_id) != Some(true);
3792 }
3793
3794 let Some(node) = self.nodes.get(descendant_id.index() as usize) else {
3795 return true;
3796 };
3797 let NodeKind::Element(element) = &node.kind else {
3798 return true;
3799 };
3800
3801 if element.tag_name != "input" {
3802 return true;
3803 }
3804
3805 if !matches!(
3806 element.attributes.get("type").map(String::as_str),
3807 Some("radio")
3808 ) {
3809 return true;
3810 }
3811
3812 if element.attributes.get("name").map(String::as_str) != Some(name) {
3813 return true;
3814 }
3815
3816 self.checked_for_node(descendant_id) != Some(true)
3817 })
3818 }
3819
3820 fn radio_group_scope_root(&self, node_id: NodeId) -> Option<NodeId> {
3821 let mut current = self.parent_of(node_id);
3822 while let Some(ancestor_id) = current {
3823 if self.tag_name_for(ancestor_id) == Some("form") {
3824 return Some(ancestor_id);
3825 }
3826 current = self.parent_of(ancestor_id);
3827 }
3828
3829 self.root_element_id()
3830 }
3831
3832 fn form_ancestor_of(&self, node_id: NodeId) -> Option<NodeId> {
3833 let mut current = self.parent_of(node_id);
3834 while let Some(ancestor_id) = current {
3835 if self.tag_name_for(ancestor_id) == Some("form") {
3836 return Some(ancestor_id);
3837 }
3838 current = self.parent_of(ancestor_id);
3839 }
3840
3841 None
3842 }
3843
3844 fn is_disabled_pseudo_class(&self, node_id: NodeId) -> bool {
3845 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3846 return false;
3847 };
3848
3849 let NodeKind::Element(element) = &node.kind else {
3850 return false;
3851 };
3852
3853 if !supports_disabled_pseudo_class(&element.tag_name) {
3854 return false;
3855 }
3856
3857 element.attributes.contains_key("disabled")
3858 }
3859
3860 fn is_enabled_pseudo_class(&self, node_id: NodeId) -> bool {
3861 let Some(node) = self.nodes.get(node_id.index() as usize) else {
3862 return false;
3863 };
3864
3865 let NodeKind::Element(element) = &node.kind else {
3866 return false;
3867 };
3868
3869 if !supports_disabled_pseudo_class(&element.tag_name) {
3870 return false;
3871 }
3872
3873 !element.attributes.contains_key("disabled")
3874 }
3875
3876 fn previous_element_sibling_of(&self, node_id: NodeId) -> Option<NodeId> {
3877 let parent_id = self.parent_of(node_id)?;
3878 let parent = self.nodes.get(parent_id.index() as usize)?;
3879 let mut previous_element = None;
3880
3881 for child in &parent.children {
3882 if *child == node_id {
3883 return previous_element;
3884 }
3885
3886 if matches!(
3887 self.nodes
3888 .get(child.index() as usize)
3889 .map(|node| &node.kind),
3890 Some(NodeKind::Element(_))
3891 ) {
3892 previous_element = Some(*child);
3893 }
3894 }
3895
3896 None
3897 }
3898
3899 fn next_element_sibling_of(&self, node_id: NodeId) -> Option<NodeId> {
3900 let parent_id = self.parent_of(node_id)?;
3901 let parent = self.nodes.get(parent_id.index() as usize)?;
3902 let mut seen_current = false;
3903
3904 for child in &parent.children {
3905 if *child == node_id {
3906 seen_current = true;
3907 continue;
3908 }
3909
3910 if !seen_current {
3911 continue;
3912 }
3913
3914 if matches!(
3915 self.nodes
3916 .get(child.index() as usize)
3917 .map(|node| &node.kind),
3918 Some(NodeKind::Element(_))
3919 ) {
3920 return Some(*child);
3921 }
3922 }
3923
3924 None
3925 }
3926
3927 fn element_child_position(&self, node_id: NodeId) -> Option<usize> {
3928 let parent_id = self.parent_of(node_id)?;
3929 let parent = self.nodes.get(parent_id.index() as usize)?;
3930 let mut position = 0;
3931
3932 for child in &parent.children {
3933 if matches!(
3934 self.nodes
3935 .get(child.index() as usize)
3936 .map(|node| &node.kind),
3937 Some(NodeKind::Element(_))
3938 ) {
3939 position += 1;
3940 if *child == node_id {
3941 return Some(position);
3942 }
3943 } else if *child == node_id {
3944 return None;
3945 }
3946 }
3947
3948 None
3949 }
3950
3951 fn dump_node(&self, node_id: NodeId, indent: usize, output: &mut String) {
3952 let node = &self.nodes[node_id.index() as usize];
3953 let children = node.children.clone();
3954
3955 match &node.kind {
3956 NodeKind::Document => {
3957 write_indent(output, indent);
3958 output.push_str("#document");
3959 if !children.is_empty() {
3960 output.push('\n');
3961 for (index, child) in children.iter().enumerate() {
3962 self.dump_node(*child, indent + 1, output);
3963 if index + 1 < children.len() {
3964 output.push('\n');
3965 }
3966 }
3967 }
3968 }
3969 NodeKind::Element(element) => {
3970 let attributes = format_attributes(&element.attributes);
3971 write_indent(output, indent);
3972 if children.is_empty() {
3973 if attributes.is_empty() {
3974 let _ = write!(output, "<{} />", element.tag_name);
3975 } else {
3976 let _ = write!(output, "<{} {} />", element.tag_name, attributes);
3977 }
3978 } else {
3979 if attributes.is_empty() {
3980 let _ = write!(output, "<{}>", element.tag_name);
3981 } else {
3982 let _ = write!(output, "<{} {}>", element.tag_name, attributes);
3983 }
3984 output.push('\n');
3985 for (index, child) in children.iter().enumerate() {
3986 self.dump_node(*child, indent + 1, output);
3987 if index + 1 < children.len() {
3988 output.push('\n');
3989 }
3990 }
3991 output.push('\n');
3992 write_indent(output, indent);
3993 let _ = write!(output, "</{}>", element.tag_name);
3994 }
3995 }
3996 NodeKind::Text(text) => {
3997 write_indent(output, indent);
3998 let _ = write!(output, "\"{}\"", escape_text(&text.value));
3999 }
4000 NodeKind::Comment(comment) => {
4001 write_indent(output, indent);
4002 let _ = write!(output, "<!-- {} -->", comment);
4003 }
4004 }
4005 }
4006
4007 fn tag_name_for(&self, node_id: NodeId) -> Option<&str> {
4008 match &self.nodes[node_id.index() as usize].kind {
4009 NodeKind::Element(element) => Some(element.tag_name.as_str()),
4010 _ => None,
4011 }
4012 }
4013
4014 fn child_element_with_tag_name(&self, parent: NodeId, tag_name: &str) -> Option<NodeId> {
4015 let parent = self.nodes.get(parent.index() as usize)?;
4016 parent.children.iter().find_map(|child| {
4017 matches!(
4018 self.nodes
4019 .get(child.index() as usize)
4020 .map(|node| &node.kind),
4021 Some(NodeKind::Element(element)) if element.tag_name == tag_name
4022 )
4023 .then_some(*child)
4024 })
4025 }
4026}
4027
4028fn selector_not_supported(selector: &str) -> String {
4029 format!(
4030 "unsupported selector `{selector}`; supported forms are #id, .class, tag, tag.class, #id.class, [attr], [attr=value], [attr^=value], [attr$=value], [attr*=value], [attr~=value], [attr|=value], optional attribute selector flags like `[attr=value i]` and `[attr=value s]`, bounded logical pseudo-classes like `:not(.primary)`, `:is(.primary, .secondary)`, and `:where(.primary, .secondary)`, structural pseudo-classes like `:first-child`, `:last-child`, `:nth-child(2)`, `:nth-child(odd)`, `:nth-child(2n+1)`, and `:nth-last-child(2)`, state pseudo-classes like `:checked`, `:disabled`, `:enabled`, `:indeterminate`, `:default`, `:valid`, `:invalid`, `:in-range`, and `:out-of-range`, descendant combinators like `A B`, adjacent sibling combinators like `A + B`, general sibling combinators like `A ~ B`, and child combinators like `A > B`; additional bounded structural pseudo-classes include `:root`, `:empty`, `:only-child`, `:only-of-type`, `:first-of-type`, `:last-of-type`, `:nth-of-type(2)`, `:nth-of-type(... of <selector-list>)`, `:nth-last-of-type(2)`, and `:nth-last-of-type(... of <selector-list>)`; additional bounded selector grammar now also includes `:scope`, `:has(...)`, `:lang(...)`, `:defined`, `:nth-child(... of <selector-list>)` / `:nth-last-child(... of <selector-list>)`, `:focus`, `:focus-visible`, `:focus-within`, `:target`, and `:blank`; form-editable state pseudo-classes also include `:read-only` and `:read-write`"
4031 )
4032}
4033
4034fn parse_nth_child_argument(
4035 selector: &str,
4036 pos: &mut usize,
4037) -> Result<SelectorNthChildPattern, String> {
4038 let argument = parse_parenthesized_argument(selector, pos)?;
4039 let (formula_text, of_selectors) = split_nth_child_argument(&argument)?;
4040
4041 let mut formula: String = formula_text
4042 .chars()
4043 .filter(|ch| !ch.is_ascii_whitespace())
4044 .collect();
4045
4046 if formula.is_empty() {
4047 return Err(selector_not_supported(selector));
4048 }
4049
4050 formula.make_ascii_lowercase();
4051 let parsed_formula = match formula.as_str() {
4052 "odd" => SelectorNthChildPattern {
4053 step: 2,
4054 offset: 1,
4055 of_selectors: of_selectors
4056 .as_deref()
4057 .map(parse_nth_child_of_selectors)
4058 .transpose()?,
4059 },
4060 "even" => SelectorNthChildPattern {
4061 step: 2,
4062 offset: 0,
4063 of_selectors: of_selectors
4064 .as_deref()
4065 .map(parse_nth_child_of_selectors)
4066 .transpose()?,
4067 },
4068 _ => {
4069 if let Some(n_index) = formula.find('n') {
4070 if formula[n_index + 1..].contains('n') {
4071 return Err(selector_not_supported(selector));
4072 }
4073
4074 let step = match &formula[..n_index] {
4075 "" | "+" => 1,
4076 "-" => -1,
4077 value => value
4078 .parse::<isize>()
4079 .map_err(|_| selector_not_supported(selector))?,
4080 };
4081 let offset = if formula[n_index + 1..].is_empty() {
4082 0
4083 } else {
4084 formula[n_index + 1..]
4085 .parse::<isize>()
4086 .map_err(|_| selector_not_supported(selector))?
4087 };
4088
4089 SelectorNthChildPattern {
4090 step,
4091 offset,
4092 of_selectors: of_selectors
4093 .as_deref()
4094 .map(parse_nth_child_of_selectors)
4095 .transpose()?,
4096 }
4097 } else {
4098 let offset = formula
4099 .parse::<isize>()
4100 .map_err(|_| selector_not_supported(selector))?;
4101 SelectorNthChildPattern {
4102 step: 0,
4103 offset,
4104 of_selectors: of_selectors
4105 .as_deref()
4106 .map(parse_nth_child_of_selectors)
4107 .transpose()?,
4108 }
4109 }
4110 }
4111 };
4112
4113 Ok(parsed_formula)
4114}
4115
4116fn split_nth_child_argument(argument: &str) -> Result<(String, Option<String>), String> {
4117 let argument = argument.trim();
4118 if argument.is_empty() {
4119 return Err(selector_not_supported(argument));
4120 }
4121
4122 let bytes = argument.as_bytes();
4123 let mut pos = 0;
4124 while pos < bytes.len() {
4125 if bytes[pos].is_ascii_whitespace() {
4126 let formula_end = pos;
4127 skip_selector_whitespace(bytes, &mut pos);
4128 if is_of_keyword(bytes, pos) {
4129 let of_start = pos + 2;
4130 if of_start >= bytes.len() {
4131 return Err(selector_not_supported(argument));
4132 }
4133 return Ok((
4134 argument[..formula_end].trim_end().to_string(),
4135 Some(argument[of_start..].trim_start().to_string()),
4136 ));
4137 }
4138 }
4139 pos += 1;
4140 }
4141
4142 Ok((argument.to_string(), None))
4143}
4144
4145fn is_of_keyword(bytes: &[u8], pos: usize) -> bool {
4146 match (bytes.get(pos), bytes.get(pos + 1), bytes.get(pos + 2)) {
4147 (Some(b'o'), Some(b'f'), Some(next)) => next.is_ascii_whitespace(),
4148 (Some(b'O'), Some(b'F'), Some(next)) => next.is_ascii_whitespace(),
4149 _ => false,
4150 }
4151}
4152
4153fn parse_nth_child_of_selectors(argument: &str) -> Result<Vec<SelectorChain>, String> {
4154 let mut chains = Vec::new();
4155
4156 for item in split_selector_list_items(argument)? {
4157 chains.push(DomStore::parse_selector_chain(item)?);
4158 }
4159
4160 if chains.is_empty() {
4161 return Err(selector_not_supported(argument));
4162 }
4163
4164 Ok(chains)
4165}
4166
4167fn parse_logical_pseudo_argument(
4168 selector: &str,
4169 pos: &mut usize,
4170) -> Result<Vec<SelectorChain>, String> {
4171 let argument = parse_parenthesized_argument(selector, pos)?;
4172 let mut chains = Vec::new();
4173 for item in
4174 split_selector_list_items(&argument).map_err(|_| selector_not_supported(selector))?
4175 {
4176 chains.push(
4177 DomStore::parse_selector_chain(item).map_err(|_| selector_not_supported(selector))?,
4178 );
4179 }
4180
4181 if chains.is_empty() {
4182 return Err(selector_not_supported(selector));
4183 }
4184
4185 Ok(chains)
4186}
4187
4188fn parse_relative_selector_argument(
4189 selector: &str,
4190 pos: &mut usize,
4191) -> Result<Vec<SelectorRelativeSelector>, String> {
4192 let argument = parse_parenthesized_argument(selector, pos)?;
4193 let mut relative_selectors = Vec::new();
4194
4195 for item in
4196 split_selector_list_items(&argument).map_err(|_| selector_not_supported(selector))?
4197 {
4198 relative_selectors.push(
4199 parse_relative_selector_item(item).map_err(|_| selector_not_supported(selector))?,
4200 );
4201 }
4202
4203 if relative_selectors.is_empty() {
4204 return Err(selector_not_supported(selector));
4205 }
4206
4207 Ok(relative_selectors)
4208}
4209
4210fn parse_lang_argument(selector: &str, pos: &mut usize) -> Result<Vec<String>, String> {
4211 let argument = parse_parenthesized_argument(selector, pos)?;
4212 let argument = argument.trim();
4213 if argument.is_empty() {
4214 return Err(selector_not_supported(selector));
4215 }
4216
4217 let mut langs = Vec::new();
4218 for item in argument.split(',') {
4219 let item = item.trim();
4220 if item.is_empty() {
4221 return Err(selector_not_supported(selector));
4222 }
4223
4224 let bytes = item.as_bytes();
4225 let mut parse_pos = 0usize;
4226 let lang = parse_selector_token(item, &mut parse_pos)
4227 .map_err(|_| selector_not_supported(selector))?;
4228 skip_selector_whitespace(bytes, &mut parse_pos);
4229 if parse_pos != bytes.len() {
4230 return Err(selector_not_supported(selector));
4231 }
4232
4233 if !lang
4234 .chars()
4235 .all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
4236 {
4237 return Err(selector_not_supported(selector));
4238 }
4239
4240 langs.push(lang.to_ascii_lowercase());
4241 }
4242
4243 if langs.is_empty() {
4244 return Err(selector_not_supported(selector));
4245 }
4246
4247 Ok(langs)
4248}
4249
4250fn parse_dir_argument(selector: &str, pos: &mut usize) -> Result<SelectorDirValue, String> {
4251 let argument = parse_parenthesized_argument(selector, pos)?;
4252 let argument = argument.trim();
4253 if argument.is_empty() {
4254 return Err(selector_not_supported(selector));
4255 }
4256
4257 let mut parse_pos = 0usize;
4258 let dir = parse_selector_token(argument, &mut parse_pos)
4259 .map_err(|_| selector_not_supported(selector))?;
4260 skip_selector_whitespace(argument.as_bytes(), &mut parse_pos);
4261 if parse_pos != argument.len() {
4262 return Err(selector_not_supported(selector));
4263 }
4264
4265 match dir.to_ascii_lowercase().as_str() {
4266 "ltr" => Ok(SelectorDirValue::Ltr),
4267 "rtl" => Ok(SelectorDirValue::Rtl),
4268 _ => Err(selector_not_supported(selector)),
4269 }
4270}
4271
4272fn parse_relative_selector_item(selector: &str) -> Result<SelectorRelativeSelector, String> {
4273 let bytes = selector.as_bytes();
4274 let mut pos = 0;
4275 skip_selector_whitespace(bytes, &mut pos);
4276
4277 let combinator = match bytes.get(pos).copied() {
4278 Some(b'>') => {
4279 pos += 1;
4280 Some(SelectorCombinator::Child)
4281 }
4282 Some(b'+') => {
4283 pos += 1;
4284 Some(SelectorCombinator::AdjacentSibling)
4285 }
4286 Some(b'~') => {
4287 pos += 1;
4288 Some(SelectorCombinator::GeneralSibling)
4289 }
4290 Some(byte) if is_selector_combinator_byte(byte) => {
4291 return Err(selector_not_supported(selector));
4292 }
4293 _ => None,
4294 };
4295
4296 if combinator.is_some() {
4297 skip_selector_whitespace(bytes, &mut pos);
4298 if pos >= bytes.len() {
4299 return Err(selector_not_supported(selector));
4300 }
4301 }
4302
4303 let chain = DomStore::parse_selector_chain_from_pos(selector, &mut pos)?;
4304 skip_selector_whitespace(bytes, &mut pos);
4305 if pos != bytes.len() {
4306 return Err(selector_not_supported(selector));
4307 }
4308
4309 Ok(SelectorRelativeSelector { combinator, chain })
4310}
4311
4312fn parse_selector_attribute_operator_and_value(
4313 selector: &str,
4314 pos: &mut usize,
4315) -> Result<
4316 (
4317 SelectorAttributeOperator,
4318 Option<String>,
4319 SelectorAttributeCaseSensitivity,
4320 ),
4321 String,
4322> {
4323 let bytes = selector.as_bytes();
4324 let Some(current) = bytes.get(*pos).copied() else {
4325 return Err(selector_not_supported(selector));
4326 };
4327
4328 let operator = match current {
4329 b'=' => {
4330 *pos += 1;
4331 SelectorAttributeOperator::Exact
4332 }
4333 b'^' => {
4334 if bytes.get(*pos + 1) != Some(&b'=') {
4335 return Err(selector_not_supported(selector));
4336 }
4337 *pos += 2;
4338 SelectorAttributeOperator::Prefix
4339 }
4340 b'$' => {
4341 if bytes.get(*pos + 1) != Some(&b'=') {
4342 return Err(selector_not_supported(selector));
4343 }
4344 *pos += 2;
4345 SelectorAttributeOperator::Suffix
4346 }
4347 b'*' => {
4348 if bytes.get(*pos + 1) != Some(&b'=') {
4349 return Err(selector_not_supported(selector));
4350 }
4351 *pos += 2;
4352 SelectorAttributeOperator::Contains
4353 }
4354 b'~' => {
4355 if bytes.get(*pos + 1) != Some(&b'=') {
4356 return Err(selector_not_supported(selector));
4357 }
4358 *pos += 2;
4359 SelectorAttributeOperator::Includes
4360 }
4361 b'|' => {
4362 if bytes.get(*pos + 1) != Some(&b'=') {
4363 return Err(selector_not_supported(selector));
4364 }
4365 *pos += 2;
4366 SelectorAttributeOperator::DashMatch
4367 }
4368 _ => return Err(selector_not_supported(selector)),
4369 };
4370
4371 skip_selector_whitespace(bytes, pos);
4372 let value = parse_selector_attribute_value(selector, pos)?;
4373 skip_selector_whitespace(bytes, pos);
4374 let case_sensitivity = parse_selector_attribute_case_sensitivity(selector, pos)?;
4375 Ok((operator, Some(value), case_sensitivity))
4376}
4377
4378fn parse_selector_attribute_value(selector: &str, pos: &mut usize) -> Result<String, String> {
4379 let bytes = selector.as_bytes();
4380 match bytes.get(*pos).copied() {
4381 Some(quote @ (b'"' | b'\'')) => {
4382 *pos += 1;
4383 let mut value = String::new();
4384 while *pos < bytes.len() {
4385 match bytes[*pos] {
4386 b'\\' => {
4387 *pos += 1;
4388 value.push(skip_selector_escape(selector, pos)?);
4389 }
4390 byte if byte == quote => {
4391 *pos += 1;
4392 return Ok(value);
4393 }
4394 _ => {
4395 let ch = selector[*pos..]
4396 .chars()
4397 .next()
4398 .ok_or_else(|| selector_not_supported(selector))?;
4399 value.push(ch);
4400 *pos += ch.len_utf8();
4401 }
4402 }
4403 }
4404
4405 Err(selector_not_supported(selector))
4406 }
4407 Some(_) => {
4408 let mut value = String::new();
4409 while *pos < bytes.len() {
4410 match bytes[*pos] {
4411 b'\\' => {
4412 *pos += 1;
4413 value.push(skip_selector_escape(selector, pos)?);
4414 }
4415 byte if byte.is_ascii_whitespace() || byte == b']' => break,
4416 _ => {
4417 let ch = selector[*pos..]
4418 .chars()
4419 .next()
4420 .ok_or_else(|| selector_not_supported(selector))?;
4421 value.push(ch);
4422 *pos += ch.len_utf8();
4423 }
4424 }
4425 }
4426
4427 if value.is_empty() {
4428 return Err(selector_not_supported(selector));
4429 }
4430
4431 Ok(value)
4432 }
4433 None => Err(selector_not_supported(selector)),
4434 }
4435}
4436
4437fn parse_selector_attribute_case_sensitivity(
4438 selector: &str,
4439 pos: &mut usize,
4440) -> Result<SelectorAttributeCaseSensitivity, String> {
4441 let bytes = selector.as_bytes();
4442 match bytes.get(*pos).copied() {
4443 Some(b']') | None => Ok(SelectorAttributeCaseSensitivity::CaseSensitive),
4444 Some(flag) => {
4445 *pos += 1;
4446 let case_sensitivity = match flag.to_ascii_lowercase() {
4447 b'i' => SelectorAttributeCaseSensitivity::AsciiInsensitive,
4448 b's' => SelectorAttributeCaseSensitivity::CaseSensitive,
4449 _ => SelectorAttributeCaseSensitivity::CaseSensitive,
4450 };
4451 skip_selector_whitespace(bytes, pos);
4452 if bytes.get(*pos) != Some(&b']') {
4453 return Err(selector_not_supported(selector));
4454 }
4455 Ok(case_sensitivity)
4456 }
4457 }
4458}
4459
4460fn starts_with_ignore_ascii_case(value: &str, prefix: &str) -> bool {
4461 value
4462 .get(..prefix.len())
4463 .is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
4464}
4465
4466fn ends_with_ignore_ascii_case(value: &str, suffix: &str) -> bool {
4467 value
4468 .get(value.len().saturating_sub(suffix.len())..)
4469 .is_some_and(|candidate| candidate.eq_ignore_ascii_case(suffix))
4470}
4471
4472fn contains_ignore_ascii_case(value: &str, needle: &str) -> bool {
4473 if needle.is_empty() {
4474 return true;
4475 }
4476
4477 value
4478 .to_ascii_lowercase()
4479 .contains(&needle.to_ascii_lowercase())
4480}
4481
4482fn lang_matches_range(lang: &str, range: &str) -> bool {
4483 lang == range
4484 || (lang.len() > range.len()
4485 && lang.starts_with(range)
4486 && lang.as_bytes().get(range.len()) == Some(&b'-'))
4487}
4488
4489fn parse_parenthesized_argument(selector: &str, pos: &mut usize) -> Result<String, String> {
4490 let bytes = selector.as_bytes();
4491 if *pos >= bytes.len() || bytes[*pos] != b'(' {
4492 return Err(selector_not_supported(selector));
4493 }
4494 *pos += 1;
4495
4496 let start = *pos;
4497 let mut depth = 1usize;
4498 let mut in_quote: Option<u8> = None;
4499 let mut bracket_depth = 0usize;
4500 while *pos < bytes.len() {
4501 let byte = bytes[*pos];
4502 match in_quote {
4503 Some(quote) => match byte {
4504 b'\\' => {
4505 *pos += 1;
4506 skip_selector_escape(selector, pos)?;
4507 continue;
4508 }
4509 byte if byte == quote => {
4510 in_quote = None;
4511 *pos += 1;
4512 continue;
4513 }
4514 _ => {
4515 let ch = selector[*pos..]
4516 .chars()
4517 .next()
4518 .ok_or_else(|| selector_not_supported(selector))?;
4519 *pos += ch.len_utf8();
4520 continue;
4521 }
4522 },
4523 None => match byte {
4524 b'\'' | b'"' => {
4525 in_quote = Some(byte);
4526 *pos += 1;
4527 }
4528 b'\\' => {
4529 *pos += 1;
4530 skip_selector_escape(selector, pos)?;
4531 continue;
4532 }
4533 b'[' => {
4534 bracket_depth += 1;
4535 *pos += 1;
4536 }
4537 b']' => {
4538 if bracket_depth == 0 {
4539 return Err(selector_not_supported(selector));
4540 }
4541 bracket_depth -= 1;
4542 *pos += 1;
4543 }
4544 b'(' if bracket_depth == 0 => {
4545 depth += 1;
4546 *pos += 1;
4547 }
4548 b')' if bracket_depth == 0 => {
4549 depth -= 1;
4550 if depth == 0 {
4551 let argument = selector[start..*pos].to_string();
4552 *pos += 1;
4553 return Ok(argument);
4554 }
4555 *pos += 1;
4556 }
4557 _ => {
4558 let ch = selector[*pos..]
4559 .chars()
4560 .next()
4561 .ok_or_else(|| selector_not_supported(selector))?;
4562 *pos += ch.len_utf8();
4563 }
4564 },
4565 }
4566 }
4567
4568 Err(selector_not_supported(selector))
4569}
4570
4571fn split_selector_list_items(selector: &str) -> Result<Vec<&str>, String> {
4572 let bytes = selector.as_bytes();
4573 let mut items = Vec::new();
4574 let mut depth = 0usize;
4575 let mut bracket_depth = 0usize;
4576 let mut in_quote: Option<u8> = None;
4577 let mut start = 0usize;
4578 let mut pos = 0usize;
4579 while pos < bytes.len() {
4580 let byte = bytes[pos];
4581 match in_quote {
4582 Some(quote) => match byte {
4583 b'\\' => {
4584 pos += 1;
4585 skip_selector_escape(selector, &mut pos)?;
4586 continue;
4587 }
4588 byte if byte == quote => {
4589 in_quote = None;
4590 pos += 1;
4591 continue;
4592 }
4593 _ => {
4594 let ch = selector[pos..]
4595 .chars()
4596 .next()
4597 .ok_or_else(|| selector_not_supported(selector))?;
4598 pos += ch.len_utf8();
4599 continue;
4600 }
4601 },
4602 None => match byte {
4603 b'\'' | b'"' => {
4604 in_quote = Some(byte);
4605 pos += 1;
4606 }
4607 b'\\' => {
4608 pos += 1;
4609 skip_selector_escape(selector, &mut pos)?;
4610 }
4611 b'(' => {
4612 depth += 1;
4613 pos += 1;
4614 }
4615 b')' => {
4616 if depth == 0 {
4617 return Err(selector_not_supported(selector));
4618 }
4619 depth -= 1;
4620 pos += 1;
4621 }
4622 b'[' => {
4623 bracket_depth += 1;
4624 pos += 1;
4625 }
4626 b']' => {
4627 if bracket_depth == 0 {
4628 return Err(selector_not_supported(selector));
4629 }
4630 bracket_depth -= 1;
4631 pos += 1;
4632 }
4633 b',' if depth == 0 && bracket_depth == 0 => {
4634 let item = selector[start..pos].trim();
4635 if item.is_empty() {
4636 return Err(selector_not_supported(selector));
4637 }
4638 items.push(item);
4639 pos += 1;
4640 start = pos;
4641 }
4642 _ => {
4643 let ch = selector[pos..]
4644 .chars()
4645 .next()
4646 .ok_or_else(|| selector_not_supported(selector))?;
4647 pos += ch.len_utf8();
4648 }
4649 },
4650 }
4651 }
4652
4653 if depth != 0 || bracket_depth != 0 || in_quote.is_some() {
4654 return Err(selector_not_supported(selector));
4655 }
4656
4657 let item = selector[start..].trim();
4658 if item.is_empty() {
4659 return Err(selector_not_supported(selector));
4660 }
4661 items.push(item);
4662
4663 Ok(items)
4664}
4665
4666fn parse_selector_token(selector: &str, pos: &mut usize) -> Result<String, String> {
4667 let bytes = selector.as_bytes();
4668 let mut token = String::new();
4669
4670 while *pos < bytes.len() {
4671 let byte = bytes[*pos];
4672 if byte == b'\\' {
4673 *pos += 1;
4674 token.push(skip_selector_escape(selector, pos)?);
4675 continue;
4676 }
4677
4678 if is_simple_name_byte(byte) {
4679 token.push(byte as char);
4680 *pos += 1;
4681 continue;
4682 }
4683
4684 break;
4685 }
4686
4687 if token.is_empty() {
4688 return Err(selector_not_supported(selector));
4689 }
4690
4691 Ok(token)
4692}
4693
4694fn skip_selector_escape(selector: &str, pos: &mut usize) -> Result<char, String> {
4695 if *pos >= selector.len() {
4696 return Err(selector_not_supported(selector));
4697 }
4698
4699 let bytes = selector.as_bytes();
4700 let mut end = *pos;
4701 let mut digits = 0usize;
4702 while end < bytes.len() && digits < 6 && bytes[end].is_ascii_hexdigit() {
4703 end += 1;
4704 digits += 1;
4705 }
4706
4707 if digits > 0 {
4708 let value = u32::from_str_radix(&selector[*pos..end], 16)
4709 .map_err(|_| selector_not_supported(selector))?;
4710 let ch = char::from_u32(value).ok_or_else(|| selector_not_supported(selector))?;
4711 if ch.is_control() {
4712 return Err(selector_not_supported(selector));
4713 }
4714 *pos = end;
4715
4716 if *pos < bytes.len() && bytes[*pos].is_ascii_whitespace() {
4717 *pos += 1;
4718 }
4719
4720 return Ok(ch);
4721 }
4722
4723 let ch = selector[*pos..]
4724 .chars()
4725 .next()
4726 .ok_or_else(|| selector_not_supported(selector))?;
4727 *pos += ch.len_utf8();
4728 Ok(ch)
4729}
4730
4731fn skip_selector_whitespace(bytes: &[u8], pos: &mut usize) -> bool {
4732 let start = *pos;
4733 while *pos < bytes.len() && bytes[*pos].is_ascii_whitespace() {
4734 *pos += 1;
4735 }
4736
4737 *pos != start
4738}
4739
4740fn is_selector_combinator_byte(byte: u8) -> bool {
4741 matches!(byte, b'>' | b'+' | b'~' | b',')
4742}
4743
4744struct HtmlParser<'a> {
4745 input: &'a str,
4746 bytes: &'a [u8],
4747 pos: usize,
4748}
4749
4750impl<'a> HtmlParser<'a> {
4751 fn new(input: &'a str) -> Self {
4752 Self {
4753 input,
4754 bytes: input.as_bytes(),
4755 pos: 0,
4756 }
4757 }
4758
4759 fn parse_into(&mut self, store: &mut DomStore) -> Result<(), String> {
4760 self.parse_into_with_stack(store, vec![store.document_id], 1)
4761 }
4762
4763 fn parse_fragment_into(&mut self, store: &mut DomStore, parent: NodeId) -> Result<(), String> {
4764 self.parse_into_with_stack(store, vec![store.document_id, parent], 2)
4765 }
4766
4767 fn parse_into_with_stack(
4768 &mut self,
4769 store: &mut DomStore,
4770 mut stack: Vec<NodeId>,
4771 expected_stack_len: usize,
4772 ) -> Result<(), String> {
4773 while self.pos < self.bytes.len() {
4774 let current_parent = *stack
4775 .last()
4776 .expect("document root should always be on stack");
4777 if let Some(raw_text_tag) = store
4778 .tag_name_for(current_parent)
4779 .filter(|tag| is_raw_text_element(tag))
4780 .map(|tag| tag.to_string())
4781 {
4782 let closing_tag = format!("</{}>", raw_text_tag);
4783 let rest = &self.input[self.pos..];
4784 if let Some(offset) = find_case_insensitive(rest, &closing_tag) {
4785 if offset > 0 {
4786 store.add_text(current_parent, rest[..offset].to_string());
4787 self.pos += offset;
4788 continue;
4789 }
4790 } else {
4791 if !rest.is_empty() {
4792 store.add_text(current_parent, rest.to_string());
4793 }
4794 self.pos = self.bytes.len();
4795 break;
4796 }
4797 }
4798
4799 if self.bytes[self.pos] == b'<' {
4800 if self.starts_with_bytes(b"<!--") {
4801 let parent = *stack
4802 .last()
4803 .expect("document root should always be on stack");
4804 self.parse_comment(store, parent)?;
4805 continue;
4806 }
4807
4808 if self.starts_with_bytes(b"</") {
4809 self.parse_closing_tag(store, &mut stack)?;
4810 continue;
4811 }
4812
4813 if self.starts_with_bytes(b"<!") {
4814 self.parse_declaration()?;
4815 continue;
4816 }
4817
4818 self.parse_start_tag(store, &mut stack)?;
4819 continue;
4820 }
4821
4822 let parent = *stack
4823 .last()
4824 .expect("document root should always be on stack");
4825 self.parse_text(store, parent)?;
4826 }
4827
4828 if stack.len() != expected_stack_len {
4829 let open_id = *stack
4830 .last()
4831 .expect("document root should always be on stack");
4832 let tag_name = store.tag_name_for(open_id).unwrap_or("unknown").to_string();
4833 return Err(format!("unclosed tag <{}>", tag_name));
4834 }
4835
4836 Ok(())
4837 }
4838
4839 fn starts_with_bytes(&self, pattern: &[u8]) -> bool {
4840 self.bytes[self.pos..].starts_with(pattern)
4841 }
4842
4843 fn current_byte(&self) -> Option<u8> {
4844 self.bytes.get(self.pos).copied()
4845 }
4846
4847 fn skip_ascii_whitespace(&mut self) {
4848 while matches!(
4849 self.current_byte(),
4850 Some(b' ' | b'\n' | b'\r' | b'\t' | 0x0c)
4851 ) {
4852 self.pos += 1;
4853 }
4854 }
4855
4856 fn parse_text(&mut self, store: &mut DomStore, parent: NodeId) -> Result<(), String> {
4857 let rest = &self.input[self.pos..];
4858 let next_tag = rest.find('<').unwrap_or(rest.len());
4859 let value = decode_html_entities(&rest[..next_tag]);
4860 self.pos += next_tag;
4861
4862 if !value.is_empty() {
4863 store.add_text(parent, value);
4864 }
4865
4866 Ok(())
4867 }
4868
4869 fn parse_comment(&mut self, store: &mut DomStore, parent: NodeId) -> Result<(), String> {
4870 self.pos += 4;
4871 let rest = &self.input[self.pos..];
4872 let end = rest
4873 .find("-->")
4874 .ok_or_else(|| format!("unterminated comment at byte {}", self.pos - 4))?;
4875 let value = &rest[..end];
4876 self.pos += end + 3;
4877 store.add_comment(parent, value.to_string());
4878 Ok(())
4879 }
4880
4881 fn parse_declaration(&mut self) -> Result<(), String> {
4882 self.pos += 2;
4883 let rest = &self.input[self.pos..];
4884 let end = rest
4885 .find('>')
4886 .ok_or_else(|| format!("unterminated declaration at byte {}", self.pos - 2))?;
4887 self.pos += end + 1;
4888 Ok(())
4889 }
4890
4891 fn parse_start_tag(
4892 &mut self,
4893 store: &mut DomStore,
4894 stack: &mut Vec<NodeId>,
4895 ) -> Result<(), String> {
4896 self.pos += 1;
4897 if self.pos >= self.bytes.len() {
4898 return Err("unexpected end of input after `<`".to_string());
4899 }
4900
4901 if !self
4902 .current_byte()
4903 .map(is_simple_name_byte)
4904 .unwrap_or(false)
4905 {
4906 return Err(format!("invalid tag name at byte {}", self.pos));
4907 }
4908
4909 let tag_name = self.parse_name_token("tag")?;
4910 let mut attributes = BTreeMap::new();
4911 let start_tag_name = tag_name.clone();
4912
4913 loop {
4914 self.skip_ascii_whitespace();
4915 if self.pos >= self.bytes.len() {
4916 return Err(format!("unclosed start tag <{}>", start_tag_name));
4917 }
4918
4919 if self.starts_with_bytes(b"/>") {
4920 self.pos += 2;
4921 self.finish_start_tag(store, stack, tag_name, attributes, true);
4922 return Ok(());
4923 }
4924
4925 if self.current_byte() == Some(b'>') {
4926 self.pos += 1;
4927 self.finish_start_tag(store, stack, tag_name, attributes, false);
4928 return Ok(());
4929 }
4930
4931 let attribute_name = self.parse_name_token("attribute")?;
4932 self.skip_ascii_whitespace();
4933
4934 let value = if self.current_byte() == Some(b'=') {
4935 self.pos += 1;
4936 self.skip_ascii_whitespace();
4937 self.parse_attribute_value()?
4938 } else {
4939 String::new()
4940 };
4941
4942 attributes.insert(attribute_name, value);
4943 }
4944 }
4945
4946 fn finish_start_tag(
4947 &mut self,
4948 store: &mut DomStore,
4949 stack: &mut Vec<NodeId>,
4950 tag_name: String,
4951 attributes: BTreeMap<String, String>,
4952 self_closing: bool,
4953 ) {
4954 let parent = *stack
4955 .last()
4956 .expect("document root should always be on stack");
4957 let node_id = store.add_element(parent, tag_name.clone(), attributes);
4958 if !self_closing && !is_void_element(&tag_name) {
4959 stack.push(node_id);
4960 }
4961 }
4962
4963 fn parse_closing_tag(
4964 &mut self,
4965 store: &mut DomStore,
4966 stack: &mut Vec<NodeId>,
4967 ) -> Result<(), String> {
4968 self.pos += 2;
4969 self.skip_ascii_whitespace();
4970 if self.pos >= self.bytes.len() {
4971 return Err("unexpected end of input in closing tag".to_string());
4972 }
4973
4974 if !self
4975 .current_byte()
4976 .map(is_simple_name_byte)
4977 .unwrap_or(false)
4978 {
4979 return Err(format!("invalid closing tag at byte {}", self.pos));
4980 }
4981
4982 let closing_name = self.parse_name_token("closing tag")?;
4983 self.skip_ascii_whitespace();
4984 if self.current_byte() != Some(b'>') {
4985 return Err(format!(
4986 "expected `>` to close `</{}>` at byte {}",
4987 closing_name, self.pos
4988 ));
4989 }
4990 self.pos += 1;
4991
4992 if stack.len() == 1 {
4993 return Err(format!("unexpected closing tag </{}>", closing_name));
4994 }
4995
4996 let open_id = stack.pop().expect("stack length checked above");
4997 let open_name = store.tag_name_for(open_id).unwrap_or("unknown").to_string();
4998 if open_name != closing_name {
4999 return Err(format!(
5000 "mismatched closing tag </{}>, expected </{}>",
5001 closing_name, open_name
5002 ));
5003 }
5004
5005 Ok(())
5006 }
5007
5008 fn parse_name_token(&mut self, kind: &str) -> Result<String, String> {
5009 let start = self.pos;
5010 while let Some(byte) = self.current_byte() {
5011 if is_simple_name_byte(byte) {
5012 self.pos += 1;
5013 } else {
5014 break;
5015 }
5016 }
5017
5018 if self.pos == start {
5019 return Err(format!("expected {} name at byte {}", kind, start));
5020 }
5021
5022 Ok(self.input[start..self.pos].to_ascii_lowercase())
5023 }
5024
5025 fn parse_attribute_value(&mut self) -> Result<String, String> {
5026 match self.current_byte() {
5027 Some(quote @ b'"') | Some(quote @ b'\'') => {
5028 self.pos += 1;
5029 let rest = &self.bytes[self.pos..];
5030 let end = rest
5031 .iter()
5032 .position(|byte| *byte == quote)
5033 .ok_or_else(|| format!("unterminated quoted attribute at byte {}", self.pos))?;
5034 let value = decode_html_entities(&self.input[self.pos..self.pos + end]);
5035 self.pos += end + 1;
5036 Ok(value)
5037 }
5038 Some(_) => {
5039 let start = self.pos;
5040 while let Some(byte) = self.current_byte() {
5041 if byte.is_ascii_whitespace() || byte == b'>' {
5042 break;
5043 }
5044 self.pos += 1;
5045 }
5046
5047 if self.pos == start {
5048 return Err(format!("expected attribute value at byte {}", start));
5049 }
5050
5051 Ok(decode_html_entities(&self.input[start..self.pos]))
5052 }
5053 None => Err("unexpected end of input while parsing attribute value".to_string()),
5054 }
5055 }
5056}
5057
5058fn write_indent(output: &mut String, indent: usize) {
5059 for _ in 0..indent {
5060 output.push_str(" ");
5061 }
5062}
5063
5064fn format_attributes(attributes: &BTreeMap<String, String>) -> String {
5065 let mut parts = Vec::new();
5066 for (name, value) in attributes {
5067 if value.is_empty() {
5068 parts.push(name.clone());
5069 } else {
5070 parts.push(format!(r#"{name}="{}""#, escape_attr(value)));
5071 }
5072 }
5073 parts.join(" ")
5074}
5075
5076fn escape_html_text(value: &str) -> String {
5077 value
5078 .replace('&', "&")
5079 .replace('<', "<")
5080 .replace('>', ">")
5081}
5082
5083fn escape_html_attribute(value: &str) -> String {
5084 value
5085 .replace('&', "&")
5086 .replace('<', "<")
5087 .replace('>', ">")
5088 .replace('\"', """)
5089}
5090
5091fn decode_html_entities(value: &str) -> String {
5092 let mut output = String::new();
5093 let mut rest = value;
5094
5095 while let Some(amp_index) = rest.find('&') {
5096 output.push_str(&rest[..amp_index]);
5097 let candidate = &rest[amp_index + 1..];
5098 let Some((decoded, consumed)) = decode_html_entity_candidate(candidate) else {
5099 output.push('&');
5100 rest = candidate;
5101 continue;
5102 };
5103
5104 output.push_str(&decoded);
5105 rest = &candidate[consumed..];
5106 }
5107
5108 output.push_str(rest);
5109 output
5110}
5111
5112fn decode_html_entity_candidate(candidate: &str) -> Option<(String, usize)> {
5113 if let Some(semi_index) = candidate.find(';') {
5114 let entity = &candidate[..semi_index];
5115 if let Some(decoded) = decode_html_named_or_numeric_entity(entity) {
5116 return Some((decoded, semi_index + 1));
5117 }
5118 }
5119
5120 if let Some((entity, consumed)) = decode_html_numeric_entity_without_semicolon(candidate) {
5121 if let Some(decoded) = decode_html_named_or_numeric_entity(entity) {
5122 return Some((decoded, consumed));
5123 }
5124 }
5125
5126 if let Some((entity, consumed)) = decode_html_named_entity_without_semicolon(candidate) {
5127 if let Some(decoded) = decode_html_named_or_numeric_entity(entity) {
5128 return Some((decoded, consumed));
5129 }
5130 }
5131
5132 None
5133}
5134
5135fn decode_html_named_or_numeric_entity(entity: &str) -> Option<String> {
5136 match entity {
5137 "AMP" | "amp" => Some("&".to_string()),
5138 "LT" | "lt" => Some("<".to_string()),
5139 "GT" | "gt" => Some(">".to_string()),
5140 "QUOT" | "quot" => Some("\"".to_string()),
5141 "apos" => Some("'".to_string()),
5142 "NBSP" | "nbsp" => Some("\u{a0}".to_string()),
5143 "COPY" | "copy" => Some("©".to_string()),
5144 "REG" | "reg" => Some("®".to_string()),
5145 _ if entity.starts_with("#x") || entity.starts_with("#X") => {
5146 u32::from_str_radix(&entity[2..], 16)
5147 .ok()
5148 .and_then(char::from_u32)
5149 .map(|ch| {
5150 let mut buf = String::new();
5151 buf.push(ch);
5152 buf
5153 })
5154 }
5155 _ if entity.starts_with('#') => entity[1..]
5156 .parse::<u32>()
5157 .ok()
5158 .and_then(char::from_u32)
5159 .map(|ch| {
5160 let mut buf = String::new();
5161 buf.push(ch);
5162 buf
5163 }),
5164 _ => None,
5165 }
5166}
5167
5168fn decode_html_numeric_entity_without_semicolon(candidate: &str) -> Option<(&str, usize)> {
5169 let rest = candidate.strip_prefix('#')?;
5170
5171 let (digits, consumed_prefix) =
5172 if let Some(hex_rest) = rest.strip_prefix('x').or_else(|| rest.strip_prefix('X')) {
5173 let consumed = hex_rest
5174 .chars()
5175 .take_while(|ch| ch.is_ascii_hexdigit())
5176 .count();
5177 if consumed == 0 {
5178 return None;
5179 }
5180 (&hex_rest[..consumed], 2)
5181 } else {
5182 let consumed = rest.chars().take_while(|ch| ch.is_ascii_digit()).count();
5183 if consumed == 0 {
5184 return None;
5185 }
5186 (&rest[..consumed], 1)
5187 };
5188
5189 let consumed = consumed_prefix + digits.len();
5190 Some((&candidate[..consumed], consumed))
5191}
5192
5193fn decode_html_named_entity_without_semicolon(candidate: &str) -> Option<(&'static str, usize)> {
5194 for entity in [
5195 "NBSP", "nbsp", "QUOT", "quot", "apos", "AMP", "amp", "LT", "lt", "GT", "gt", "COPY",
5196 "copy", "REG", "reg",
5197 ] {
5198 if candidate.starts_with(entity) {
5199 let next = candidate.as_bytes().get(entity.len()).copied();
5200 if next.is_none_or(|byte| !byte.is_ascii_alphanumeric() && byte != b'=') {
5201 return Some((entity, entity.len()));
5202 }
5203 } else {
5204 continue;
5205 }
5206 }
5207
5208 None
5209}
5210
5211fn escape_text(value: &str) -> String {
5212 value
5213 .replace('\\', "\\\\")
5214 .replace('\"', "\\\"")
5215 .replace('\n', "\\n")
5216 .replace('\r', "\\r")
5217 .replace('\t', "\\t")
5218}
5219
5220fn escape_attr(value: &str) -> String {
5221 value
5222 .replace('\\', "\\\\")
5223 .replace('\"', "\\\"")
5224 .replace('\n', "\\n")
5225 .replace('\r', "\\r")
5226 .replace('\t', "\\t")
5227}
5228
5229fn is_simple_name_byte(byte: u8) -> bool {
5230 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_')
5231}
5232
5233fn normalize_attribute_name(name: &str) -> Result<String, String> {
5234 let trimmed = name.trim();
5235 if trimmed.is_empty() {
5236 return Err("attribute name must not be empty".to_string());
5237 }
5238 Ok(trimmed.to_ascii_lowercase())
5239}
5240
5241fn attribute_affects_indexes(name: &str) -> bool {
5242 matches!(name, "id" | "class" | "name")
5243}
5244
5245fn attribute_affects_form_controls(name: &str) -> bool {
5246 matches!(name, "value" | "checked" | "selected" | "type")
5247}
5248
5249fn element_namespace_for_root(tag_name: &str) -> &'static str {
5250 match tag_name {
5251 "svg" => SVG_NAMESPACE_URI,
5252 "math" => MATHML_NAMESPACE_URI,
5253 _ => HTML_NAMESPACE_URI,
5254 }
5255}
5256
5257fn supports_disabled_pseudo_class(tag_name: &str) -> bool {
5258 matches!(
5259 tag_name,
5260 "button" | "fieldset" | "input" | "option" | "optgroup" | "select" | "textarea"
5261 )
5262}
5263
5264fn is_void_element(tag_name: &str) -> bool {
5265 matches!(
5266 tag_name,
5267 "area"
5268 | "base"
5269 | "br"
5270 | "col"
5271 | "embed"
5272 | "hr"
5273 | "img"
5274 | "input"
5275 | "link"
5276 | "meta"
5277 | "param"
5278 | "source"
5279 | "track"
5280 | "wbr"
5281 )
5282}
5283
5284fn adjust_svg_element_name(name: &str) -> &str {
5285 match name {
5286 "altglyph" => "altGlyph",
5287 "altglyphdef" => "altGlyphDef",
5288 "altglyphitem" => "altGlyphItem",
5289 "animatecolor" => "animateColor",
5290 "animatemotion" => "animateMotion",
5291 "animatetransform" => "animateTransform",
5292 "clippath" => "clipPath",
5293 "feblend" => "feBlend",
5294 "fecolormatrix" => "feColorMatrix",
5295 "fecomponenttransfer" => "feComponentTransfer",
5296 "fecomposite" => "feComposite",
5297 "feconvolvematrix" => "feConvolveMatrix",
5298 "fediffuselighting" => "feDiffuseLighting",
5299 "fedisplacementmap" => "feDisplacementMap",
5300 "fedistantlight" => "feDistantLight",
5301 "fedropshadow" => "feDropShadow",
5302 "feflood" => "feFlood",
5303 "fefunca" => "feFuncA",
5304 "fefuncb" => "feFuncB",
5305 "fefuncg" => "feFuncG",
5306 "fefuncr" => "feFuncR",
5307 "fegaussianblur" => "feGaussianBlur",
5308 "feimage" => "feImage",
5309 "femerge" => "feMerge",
5310 "femergenode" => "feMergeNode",
5311 "femorphology" => "feMorphology",
5312 "feoffset" => "feOffset",
5313 "fepointlight" => "fePointLight",
5314 "fespecularlighting" => "feSpecularLighting",
5315 "fespotlight" => "feSpotLight",
5316 "fetile" => "feTile",
5317 "feturbulence" => "feTurbulence",
5318 "foreignobject" => "foreignObject",
5319 "glyphref" => "glyphRef",
5320 "lineargradient" => "linearGradient",
5321 "radialgradient" => "radialGradient",
5322 "textpath" => "textPath",
5323 _ => name,
5324 }
5325}
5326
5327fn adjust_svg_attribute_name(name: &str) -> &str {
5328 match name {
5329 "attributename" => "attributeName",
5330 "attributetype" => "attributeType",
5331 "basefrequency" => "baseFrequency",
5332 "baseprofile" => "baseProfile",
5333 "calcmode" => "calcMode",
5334 "clippathunits" => "clipPathUnits",
5335 "diffuseconstant" => "diffuseConstant",
5336 "edgemode" => "edgeMode",
5337 "filterunits" => "filterUnits",
5338 "glyphref" => "glyphRef",
5339 "gradienttransform" => "gradientTransform",
5340 "gradientunits" => "gradientUnits",
5341 "kernelmatrix" => "kernelMatrix",
5342 "kernelunitlength" => "kernelUnitLength",
5343 "keypoints" => "keyPoints",
5344 "keysplines" => "keySplines",
5345 "keytimes" => "keyTimes",
5346 "lengthadjust" => "lengthAdjust",
5347 "limitingconeangle" => "limitingConeAngle",
5348 "markerheight" => "markerHeight",
5349 "markerunits" => "markerUnits",
5350 "markerwidth" => "markerWidth",
5351 "maskcontentunits" => "maskContentUnits",
5352 "maskunits" => "maskUnits",
5353 "numoctaves" => "numOctaves",
5354 "pathlength" => "pathLength",
5355 "patterncontentunits" => "patternContentUnits",
5356 "patterntransform" => "patternTransform",
5357 "patternunits" => "patternUnits",
5358 "pointsatx" => "pointsAtX",
5359 "pointsaty" => "pointsAtY",
5360 "pointsatz" => "pointsAtZ",
5361 "preservealpha" => "preserveAlpha",
5362 "preserveaspectratio" => "preserveAspectRatio",
5363 "primitiveunits" => "primitiveUnits",
5364 "refx" => "refX",
5365 "refy" => "refY",
5366 "repeatcount" => "repeatCount",
5367 "repeatdur" => "repeatDur",
5368 "requiredextensions" => "requiredExtensions",
5369 "requiredfeatures" => "requiredFeatures",
5370 "specularconstant" => "specularConstant",
5371 "specularexponent" => "specularExponent",
5372 "spreadmethod" => "spreadMethod",
5373 "startoffset" => "startOffset",
5374 "stddeviation" => "stdDeviation",
5375 "stitchtiles" => "stitchTiles",
5376 "surfacescale" => "surfaceScale",
5377 "systemlanguage" => "systemLanguage",
5378 "tablevalues" => "tableValues",
5379 "targetx" => "targetX",
5380 "targety" => "targetY",
5381 "textlength" => "textLength",
5382 "viewbox" => "viewBox",
5383 "viewtarget" => "viewTarget",
5384 "xchannelselector" => "xChannelSelector",
5385 "ychannelselector" => "yChannelSelector",
5386 "zoomandpan" => "zoomAndPan",
5387 _ => name,
5388 }
5389}
5390
5391fn is_raw_text_element(tag_name: &str) -> bool {
5392 matches!(tag_name, "script" | "style")
5393}
5394
5395fn find_case_insensitive(haystack: &str, needle: &str) -> Option<usize> {
5396 let haystack_bytes = haystack.as_bytes();
5397 let needle_bytes = needle.as_bytes();
5398 if needle_bytes.is_empty() {
5399 return Some(0);
5400 }
5401
5402 haystack_bytes
5403 .windows(needle_bytes.len())
5404 .position(|window| {
5405 window
5406 .iter()
5407 .zip(needle_bytes.iter())
5408 .all(|(hay, nee)| hay.eq_ignore_ascii_case(nee))
5409 })
5410}
5411
5412fn is_text_input_type(input_type: Option<&str>) -> bool {
5413 matches!(
5414 input_type.unwrap_or("text"),
5415 "text"
5416 | "search"
5417 | "url"
5418 | "tel"
5419 | "email"
5420 | "password"
5421 | "number"
5422 | "date"
5423 | "datetime-local"
5424 | "month"
5425 | "week"
5426 | "time"
5427 | "color"
5428 )
5429}
5430
5431fn is_blank_input_type(input_type: Option<&str>) -> bool {
5432 matches!(
5433 input_type.unwrap_or("text"),
5434 "text"
5435 | "search"
5436 | "url"
5437 | "tel"
5438 | "email"
5439 | "password"
5440 | "number"
5441 | "date"
5442 | "datetime-local"
5443 | "month"
5444 | "week"
5445 | "time"
5446 )
5447}
5448
5449fn is_pattern_input_type(input_type: Option<&str>) -> bool {
5450 matches!(
5451 input_type.unwrap_or("text"),
5452 "text" | "search" | "url" | "tel" | "email" | "password"
5453 )
5454}
5455
5456fn is_checkable_input_type(input_type: Option<&str>) -> bool {
5457 matches!(input_type.unwrap_or("text"), "checkbox" | "radio")
5458}
5459
5460fn is_file_input_type(input_type: Option<&str>) -> bool {
5461 matches!(input_type.unwrap_or("text"), "file")
5462}