Skip to main content

bt_dom/
html_dom.rs

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('&', "&amp;")
5079        .replace('<', "&lt;")
5080        .replace('>', "&gt;")
5081}
5082
5083fn escape_html_attribute(value: &str) -> String {
5084    value
5085        .replace('&', "&amp;")
5086        .replace('<', "&lt;")
5087        .replace('>', "&gt;")
5088        .replace('\"', "&quot;")
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}