1use alloc::collections::BTreeMap;
13use alloc::collections::BTreeSet;
14use alloc::format;
15use alloc::string::String;
16use alloc::string::ToString;
17use alloc::vec::Vec;
18
19use super::value::Value;
20use crate::os_lib::dom::{Node, NodeType};
21
22pub struct DomNode {
24 pub tag: String,
26 pub is_text: bool,
27 pub id: String,
28 pub classes: Vec<String>,
29 pub style: BTreeMap<String, String>,
31 pub attrs: BTreeMap<String, String>,
33 pub text_override: Option<String>,
35 pub initial_text: String,
37 pub parent: Option<usize>,
38 pub children: Vec<usize>,
39 pub classes_dirty: bool,
41 pub style_dirty: bool,
43}
44
45pub struct MutObsReg {
47 pub id: u32,
48 pub target_idx: usize,
49 pub child_list: bool,
50 pub attributes: bool,
51 pub subtree: bool,
52 pub callback: Value,
53 pub attribute_old_value: bool,
56 pub attribute_filter: Option<Vec<String>>,
59 pub character_data: bool,
66 pub character_data_old_value: bool,
70}
71
72pub struct MutRec {
74 pub type_: &'static str, pub target_idx: usize,
76 pub added: Vec<usize>,
77 pub removed: Vec<usize>,
78 pub attribute_name: Option<String>,
79 pub old_value: Option<String>,
81}
82
83pub struct Listener {
85 pub node: usize,
86 pub event: String,
87 pub func: Value,
88 pub capture: bool,
90 pub once: bool,
92 pub id: u64,
94 pub signal: Option<Value>,
101}
102
103pub fn is_signal_aborted(signal: &Value) -> bool {
105 matches!(signal, Value::Object(o) if matches!(o.borrow().props.get("aborted"), Some(Value::Bool(true))))
106}
107
108fn is_listener_aborted(l: &Listener) -> bool {
109 l.signal.as_ref().is_some_and(is_signal_aborted)
110}
111
112pub struct DomBridge {
114 pub nodes: Vec<DomNode>,
115 pub listeners: Vec<Listener>,
116 pub dirty: bool,
118 pub built: bool,
120 pub rects: BTreeMap<usize, (i32, i32, i32, i32)>,
123 pub border_widths: BTreeMap<usize, (i32, i32, i32, i32)>,
125 pub computed_styles: BTreeMap<usize, BTreeMap<String, String>>,
128 pub scroll_tops: BTreeMap<usize, i32>,
131 pub scroll_heights: BTreeMap<usize, i32>,
134 pub next_listener_id: u64,
136 pub mut_observers: Vec<MutObsReg>,
138 pub mut_records: Vec<(u32, MutRec)>,
140 pub next_mut_obs_id: u32,
142 pub pointer_captures: BTreeSet<(usize, i32)>,
149 pub focused_idx: Option<usize>,
158 pub pending_scroll_abs_y: Option<i32>,
168 pub pending_scroll_by_y: Option<i32>,
171 pub on_handlers: BTreeMap<(usize, String), Value>,
180}
181
182impl Default for DomBridge {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188#[derive(Default)]
194pub struct ValidityFlags {
195 pub value_missing: bool,
196 pub type_mismatch: bool,
197 pub pattern_mismatch: bool,
198 pub too_long: bool,
199 pub too_short: bool,
200 pub range_overflow: bool,
201 pub range_underflow: bool,
202 pub step_mismatch: bool,
203 pub bad_input: bool,
204 pub custom_error: bool,
205}
206impl ValidityFlags {
207 pub fn valid(&self) -> bool {
208 !(self.value_missing
209 || self.type_mismatch
210 || self.pattern_mismatch
211 || self.too_long
212 || self.too_short
213 || self.range_overflow
214 || self.range_underflow
215 || self.step_mismatch
216 || self.bad_input
217 || self.custom_error)
218 }
219}
220
221#[derive(Clone, Copy)]
230enum AttrOp {
231 Exists,
232 Eq,
233 Prefix,
234 Suffix,
235 Contains,
236 Word,
237}
238
239struct AttrSel {
240 name: String,
241 op: AttrOp,
242 value: String,
243}
244
245#[derive(Default)]
246struct CompoundSel {
247 tag: Option<String>,
248 id: Option<String>,
249 classes: Vec<String>,
250 attrs: Vec<AttrSel>,
251 not: Vec<CompoundSel>,
254 is_list: Vec<CompoundSel>,
259 pos: Vec<PosPseudo>,
261 pos_of_type: Vec<PosPseudo>,
264 is_scope: bool,
268}
269
270#[derive(Clone, Copy)]
271struct NthFormula {
272 a: i32,
273 b: i32,
274}
275
276#[derive(Clone, Copy)]
277enum PosPseudo {
278 First,
279 Last,
280 Only,
281 Nth(NthFormula),
282 NthLast(NthFormula),
285}
286
287fn parse_nth_formula(s: &str) -> Option<NthFormula> {
289 let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
290 let s = s.to_lowercase();
291 if s == "odd" {
292 return Some(NthFormula { a: 2, b: 1 });
293 }
294 if s == "even" {
295 return Some(NthFormula { a: 2, b: 0 });
296 }
297 if let Some(n_pos) = s.find('n') {
298 let a_part = s.get(..n_pos).unwrap_or("");
299 let a = match a_part {
300 "" | "+" => 1,
301 "-" => -1,
302 _ => a_part.parse::<i32>().ok()?,
303 };
304 let b_part = s.get(n_pos + 1..).unwrap_or("");
305 let b = if b_part.is_empty() {
306 0
307 } else {
308 b_part.parse::<i32>().ok()?
309 };
310 return Some(NthFormula { a, b });
311 }
312 let n = s.parse::<i32>().ok()?;
313 Some(NthFormula { a: 0, b: n })
314}
315
316fn nth_formula_matches(f: NthFormula, position: usize) -> bool {
319 let p = position as i32;
320 if f.a == 0 {
321 return p == f.b;
322 }
323 let diff = p - f.b;
324 diff % f.a == 0 && diff / f.a >= 0
325}
326
327#[derive(Clone, Copy)]
328enum Combinator {
329 Descendant,
330 Child,
331 Adjacent,
332 General,
333}
334
335struct SelStep {
336 compound: CompoundSel,
337 combinator: Option<Combinator>,
339}
340
341fn parse_attr_sel(inner: &str) -> Option<AttrSel> {
344 let inner = inner.trim();
345 for (token, op) in [
346 ("^=", AttrOp::Prefix),
347 ("$=", AttrOp::Suffix),
348 ("*=", AttrOp::Contains),
349 ("~=", AttrOp::Word),
350 ("=", AttrOp::Eq),
351 ] {
352 if let Some((name, val)) = inner.split_once(token) {
353 let val = val.trim().trim_matches(|c| c == '"' || c == '\'');
354 return Some(AttrSel {
355 name: name.trim().to_string(),
356 op,
357 value: val.to_string(),
358 });
359 }
360 }
361 if inner.is_empty() {
362 return None;
363 }
364 Some(AttrSel {
365 name: inner.to_string(),
366 op: AttrOp::Exists,
367 value: String::new(),
368 })
369}
370
371fn parse_compound(s: &str) -> Option<CompoundSel> {
377 let s = s.trim();
378 if s.is_empty() {
379 return None;
380 }
381 if s == "*" {
382 return Some(CompoundSel::default());
383 }
384 let mut c = CompoundSel::default();
385 let mut cur = String::new();
386 let mut mode = ' '; let chars: Vec<char> = s.chars().collect();
388 let mut i = 0;
389 let flush = |mode: char, cur: &mut String, c: &mut CompoundSel| {
390 if cur.is_empty() {
391 return;
392 }
393 match mode {
394 ' ' => {
395 if cur.as_str() != "*" {
396 c.tag = Some(cur.to_lowercase());
397 }
398 }
399 '#' => c.id = Some(cur.clone()),
400 '.' => c.classes.push(cur.clone()),
401 _ => {}
402 }
403 cur.clear();
404 };
405 while i < chars.len() {
406 let ch = chars[i];
407 match ch {
408 '#' | '.' => {
409 flush(mode, &mut cur, &mut c);
410 mode = ch;
411 }
412 '[' => {
413 flush(mode, &mut cur, &mut c);
414 let start = i + 1;
415 let mut depth = 1;
416 let mut j = start;
417 while j < chars.len() && depth > 0 {
418 match chars[j] {
419 '[' => depth += 1,
420 ']' => depth -= 1,
421 _ => {}
422 }
423 if depth > 0 {
424 j += 1;
425 }
426 }
427 let inner: String = chars[start..j.min(chars.len())].iter().collect();
428 if let Some(attr) = parse_attr_sel(&inner) {
429 c.attrs.push(attr);
430 }
431 i = j;
432 mode = ' ';
433 }
434 ':' => {
435 flush(mode, &mut cur, &mut c);
440 let mut j = i + 1;
441 let name_start = j;
442 while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '-') {
443 j += 1;
444 }
445 let name: String = chars[name_start..j].iter().collect::<String>().to_lowercase();
446 if j < chars.len() && chars[j] == '(' {
447 let paren_start = j + 1;
448 let mut depth = 1;
449 let mut k = paren_start;
450 while k < chars.len() && depth > 0 {
451 match chars[k] {
452 '(' => depth += 1,
453 ')' => depth -= 1,
454 _ => {}
455 }
456 if depth > 0 {
457 k += 1;
458 }
459 }
460 if name == "not" {
461 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
462 for part in inner.split(',') {
463 if let Some(inner_compound) = parse_compound(part.trim()) {
464 c.not.push(inner_compound);
465 }
466 }
467 } else if name == "nth-child" {
468 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
469 if let Some(formula) = parse_nth_formula(&inner) {
470 c.pos.push(PosPseudo::Nth(formula));
471 }
472 } else if name == "nth-of-type" {
473 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
474 if let Some(formula) = parse_nth_formula(&inner) {
475 c.pos_of_type.push(PosPseudo::Nth(formula));
476 }
477 } else if name == "nth-last-child" {
478 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
479 if let Some(formula) = parse_nth_formula(&inner) {
480 c.pos.push(PosPseudo::NthLast(formula));
481 }
482 } else if name == "nth-last-of-type" {
483 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
484 if let Some(formula) = parse_nth_formula(&inner) {
485 c.pos_of_type.push(PosPseudo::NthLast(formula));
486 }
487 } else if name == "is" || name == "where" {
488 let inner: String = chars[paren_start..k.min(chars.len())].iter().collect();
489 for part in inner.split(',') {
490 if let Some(inner_compound) = parse_compound(part.trim()) {
491 c.is_list.push(inner_compound);
492 }
493 }
494 }
495 j = k;
496 } else {
497 match name.as_str() {
498 "first-child" => c.pos.push(PosPseudo::First),
499 "last-child" => c.pos.push(PosPseudo::Last),
500 "only-child" => c.pos.push(PosPseudo::Only),
501 "first-of-type" => c.pos_of_type.push(PosPseudo::First),
502 "last-of-type" => c.pos_of_type.push(PosPseudo::Last),
503 "only-of-type" => c.pos_of_type.push(PosPseudo::Only),
504 "scope" => c.is_scope = true,
506 _ => {}
507 }
508 }
509 i = j;
510 mode = ' ';
511 }
512 _ => cur.push(ch),
513 }
514 i += 1;
515 }
516 flush(mode, &mut cur, &mut c);
517 if c.tag.is_none()
518 && c.id.is_none()
519 && c.classes.is_empty()
520 && c.attrs.is_empty()
521 && c.not.is_empty()
522 && c.is_list.is_empty()
523 && c.pos.is_empty()
524 && c.pos_of_type.is_empty()
525 && !c.is_scope
526 {
527 return None;
528 }
529 Some(c)
530}
531
532fn split_top_level_commas(s: &str) -> Vec<String> {
538 let mut parts = Vec::new();
539 let mut cur = String::new();
540 let mut depth = 0i32;
541 for ch in s.chars() {
542 match ch {
543 '[' | '(' => {
544 depth += 1;
545 cur.push(ch);
546 }
547 ']' | ')' => {
548 depth -= 1;
549 cur.push(ch);
550 }
551 ',' if depth == 0 => {
552 parts.push(core::mem::take(&mut cur));
553 }
554 _ => cur.push(ch),
555 }
556 }
557 parts.push(cur);
558 parts
559}
560
561fn tokenize_selector_group(s: &str) -> Vec<String> {
565 let mut tokens = Vec::new();
566 let mut cur = String::new();
567 let mut depth = 0i32;
568 for ch in s.chars() {
569 match ch {
570 '[' | '(' => {
571 depth += 1;
572 cur.push(ch);
573 }
574 ']' | ')' => {
575 depth -= 1;
576 cur.push(ch);
577 }
578 '>' | '+' | '~' if depth == 0 => {
579 if !cur.trim().is_empty() {
580 tokens.push(cur.trim().to_string());
581 cur.clear();
582 }
583 tokens.push(ch.to_string());
584 }
585 c if c.is_whitespace() && depth == 0 => {
586 if !cur.trim().is_empty() {
587 tokens.push(cur.trim().to_string());
588 cur.clear();
589 }
590 }
591 _ => cur.push(ch),
592 }
593 }
594 if !cur.trim().is_empty() {
595 tokens.push(cur.trim().to_string());
596 }
597 tokens
598}
599
600fn build_steps(tokens: &[String]) -> Vec<SelStep> {
602 let mut steps: Vec<SelStep> = Vec::new();
603 let mut pending: Option<Combinator> = None;
604 for tok in tokens {
605 match tok.as_str() {
606 ">" => pending = Some(Combinator::Child),
607 "+" => pending = Some(Combinator::Adjacent),
608 "~" => pending = Some(Combinator::General),
609 _ => {
610 let combinator = if steps.is_empty() {
611 None
612 } else {
613 Some(pending.take().unwrap_or(Combinator::Descendant))
614 };
615 if let Some(compound) = parse_compound(tok) {
616 steps.push(SelStep { compound, combinator });
617 }
618 }
619 }
620 }
621 steps
622}
623
624impl DomBridge {
625 pub fn new() -> Self {
626 DomBridge {
627 nodes: Vec::new(),
628 listeners: Vec::new(),
629 dirty: false,
630 built: false,
631 rects: BTreeMap::new(),
632 border_widths: BTreeMap::new(),
633 computed_styles: BTreeMap::new(),
634 scroll_tops: BTreeMap::new(),
635 scroll_heights: BTreeMap::new(),
636 next_listener_id: 1,
637 mut_observers: Vec::new(),
638 mut_records: Vec::new(),
639 next_mut_obs_id: 1,
640 pointer_captures: BTreeSet::new(),
641 focused_idx: None,
642 pending_scroll_abs_y: None,
643 pending_scroll_by_y: None,
644 on_handlers: BTreeMap::new(),
645 }
646 }
647
648 pub fn set_on_handler(&mut self, node: usize, key: &str, val: Value) {
650 self.on_handlers.insert((node, key.to_string()), val);
651 }
652 pub fn get_on_handler(&self, node: usize, key: &str) -> Option<Value> {
654 self.on_handlers.get(&(node, key.to_string())).cloned()
655 }
656
657 pub fn set_pointer_capture(&mut self, idx: usize, pointer_id: i32) {
659 self.pointer_captures.insert((idx, pointer_id));
660 }
661 pub fn release_pointer_capture(&mut self, idx: usize, pointer_id: i32) {
663 self.pointer_captures.remove(&(idx, pointer_id));
664 }
665 pub fn has_pointer_capture(&self, idx: usize, pointer_id: i32) -> bool {
667 self.pointer_captures.contains(&(idx, pointer_id))
668 }
669
670 fn notify_child_list(&mut self, parent_idx: usize, added: Vec<usize>, removed: Vec<usize>) {
672 for obs in &self.mut_observers {
673 let watches = obs.child_list
674 && (obs.target_idx == parent_idx
675 || (obs.subtree && self.is_ancestor_of(obs.target_idx, parent_idx)));
676 if watches {
677 self.mut_records.push((
678 obs.id,
679 MutRec {
680 type_: "childList",
681 target_idx: parent_idx,
682 added: added.clone(),
683 removed: removed.clone(),
684 attribute_name: None,
685 old_value: None,
686 },
687 ));
688 }
689 }
690 }
691
692 fn notify_attribute(&mut self, idx: usize, attr_name: &str, old_value: Option<&str>) {
698 for obs in &self.mut_observers {
699 let watches = obs.attributes
700 && (obs.target_idx == idx
701 || (obs.subtree && self.is_ancestor_of(obs.target_idx, idx)))
702 && obs
703 .attribute_filter
704 .as_ref()
705 .is_none_or(|f| f.iter().any(|n| n == attr_name));
706 if watches {
707 self.mut_records.push((
708 obs.id,
709 MutRec {
710 type_: "attributes",
711 target_idx: idx,
712 added: Vec::new(),
713 removed: Vec::new(),
714 attribute_name: Some(String::from(attr_name)),
715 old_value: if obs.attribute_old_value {
716 Some(old_value.unwrap_or("").to_string())
717 } else {
718 None
719 },
720 },
721 ));
722 }
723 }
724 }
725
726 fn notify_character_data(&mut self, idx: usize, old_value: Option<&str>) {
732 for obs in &self.mut_observers {
733 let watches = obs.character_data
734 && (obs.target_idx == idx
735 || (obs.subtree && self.is_ancestor_of(obs.target_idx, idx)));
736 if watches {
737 self.mut_records.push((
738 obs.id,
739 MutRec {
740 type_: "characterData",
741 target_idx: idx,
742 added: Vec::new(),
743 removed: Vec::new(),
744 attribute_name: None,
745 old_value: if obs.character_data_old_value {
746 Some(old_value.unwrap_or("").to_string())
747 } else {
748 None
749 },
750 },
751 ));
752 }
753 }
754 }
755
756 pub fn set_rect(&mut self, idx: usize, x: i32, y: i32, w: i32, h: i32) {
758 self.rects.insert(idx, (x, y, w, h));
759 }
760 pub fn get_rect(&self, idx: usize) -> Option<(i32, i32, i32, i32)> {
761 self.rects.get(&idx).copied()
762 }
763 pub fn set_border_widths(&mut self, idx: usize, top: i32, right: i32, bottom: i32, left: i32) {
764 self.border_widths.insert(idx, (top, right, bottom, left));
765 }
766 pub fn get_border_widths(&self, idx: usize) -> (i32, i32, i32, i32) {
767 self.border_widths.get(&idx).copied().unwrap_or((0, 0, 0, 0))
768 }
769 pub fn clear_rects(&mut self) {
770 self.rects.clear();
771 self.border_widths.clear();
772 }
773
774 pub fn set_computed_style(&mut self, idx: usize, prop: &str, val: &str) {
776 self.computed_styles
777 .entry(idx)
778 .or_default()
779 .insert(String::from(prop), String::from(val));
780 }
781 pub fn get_computed(&self, idx: usize, prop: &str) -> String {
783 if let Some(m) = self.computed_styles.get(&idx) {
784 if let Some(v) = m.get(prop) {
785 return v.clone();
786 }
787 }
788 self.nodes
789 .get(idx)
790 .and_then(|n| n.style.get(prop))
791 .cloned()
792 .unwrap_or_default()
793 }
794 pub fn clear_computed(&mut self) {
795 self.computed_styles.clear();
796 }
797
798 pub fn build_from(&mut self, root: &Node) {
800 self.nodes.clear();
801 self.listeners.clear();
802 self.dirty = false;
803 self.built = true;
804 Self::collect(root, None, &mut self.nodes);
805 let n = self.nodes.len();
807 for i in 0..n {
808 if !self.nodes[i].is_text {
809 let t = self.collect_text(i);
810 self.nodes[i].initial_text = t;
811 }
812 }
813 {
820 let mut last_open_by_name: alloc::collections::BTreeMap<String, usize> = alloc::collections::BTreeMap::new();
821 for i in 0..n {
822 let node = &self.nodes[i];
823 if node.tag == "details" && node.attrs.contains_key("open") {
824 if let Some(name) = node.attrs.get("name").filter(|s| !s.is_empty()) {
825 last_open_by_name.insert(name.clone(), i);
826 }
827 }
828 }
829 for i in 0..n {
830 let node = &self.nodes[i];
831 if node.tag == "details" && node.attrs.contains_key("open") {
832 if let Some(name) = node.attrs.get("name").filter(|s| !s.is_empty()) {
833 if last_open_by_name.get(name) != Some(&i) {
834 self.nodes[i].attrs.remove("open");
835 }
836 }
837 }
838 }
839 }
840 }
841
842 fn collect(node: &Node, parent: Option<usize>, out: &mut Vec<DomNode>) -> usize {
844 let idx = out.len();
845 let dn = match &node.node_type {
846 NodeType::Text(t) => DomNode {
847 tag: String::new(),
848 is_text: true,
849 id: String::new(),
850 classes: Vec::new(),
851 style: BTreeMap::new(),
852 attrs: BTreeMap::new(),
853 text_override: None,
854 initial_text: t.clone(),
855 parent,
856 children: Vec::new(),
857 classes_dirty: false,
858 style_dirty: false,
859 },
860 NodeType::Element {
861 tag_name,
862 attributes,
863 classes,
864 id,
865 } => {
866 let style =
867 parse_inline_style(attributes.get("style").map(|s| s.as_str()).unwrap_or(""));
868 DomNode {
869 tag: tag_name.clone(),
870 is_text: false,
871 id: id.clone().unwrap_or_default(),
872 classes: classes.clone(),
873 style,
874 attrs: attributes.clone(),
875 text_override: None,
876 initial_text: String::new(),
877 parent,
878 children: Vec::new(),
879 classes_dirty: false,
880 style_dirty: false,
881 }
882 }
883 };
884 out.push(dn);
885 let mut kids = Vec::new();
886 for c in &node.children {
887 kids.push(Self::collect(c, Some(idx), out));
888 }
889 out[idx].children = kids;
890 idx
891 }
892
893 fn collect_text(&self, idx: usize) -> String {
895 let node = &self.nodes[idx];
896 if node.tag == "#comment" {
897 return node.text_override.clone().unwrap_or_else(|| node.initial_text.clone());
898 }
899 let mut s = String::new();
900 self.collect_text_into(idx, &mut s);
901 s
902 }
903 fn collect_text_into(&self, idx: usize, out: &mut String) {
904 let node = &self.nodes[idx];
905 if node.is_text {
906 out.push_str(node.text_override.as_deref().unwrap_or(&node.initial_text));
907 return;
908 }
909 for &c in &node.children {
910 self.collect_text_into(c, out);
911 }
912 }
913
914 pub fn build_id_index(&self) -> alloc::collections::BTreeMap<&str, usize> {
930 let mut map: alloc::collections::BTreeMap<&str, usize> =
931 alloc::collections::BTreeMap::new();
932 if !self.built {
933 return map;
934 }
935 for i in 0..self.nodes.len() {
936 let n = &self.nodes[i];
937 if n.is_text || n.id.is_empty() {
938 continue;
939 }
940 if !self.is_attached(i) {
941 continue;
942 }
943 map.entry(n.id.as_str()).or_insert(i);
944 }
945 map
946 }
947
948 pub fn get_element_by_id(&self, id: &str) -> Option<usize> {
949 if !self.built {
950 return None;
951 }
952 if let Some(rest) = id.strip_prefix("_node_idx_") {
953 if let Ok(idx) = rest.parse::<usize>() {
954 if idx < self.nodes.len() && !self.nodes[idx].is_text && self.is_attached(idx) {
955 return Some(idx);
956 }
957 }
958 }
959 (0..self.nodes.len())
960 .find(|&i| !self.nodes[i].is_text && self.nodes[i].id == id && self.is_attached(i))
961 }
962
963 pub fn is_attached(&self, idx: usize) -> bool {
966 let mut cur = idx;
967 let mut guard = 0;
968 loop {
969 if cur == 0 {
970 return true;
971 }
972 match self.nodes.get(cur).and_then(|n| n.parent) {
973 Some(p) => {
974 cur = p;
975 guard += 1;
976 if guard > self.nodes.len() {
977 return false;
978 }
979 }
980 None => return false,
981 }
982 }
983 }
984
985 pub fn query(&self, selector: &str) -> Option<usize> {
987 self.query_all(selector).into_iter().next()
988 }
989
990 pub fn query_all(&self, selector: &str) -> Vec<usize> {
995 self.query_all_scoped(selector, None)
996 }
997
998 pub fn query_all_scoped(&self, selector: &str, scope_root: Option<usize>) -> Vec<usize> {
1003 let sel = selector.trim();
1004 let mut out = Vec::new();
1005 if !self.built || sel.is_empty() {
1006 return out;
1007 }
1008 for i in 0..self.nodes.len() {
1009 if !self.is_attached(i) {
1010 continue;
1011 }
1012 if self.node_matches_scoped(i, sel, scope_root) {
1013 out.push(i);
1014 }
1015 }
1016 out
1017 }
1018
1019 pub fn set_text_content(&mut self, idx: usize, val: &str) {
1024 if idx >= self.nodes.len() {
1025 return;
1026 }
1027 if self.nodes[idx].tag == "#comment" {
1028 let old_val = self.nodes[idx]
1029 .text_override
1030 .as_deref()
1031 .unwrap_or(&self.nodes[idx].initial_text)
1032 .to_string();
1033 self.nodes[idx].text_override = Some(val.to_string());
1034 if old_val != val {
1035 self.notify_character_data(idx, Some(&old_val));
1036 }
1037 self.dirty = true;
1038 return;
1039 }
1040 let text_nodes = self.descendant_text_nodes(idx);
1041 if text_nodes.is_empty() && !self.nodes[idx].is_text {
1042 let t = self.new_text_node(val);
1045 self.nodes[idx].children.clear();
1046 self.nodes[t].parent = Some(idx);
1047 self.nodes[idx].children.push(t);
1048 self.notify_child_list(idx, alloc::vec![t], Vec::new());
1049 } else {
1050 for (k, t) in text_nodes.iter().enumerate() {
1051 let new_val = if k == 0 { val.to_string() } else { String::new() };
1052 let old_val = self.nodes[*t]
1057 .text_override
1058 .as_deref()
1059 .unwrap_or(&self.nodes[*t].initial_text)
1060 .to_string();
1061 self.nodes[*t].text_override = Some(new_val.clone());
1062 if old_val != new_val {
1063 self.notify_character_data(*t, Some(&old_val));
1064 }
1065 }
1066 }
1067 self.dirty = true;
1068 }
1069
1070 pub fn normalize_node(&mut self, idx: usize) {
1074 if idx >= self.nodes.len() || self.nodes[idx].is_text {
1075 return;
1076 }
1077 let children = self.nodes[idx].children.clone();
1078 let mut run_start: Option<usize> = None;
1079 let mut to_remove: Vec<usize> = Vec::new();
1080 for &c in &children {
1081 if self.nodes[c].is_text {
1082 match run_start {
1083 None => run_start = Some(c),
1084 Some(first) => {
1085 let more = self.text_of(c);
1086 let mut cur = self.text_of(first);
1087 cur.push_str(&more);
1088 self.nodes[first].text_override = Some(cur);
1089 to_remove.push(c);
1090 }
1091 }
1092 } else {
1093 run_start = None;
1094 self.normalize_node(c);
1095 }
1096 }
1097 for &c in &children {
1098 if self.nodes.get(c).map(|n| n.is_text).unwrap_or(false)
1099 && !to_remove.contains(&c)
1100 && self.text_of(c).is_empty()
1101 {
1102 to_remove.push(c);
1103 }
1104 }
1105 for r in to_remove {
1106 self.remove_node(r);
1107 }
1108 }
1109 fn text_of(&self, idx: usize) -> String {
1112 let n = &self.nodes[idx];
1113 n.text_override.as_deref().unwrap_or(&n.initial_text).to_string()
1114 }
1115
1116 fn new_element_node(&mut self, tag: &str) -> usize {
1119 let idx = self.nodes.len();
1120 self.nodes.push(DomNode {
1121 tag: tag.to_lowercase(),
1122 is_text: false,
1123 id: String::new(),
1124 classes: Vec::new(),
1125 style: BTreeMap::new(),
1126 attrs: BTreeMap::new(),
1127 text_override: None,
1128 initial_text: String::new(),
1129 parent: None,
1130 children: Vec::new(),
1131 classes_dirty: false,
1132 style_dirty: false,
1133 });
1134 idx
1135 }
1136
1137 fn new_text_node(&mut self, text: &str) -> usize {
1138 let idx = self.nodes.len();
1139 self.nodes.push(DomNode {
1140 tag: String::new(),
1141 is_text: true,
1142 id: String::new(),
1143 classes: Vec::new(),
1144 style: BTreeMap::new(),
1145 attrs: BTreeMap::new(),
1146 text_override: Some(text.to_string()),
1147 initial_text: text.to_string(),
1148 parent: None,
1149 children: Vec::new(),
1150 classes_dirty: false,
1151 style_dirty: false,
1152 });
1153 idx
1154 }
1155
1156 pub fn create_element(&mut self, tag: &str) -> usize {
1158 self.new_element_node(tag)
1159 }
1160
1161 pub fn create_text_node(&mut self, text: &str) -> usize {
1163 self.new_text_node(text)
1164 }
1165
1166 pub fn create_comment(&mut self, text: &str) -> usize {
1168 let idx = self.nodes.len();
1169 self.nodes.push(DomNode {
1170 tag: String::from("#comment"),
1171 is_text: false,
1172 id: String::new(),
1173 classes: Vec::new(),
1174 style: BTreeMap::new(),
1175 attrs: BTreeMap::new(),
1176 text_override: Some(text.to_string()),
1177 initial_text: text.to_string(),
1178 parent: None,
1179 children: Vec::new(),
1180 classes_dirty: false,
1181 style_dirty: false,
1182 });
1183 idx
1184 }
1185
1186 pub fn create_document_fragment(&mut self) -> usize {
1192 self.new_element_node("#fragment")
1193 }
1194
1195 pub fn template_content(&mut self, idx: usize) -> usize {
1204 if let Some(f) = self
1205 .get_attr(idx, "_content_frag")
1206 .and_then(|s| s.parse::<usize>().ok())
1207 {
1208 return f;
1209 }
1210 let f = self.create_document_fragment();
1211 let kids = core::mem::take(&mut self.nodes[idx].children);
1212 for &k in &kids {
1213 self.nodes[k].parent = Some(f);
1214 }
1215 self.nodes[f].children = kids;
1216 self.set_attr(idx, "_content_frag", &f.to_string());
1217 f
1218 }
1219
1220 fn expand_fragment(&mut self, child: usize) -> Vec<usize> {
1224 if self.nodes.get(child).map(|n| n.tag == "#fragment" || n.tag == "#document-fragment").unwrap_or(false) {
1225 let kids = core::mem::take(&mut self.nodes[child].children);
1226 for &k in &kids {
1227 self.nodes[k].parent = None;
1228 }
1229 kids
1230 } else {
1231 alloc::vec![child]
1232 }
1233 }
1234
1235 pub fn append_child(&mut self, parent: usize, child: usize) {
1238 if parent >= self.nodes.len() || child >= self.nodes.len() || parent == child {
1239 return;
1240 }
1241 let kids = self.expand_fragment(child);
1242 if kids.is_empty() {
1243 return;
1244 }
1245 for &k in &kids {
1246 self.detach(k);
1247 self.nodes[k].parent = Some(parent);
1248 self.nodes[parent].children.push(k);
1249 }
1250 self.dirty = true;
1251 self.notify_child_list(parent, kids, Vec::new());
1252 }
1253
1254 pub fn remove_child(&mut self, parent: usize, child: usize) {
1256 let p = if parent < self.nodes.len() { Some(parent) } else { self.nodes.get(child).and_then(|n| n.parent) };
1257 self.detach(child);
1258 self.dirty = true;
1259 if let Some(p) = p { self.notify_child_list(p, Vec::new(), alloc::vec![child]); }
1260 }
1261 pub fn remove_node(&mut self, idx: usize) {
1262 let p = self.nodes.get(idx).and_then(|n| n.parent);
1263 self.detach(idx);
1264 self.dirty = true;
1265 if let Some(p) = p { self.notify_child_list(p, Vec::new(), alloc::vec![idx]); }
1266 }
1267
1268 pub fn insert_before(&mut self, parent: usize, new_child: usize, ref_child: Option<usize>) {
1271 if parent >= self.nodes.len() || new_child >= self.nodes.len() || parent == new_child {
1272 return;
1273 }
1274 let kids = self.expand_fragment(new_child);
1275 if kids.is_empty() {
1276 return;
1277 }
1278 let pos = ref_child.and_then(|r| self.nodes[parent].children.iter().position(|&c| c == r));
1279 for (i, &k) in kids.iter().enumerate() {
1280 self.detach(k);
1281 self.nodes[k].parent = Some(parent);
1282 match pos {
1283 Some(p) => self.nodes[parent].children.insert(p + i, k),
1284 None => self.nodes[parent].children.push(k),
1285 }
1286 }
1287 self.dirty = true;
1288 self.notify_child_list(parent, kids, Vec::new());
1289 }
1290
1291 pub fn replace_child(&mut self, parent: usize, new_child: usize, old_child: usize) {
1293 if parent >= self.nodes.len() || new_child >= self.nodes.len() {
1294 return;
1295 }
1296 if !self.nodes[parent].children.contains(&old_child) {
1297 return;
1298 }
1299 self.detach(new_child); if let Some(p) = self.nodes[parent]
1301 .children
1302 .iter()
1303 .position(|&c| c == old_child)
1304 {
1305 self.nodes[old_child].parent = None;
1306 self.nodes[parent].children[p] = new_child;
1307 self.nodes[new_child].parent = Some(parent);
1308 self.dirty = true;
1309 self.notify_child_list(parent, alloc::vec![new_child], alloc::vec![old_child]);
1310 }
1311 }
1312
1313 pub fn clone_node(&mut self, idx: usize, deep: bool) -> usize {
1315 if idx >= self.nodes.len() {
1316 return idx;
1317 }
1318 let (is_text, tag, id, classes, style, attrs, text_override, initial_text, children) = {
1319 let n = &self.nodes[idx];
1320 (
1321 n.is_text,
1322 n.tag.clone(),
1323 n.id.clone(),
1324 n.classes.clone(),
1325 n.style.clone(),
1326 n.attrs.clone(),
1327 n.text_override.clone(),
1328 n.initial_text.clone(),
1329 n.children.clone(),
1330 )
1331 };
1332 let new_idx = self.nodes.len();
1333 self.nodes.push(DomNode {
1334 tag,
1335 is_text,
1336 id,
1337 classes,
1338 style,
1339 attrs,
1340 text_override,
1341 initial_text,
1342 parent: None,
1343 children: Vec::new(),
1344 classes_dirty: false,
1345 style_dirty: false,
1346 });
1347 if deep && !is_text {
1348 for c in children {
1349 let cc = self.clone_node(c, true);
1350 self.nodes[cc].parent = Some(new_idx);
1351 self.nodes[new_idx].children.push(cc);
1352 }
1353 }
1354 let frag_attr = self.nodes[new_idx]
1364 .attrs
1365 .get("_template_content_idx")
1366 .or_else(|| self.nodes[new_idx].attrs.get("_content_frag"))
1367 .cloned();
1368 if let Some(frag_idx) = frag_attr.and_then(|s| s.parse::<usize>().ok()) {
1369 let new_frag = self.clone_node(frag_idx, true);
1370 self.nodes[new_idx]
1371 .attrs
1372 .insert("_template_content_idx".to_string(), new_frag.to_string());
1373 }
1374 self.dirty = true;
1375 new_idx
1376 }
1377
1378 fn detach(&mut self, idx: usize) {
1380 if idx >= self.nodes.len() {
1381 return;
1382 }
1383 if let Some(p) = self.nodes[idx].parent {
1384 if let Some(pos) = self.nodes[p].children.iter().position(|&c| c == idx) {
1385 self.nodes[p].children.remove(pos);
1386 }
1387 }
1388 self.nodes[idx].parent = None;
1389 }
1390
1391 pub fn insert_adjacent_node(&mut self, idx: usize, position: &str, node: usize) {
1393 match position {
1394 "beforeend" => self.append_child(idx, node),
1395 "afterbegin" => {
1396 let first = self
1397 .nodes
1398 .get(idx)
1399 .and_then(|n| n.children.first().copied());
1400 self.insert_before(idx, node, first);
1401 }
1402 "beforebegin" => {
1403 if let Some(p) = self.nodes.get(idx).and_then(|n| n.parent) {
1404 self.insert_before(p, node, Some(idx));
1405 }
1406 }
1407 "afterend" => {
1408 if let Some(p) = self.nodes.get(idx).and_then(|n| n.parent) {
1409 let next = self.nodes.get(p).and_then(|n| {
1410 n.children
1411 .iter()
1412 .position(|&c| c == idx)
1413 .and_then(|i| n.children.get(i + 1).copied())
1414 });
1415 self.insert_before(p, node, next);
1416 }
1417 }
1418 _ => {}
1419 }
1420 }
1421
1422 pub fn insert_adjacent_html(&mut self, idx: usize, position: &str, html: &str) {
1424 let root = crate::os_lib::dom::parse_html(html);
1425 let imported: Vec<usize> = root.children.iter().map(|c| self.import_node(c)).collect();
1426 if position == "afterbegin" {
1427 let first = self
1429 .nodes
1430 .get(idx)
1431 .and_then(|n| n.children.first().copied());
1432 for node in imported {
1433 self.insert_before(idx, node, first);
1434 }
1435 } else {
1436 for node in imported {
1437 self.insert_adjacent_node(idx, position, node);
1438 }
1439 }
1440 self.dirty = true;
1441 }
1442
1443 pub fn set_inner_html(&mut self, idx: usize, html: &str) {
1449 if idx >= self.nodes.len() || self.nodes[idx].is_text {
1450 return;
1451 }
1452 let root = crate::os_lib::dom::parse_html(html);
1453 let old: Vec<usize> = self.nodes[idx].children.clone();
1455 for &c in &old {
1456 self.nodes[c].parent = None;
1457 }
1458 self.nodes[idx].children.clear();
1459 let mut new_children = Vec::new();
1461 for child in &root.children {
1462 let ci = self.import_node(child);
1463 self.nodes[ci].parent = Some(idx);
1464 new_children.push(ci);
1465 }
1466 self.nodes[idx].children = new_children.clone();
1467 self.dirty = true;
1468 self.notify_child_list(idx, new_children, old);
1469 }
1470
1471 pub fn set_outer_html(&mut self, idx: usize, html: &str) {
1473 if idx >= self.nodes.len() {
1474 return;
1475 }
1476 let parent = match self.nodes[idx].parent {
1477 Some(p) => p,
1478 None => return,
1479 };
1480 let pos = match self.nodes[parent].children.iter().position(|&c| c == idx) {
1481 Some(p) => p,
1482 None => return,
1483 };
1484 let root = crate::os_lib::dom::parse_html(html);
1485 let mut new_inserted = Vec::new();
1486 for child in &root.children {
1487 let ci = self.import_node(child);
1488 self.nodes[ci].parent = Some(parent);
1489 new_inserted.push(ci);
1490 }
1491 self.nodes[idx].parent = None;
1492 self.nodes[parent].children.remove(pos);
1493 for (i, &ci) in new_inserted.iter().enumerate() {
1494 self.nodes[parent].children.insert(pos + i, ci);
1495 }
1496 self.dirty = true;
1497 self.notify_child_list(parent, new_inserted, alloc::vec![idx]);
1498 }
1499
1500 fn import_node(&mut self, node: &Node) -> usize {
1502 match &node.node_type {
1503 NodeType::Text(t) => self.new_text_node(t),
1504 NodeType::Element {
1505 tag_name,
1506 attributes,
1507 classes,
1508 id,
1509 } => {
1510 let idx = self.new_element_node(tag_name);
1511 {
1512 let style = parse_inline_style(
1513 attributes.get("style").map(|s| s.as_str()).unwrap_or(""),
1514 );
1515 let n = &mut self.nodes[idx];
1516 n.id = id.clone().unwrap_or_default();
1517 n.classes = classes.clone();
1518 n.style = style;
1519 n.attrs = attributes.clone();
1520 }
1521 let mut kids = Vec::new();
1522 for c in &node.children {
1523 let ci = self.import_node(c);
1524 self.nodes[ci].parent = Some(idx);
1525 kids.push(ci);
1526 }
1527 self.nodes[idx].children = kids;
1528 idx
1529 }
1530 }
1531 }
1532
1533 pub fn to_dom_node(&self) -> Node {
1536 if self.nodes.is_empty() {
1537 return Node::new_text(String::new());
1538 }
1539 self.build_node(0)
1540 }
1541
1542 fn build_node(&self, idx: usize) -> Node {
1543 let n = &self.nodes[idx];
1544 if n.is_text {
1545 return Node::new_text(
1546 n.text_override
1547 .clone()
1548 .unwrap_or_else(|| n.initial_text.clone()),
1549 );
1550 }
1551 let mut attrs = n.attrs.clone();
1552 let id = if n.id.is_empty() {
1553 let gen_id = alloc::format!("_node_idx_{}", idx);
1554 attrs.insert("id".to_string(), gen_id.clone());
1555 Some(gen_id)
1556 } else {
1557 attrs.insert("id".to_string(), n.id.clone());
1558 Some(n.id.clone())
1559 };
1560 if n.classes.is_empty() {
1561 attrs.remove("class");
1562 } else {
1563 attrs.insert("class".to_string(), n.classes.join(" "));
1564 }
1565 if n.style.is_empty() {
1566 attrs.remove("style");
1567 } else {
1568 attrs.insert("style".to_string(), serialize_inline_style(&n.style));
1569 }
1570 let mut node = Node::new_element_direct(n.tag.clone(), attrs, id, n.classes.clone());
1571 for &c in &n.children {
1572 node.children.push(self.build_node(c));
1573 }
1574 node
1575 }
1576
1577 pub fn get_text_content(&self, idx: usize) -> String {
1579 if idx >= self.nodes.len() {
1580 return String::new();
1581 }
1582 self.collect_text(idx)
1583 }
1584
1585 fn descendant_text_nodes(&self, idx: usize) -> Vec<usize> {
1586 let mut out = Vec::new();
1587 self.descendant_text_into(idx, &mut out);
1588 out
1589 }
1590 fn descendant_text_into(&self, idx: usize, out: &mut Vec<usize>) {
1591 let node = &self.nodes[idx];
1592 if node.is_text {
1593 out.push(idx);
1594 return;
1595 }
1596 for &c in &node.children {
1597 self.descendant_text_into(c, out);
1598 }
1599 }
1600
1601 pub fn set_style(&mut self, idx: usize, prop: &str, val: &str) {
1606 let old_value = {
1607 let Some(n) = self.nodes.get_mut(idx) else {
1608 return;
1609 };
1610 let old_value = serialize_inline_style(&n.style);
1611 let key = css_prop_from_camel(prop);
1612 if val.is_empty() {
1613 n.style.remove(&key);
1614 } else {
1615 n.style.insert(key, val.to_string());
1616 }
1617 n.style_dirty = true;
1618 old_value
1619 };
1620 self.dirty = true;
1621 self.notify_attribute(idx, "style", Some(&old_value));
1622 }
1623
1624 pub fn get_style(&self, idx: usize, prop: &str) -> String {
1625 let raw = self
1626 .nodes
1627 .get(idx)
1628 .and_then(|n| n.style.get(&css_prop_from_camel(prop)).cloned())
1629 .unwrap_or_default();
1630 strip_style_priority(&raw).0.to_string()
1631 }
1632
1633 pub fn get_style_priority(&self, idx: usize, prop: &str) -> String {
1638 let raw = self
1639 .nodes
1640 .get(idx)
1641 .and_then(|n| n.style.get(&css_prop_from_camel(prop)).cloned())
1642 .unwrap_or_default();
1643 if strip_style_priority(&raw).1 {
1644 "important".to_string()
1645 } else {
1646 String::new()
1647 }
1648 }
1649
1650 pub fn get_css_text(&self, idx: usize) -> String {
1655 self.nodes
1656 .get(idx)
1657 .map(|n| serialize_inline_style(&n.style))
1658 .unwrap_or_default()
1659 }
1660 pub fn set_css_text(&mut self, idx: usize, text: &str) {
1664 let old_value = {
1665 let Some(n) = self.nodes.get_mut(idx) else {
1666 return;
1667 };
1668 let old_value = serialize_inline_style(&n.style);
1669 n.style = parse_inline_style(text);
1670 n.style_dirty = true;
1671 old_value
1672 };
1673 self.dirty = true;
1674 self.notify_attribute(idx, "style", Some(&old_value));
1675 }
1676
1677 pub fn get_outer_html(&self, idx: usize) -> String {
1681 let mut out = String::new();
1682 self.serialize_node_html(idx, &mut out);
1683 out
1684 }
1685 pub fn get_inner_html(&self, idx: usize) -> String {
1690 let mut out = String::new();
1691 if let Some(n) = self.nodes.get(idx) {
1692 for &c in &n.children {
1693 self.serialize_node_html(c, &mut out);
1694 }
1695 }
1696 out
1697 }
1698 fn serialize_node_html(&self, idx: usize, out: &mut String) {
1699 let Some(n) = self.nodes.get(idx) else { return };
1700 if n.is_text {
1701 out.push_str(&html_escape_text(
1702 n.text_override.as_deref().unwrap_or(&n.initial_text),
1703 ));
1704 return;
1705 }
1706 if n.tag == "#comment" {
1707 out.push_str("<!--");
1708 out.push_str(n.text_override.as_deref().unwrap_or(&n.initial_text));
1709 out.push_str("-->");
1710 return;
1711 }
1712 out.push('<');
1713 out.push_str(&n.tag);
1714 if !n.id.is_empty() {
1715 out.push_str(&format!(" id=\"{}\"", html_escape_attr(&n.id)));
1716 }
1717 if !n.classes.is_empty() {
1718 out.push_str(&format!(" class=\"{}\"", html_escape_attr(&n.classes.join(" "))));
1719 }
1720 if !n.style.is_empty() {
1721 out.push_str(&format!(
1722 " style=\"{}\"",
1723 html_escape_attr(&serialize_inline_style(&n.style))
1724 ));
1725 }
1726 for (k, v) in &n.attrs {
1734 if k == "id" || k == "class" || k == "style" || k.starts_with('_') {
1735 continue;
1736 }
1737 out.push(' ');
1738 out.push_str(k);
1739 out.push_str("=\"");
1740 out.push_str(&html_escape_attr(v));
1741 out.push('"');
1742 }
1743 out.push('>');
1744 if matches!(
1746 n.tag.as_str(),
1747 "br" | "img" | "input" | "hr" | "meta" | "link" | "area" | "base" | "col"
1748 | "embed" | "param" | "source" | "track" | "wbr"
1749 ) {
1750 return;
1751 }
1752 for &c in &n.children {
1753 self.serialize_node_html(c, out);
1754 }
1755 out.push_str("</");
1756 out.push_str(&n.tag);
1757 out.push('>');
1758 }
1759
1760 pub fn class_add(&mut self, idx: usize, cls: &str) {
1765 let old_value = {
1766 let Some(n) = self.nodes.get_mut(idx) else {
1767 return;
1768 };
1769 if n.classes.iter().any(|c| c == cls) {
1770 return;
1771 }
1772 let old_value = n.classes.join(" ");
1773 n.classes.push(cls.to_string());
1774 n.classes_dirty = true;
1775 old_value
1776 };
1777 self.dirty = true;
1778 self.notify_attribute(idx, "class", Some(&old_value));
1779 }
1780 pub fn class_remove(&mut self, idx: usize, cls: &str) {
1781 let old_value = {
1782 let Some(n) = self.nodes.get_mut(idx) else {
1783 return;
1784 };
1785 let before = n.classes.len();
1786 let old_value = n.classes.join(" ");
1787 n.classes.retain(|c| c != cls);
1788 if n.classes.len() == before {
1789 return;
1790 }
1791 n.classes_dirty = true;
1792 old_value
1793 };
1794 self.dirty = true;
1795 self.notify_attribute(idx, "class", Some(&old_value));
1796 }
1797 pub fn class_toggle(&mut self, idx: usize, cls: &str) -> bool {
1798 if self.class_contains(idx, cls) {
1799 self.class_remove(idx, cls);
1800 false
1801 } else {
1802 self.class_add(idx, cls);
1803 true
1804 }
1805 }
1806 pub fn class_contains(&self, idx: usize, cls: &str) -> bool {
1807 self.nodes
1808 .get(idx)
1809 .map(|n| n.classes.iter().any(|c| c == cls))
1810 .unwrap_or(false)
1811 }
1812
1813 fn collect_descendant_options(&self, idx: usize, out: &mut Vec<usize>) {
1823 let Some(n) = self.nodes.get(idx) else { return };
1824 for &c in &n.children {
1825 let Some(cn) = self.nodes.get(c) else { continue };
1826 if cn.tag == "option" {
1827 out.push(c);
1828 } else {
1829 self.collect_descendant_options(c, out);
1830 }
1831 }
1832 }
1833 pub fn select_options(&self, idx: usize) -> Vec<usize> {
1837 let mut out = Vec::new();
1838 self.collect_descendant_options(idx, &mut out);
1839 out
1840 }
1841 fn option_value(&self, opt_idx: usize) -> String {
1844 self.get_attr(opt_idx, "value").unwrap_or_else(|| {
1845 self.nodes
1846 .get(opt_idx)
1847 .map(|n| n.text_override.clone().unwrap_or_else(|| n.initial_text.clone()))
1848 .unwrap_or_default()
1849 })
1850 }
1851 pub fn selected_option_indices(&self, idx: usize) -> Vec<usize> {
1856 let options = self.select_options(idx);
1857 let selected: Vec<usize> = options.iter().copied().filter(|&o| self.has_attr(o, "selected")).collect();
1858 if !selected.is_empty() {
1859 selected
1860 } else {
1861 options.first().copied().into_iter().collect()
1862 }
1863 }
1864 pub fn select_effective_value(&self, idx: usize) -> String {
1869 let options = self.select_options(idx);
1870 let target = options
1871 .iter()
1872 .copied()
1873 .find(|&o| self.has_attr(o, "selected"))
1874 .or_else(|| options.first().copied());
1875 match target {
1876 Some(o) => self.option_value(o),
1877 None => String::new(),
1878 }
1879 }
1880 pub fn effective_form_value(&self, idx: usize) -> String {
1889 let tag = self.nodes.get(idx).map(|n| n.tag.as_str()).unwrap_or("");
1890 if tag == "select" {
1891 self.select_effective_value(idx)
1892 } else if tag == "textarea" {
1893 self.get_attr(idx, "value").unwrap_or_else(|| {
1894 self.nodes
1895 .get(idx)
1896 .map(|n| n.initial_text.clone())
1897 .unwrap_or_default()
1898 })
1899 } else {
1900 self.get_attr(idx, "value").unwrap_or_default()
1901 }
1902 }
1903 pub fn set_select_value(&mut self, idx: usize, val: &str) {
1906 let options = self.select_options(idx);
1907 for &o in &options {
1908 if self.option_value(o) == val {
1909 self.set_attr(o, "selected", "selected");
1910 } else {
1911 self.remove_attr(o, "selected");
1912 }
1913 }
1914 self.dirty = true;
1915 }
1916 pub fn select_selected_index(&self, idx: usize) -> i32 {
1919 let options = self.select_options(idx);
1920 match options.iter().position(|&o| self.has_attr(o, "selected")) {
1921 Some(p) => p as i32,
1922 None if !options.is_empty() => 0,
1923 None => -1,
1924 }
1925 }
1926 pub fn set_select_selected_index(&mut self, idx: usize, n: i32) {
1928 let options = self.select_options(idx);
1929 for (i, &o) in options.iter().enumerate() {
1930 if i as i32 == n {
1931 self.set_attr(o, "selected", "selected");
1932 } else {
1933 self.remove_attr(o, "selected");
1934 }
1935 }
1936 self.dirty = true;
1937 }
1938 pub fn set_select_length(&mut self, idx: usize, n: usize) {
1943 let options = self.select_options(idx);
1944 if n < options.len() {
1945 for &o in &options[n..] {
1946 if let Some(parent) = self.nodes.get(o).and_then(|node| node.parent) {
1947 self.remove_child(parent, o);
1948 }
1949 }
1950 } else {
1951 for _ in options.len()..n {
1952 let opt = self.create_element("option");
1953 self.append_child(idx, opt);
1954 }
1955 }
1956 self.dirty = true;
1957 }
1958 pub fn option_index(&self, idx: usize) -> i32 {
1964 match self.closest_tag(idx, "select") {
1965 Some(sel) => self
1966 .select_options(sel)
1967 .iter()
1968 .position(|&o| o == idx)
1969 .map(|p| p as i32)
1970 .unwrap_or(0),
1971 None => 0,
1972 }
1973 }
1974
1975 pub fn get_attr(&self, idx: usize, name: &str) -> Option<String> {
1976 let n = self.nodes.get(idx)?;
1977 match name {
1978 "id" => Some(n.id.clone()),
1979 "class" => Some(n.classes.join(" ")),
1980 _ => n.attrs.get(name).cloned(),
1981 }
1982 }
1983 pub fn set_attr(&mut self, idx: usize, name: &str, val: &str) {
1984 let old_value = self.get_attr(idx, name);
1985 if let Some(n) = self.nodes.get_mut(idx) {
1986 match name {
1987 "id" => n.id = val.to_string(),
1988 "class" => {
1989 n.classes = val.split_whitespace().map(String::from).collect();
1990 n.classes_dirty = true;
1991 }
1992 _ => {
1993 n.attrs.insert(name.to_string(), val.to_string());
1994 }
1995 }
1996 self.dirty = true;
1997 }
1998 self.notify_attribute(idx, name, old_value.as_deref());
1999 }
2000
2001 pub fn has_attr(&self, idx: usize, name: &str) -> bool {
2003 match self.nodes.get(idx) {
2004 Some(n) => match name {
2005 "id" => !n.id.is_empty(),
2006 "class" => !n.classes.is_empty(),
2007 _ => n.attrs.contains_key(name),
2008 },
2009 None => false,
2010 }
2011 }
2012
2013 pub fn remove_attr(&mut self, idx: usize, name: &str) {
2015 let old_value = self.get_attr(idx, name);
2019 if let Some(n) = self.nodes.get_mut(idx) {
2020 match name {
2021 "id" => n.id = String::new(),
2022 "class" => {
2023 n.classes.clear();
2024 n.classes_dirty = true;
2025 }
2026 _ => {
2027 n.attrs.remove(name);
2028 }
2029 }
2030 self.dirty = true;
2031 }
2032 self.notify_attribute(idx, name, old_value.as_deref());
2033 }
2034
2035 pub fn close_details_group_siblings(&mut self, idx: usize) -> alloc::vec::Vec<usize> {
2047 let name = match self.nodes.get(idx).and_then(|n| n.attrs.get("name")) {
2048 Some(n) if !n.is_empty() => n.clone(),
2049 _ => return alloc::vec::Vec::new(),
2050 };
2051 let siblings: alloc::vec::Vec<usize> = self
2052 .nodes
2053 .iter()
2054 .enumerate()
2055 .filter(|(i, n)| {
2056 *i != idx
2057 && n.tag == "details"
2058 && n.attrs.get("name").map(|s| s.as_str()) == Some(name.as_str())
2059 && n.attrs.contains_key("open")
2060 })
2061 .map(|(i, _)| i)
2062 .collect();
2063 for &i in &siblings {
2064 self.remove_attr(i, "open");
2065 }
2066 siblings
2067 }
2068
2069 pub fn uncheck_radio_group_siblings(&mut self, idx: usize) {
2070 let name = match self.nodes.get(idx).and_then(|n| n.attrs.get("name")) {
2071 Some(n) if !n.is_empty() => n.clone(),
2072 _ => return,
2073 };
2074 let siblings: Vec<usize> = self
2075 .nodes
2076 .iter()
2077 .enumerate()
2078 .filter(|(i, n)| {
2079 *i != idx
2080 && n.tag == "input"
2081 && n.attrs.get("type").map(|t| t.as_str()) == Some("radio")
2082 && n.attrs.get("name").map(|s| s.as_str()) == Some(name.as_str())
2083 })
2084 .map(|(i, _)| i)
2085 .collect();
2086 for i in siblings {
2087 self.remove_attr(i, "checked");
2088 self.set_attr(i, "_live_checked", "false");
2089 }
2090 }
2091
2092 pub fn node_matches(&self, idx: usize, selector: &str) -> bool {
2100 self.node_matches_scoped(idx, selector, None)
2101 }
2102
2103 pub fn node_matches_scoped(&self, idx: usize, selector: &str, scope_root: Option<usize>) -> bool {
2109 if self.nodes.get(idx).map(|n| n.is_text).unwrap_or(true) {
2110 return false;
2111 }
2112 for part in split_top_level_commas(selector) {
2113 let steps = build_steps(&tokenize_selector_group(part.trim()));
2114 if steps.is_empty() {
2115 continue;
2116 }
2117 let last = steps.len() - 1;
2118 if self.compound_matches(idx, &steps[last].compound, scope_root)
2119 && self.match_selector_chain(idx, &steps, last, scope_root)
2120 {
2121 return true;
2122 }
2123 }
2124 false
2125 }
2126
2127 fn compound_matches(&self, idx: usize, c: &CompoundSel, scope_root: Option<usize>) -> bool {
2129 if c.is_scope && Some(idx) != scope_root {
2130 return false;
2131 }
2132 let n = match self.nodes.get(idx) {
2133 Some(n) if !n.is_text => n,
2134 _ => return false,
2135 };
2136 if let Some(tag) = &c.tag {
2137 if &n.tag != tag {
2138 return false;
2139 }
2140 }
2141 if let Some(id) = &c.id {
2142 if &n.id != id {
2143 return false;
2144 }
2145 }
2146 if !c.classes.iter().all(|cls| n.classes.iter().any(|nc| nc == cls)) {
2147 return false;
2148 }
2149 for attr in &c.attrs {
2150 let val = match n.attrs.get(&attr.name) {
2151 Some(v) => v,
2152 None => return false,
2153 };
2154 let ok = match attr.op {
2155 AttrOp::Exists => true,
2156 AttrOp::Eq => val == &attr.value,
2157 AttrOp::Prefix => val.starts_with(attr.value.as_str()),
2158 AttrOp::Suffix => val.ends_with(attr.value.as_str()),
2159 AttrOp::Contains => !attr.value.is_empty() && val.contains(attr.value.as_str()),
2160 AttrOp::Word => val.split_whitespace().any(|w| w == attr.value),
2161 };
2162 if !ok {
2163 return false;
2164 }
2165 }
2166 for not_sel in &c.not {
2167 if self.compound_matches(idx, not_sel, scope_root) {
2168 return false;
2169 }
2170 }
2171 if !c.is_list.is_empty()
2172 && !c.is_list.iter().any(|s| self.compound_matches(idx, s, scope_root))
2173 {
2174 return false;
2175 }
2176 if !c.pos.is_empty() {
2177 let (position, count) = self.element_sibling_position(idx);
2178 for p in &c.pos {
2179 let ok = match p {
2180 PosPseudo::First => position == 1,
2181 PosPseudo::Last => position == count,
2182 PosPseudo::Only => position == 1 && count == 1,
2183 PosPseudo::Nth(f) => nth_formula_matches(*f, position),
2184 PosPseudo::NthLast(f) => nth_formula_matches(*f, count - position + 1),
2185 };
2186 if !ok {
2187 return false;
2188 }
2189 }
2190 }
2191 if !c.pos_of_type.is_empty() {
2192 let (position, count) = self.element_type_sibling_position(idx);
2193 for p in &c.pos_of_type {
2194 let ok = match p {
2195 PosPseudo::First => position == 1,
2196 PosPseudo::Last => position == count,
2197 PosPseudo::Only => position == 1 && count == 1,
2198 PosPseudo::Nth(f) => nth_formula_matches(*f, position),
2199 PosPseudo::NthLast(f) => nth_formula_matches(*f, count - position + 1),
2200 };
2201 if !ok {
2202 return false;
2203 }
2204 }
2205 }
2206 true
2207 }
2208
2209 fn element_sibling_position(&self, idx: usize) -> (usize, usize) {
2212 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2213 Some(p) => p,
2214 None => return (1, 1),
2215 };
2216 let siblings = match self.nodes.get(parent) {
2217 Some(n) => &n.children,
2218 None => return (1, 1),
2219 };
2220 let elements: Vec<usize> = siblings
2221 .iter()
2222 .filter(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true))
2223 .copied()
2224 .collect();
2225 let count = elements.len();
2226 let position = elements.iter().position(|&c| c == idx).map(|p| p + 1).unwrap_or(0);
2227 (position, count)
2228 }
2229
2230 fn element_type_sibling_position(&self, idx: usize) -> (usize, usize) {
2233 let tag = match self.nodes.get(idx) {
2234 Some(n) => n.tag.clone(),
2235 None => return (1, 1),
2236 };
2237 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2238 Some(p) => p,
2239 None => return (1, 1),
2240 };
2241 let siblings = match self.nodes.get(parent) {
2242 Some(n) => &n.children,
2243 None => return (1, 1),
2244 };
2245 let same_type: Vec<usize> = siblings
2246 .iter()
2247 .filter(|&&c| self.nodes.get(c).map(|n| !n.is_text && n.tag == tag).unwrap_or(false))
2248 .copied()
2249 .collect();
2250 let count = same_type.len();
2251 let position = same_type.iter().position(|&c| c == idx).map(|p| p + 1).unwrap_or(0);
2252 (position, count)
2253 }
2254
2255 fn match_selector_chain(
2258 &self,
2259 idx: usize,
2260 steps: &[SelStep],
2261 step_i: usize,
2262 scope_root: Option<usize>,
2263 ) -> bool {
2264 if step_i == 0 {
2265 return true;
2266 }
2267 let combinator = match steps[step_i].combinator {
2268 Some(c) => c,
2269 None => return true,
2270 };
2271 match combinator {
2272 Combinator::Descendant => {
2273 let mut cur = self.nodes.get(idx).and_then(|n| n.parent);
2274 let mut guard = 0;
2275 while let Some(p) = cur {
2276 if self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2277 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2278 {
2279 return true;
2280 }
2281 cur = self.nodes.get(p).and_then(|n| n.parent);
2282 guard += 1;
2283 if guard > self.nodes.len() {
2284 break;
2285 }
2286 }
2287 false
2288 }
2289 Combinator::Child => match self.nodes.get(idx).and_then(|n| n.parent) {
2290 Some(p) => {
2291 self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2292 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2293 }
2294 None => false,
2295 },
2296 Combinator::Adjacent => match self.prev_element_sibling(idx) {
2297 Some(p) => {
2298 self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2299 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2300 }
2301 None => false,
2302 },
2303 Combinator::General => {
2304 for p in self.preceding_element_siblings(idx) {
2305 if self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2306 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2307 {
2308 return true;
2309 }
2310 }
2311 false
2312 }
2313 }
2314 }
2315
2316 fn prev_element_sibling(&self, idx: usize) -> Option<usize> {
2318 let parent = self.nodes.get(idx)?.parent?;
2319 let siblings = &self.nodes.get(parent)?.children;
2320 let pos = siblings.iter().position(|&c| c == idx)?;
2321 siblings[..pos].iter().rev().find(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true)).copied()
2322 }
2323
2324 fn preceding_element_siblings(&self, idx: usize) -> Vec<usize> {
2326 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2327 Some(p) => p,
2328 None => return Vec::new(),
2329 };
2330 let siblings = match self.nodes.get(parent) {
2331 Some(n) => &n.children,
2332 None => return Vec::new(),
2333 };
2334 let pos = match siblings.iter().position(|&c| c == idx) {
2335 Some(p) => p,
2336 None => return Vec::new(),
2337 };
2338 siblings[..pos]
2339 .iter()
2340 .filter(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true))
2341 .copied()
2342 .collect()
2343 }
2344
2345 pub fn is_ancestor_of(&self, anc: usize, idx: usize) -> bool {
2347 let mut cur = idx;
2348 let mut guard = 0;
2349 while let Some(p) = self.nodes.get(cur).and_then(|n| n.parent) {
2350 if p == anc {
2351 return true;
2352 }
2353 cur = p;
2354 guard += 1;
2355 if guard > self.nodes.len() {
2356 break;
2357 }
2358 }
2359 false
2360 }
2361
2362 pub fn document_position(&self, this_idx: usize, other_idx: usize) -> u32 {
2370 const DISCONNECTED: u32 = 0x01;
2371 const PRECEDING: u32 = 0x02;
2372 const FOLLOWING: u32 = 0x04;
2373 const CONTAINS: u32 = 0x08;
2374 const CONTAINED_BY: u32 = 0x10;
2375 const IMPLEMENTATION_SPECIFIC: u32 = 0x20;
2376
2377 if this_idx == other_idx {
2378 return 0;
2379 }
2380 if self.is_ancestor_of(other_idx, this_idx) {
2381 return CONTAINS | PRECEDING;
2383 }
2384 if self.is_ancestor_of(this_idx, other_idx) {
2385 return CONTAINED_BY | FOLLOWING;
2387 }
2388 let chain_of = |start: usize| -> Vec<usize> {
2389 let mut chain = alloc::vec![start];
2390 let mut cur = start;
2391 let mut guard = 0;
2392 while let Some(p) = self.nodes.get(cur).and_then(|n| n.parent) {
2393 chain.push(p);
2394 cur = p;
2395 guard += 1;
2396 if guard > self.nodes.len() {
2397 break;
2398 }
2399 }
2400 chain.reverse(); chain
2402 };
2403 let chain_a = chain_of(this_idx);
2404 let chain_b = chain_of(other_idx);
2405 if chain_a.first() != chain_b.first() {
2406 let order = if this_idx < other_idx { FOLLOWING } else { PRECEDING };
2408 return DISCONNECTED | IMPLEMENTATION_SPECIFIC | order;
2409 }
2410 let mut i = 0;
2411 while i < chain_a.len() && i < chain_b.len() && chain_a[i] == chain_b[i] {
2412 i += 1;
2413 }
2414 if i == 0 || i >= chain_a.len() || i >= chain_b.len() {
2415 return DISCONNECTED | IMPLEMENTATION_SPECIFIC;
2417 }
2418 let lca = chain_a[i - 1];
2419 let child_a = chain_a[i];
2420 let child_b = chain_b[i];
2421 let siblings = match self.nodes.get(lca) {
2422 Some(n) => &n.children,
2423 None => return DISCONNECTED | IMPLEMENTATION_SPECIFIC,
2424 };
2425 let pos_a = siblings.iter().position(|&c| c == child_a);
2426 let pos_b = siblings.iter().position(|&c| c == child_b);
2427 match (pos_a, pos_b) {
2428 (Some(pa), Some(pb)) if pa < pb => FOLLOWING,
2429 (Some(pa), Some(pb)) if pa > pb => PRECEDING,
2430 _ => DISCONNECTED | IMPLEMENTATION_SPECIFIC,
2431 }
2432 }
2433
2434 pub fn offset_parent(&self, idx: usize) -> Option<usize> {
2439 let mut cur = self.nodes.get(idx).and_then(|n| n.parent);
2440 let mut body_idx = None;
2441 let mut guard = 0;
2442 while let Some(c) = cur {
2443 if self.nodes.get(c).map(|n| !n.is_text && n.tag == "body").unwrap_or(false) {
2444 body_idx = Some(c);
2445 }
2446 let pos = self.get_computed(c, "position");
2447 if pos == "absolute" || pos == "relative" || pos == "fixed" || pos == "sticky" {
2448 return Some(c);
2449 }
2450 cur = self.nodes.get(c).and_then(|n| n.parent);
2451 guard += 1;
2452 if guard > self.nodes.len() {
2453 break;
2454 }
2455 }
2456 body_idx
2457 }
2458
2459 pub fn closest_tag(&self, idx: usize, tag: &str) -> Option<usize> {
2460 let mut cur = Some(idx);
2461 let mut guard = 0;
2462 while let Some(c) = cur {
2463 if self
2464 .nodes
2465 .get(c)
2466 .map(|n| !n.is_text && n.tag == tag)
2467 .unwrap_or(false)
2468 {
2469 return Some(c);
2470 }
2471 cur = self.nodes.get(c).and_then(|n| n.parent);
2472 guard += 1;
2473 if guard > self.nodes.len() {
2474 break;
2475 }
2476 }
2477 None
2478 }
2479
2480 pub fn associated_form(&self, idx: usize) -> Option<usize> {
2489 if let Some(form_id) = self.get_attr(idx, "form") {
2490 if let Some(fi) = self.get_element_by_id(&form_id) {
2491 if self.nodes.get(fi).map(|n| n.tag == "form").unwrap_or(false) {
2492 return Some(fi);
2493 }
2494 }
2495 }
2496 self.closest_tag(idx, "form")
2497 }
2498
2499 pub fn form_associated_controls(&self, form_idx: usize, tags: &[&str]) -> Vec<usize> {
2507 let mut out = self.descendants_by_tags(form_idx, tags);
2508 if let Some(form_id) = self.get_attr(form_idx, "id") {
2509 if !form_id.is_empty() {
2510 for (i, n) in self.nodes.iter().enumerate() {
2511 if n.is_text || out.contains(&i) {
2512 continue;
2513 }
2514 if !tags.contains(&n.tag.as_str()) {
2515 continue;
2516 }
2517 if n.attrs.get("form").map(|v| v == &form_id).unwrap_or(false) {
2518 out.push(i);
2519 }
2520 }
2521 }
2522 }
2523 out
2524 }
2525
2526 pub fn first_by_tag(&self, tag: &str) -> Option<usize> {
2528 (0..self.nodes.len())
2529 .find(|&i| !self.nodes[i].is_text && self.nodes[i].tag == tag && self.is_attached(i))
2530 }
2531
2532 pub fn element_children(&self, idx: usize) -> Vec<usize> {
2534 self.nodes
2535 .get(idx)
2536 .map(|n| {
2537 n.children
2538 .iter()
2539 .copied()
2540 .filter(|c| self.nodes.get(*c).map(|cn| !cn.is_text).unwrap_or(false))
2541 .collect()
2542 })
2543 .unwrap_or_default()
2544 }
2545
2546 pub fn descendants_by_tag_name(&self, root: usize, tag: &str) -> Vec<usize> {
2567 let want_all = tag == "*";
2568 let lower = tag.to_ascii_lowercase();
2569 let mut out = Vec::new();
2570 let mut stack: Vec<usize> = self.element_children(root);
2571 let mut i = 0;
2572 while i < stack.len() {
2573 let idx = stack[i];
2574 i += 1;
2575 if let Some(n) = self.nodes.get(idx) {
2576 if !n.is_text && (want_all || n.tag.eq_ignore_ascii_case(&lower)) {
2577 out.push(idx);
2578 }
2579 for &c in &n.children {
2580 if self.nodes.get(c).map(|cn| !cn.is_text).unwrap_or(false) {
2581 stack.push(c);
2582 }
2583 }
2584 }
2585 }
2586 out
2587 }
2588
2589 pub fn descendants_by_tags(&self, root: usize, tags: &[&str]) -> Vec<usize> {
2590 let mut out = Vec::new();
2591 let mut stack: Vec<usize> = self.element_children(root);
2592 let mut i = 0;
2594 while i < stack.len() {
2595 let idx = stack[i];
2596 i += 1;
2597 if let Some(n) = self.nodes.get(idx) {
2598 if !n.is_text && tags.iter().any(|t| n.tag == *t) {
2599 out.push(idx);
2600 }
2601 for &c in &n.children {
2603 if self.nodes.get(c).map(|cn| !cn.is_text).unwrap_or(false) {
2604 stack.push(c);
2605 }
2606 }
2607 }
2608 }
2609 out
2610 }
2611
2612 pub fn is_disabled(&self, idx: usize) -> bool {
2621 let n = match self.nodes.get(idx) {
2622 Some(n) => n,
2623 None => return false,
2624 };
2625 if n.attrs.contains_key("disabled") {
2626 return true;
2627 }
2628 let mut cur = n.parent;
2629 while let Some(i) = cur {
2630 let anc = match self.nodes.get(i) {
2631 Some(a) => a,
2632 None => break,
2633 };
2634 if anc.tag == "fieldset" && anc.attrs.contains_key("disabled") {
2635 return true;
2636 }
2637 cur = anc.parent;
2638 }
2639 false
2640 }
2641
2642 pub fn validity_flags(&self, idx: usize, value: &str) -> ValidityFlags {
2647 let mut f = ValidityFlags::default();
2648 let n = match self.nodes.get(idx) {
2649 Some(n) => n,
2650 None => return f,
2651 };
2652 if n.is_text
2660 || self.is_disabled(idx)
2661 || !matches!(n.tag.as_str(), "button" | "input" | "select" | "textarea")
2662 {
2663 return f;
2664 }
2665 let input_type = n
2666 .attrs
2667 .get("type")
2668 .map(|s| s.trim().to_lowercase())
2669 .unwrap_or_default();
2670 if matches!(
2671 input_type.as_str(),
2672 "submit" | "button" | "reset" | "hidden" | "image"
2673 ) {
2674 return f;
2675 }
2676 f.custom_error = n
2677 .attrs
2678 .get("_custom_validity")
2679 .map(|s| !s.is_empty())
2680 .unwrap_or(false);
2681 let trimmed = value.trim();
2682 f.value_missing = n.attrs.contains_key("required") && !self.required_satisfied(n, &input_type, trimmed);
2685 if matches!(input_type.as_str(), "checkbox" | "radio") || value.is_empty() {
2686 return f; }
2688 let char_len = value.chars().count();
2689 if let Some(ml) = n.attrs.get("minlength").and_then(|v| v.trim().parse::<usize>().ok()) {
2690 f.too_short = char_len < ml;
2691 }
2692 if let Some(ml) = n.attrs.get("maxlength").and_then(|v| v.trim().parse::<usize>().ok()) {
2693 f.too_long = char_len > ml;
2694 }
2695 match input_type.as_str() {
2696 "email" => f.type_mismatch = !Self::is_valid_email_value(n, value),
2697 "url" => {
2698 f.type_mismatch = !(value.starts_with("http://") || value.starts_with("https://"))
2699 }
2700 "date" | "month" | "datetime-local" => match super::builtins::input_value_as_date_ms(&input_type, value) {
2718 Some(ms) => {
2719 if let Some(min) = n
2720 .attrs
2721 .get("min")
2722 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2723 {
2724 f.range_underflow = ms < min;
2725 }
2726 if let Some(max) = n
2727 .attrs
2728 .get("max")
2729 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2730 {
2731 f.range_overflow = ms > max;
2732 }
2733 }
2734 None => f.type_mismatch = true,
2735 },
2736 "number" | "range" => match value.trim().parse::<f64>() {
2743 Ok(num) => {
2744 if let Some(min) = n.attrs.get("min").and_then(|v| v.trim().parse::<f64>().ok()) {
2745 f.range_underflow = num < min;
2746 }
2747 if let Some(max) = n.attrs.get("max").and_then(|v| v.trim().parse::<f64>().ok()) {
2748 f.range_overflow = num > max;
2749 }
2750 let step_attr = n.attrs.get("step").map(|s| s.trim());
2751 if step_attr != Some("any") {
2752 let step = step_attr
2753 .and_then(|s| s.parse::<f64>().ok())
2754 .filter(|s| *s > 0.0)
2755 .unwrap_or(1.0);
2756 let base = n
2757 .attrs
2758 .get("min")
2759 .and_then(|v| v.trim().parse::<f64>().ok())
2760 .unwrap_or(0.0);
2761 let steps = (num - base) / step;
2762 f.step_mismatch = libm::fabs(steps - libm::round(steps)) > 1e-9;
2763 }
2764 }
2765 Err(_) => f.bad_input = true,
2766 },
2767 _ => {}
2768 }
2769 if let Some(pat) = n.attrs.get("pattern") {
2770 if !pat.is_empty() {
2771 f.pattern_mismatch = !Self::pattern_matches(pat, value);
2772 }
2773 }
2774 f
2775 }
2776
2777 fn required_satisfied(&self, n: &DomNode, input_type: &str, trimmed_value: &str) -> bool {
2782 let is_checked = |node: &DomNode| {
2783 if let Some(lc) = node.attrs.get("_live_checked") {
2784 lc == "true"
2785 } else {
2786 node.attrs.contains_key("checked")
2787 }
2788 };
2789 match input_type {
2790 "checkbox" => is_checked(n),
2791 "radio" => {
2792 let name = n.attrs.get("name").cloned().unwrap_or_default();
2793 if name.is_empty() {
2794 is_checked(n)
2795 } else {
2796 self.nodes.iter().any(|other| {
2797 other.tag == "input"
2798 && other
2799 .attrs
2800 .get("type")
2801 .map(|t| t.eq_ignore_ascii_case("radio"))
2802 .unwrap_or(false)
2803 && other.attrs.get("name").map(|nm| nm == &name).unwrap_or(false)
2804 && is_checked(other)
2805 })
2806 }
2807 }
2808 _ => !trimmed_value.is_empty(),
2809 }
2810 }
2811
2812 pub fn validate_field(&self, idx: usize, value: &str) -> Option<String> {
2818 let n = self.nodes.get(idx)?;
2819 if n.is_text {
2820 return None;
2821 }
2822 if self.is_disabled(idx) {
2825 return None;
2826 }
2827 if !matches!(n.tag.as_str(), "button" | "input" | "select" | "textarea") {
2836 return None;
2837 }
2838 let input_type = n
2839 .attrs
2840 .get("type")
2841 .map(|s| s.trim().to_lowercase())
2842 .unwrap_or_default();
2843 if matches!(
2844 input_type.as_str(),
2845 "submit" | "button" | "reset" | "hidden" | "image"
2846 ) {
2847 return None;
2848 }
2849 if let Some(cv) = n.attrs.get("_custom_validity") {
2851 if !cv.is_empty() {
2852 return Some(cv.clone());
2853 }
2854 }
2855 let trimmed = value.trim();
2856 if n.attrs.contains_key("required") && !self.required_satisfied(n, &input_type, trimmed) {
2866 return Some(match input_type.as_str() {
2867 "checkbox" => String::from("このフィールドをオンにしてください。"),
2868 "radio" => String::from("いずれかを選択してください。"),
2869 _ => String::from("このフィールドを入力してください。"),
2870 });
2871 }
2872 if matches!(input_type.as_str(), "checkbox" | "radio") {
2873 return None;
2874 }
2875 if value.is_empty() {
2877 return None;
2878 }
2879 let char_len = value.chars().count();
2881 if let Some(ml) = n
2882 .attrs
2883 .get("minlength")
2884 .and_then(|v| v.trim().parse::<usize>().ok())
2885 {
2886 if char_len < ml {
2887 return Some(alloc::format!(
2888 "{}文字以上で入力してください(現在{}文字)。",
2889 ml,
2890 char_len
2891 ));
2892 }
2893 }
2894 if let Some(ml) = n
2895 .attrs
2896 .get("maxlength")
2897 .and_then(|v| v.trim().parse::<usize>().ok())
2898 {
2899 if char_len > ml {
2900 return Some(alloc::format!(
2901 "{}文字以下で入力してください(現在{}文字)。",
2902 ml,
2903 char_len
2904 ));
2905 }
2906 }
2907 match input_type.as_str() {
2909 "email" if !Self::is_valid_email_value(n, value) => {
2910 return Some(String::from("メールアドレスを入力してください。"));
2911 }
2912 "url" if !(value.starts_with("http://") || value.starts_with("https://")) => {
2913 return Some(String::from("URL を入力してください。"));
2914 }
2915 "date" | "month" | "datetime-local" => match super::builtins::input_value_as_date_ms(&input_type, value) {
2923 Some(ms) => {
2924 if let Some(min) = n
2925 .attrs
2926 .get("min")
2927 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2928 {
2929 if ms < min {
2930 return Some(alloc::format!(
2931 "{}以降の日付を入力してください。",
2932 n.attrs.get("min").cloned().unwrap_or_default()
2933 ));
2934 }
2935 }
2936 if let Some(max) = n
2937 .attrs
2938 .get("max")
2939 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2940 {
2941 if ms > max {
2942 return Some(alloc::format!(
2943 "{}以前の日付を入力してください。",
2944 n.attrs.get("max").cloned().unwrap_or_default()
2945 ));
2946 }
2947 }
2948 }
2949 None => return Some(String::from("正しい日付を入力してください。")),
2950 },
2951 "number" | "range" => match value.trim().parse::<f64>() {
2955 Ok(num) => {
2956 if let Some(min) = n
2957 .attrs
2958 .get("min")
2959 .and_then(|v| v.trim().parse::<f64>().ok())
2960 {
2961 if num < min {
2962 return Some(alloc::format!("{}以上の値を入力してください。", min));
2963 }
2964 }
2965 if let Some(max) = n
2966 .attrs
2967 .get("max")
2968 .and_then(|v| v.trim().parse::<f64>().ok())
2969 {
2970 if num > max {
2971 return Some(alloc::format!("{}以下の値を入力してください。", max));
2972 }
2973 }
2974 let step_attr = n.attrs.get("step").map(|s| s.trim());
2978 if step_attr != Some("any") {
2979 let step = step_attr
2980 .and_then(|s| s.parse::<f64>().ok())
2981 .filter(|s| *s > 0.0)
2982 .unwrap_or(1.0);
2983 let base = n
2984 .attrs
2985 .get("min")
2986 .and_then(|v| v.trim().parse::<f64>().ok())
2987 .unwrap_or(0.0);
2988 let steps = (num - base) / step;
2989 if libm::fabs(steps - libm::round(steps)) > 1e-9 {
2990 return Some(alloc::format!(
2991 "{} の倍数(基準値 {})で入力してください。",
2992 step,
2993 base
2994 ));
2995 }
2996 }
2997 }
2998 Err(_) => return Some(String::from("数値を入力してください。")),
2999 },
3000 _ => {}
3001 }
3002 if let Some(pat) = n.attrs.get("pattern") {
3004 if !pat.is_empty() && !Self::pattern_matches(pat, value) {
3005 return Some(String::from("要求された形式で入力してください。"));
3006 }
3007 }
3008 None
3009 }
3010
3011 fn is_valid_email(s: &str) -> bool {
3013 let (local, domain) = match s.split_once('@') {
3014 Some(v) => v,
3015 None => return false,
3016 };
3017 if local.is_empty() || domain.is_empty() {
3018 return false;
3019 }
3020 if domain.contains('@') {
3021 return false;
3022 }
3023 match domain.split_once('.') {
3025 Some((a, b)) => !a.is_empty() && !b.is_empty(),
3026 None => false,
3027 }
3028 }
3029
3030 fn is_valid_email_value(n: &DomNode, value: &str) -> bool {
3038 if n.attrs.contains_key("multiple") {
3039 value.split(',').all(|part| Self::is_valid_email(part.trim()))
3040 } else {
3041 Self::is_valid_email(value)
3042 }
3043 }
3044
3045 fn pattern_matches(pat: &str, val: &str) -> bool {
3052 let anchored = format!("^(?:{})$", pat);
3053 let re = super::regex::Regex::new(&anchored, "");
3054 let chars: alloc::vec::Vec<char> = val.chars().collect();
3055 re.find_at(&chars, 0).is_some()
3056 }
3057
3058 pub fn attr_names(&self, idx: usize) -> Vec<String> {
3060 let mut out = Vec::new();
3061 if let Some(n) = self.nodes.get(idx) {
3062 if !n.id.is_empty() {
3063 out.push(String::from("id"));
3064 }
3065 if !n.classes.is_empty() {
3066 out.push(String::from("class"));
3067 }
3068 for k in n.attrs.keys() {
3073 if k != "id" && k != "class" && !k.starts_with('_') {
3074 out.push(k.clone());
3075 }
3076 }
3077 }
3078 out
3079 }
3080
3081 pub fn add_listener(&mut self, node: usize, event: &str, func: Value) {
3082 self.add_listener_opts(node, event, func, false, false);
3083 }
3084
3085 pub fn add_listener_opts(
3088 &mut self,
3089 node: usize,
3090 event: &str,
3091 func: Value,
3092 capture: bool,
3093 once: bool,
3094 ) -> u64 {
3095 self.add_listener_opts_signal(node, event, func, capture, once, None)
3096 }
3097
3098 pub fn add_listener_opts_signal(
3101 &mut self,
3102 node: usize,
3103 event: &str,
3104 func: Value,
3105 capture: bool,
3106 once: bool,
3107 signal: Option<Value>,
3108 ) -> u64 {
3109 if signal.as_ref().is_some_and(is_signal_aborted) {
3110 return 0;
3111 }
3112 if self.listeners.iter().any(|l| {
3114 l.node == node && l.event == event && l.capture == capture && l.func.same_ref(&func)
3115 }) {
3116 return 0;
3117 }
3118 let id = self.next_listener_id;
3119 self.next_listener_id += 1;
3120 self.listeners.push(Listener {
3121 node,
3122 event: event.to_string(),
3123 func,
3124 capture,
3125 once,
3126 id,
3127 signal,
3128 });
3129 id
3130 }
3131
3132 pub fn remove_listener(&mut self, node: usize, event: &str, func: &Value, capture: bool) {
3134 self.listeners.retain(|l| {
3135 !(l.node == node && l.event == event && l.capture == capture && l.func.same_ref(func))
3136 });
3137 }
3138
3139 pub fn remove_listener_by_id(&mut self, id: u64) {
3141 self.listeners.retain(|l| l.id != id);
3142 }
3143
3144 pub fn has_listener(&self, event: &str) -> bool {
3146 self.listeners.iter().any(|l| l.event == event)
3147 }
3148
3149 pub fn listeners_for(&self, node: usize, event: &str) -> Vec<Value> {
3153 self.listeners
3154 .iter()
3155 .filter(|l| l.node == node && l.event == event && !l.capture && !is_listener_aborted(l))
3156 .map(|l| l.func.clone())
3157 .collect()
3158 }
3159
3160 pub fn listeners_phase(
3164 &self,
3165 node: usize,
3166 event: &str,
3167 want_capture: bool,
3168 ) -> Vec<(Value, bool, u64)> {
3169 self.listeners
3170 .iter()
3171 .filter(|l| l.node == node && l.event == event && l.capture == want_capture && !is_listener_aborted(l))
3172 .map(|l| (l.func.clone(), l.once, l.id))
3173 .collect()
3174 }
3175
3176 pub fn has_listener_on(&self, node: usize, event: &str) -> bool {
3178 self.listeners
3179 .iter()
3180 .any(|l| l.node == node && l.event == event)
3181 }
3182
3183 pub fn apply_overrides(&self, root: &mut Node) {
3185 let mut counter = 0usize;
3186 self.apply_node(root, &mut counter);
3187 }
3188 fn apply_node(&self, node: &mut Node, counter: &mut usize) {
3189 let idx = *counter;
3190 *counter += 1;
3191 if let Some(dn) = self.nodes.get(idx) {
3192 match &mut node.node_type {
3193 NodeType::Text(t) => {
3194 if let Some(ov) = &dn.text_override {
3195 *t = ov.clone();
3196 }
3197 }
3198 NodeType::Element {
3199 attributes,
3200 classes,
3201 ..
3202 } => {
3203 if dn.classes_dirty {
3204 *classes = dn.classes.clone();
3205 attributes.insert("class".to_string(), dn.classes.join(" "));
3206 }
3207 if dn.style_dirty {
3208 attributes.insert("style".to_string(), serialize_inline_style(&dn.style));
3209 }
3210 }
3211 }
3212 }
3213 for c in &mut node.children {
3214 self.apply_node(c, counter);
3215 }
3216 }
3217}
3218
3219fn html_escape_text(s: &str) -> String {
3221 s.replace('&', "&").replace('<', "<").replace('>', ">")
3222}
3223fn html_escape_attr(s: &str) -> String {
3225 s.replace('&', "&").replace('"', """)
3226}
3227
3228fn strip_style_priority(raw: &str) -> (&str, bool) {
3234 let trimmed = raw.trim_end();
3235 if let Some(pos) = trimmed.to_ascii_lowercase().rfind("!important") {
3236 if trimmed.get(pos..).unwrap_or("").eq_ignore_ascii_case("!important") {
3237 let before = trimmed.get(..pos).unwrap_or("").trim_end();
3238 return (before, true);
3239 }
3240 }
3241 (raw, false)
3242}
3243
3244fn parse_inline_style(s: &str) -> BTreeMap<String, String> {
3246 let mut m = BTreeMap::new();
3247 for decl in s.split(';') {
3248 let decl = decl.trim();
3249 if decl.is_empty() {
3250 continue;
3251 }
3252 if let Some(colon) = decl.find(':') {
3253 let k = decl.get(..colon).unwrap_or("").trim().to_lowercase();
3254 let v = decl.get(colon + 1..).unwrap_or("").trim().to_string();
3255 if !k.is_empty() {
3256 m.insert(k, v);
3257 }
3258 }
3259 }
3260 m
3261}
3262
3263fn serialize_inline_style(m: &BTreeMap<String, String>) -> String {
3264 let mut parts = Vec::new();
3265 for (k, v) in m {
3266 parts.push(format!("{}: {}", k, v));
3267 }
3268 parts.join("; ")
3269}
3270
3271fn css_prop_from_camel(prop: &str) -> String {
3273 if prop.contains('-') || !prop.chars().any(|c| c.is_ascii_uppercase()) {
3274 return prop.to_lowercase();
3275 }
3276 let mut out = String::new();
3277 for c in prop.chars() {
3278 if c.is_ascii_uppercase() {
3279 out.push('-');
3280 out.push(c.to_ascii_lowercase());
3281 } else {
3282 out.push(c);
3283 }
3284 }
3285 out
3286}