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};
21use crate::os_lib::layout::intersection_observer::RootMargin;
22use crate::os_lib::layout::resize_observer::ResizeObserverBoxOptions;
23
24pub struct DomNode {
26 pub tag: String,
28 pub is_text: bool,
29 pub id: String,
30 pub classes: Vec<String>,
31 pub style: BTreeMap<String, String>,
33 pub attrs: BTreeMap<String, String>,
35 pub text_override: Option<String>,
37 pub initial_text: String,
39 pub parent: Option<usize>,
40 pub children: Vec<usize>,
41 pub classes_dirty: bool,
43 pub style_dirty: bool,
45}
46
47#[derive(Clone)]
49pub struct ObservedTarget {
50 pub target: Value,
51 pub target_idx: usize,
52 pub prev_ratio: Option<f64>,
53 pub prev_is_intersecting: bool,
54}
55
56#[derive(Clone)]
58pub struct IntersectionObserverReg {
59 pub id: u32,
60 pub callback: Value,
61 pub root: Value,
62 pub root_margin: RootMargin,
63 pub thresholds: Vec<f64>,
64 pub targets: Vec<ObservedTarget>,
65 pub queued_records: Vec<Value>,
66}
67
68#[derive(Clone)]
70pub struct ObservedResizeTarget {
71 pub target: Value,
72 pub target_idx: usize,
73 pub box_option: ResizeObserverBoxOptions,
74 pub last_inline_size: f64,
75 pub last_block_size: f64,
76}
77
78#[derive(Clone)]
80pub struct ResizeObserverReg {
81 pub id: u32,
82 pub callback: Value,
83 pub targets: Vec<ObservedResizeTarget>,
84}
85
86pub struct MutObsReg {
88 pub id: u32,
89 pub target_idx: usize,
90 pub child_list: bool,
91 pub attributes: bool,
92 pub subtree: bool,
93 pub callback: Value,
94 pub observer: Value,
95 pub attribute_old_value: bool,
98 pub attribute_filter: Option<Vec<String>>,
101 pub character_data: bool,
108 pub character_data_old_value: bool,
112}
113
114pub struct MutRec {
116 pub type_: &'static str, pub target_idx: usize,
118 pub added: Vec<usize>,
119 pub removed: Vec<usize>,
120 pub attribute_name: Option<String>,
121 pub old_value: Option<String>,
123}
124
125pub struct Listener {
127 pub node: usize,
128 pub event: String,
129 pub func: Value,
130 pub capture: bool,
132 pub once: bool,
134 pub id: u64,
136 pub signal: Option<Value>,
143}
144
145pub fn is_signal_aborted(signal: &Value) -> bool {
147 matches!(signal, Value::Object(o) if matches!(o.borrow().props.get("aborted"), Some(Value::Bool(true))))
148}
149
150fn is_listener_aborted(l: &Listener) -> bool {
151 l.signal.as_ref().is_some_and(is_signal_aborted)
152}
153
154pub struct DomBridge {
156 pub nodes: Vec<DomNode>,
157 pub listeners: Vec<Listener>,
158 pub dirty: bool,
160 pub structural_dirty: bool,
162 pub pending_style_updates: Vec<(usize, String, String)>,
164 pub js_created_scripts: alloc::collections::BTreeSet<usize>,
166 pub pending_dynamic_scripts: Vec<usize>,
170 pub expandos: BTreeMap<usize, BTreeMap<String, Value>>,
175 pub node_protos: BTreeMap<usize, super::value::ObjRef>,
178 pub ce_queue: Vec<CeEvent>,
181 pub built: bool,
183 pub rects: BTreeMap<usize, (i32, i32, i32, i32)>,
186 pub border_widths: BTreeMap<usize, (i32, i32, i32, i32)>,
188 pub computed_styles: BTreeMap<usize, BTreeMap<String, String>>,
191 pub scroll_tops: BTreeMap<usize, i32>,
194 pub scroll_lefts: BTreeMap<usize, i32>,
197 pub scroll_heights: BTreeMap<usize, i32>,
200 pub scroll_widths: BTreeMap<usize, i32>,
203 pub next_listener_id: u64,
205 pub mut_observers: Vec<MutObsReg>,
207 pub mut_records: Vec<(u32, MutRec)>,
209 pub next_mut_obs_id: u32,
211 pub io_observers: Vec<IntersectionObserverReg>,
213 pub next_io_obs_id: u32,
215 pub ro_observers: Vec<ResizeObserverReg>,
217 pub next_ro_obs_id: u32,
219 pub pointer_captures: BTreeSet<(usize, i32)>,
226 pub focused_idx: Option<usize>,
235 pub pending_scroll_abs_y: Option<i32>,
245 pub pending_scroll_by_y: Option<i32>,
248 pub on_handlers: BTreeMap<(usize, String), Value>,
257 pub selection: Option<Value>,
259 pub custom_elements: crate::os_lib::custom_elements::CustomElementRegistry,
261 pub shadow_tree: crate::os_lib::shadow_dom::ShadowTreeRegistry,
263 pub pending_parent_messages: Vec<(Value, String)>,
265 pub pending_child_messages: Vec<(String, Value, String)>,
267}
268
269impl Default for DomBridge {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275#[derive(Default)]
281pub struct ValidityFlags {
282 pub value_missing: bool,
283 pub type_mismatch: bool,
284 pub pattern_mismatch: bool,
285 pub too_long: bool,
286 pub too_short: bool,
287 pub range_overflow: bool,
288 pub range_underflow: bool,
289 pub step_mismatch: bool,
290 pub bad_input: bool,
291 pub custom_error: bool,
292}
293impl ValidityFlags {
294 pub fn valid(&self) -> bool {
295 !(self.value_missing
296 || self.type_mismatch
297 || self.pattern_mismatch
298 || self.too_long
299 || self.too_short
300 || self.range_overflow
301 || self.range_underflow
302 || self.step_mismatch
303 || self.bad_input
304 || self.custom_error)
305 }
306}
307
308use crate::os_lib::dom_selector::*;
311
312impl DomBridge {
313 pub fn new() -> Self {
314 DomBridge {
315 nodes: Vec::new(),
316 listeners: Vec::new(),
317 dirty: false,
318 structural_dirty: false,
319 pending_style_updates: Vec::new(),
320 js_created_scripts: alloc::collections::BTreeSet::new(),
321 pending_dynamic_scripts: Vec::new(),
322 expandos: BTreeMap::new(),
323 node_protos: BTreeMap::new(),
324 ce_queue: Vec::new(),
325 built: false,
326 rects: BTreeMap::new(),
327 border_widths: BTreeMap::new(),
328 computed_styles: BTreeMap::new(),
329 scroll_tops: BTreeMap::new(),
330 scroll_lefts: BTreeMap::new(),
331 scroll_heights: BTreeMap::new(),
332 scroll_widths: BTreeMap::new(),
333 next_listener_id: 1,
334 mut_observers: Vec::new(),
335 mut_records: Vec::new(),
336 next_mut_obs_id: 1,
337 io_observers: Vec::new(),
338 next_io_obs_id: 1,
339 ro_observers: Vec::new(),
340 next_ro_obs_id: 1,
341 pointer_captures: BTreeSet::new(),
342 focused_idx: None,
343 pending_scroll_abs_y: None,
344 pending_scroll_by_y: None,
345 on_handlers: BTreeMap::new(),
346 selection: None,
347 custom_elements: crate::os_lib::custom_elements::CustomElementRegistry::new(),
348 shadow_tree: crate::os_lib::shadow_dom::ShadowTreeRegistry::new(),
349 pending_parent_messages: Vec::new(),
350 pending_child_messages: Vec::new(),
351 }
352 }
353
354 pub fn ensure_default_document(&mut self) {
356 if self.nodes.is_empty() {
357 self.nodes.push(DomNode {
358 tag: String::from("html"),
359 is_text: false,
360 id: String::new(),
361 classes: Vec::new(),
362 style: BTreeMap::new(),
363 attrs: BTreeMap::new(),
364 text_override: None,
365 initial_text: String::new(),
366 parent: None,
367 children: alloc::vec![1, 2],
368 classes_dirty: false,
369 style_dirty: false,
370 });
371 self.nodes.push(DomNode {
372 tag: String::from("head"),
373 is_text: false,
374 id: String::new(),
375 classes: Vec::new(),
376 style: BTreeMap::new(),
377 attrs: BTreeMap::new(),
378 text_override: None,
379 initial_text: String::new(),
380 parent: Some(0),
381 children: Vec::new(),
382 classes_dirty: false,
383 style_dirty: false,
384 });
385 self.nodes.push(DomNode {
386 tag: String::from("body"),
387 is_text: false,
388 id: String::new(),
389 classes: Vec::new(),
390 style: BTreeMap::new(),
391 attrs: BTreeMap::new(),
392 text_override: None,
393 initial_text: String::new(),
394 parent: Some(0),
395 children: Vec::new(),
396 classes_dirty: false,
397 style_dirty: false,
398 });
399 self.built = true;
400 }
401 }
402
403 pub fn set_on_handler(&mut self, node: usize, key: &str, val: Value) {
405 self.on_handlers.insert((node, key.to_string()), val);
406 }
407 pub fn get_on_handler(&self, node: usize, key: &str) -> Option<Value> {
409 self.on_handlers.get(&(node, key.to_string())).cloned()
410 }
411
412 pub fn set_pointer_capture(&mut self, idx: usize, pointer_id: i32) {
414 self.pointer_captures.insert((idx, pointer_id));
415 }
416 pub fn release_pointer_capture(&mut self, idx: usize, pointer_id: i32) {
418 self.pointer_captures.remove(&(idx, pointer_id));
419 }
420 pub fn has_pointer_capture(&self, idx: usize, pointer_id: i32) -> bool {
422 self.pointer_captures.contains(&(idx, pointer_id))
423 }
424
425 fn notify_child_list(&mut self, parent_idx: usize, added: Vec<usize>, removed: Vec<usize>) {
427 if !self.custom_elements.is_empty() {
428 for &a in &added {
429 self.ce_queue.push(CeEvent::Inserted(a));
430 }
431 if self.is_attached(parent_idx) {
432 for &r in &removed {
433 self.ce_queue.push(CeEvent::Removed(r));
434 }
435 }
436 }
437 for obs in &self.mut_observers {
438 let watches = obs.child_list
439 && (obs.target_idx == parent_idx
440 || (obs.subtree && self.is_ancestor_of(obs.target_idx, parent_idx)));
441 if watches {
442 self.mut_records.push((
443 obs.id,
444 MutRec {
445 type_: "childList",
446 target_idx: parent_idx,
447 added: added.clone(),
448 removed: removed.clone(),
449 attribute_name: None,
450 old_value: None,
451 },
452 ));
453 }
454 }
455 }
456
457 fn notify_attribute(&mut self, idx: usize, attr_name: &str, old_value: Option<&str>) {
463 if self.node_protos.contains_key(&idx) && !attr_name.starts_with('_') {
465 let observed = self
466 .nodes
467 .get(idx)
468 .and_then(|n| self.custom_elements.get(&n.tag.to_lowercase()))
469 .is_some_and(|d| d.observes_attribute(attr_name));
470 if observed {
471 let new_value = self.get_attr(idx, attr_name);
472 self.ce_queue.push(CeEvent::Attr(
473 idx,
474 attr_name.to_string(),
475 old_value.map(String::from),
476 new_value,
477 ));
478 }
479 }
480 for obs in &self.mut_observers {
481 let watches = obs.attributes
482 && (obs.target_idx == idx
483 || (obs.subtree && self.is_ancestor_of(obs.target_idx, idx)))
484 && obs
485 .attribute_filter
486 .as_ref()
487 .is_none_or(|f| f.iter().any(|n| n == attr_name));
488 if watches {
489 self.mut_records.push((
490 obs.id,
491 MutRec {
492 type_: "attributes",
493 target_idx: idx,
494 added: Vec::new(),
495 removed: Vec::new(),
496 attribute_name: Some(String::from(attr_name)),
497 old_value: if obs.attribute_old_value {
498 Some(old_value.unwrap_or("").to_string())
499 } else {
500 None
501 },
502 },
503 ));
504 }
505 }
506 }
507
508 fn notify_character_data(&mut self, idx: usize, old_value: Option<&str>) {
514 for obs in &self.mut_observers {
515 let watches = obs.character_data
516 && (obs.target_idx == idx
517 || (obs.subtree && self.is_ancestor_of(obs.target_idx, idx)));
518 if watches {
519 self.mut_records.push((
520 obs.id,
521 MutRec {
522 type_: "characterData",
523 target_idx: idx,
524 added: Vec::new(),
525 removed: Vec::new(),
526 attribute_name: None,
527 old_value: if obs.character_data_old_value {
528 Some(old_value.unwrap_or("").to_string())
529 } else {
530 None
531 },
532 },
533 ));
534 }
535 }
536 }
537
538 pub fn set_rect(&mut self, idx: usize, x: i32, y: i32, w: i32, h: i32) {
540 self.rects.insert(idx, (x, y, w, h));
541 }
542 pub fn get_rect(&self, idx: usize) -> Option<(i32, i32, i32, i32)> {
543 self.rects.get(&idx).copied()
544 }
545 pub fn set_border_widths(&mut self, idx: usize, top: i32, right: i32, bottom: i32, left: i32) {
546 self.border_widths.insert(idx, (top, right, bottom, left));
547 }
548 pub fn get_border_widths(&self, idx: usize) -> (i32, i32, i32, i32) {
549 self.border_widths.get(&idx).copied().unwrap_or((0, 0, 0, 0))
550 }
551 pub fn clear_rects(&mut self) {
552 self.rects.clear();
553 self.border_widths.clear();
554 }
555
556 pub fn set_computed_style(&mut self, idx: usize, prop: &str, val: &str) {
558 self.computed_styles
559 .entry(idx)
560 .or_default()
561 .insert(String::from(prop), String::from(val));
562 }
563 pub fn get_computed(&self, idx: usize, prop: &str) -> String {
565 if let Some(m) = self.computed_styles.get(&idx) {
566 if let Some(v) = m.get(prop) {
567 return v.clone();
568 }
569 }
570 self.nodes
571 .get(idx)
572 .and_then(|n| n.style.get(prop))
573 .cloned()
574 .unwrap_or_default()
575 }
576 pub fn clear_computed(&mut self) {
577 self.computed_styles.clear();
578 }
579
580 pub fn clear_dirty(&mut self) {
582 self.dirty = false;
583 self.structural_dirty = false;
584 self.pending_style_updates.clear();
585 }
586
587 pub fn mark_structural_dirty(&mut self) {
589 self.dirty = true;
590 self.structural_dirty = true;
591 }
592
593 pub fn build_from(&mut self, root: &Node) {
595 self.nodes.clear();
596 self.listeners.clear();
597 self.selection = None;
598 self.clear_dirty();
599 self.built = true;
600 Self::collect(root, None, &mut self.nodes);
601 let n = self.nodes.len();
603 for i in 0..n {
604 if !self.nodes[i].is_text {
605 let t = self.collect_text(i);
606 self.nodes[i].initial_text = t;
607 }
608 }
609 {
616 let mut last_open_by_name: alloc::collections::BTreeMap<String, usize> = alloc::collections::BTreeMap::new();
617 for i in 0..n {
618 let node = &self.nodes[i];
619 if node.tag == "details" && node.attrs.contains_key("open") {
620 if let Some(name) = node.attrs.get("name").filter(|s| !s.is_empty()) {
621 last_open_by_name.insert(name.clone(), i);
622 }
623 }
624 }
625 for i in 0..n {
626 let node = &self.nodes[i];
627 if node.tag == "details" && node.attrs.contains_key("open") {
628 if let Some(name) = node.attrs.get("name").filter(|s| !s.is_empty()) {
629 if last_open_by_name.get(name) != Some(&i) {
630 self.nodes[i].attrs.remove("open");
631 }
632 }
633 }
634 }
635 }
636 }
637
638 fn collect(node: &Node, parent: Option<usize>, out: &mut Vec<DomNode>) -> usize {
640 let idx = out.len();
641 let dn = match &node.node_type {
642 NodeType::Text(t) => DomNode {
643 tag: String::new(),
644 is_text: true,
645 id: String::new(),
646 classes: Vec::new(),
647 style: BTreeMap::new(),
648 attrs: BTreeMap::new(),
649 text_override: None,
650 initial_text: t.clone(),
651 parent,
652 children: Vec::new(),
653 classes_dirty: false,
654 style_dirty: false,
655 },
656 NodeType::Element {
657 tag_name,
658 attributes,
659 classes,
660 id,
661 } => {
662 let style =
663 parse_inline_style(attributes.get("style").map(|s| s.as_str()).unwrap_or(""));
664 DomNode {
665 tag: tag_name.clone(),
666 is_text: false,
667 id: id.clone().unwrap_or_default(),
668 classes: classes.clone(),
669 style,
670 attrs: attributes.clone(),
671 text_override: None,
672 initial_text: String::new(),
673 parent,
674 children: Vec::new(),
675 classes_dirty: false,
676 style_dirty: false,
677 }
678 }
679 };
680 out.push(dn);
681 let mut kids = Vec::new();
682 for c in &node.children {
683 kids.push(Self::collect(c, Some(idx), out));
684 }
685 out[idx].children = kids;
686 idx
687 }
688
689 fn collect_text(&self, idx: usize) -> String {
691 let node = &self.nodes[idx];
692 if node.tag == "#comment" {
693 return node.text_override.clone().unwrap_or_else(|| node.initial_text.clone());
694 }
695 let mut s = String::new();
696 self.collect_text_into(idx, &mut s);
697 s
698 }
699 fn collect_text_into(&self, idx: usize, out: &mut String) {
700 let node = &self.nodes[idx];
701 if node.is_text {
702 out.push_str(node.text_override.as_deref().unwrap_or(&node.initial_text));
703 return;
704 }
705 for &c in &node.children {
706 self.collect_text_into(c, out);
707 }
708 }
709
710 pub fn build_id_index(&self) -> alloc::collections::BTreeMap<&str, usize> {
726 let mut map: alloc::collections::BTreeMap<&str, usize> =
727 alloc::collections::BTreeMap::new();
728 if !self.built {
729 return map;
730 }
731 for i in 0..self.nodes.len() {
732 let n = &self.nodes[i];
733 if n.is_text || n.id.is_empty() {
734 continue;
735 }
736 if !self.is_attached(i) {
737 continue;
738 }
739 map.entry(n.id.as_str()).or_insert(i);
740 }
741 map
742 }
743
744 pub fn get_element_by_id(&self, id: &str) -> Option<usize> {
745 if !self.built {
746 return None;
747 }
748 if let Some(rest) = id.strip_prefix("_node_idx_") {
749 if let Ok(idx) = rest.parse::<usize>() {
750 if idx < self.nodes.len() && !self.nodes[idx].is_text && self.is_attached(idx) {
751 return Some(idx);
752 }
753 }
754 }
755 (0..self.nodes.len())
756 .find(|&i| !self.nodes[i].is_text && self.nodes[i].id == id && self.is_attached(i))
757 }
758
759 pub fn is_attached(&self, idx: usize) -> bool {
762 let mut cur = idx;
763 let mut guard = 0;
764 loop {
765 if cur == 0 {
766 return true;
767 }
768 match self.nodes.get(cur).and_then(|n| n.parent) {
769 Some(p) => {
770 cur = p;
771 guard += 1;
772 if guard > self.nodes.len() {
773 return false;
774 }
775 }
776 None => return false,
777 }
778 }
779 }
780
781 pub fn query(&self, selector: &str) -> Option<usize> {
783 self.query_all(selector).into_iter().next()
784 }
785
786 pub fn query_all(&self, selector: &str) -> Vec<usize> {
791 self.query_all_scoped(selector, None)
792 }
793
794 pub fn query_all_scoped(&self, selector: &str, scope_root: Option<usize>) -> Vec<usize> {
799 let sel = selector.trim();
800 let mut out = Vec::new();
801 if (!self.built && scope_root.is_none()) || sel.is_empty() {
802 return out;
803 }
804 for i in 0..self.nodes.len() {
805 if let Some(root) = scope_root {
806 if i != root && !self.is_ancestor_of(root, i) {
807 continue;
808 }
809 } else if !self.is_attached(i) {
810 continue;
811 }
812 if self.node_matches_scoped(i, sel, scope_root) {
813 out.push(i);
814 }
815 }
816 out
817 }
818
819 pub fn set_text_content(&mut self, idx: usize, val: &str) {
824 if idx >= self.nodes.len() {
825 return;
826 }
827 if self.nodes[idx].tag == "#comment" {
828 let old_val = self.nodes[idx]
829 .text_override
830 .as_deref()
831 .unwrap_or(&self.nodes[idx].initial_text)
832 .to_string();
833 self.nodes[idx].text_override = Some(val.to_string());
834 if old_val != val {
835 self.notify_character_data(idx, Some(&old_val));
836 }
837 self.mark_structural_dirty();
838 return;
839 }
840 if !self.nodes[idx].is_text {
846 let removed: Vec<usize> = core::mem::take(&mut self.nodes[idx].children);
847 for &c in &removed {
848 if let Some(n) = self.nodes.get_mut(c) {
849 n.parent = None;
850 }
851 }
852 let mut added = Vec::new();
853 if !val.is_empty() {
854 let t = self.new_text_node(val);
855 self.nodes[t].parent = Some(idx);
856 self.nodes[idx].children.push(t);
857 added.push(t);
858 }
859 if !removed.is_empty() || !added.is_empty() {
860 self.notify_child_list(idx, added, removed);
861 }
862 self.mark_structural_dirty();
863 return;
864 }
865 let text_nodes = self.descendant_text_nodes(idx);
866 {
867 for (k, t) in text_nodes.iter().enumerate() {
868 let new_val = if k == 0 { val.to_string() } else { String::new() };
869 let old_val = self.nodes[*t]
874 .text_override
875 .as_deref()
876 .unwrap_or(&self.nodes[*t].initial_text)
877 .to_string();
878 self.nodes[*t].text_override = Some(new_val.clone());
879 if old_val != new_val {
880 self.notify_character_data(*t, Some(&old_val));
881 }
882 }
883 }
884 self.mark_structural_dirty();
885 }
886
887 pub fn normalize_node(&mut self, idx: usize) {
891 if idx >= self.nodes.len() || self.nodes[idx].is_text {
892 return;
893 }
894 let children = self.nodes[idx].children.clone();
895 let mut run_start: Option<usize> = None;
896 let mut to_remove: Vec<usize> = Vec::new();
897 for &c in &children {
898 if self.nodes[c].is_text {
899 match run_start {
900 None => run_start = Some(c),
901 Some(first) => {
902 let more = self.text_of(c);
903 let mut cur = self.text_of(first);
904 cur.push_str(&more);
905 self.nodes[first].text_override = Some(cur);
906 to_remove.push(c);
907 }
908 }
909 } else {
910 run_start = None;
911 self.normalize_node(c);
912 }
913 }
914 for &c in &children {
915 if self.nodes.get(c).map(|n| n.is_text).unwrap_or(false)
916 && !to_remove.contains(&c)
917 && self.text_of(c).is_empty()
918 {
919 to_remove.push(c);
920 }
921 }
922 for r in to_remove {
923 self.remove_node(r);
924 }
925 }
926 fn text_of(&self, idx: usize) -> String {
929 let n = &self.nodes[idx];
930 n.text_override.as_deref().unwrap_or(&n.initial_text).to_string()
931 }
932
933 fn new_element_node(&mut self, tag: &str) -> usize {
936 let idx = self.nodes.len();
937 let lower_tag = tag.to_lowercase();
938 let mut attrs = BTreeMap::new();
939 if self.custom_elements.is_defined(&lower_tag) {
940 attrs.insert(String::from("_defined"), String::from("true"));
941 }
942 self.nodes.push(DomNode {
943 tag: lower_tag,
944 is_text: false,
945 id: String::new(),
946 classes: Vec::new(),
947 style: BTreeMap::new(),
948 attrs,
949 text_override: None,
950 initial_text: String::new(),
951 parent: None,
952 children: Vec::new(),
953 classes_dirty: false,
954 style_dirty: false,
955 });
956 idx
957 }
958
959 fn new_text_node(&mut self, text: &str) -> usize {
960 let idx = self.nodes.len();
961 self.nodes.push(DomNode {
962 tag: String::new(),
963 is_text: true,
964 id: String::new(),
965 classes: Vec::new(),
966 style: BTreeMap::new(),
967 attrs: BTreeMap::new(),
968 text_override: Some(text.to_string()),
969 initial_text: text.to_string(),
970 parent: None,
971 children: Vec::new(),
972 classes_dirty: false,
973 style_dirty: false,
974 });
975 idx
976 }
977
978 pub fn create_element(&mut self, tag: &str) -> usize {
980 let idx = self.new_element_node(tag);
981 if tag.eq_ignore_ascii_case("script") {
982 self.js_created_scripts.insert(idx);
983 }
984 idx
985 }
986
987 pub fn script_source(&self, idx: usize) -> (Option<String>, String, String) {
989 let Some(n) = self.nodes.get(idx) else { return (None, String::new(), String::new()) };
990 let src = n.attrs.get("src").filter(|s| !s.trim().is_empty()).cloned();
991 let ty = n.attrs.get("type").cloned().unwrap_or_default();
992 let text = match &n.text_override {
993 Some(t) => t.clone(),
994 None => n
995 .children
996 .iter()
997 .filter_map(|&c| self.nodes.get(c))
998 .filter(|c| c.is_text)
999 .map(|c| c.text_override.clone().unwrap_or_else(|| c.initial_text.clone()))
1000 .collect(),
1001 };
1002 (src, ty, text)
1003 }
1004
1005 pub fn is_connected(&self, mut idx: usize) -> bool {
1007 let mut guard = 0;
1008 while let Some(p) = self.nodes.get(idx).and_then(|n| n.parent) {
1009 idx = p;
1010 guard += 1;
1011 if guard > 10_000 {
1012 return false;
1013 }
1014 }
1015 idx == 0
1016 }
1017
1018 fn queue_inserted_scripts(&mut self, roots: &[usize]) {
1021 if self.js_created_scripts.is_empty() {
1022 return;
1023 }
1024 let mut stack: Vec<usize> = roots.to_vec();
1025 while let Some(i) = stack.pop() {
1026 if self.js_created_scripts.contains(&i) && self.is_connected(i) {
1027 self.js_created_scripts.remove(&i);
1028 self.pending_dynamic_scripts.push(i);
1029 }
1030 if let Some(n) = self.nodes.get(i) {
1031 stack.extend(n.children.iter().copied());
1032 }
1033 }
1034 }
1035
1036 pub fn create_text_node(&mut self, text: &str) -> usize {
1038 self.new_text_node(text)
1039 }
1040
1041 pub fn create_comment(&mut self, text: &str) -> usize {
1043 let idx = self.nodes.len();
1044 self.nodes.push(DomNode {
1045 tag: String::from("#comment"),
1046 is_text: false,
1047 id: String::new(),
1048 classes: Vec::new(),
1049 style: BTreeMap::new(),
1050 attrs: BTreeMap::new(),
1051 text_override: Some(text.to_string()),
1052 initial_text: text.to_string(),
1053 parent: None,
1054 children: Vec::new(),
1055 classes_dirty: false,
1056 style_dirty: false,
1057 });
1058 idx
1059 }
1060
1061 pub fn create_document_fragment(&mut self) -> usize {
1067 self.new_element_node("#fragment")
1068 }
1069
1070 pub fn attach_shadow(
1072 &mut self,
1073 host_idx: usize,
1074 mode: crate::os_lib::shadow_dom::ShadowRootMode,
1075 ) -> Result<usize, crate::os_lib::shadow_dom::AttachShadowError> {
1076 let tag = match self.nodes.get(host_idx) {
1077 Some(n) if !n.is_text => n.tag.clone(),
1078 _ => return Err(crate::os_lib::shadow_dom::AttachShadowError::DisallowedTag),
1079 };
1080 let root_idx = self.new_element_node("#shadow-root");
1082 self.shadow_tree.attach_shadow(host_idx, &tag, mode, root_idx)?;
1083 self.structural_dirty = true;
1084 self.dirty = true;
1085 Ok(root_idx)
1086 }
1087
1088 pub fn get_shadow_root(&self, host_idx: usize) -> Option<usize> {
1090 self.shadow_tree
1091 .get_by_host(host_idx)
1092 .filter(|d| d.is_accessible_via_element())
1093 .map(|d| d.root_node_idx)
1094 }
1095
1096 pub fn create_contextual_fragment(&mut self, context_node_idx: Option<usize>, html: &str) -> usize {
1098 let mut context_tag = alloc::string::String::from("div");
1099 if let Some(idx) = context_node_idx {
1100 if let Some(node) = self.nodes.get(idx) {
1101 if node.is_text {
1102 if let Some(p) = node.parent {
1103 if let Some(pn) = self.nodes.get(p) {
1104 if !pn.is_text && !pn.tag.starts_with('#') {
1105 context_tag = pn.tag.clone();
1106 }
1107 }
1108 }
1109 } else if !node.tag.starts_with('#') {
1110 context_tag = node.tag.clone();
1111 }
1112 }
1113 }
1114
1115 let (wrapped, depth) = crate::os_lib::dom_fragment::context_wrapper(&context_tag, html);
1116 let root = crate::os_lib::dom::parse_html(&wrapped);
1117 let frag_idx = self.create_document_fragment();
1118 let children = crate::os_lib::dom_fragment::extract_fragment_children(&root, depth);
1119
1120 let mut imported_children = Vec::new();
1121 for child in children {
1122 let ci = self.import_node(child);
1123 self.nodes[ci].parent = Some(frag_idx);
1124 imported_children.push(ci);
1125 }
1126 self.nodes[frag_idx].children = imported_children;
1127 frag_idx
1128 }
1129
1130 pub fn template_content(&mut self, idx: usize) -> usize {
1139 if let Some(f) = self
1140 .get_attr(idx, "_content_frag")
1141 .and_then(|s| s.parse::<usize>().ok())
1142 {
1143 return f;
1144 }
1145 let f = self.create_document_fragment();
1146 let kids = core::mem::take(&mut self.nodes[idx].children);
1147 for &k in &kids {
1148 self.nodes[k].parent = Some(f);
1149 }
1150 self.nodes[f].children = kids;
1151 self.set_attr(idx, "_content_frag", &f.to_string());
1152 f
1153 }
1154
1155 fn expand_fragment(&mut self, child: usize) -> Vec<usize> {
1159 if self.nodes.get(child).map(|n| n.tag == "#fragment" || n.tag == "#document-fragment").unwrap_or(false) {
1160 let kids = core::mem::take(&mut self.nodes[child].children);
1161 for &k in &kids {
1162 self.nodes[k].parent = None;
1163 }
1164 kids
1165 } else {
1166 alloc::vec![child]
1167 }
1168 }
1169
1170 pub fn append_child(&mut self, parent: usize, child: usize) {
1173 if parent >= self.nodes.len() || child >= self.nodes.len() || parent == child {
1174 return;
1175 }
1176 let kids = self.expand_fragment(child);
1177 if kids.is_empty() {
1178 return;
1179 }
1180 for &k in &kids {
1181 self.detach(k);
1182 self.nodes[k].parent = Some(parent);
1183 self.nodes[parent].children.push(k);
1184 }
1185 self.mark_structural_dirty();
1186 self.queue_inserted_scripts(&kids);
1187 self.notify_child_list(parent, kids, Vec::new());
1188 }
1189
1190 pub fn remove_child(&mut self, parent: usize, child: usize) {
1192 let p = if parent < self.nodes.len() { Some(parent) } else { self.nodes.get(child).and_then(|n| n.parent) };
1193 self.detach(child);
1194 self.mark_structural_dirty();
1195 if let Some(p) = p { self.notify_child_list(p, Vec::new(), alloc::vec![child]); }
1196 }
1197 pub fn remove_node(&mut self, idx: usize) {
1198 let p = self.nodes.get(idx).and_then(|n| n.parent);
1199 self.detach(idx);
1200 self.mark_structural_dirty();
1201 if let Some(p) = p { self.notify_child_list(p, Vec::new(), alloc::vec![idx]); }
1202 }
1203
1204 pub fn insert_before(&mut self, parent: usize, new_child: usize, ref_child: Option<usize>) {
1207 if parent >= self.nodes.len() || new_child >= self.nodes.len() || parent == new_child {
1208 return;
1209 }
1210 let kids = self.expand_fragment(new_child);
1211 if kids.is_empty() {
1212 return;
1213 }
1214 let pos = ref_child.and_then(|r| self.nodes[parent].children.iter().position(|&c| c == r));
1215 for (i, &k) in kids.iter().enumerate() {
1216 self.detach(k);
1217 self.nodes[k].parent = Some(parent);
1218 match pos {
1219 Some(p) => self.nodes[parent].children.insert(p + i, k),
1220 None => self.nodes[parent].children.push(k),
1221 }
1222 }
1223 self.mark_structural_dirty();
1224 self.queue_inserted_scripts(&kids);
1225 self.notify_child_list(parent, kids, Vec::new());
1226 }
1227
1228 pub fn replace_child(&mut self, parent: usize, new_child: usize, old_child: usize) {
1230 if parent >= self.nodes.len() || new_child >= self.nodes.len() {
1231 return;
1232 }
1233 if !self.nodes[parent].children.contains(&old_child) {
1234 return;
1235 }
1236 self.detach(new_child); if let Some(p) = self.nodes[parent]
1238 .children
1239 .iter()
1240 .position(|&c| c == old_child)
1241 {
1242 self.nodes[old_child].parent = None;
1243 self.nodes[parent].children[p] = new_child;
1244 self.nodes[new_child].parent = Some(parent);
1245 self.mark_structural_dirty();
1246 self.queue_inserted_scripts(&[new_child]);
1247 self.notify_child_list(parent, alloc::vec![new_child], alloc::vec![old_child]);
1248 }
1249 }
1250
1251 pub fn clone_node(&mut self, idx: usize, deep: bool) -> usize {
1253 let c = self.clone_node_inner(idx, deep);
1254 if c != idx && !self.custom_elements.is_empty() {
1255 self.ce_queue.push(CeEvent::Created(c));
1256 }
1257 c
1258 }
1259
1260 fn clone_node_inner(&mut self, idx: usize, deep: bool) -> usize {
1261 if idx >= self.nodes.len() {
1262 return idx;
1263 }
1264 let (is_text, tag, id, classes, style, attrs, text_override, initial_text, children) = {
1265 let n = &self.nodes[idx];
1266 (
1267 n.is_text,
1268 n.tag.clone(),
1269 n.id.clone(),
1270 n.classes.clone(),
1271 n.style.clone(),
1272 n.attrs.clone(),
1273 n.text_override.clone(),
1274 n.initial_text.clone(),
1275 n.children.clone(),
1276 )
1277 };
1278 let new_idx = self.nodes.len();
1279 self.nodes.push(DomNode {
1280 tag,
1281 is_text,
1282 id,
1283 classes,
1284 style,
1285 attrs,
1286 text_override,
1287 initial_text,
1288 parent: None,
1289 children: Vec::new(),
1290 classes_dirty: false,
1291 style_dirty: false,
1292 });
1293 if deep && !is_text {
1294 for c in children {
1295 let cc = self.clone_node_inner(c, true);
1296 self.nodes[cc].parent = Some(new_idx);
1297 self.nodes[new_idx].children.push(cc);
1298 }
1299 }
1300 let frag_attr = self.nodes[new_idx]
1310 .attrs
1311 .get("_template_content_idx")
1312 .or_else(|| self.nodes[new_idx].attrs.get("_content_frag"))
1313 .cloned();
1314 if let Some(frag_idx) = frag_attr.and_then(|s| s.parse::<usize>().ok()) {
1315 let new_frag = self.clone_node_inner(frag_idx, true);
1316 self.nodes[new_idx]
1317 .attrs
1318 .insert("_template_content_idx".to_string(), new_frag.to_string());
1319 }
1320 self.mark_structural_dirty();
1321 new_idx
1322 }
1323
1324 fn detach(&mut self, idx: usize) {
1326 if idx >= self.nodes.len() {
1327 return;
1328 }
1329 if let Some(p) = self.nodes[idx].parent {
1330 if let Some(pos) = self.nodes[p].children.iter().position(|&c| c == idx) {
1331 self.nodes[p].children.remove(pos);
1332 }
1333 }
1334 self.nodes[idx].parent = None;
1335 }
1336
1337 pub fn insert_adjacent_node(&mut self, idx: usize, position: &str, node: usize) {
1339 match position {
1340 "beforeend" => self.append_child(idx, node),
1341 "afterbegin" => {
1342 let first = self
1343 .nodes
1344 .get(idx)
1345 .and_then(|n| n.children.first().copied());
1346 self.insert_before(idx, node, first);
1347 }
1348 "beforebegin" => {
1349 if let Some(p) = self.nodes.get(idx).and_then(|n| n.parent) {
1350 self.insert_before(p, node, Some(idx));
1351 }
1352 }
1353 "afterend" => {
1354 if let Some(p) = self.nodes.get(idx).and_then(|n| n.parent) {
1355 let next = self.nodes.get(p).and_then(|n| {
1356 n.children
1357 .iter()
1358 .position(|&c| c == idx)
1359 .and_then(|i| n.children.get(i + 1).copied())
1360 });
1361 self.insert_before(p, node, next);
1362 }
1363 }
1364 _ => {}
1365 }
1366 }
1367
1368 pub fn insert_adjacent_html(&mut self, idx: usize, position: &str, html: &str) {
1370 let root = crate::os_lib::dom::parse_html(html);
1371 let imported: Vec<usize> = root.children.iter().map(|c| self.import_node(c)).collect();
1372 if position == "afterbegin" {
1373 let first = self
1375 .nodes
1376 .get(idx)
1377 .and_then(|n| n.children.first().copied());
1378 for node in imported {
1379 self.insert_before(idx, node, first);
1380 }
1381 } else {
1382 for node in imported {
1383 self.insert_adjacent_node(idx, position, node);
1384 }
1385 }
1386 self.mark_structural_dirty();
1387 }
1388
1389 pub fn set_inner_html(&mut self, idx: usize, html: &str) {
1395 if idx >= self.nodes.len() || self.nodes[idx].is_text {
1396 return;
1397 }
1398 let root = crate::os_lib::dom::parse_html(html);
1399 let old: Vec<usize> = self.nodes[idx].children.clone();
1401 for &c in &old {
1402 self.nodes[c].parent = None;
1403 }
1404 self.nodes[idx].children.clear();
1405 let mut new_children = Vec::new();
1407 for child in &root.children {
1408 let ci = self.import_node(child);
1409 self.nodes[ci].parent = Some(idx);
1410 new_children.push(ci);
1411 }
1412 self.nodes[idx].children = new_children.clone();
1413 self.mark_structural_dirty();
1414 self.notify_child_list(idx, new_children, old);
1415 }
1416
1417 pub fn set_outer_html(&mut self, idx: usize, html: &str) {
1419 if idx >= self.nodes.len() {
1420 return;
1421 }
1422 let parent = match self.nodes[idx].parent {
1423 Some(p) => p,
1424 None => return,
1425 };
1426 let pos = match self.nodes[parent].children.iter().position(|&c| c == idx) {
1427 Some(p) => p,
1428 None => return,
1429 };
1430 let root = crate::os_lib::dom::parse_html(html);
1431 let mut new_inserted = Vec::new();
1432 for child in &root.children {
1433 let ci = self.import_node(child);
1434 self.nodes[ci].parent = Some(parent);
1435 new_inserted.push(ci);
1436 }
1437 self.nodes[idx].parent = None;
1438 self.nodes[parent].children.remove(pos);
1439 for (i, &ci) in new_inserted.iter().enumerate() {
1440 self.nodes[parent].children.insert(pos + i, ci);
1441 }
1442 self.mark_structural_dirty();
1443 self.notify_child_list(parent, new_inserted, alloc::vec![idx]);
1444 }
1445
1446 pub fn import_node(&mut self, node: &Node) -> usize {
1448 match &node.node_type {
1449 NodeType::Text(t) => self.new_text_node(t),
1450 NodeType::Element {
1451 tag_name,
1452 attributes,
1453 classes,
1454 id,
1455 } => {
1456 let idx = self.new_element_node(tag_name);
1457 {
1458 let style = parse_inline_style(
1459 attributes.get("style").map(|s| s.as_str()).unwrap_or(""),
1460 );
1461 let n = &mut self.nodes[idx];
1462 n.tag = tag_name.clone();
1463 n.id = id.clone().unwrap_or_default();
1464 n.classes = classes.clone();
1465 n.style = style;
1466 n.attrs = attributes.clone();
1467 }
1468 let mut kids = Vec::new();
1469 for c in &node.children {
1470 let ci = self.import_node(c);
1471 self.nodes[ci].parent = Some(idx);
1472 kids.push(ci);
1473 }
1474 self.nodes[idx].children = kids;
1475 idx
1476 }
1477 }
1478 }
1479
1480 pub fn mark_subtree_xml(&mut self, idx: usize) {
1482 if let Some(n) = self.nodes.get_mut(idx) {
1483 n.attrs.insert("_xml".into(), "1".into());
1484 let kids = n.children.clone();
1485 for k in kids {
1486 self.mark_subtree_xml(k);
1487 }
1488 }
1489 }
1490
1491 pub fn to_dom_node(&self) -> Node {
1494 if self.nodes.is_empty() {
1495 return Node::new_text(String::new());
1496 }
1497 self.build_node(0)
1498 }
1499
1500 fn build_node(&self, idx: usize) -> Node {
1501 let n = &self.nodes[idx];
1502 if n.is_text {
1503 return Node::new_text(
1504 n.text_override
1505 .clone()
1506 .unwrap_or_else(|| n.initial_text.clone()),
1507 );
1508 }
1509 let mut attrs = n.attrs.clone();
1510 let id = if n.id.is_empty() {
1511 let gen_id = alloc::format!("_node_idx_{}", idx);
1512 attrs.insert("id".to_string(), gen_id.clone());
1513 Some(gen_id)
1514 } else {
1515 attrs.insert("id".to_string(), n.id.clone());
1516 Some(n.id.clone())
1517 };
1518 if n.classes.is_empty() {
1519 attrs.remove("class");
1520 } else {
1521 attrs.insert("class".to_string(), n.classes.join(" "));
1522 }
1523 if n.style.is_empty() {
1524 attrs.remove("style");
1525 } else {
1526 attrs.insert("style".to_string(), serialize_inline_style(&n.style));
1527 }
1528 let mut node = Node::new_element_direct(n.tag.clone(), attrs, id, n.classes.clone());
1529 for &c in &n.children {
1530 node.children.push(self.build_node(c));
1531 }
1532 node
1533 }
1534
1535 pub fn get_text_content(&self, idx: usize) -> String {
1537 if idx >= self.nodes.len() {
1538 return String::new();
1539 }
1540 self.collect_text(idx)
1541 }
1542
1543 fn descendant_text_nodes(&self, idx: usize) -> Vec<usize> {
1544 let mut out = Vec::new();
1545 self.descendant_text_into(idx, &mut out);
1546 out
1547 }
1548 fn descendant_text_into(&self, idx: usize, out: &mut Vec<usize>) {
1549 let node = &self.nodes[idx];
1550 if node.is_text {
1551 out.push(idx);
1552 return;
1553 }
1554 for &c in &node.children {
1555 self.descendant_text_into(c, out);
1556 }
1557 }
1558
1559 pub fn set_style(&mut self, idx: usize, prop: &str, val: &str) {
1564 let key = css_prop_from_camel(prop);
1565 let (old_value, changed) = {
1566 let Some(n) = self.nodes.get_mut(idx) else {
1567 return;
1568 };
1569 let old_value = serialize_inline_style(&n.style);
1570 let changed = if val.is_empty() {
1571 n.style.remove(&key).is_some()
1572 } else {
1573 n.style.insert(key.clone(), val.to_string()).as_deref() != Some(val)
1574 };
1575 if changed {
1576 n.style_dirty = true;
1577 }
1578 (old_value, changed)
1579 };
1580 if changed {
1585 self.dirty = true;
1586 if let Some(pos) = self.pending_style_updates.iter().position(|(i, k, _)| *i == idx && k == &key) {
1587 self.pending_style_updates[pos].2 = val.to_string();
1588 } else {
1589 self.pending_style_updates.push((idx, key, val.to_string()));
1590 }
1591 }
1592 self.notify_attribute(idx, "style", Some(&old_value));
1593 }
1594
1595 pub fn get_style(&self, idx: usize, prop: &str) -> String {
1596 let raw = self
1597 .nodes
1598 .get(idx)
1599 .and_then(|n| n.style.get(&css_prop_from_camel(prop)).cloned())
1600 .unwrap_or_default();
1601 strip_style_priority(&raw).0.to_string()
1602 }
1603
1604 pub fn get_style_priority(&self, idx: usize, prop: &str) -> String {
1609 let raw = self
1610 .nodes
1611 .get(idx)
1612 .and_then(|n| n.style.get(&css_prop_from_camel(prop)).cloned())
1613 .unwrap_or_default();
1614 if strip_style_priority(&raw).1 {
1615 "important".to_string()
1616 } else {
1617 String::new()
1618 }
1619 }
1620
1621 pub fn get_css_text(&self, idx: usize) -> String {
1626 self.nodes
1627 .get(idx)
1628 .map(|n| serialize_inline_style(&n.style))
1629 .unwrap_or_default()
1630 }
1631 pub fn set_css_text(&mut self, idx: usize, text: &str) {
1635 let old_value = {
1636 let Some(n) = self.nodes.get_mut(idx) else {
1637 return;
1638 };
1639 let old_value = serialize_inline_style(&n.style);
1640 n.style = parse_inline_style(text);
1641 n.style_dirty = true;
1642 old_value
1643 };
1644 self.mark_structural_dirty();
1645 self.notify_attribute(idx, "style", Some(&old_value));
1646 }
1647
1648 pub fn outline(&self, max: usize) -> Vec<String> {
1651 let mut out = Vec::new();
1652 let mut stack: Vec<(usize, usize)> = self
1653 .nodes
1654 .iter()
1655 .enumerate()
1656 .filter(|(_, n)| n.parent.is_none() && !n.is_text)
1657 .map(|(i, _)| (i, 0))
1658 .rev()
1659 .collect();
1660 while let Some((idx, depth)) = stack.pop() {
1661 if out.len() >= max {
1662 break;
1663 }
1664 let Some(n) = self.nodes.get(idx) else { continue };
1665 if n.is_text {
1666 continue;
1667 }
1668 let mut line = String::new();
1669 for _ in 0..depth.min(30) {
1670 line.push(' ');
1671 }
1672 line.push_str(&n.tag);
1673 if !n.id.is_empty() {
1674 line.push('#');
1675 line.push_str(&n.id);
1676 }
1677 for c in n.classes.iter().take(3) {
1678 line.push('.');
1679 line.push_str(c);
1680 }
1681 if !n.style.is_empty() {
1682 line.push_str(" [");
1683 for (k, v) in n.style.iter().take(4) {
1684 line.push_str(k);
1685 line.push(':');
1686 line.extend(v.chars().take(70));
1687 line.push(';');
1688 }
1689 line.push(']');
1690 }
1691 if let Some(src) = n.attrs.get("src") {
1692 line.push_str(" src=");
1693 line.extend(src.chars().take(60));
1694 }
1695 let text: String = n
1697 .children
1698 .iter()
1699 .filter_map(|&c| self.nodes.get(c))
1700 .filter(|c| c.is_text)
1701 .map(|c| c.text_override.as_deref().unwrap_or(&c.initial_text))
1702 .collect::<Vec<_>>()
1703 .join("");
1704 let text = n.text_override.as_deref().filter(|t| !t.is_empty()).unwrap_or(text.as_str()).trim();
1705 if !text.is_empty() && n.tag != "script" && n.tag != "style" {
1706 line.push_str(" \"");
1707 line.extend(text.chars().take(80));
1708 line.push('"');
1709 }
1710 out.push(line);
1711 for &c in n.children.iter().rev() {
1712 stack.push((c, depth + 1));
1713 }
1714 }
1715 out
1716 }
1717
1718 pub fn get_outer_html(&self, idx: usize) -> String {
1722 let mut out = String::new();
1723 self.serialize_node_html(idx, &mut out);
1724 out
1725 }
1726 pub fn get_inner_html(&self, idx: usize) -> String {
1731 let mut out = String::new();
1732 if let Some(n) = self.nodes.get(idx) {
1733 for &c in &n.children {
1734 self.serialize_node_html(c, &mut out);
1735 }
1736 }
1737 out
1738 }
1739 fn serialize_node_html(&self, idx: usize, out: &mut String) {
1740 let Some(n) = self.nodes.get(idx) else { return };
1741 if n.is_text {
1742 let text = n.text_override.as_deref().unwrap_or(&n.initial_text);
1743 let raw_parent = n
1746 .parent
1747 .and_then(|p| self.nodes.get(p))
1748 .is_some_and(|p| matches!(p.tag.as_str(), "script" | "style" | "xmp" | "iframe" | "noembed" | "noframes" | "plaintext"));
1749 if raw_parent {
1750 out.push_str(text);
1751 } else {
1752 out.push_str(&html_escape_text(text));
1753 }
1754 return;
1755 }
1756 if n.tag == "#comment" {
1757 out.push_str("<!--");
1758 out.push_str(n.text_override.as_deref().unwrap_or(&n.initial_text));
1759 out.push_str("-->");
1760 return;
1761 }
1762 out.push('<');
1763 out.push_str(&n.tag);
1764 if !n.id.is_empty() {
1765 out.push_str(&format!(" id=\"{}\"", html_escape_attr(&n.id)));
1766 }
1767 if !n.classes.is_empty() {
1768 out.push_str(&format!(" class=\"{}\"", html_escape_attr(&n.classes.join(" "))));
1769 }
1770 if !n.style.is_empty() {
1771 out.push_str(&format!(
1772 " style=\"{}\"",
1773 html_escape_attr(&serialize_inline_style(&n.style))
1774 ));
1775 }
1776 for (k, v) in &n.attrs {
1784 if k == "id" || k == "class" || k == "style" || k.starts_with('_') {
1785 continue;
1786 }
1787 out.push(' ');
1788 out.push_str(k);
1789 out.push_str("=\"");
1790 out.push_str(&html_escape_attr(v));
1791 out.push('"');
1792 }
1793 out.push('>');
1794 if matches!(
1796 n.tag.as_str(),
1797 "br" | "img" | "input" | "hr" | "meta" | "link" | "area" | "base" | "col"
1798 | "embed" | "param" | "source" | "track" | "wbr"
1799 ) {
1800 return;
1801 }
1802 for &c in &n.children {
1803 self.serialize_node_html(c, out);
1804 }
1805 out.push_str("</");
1806 out.push_str(&n.tag);
1807 out.push('>');
1808 }
1809
1810 pub fn class_add(&mut self, idx: usize, cls: &str) {
1815 let old_value = {
1816 let Some(n) = self.nodes.get_mut(idx) else {
1817 return;
1818 };
1819 if n.classes.iter().any(|c| c == cls) {
1820 return;
1821 }
1822 let old_value = n.classes.join(" ");
1823 n.classes.push(cls.to_string());
1824 n.classes_dirty = true;
1825 old_value
1826 };
1827 self.mark_structural_dirty();
1828 self.notify_attribute(idx, "class", Some(&old_value));
1829 }
1830 pub fn class_remove(&mut self, idx: usize, cls: &str) {
1831 let old_value = {
1832 let Some(n) = self.nodes.get_mut(idx) else {
1833 return;
1834 };
1835 let before = n.classes.len();
1836 let old_value = n.classes.join(" ");
1837 n.classes.retain(|c| c != cls);
1838 if n.classes.len() == before {
1839 return;
1840 }
1841 n.classes_dirty = true;
1842 old_value
1843 };
1844 self.mark_structural_dirty();
1845 self.notify_attribute(idx, "class", Some(&old_value));
1846 }
1847 pub fn class_toggle(&mut self, idx: usize, cls: &str) -> bool {
1848 if self.class_contains(idx, cls) {
1849 self.class_remove(idx, cls);
1850 false
1851 } else {
1852 self.class_add(idx, cls);
1853 true
1854 }
1855 }
1856 pub fn class_contains(&self, idx: usize, cls: &str) -> bool {
1857 self.nodes
1858 .get(idx)
1859 .map(|n| n.classes.iter().any(|c| c == cls))
1860 .unwrap_or(false)
1861 }
1862
1863 fn collect_descendant_options(&self, idx: usize, out: &mut Vec<usize>) {
1873 let Some(n) = self.nodes.get(idx) else { return };
1874 for &c in &n.children {
1875 let Some(cn) = self.nodes.get(c) else { continue };
1876 if cn.tag == "option" {
1877 out.push(c);
1878 } else {
1879 self.collect_descendant_options(c, out);
1880 }
1881 }
1882 }
1883 pub fn select_options(&self, idx: usize) -> Vec<usize> {
1887 let mut out = Vec::new();
1888 self.collect_descendant_options(idx, &mut out);
1889 out
1890 }
1891 fn option_value(&self, opt_idx: usize) -> String {
1894 self.get_attr(opt_idx, "value").unwrap_or_else(|| {
1895 self.nodes
1896 .get(opt_idx)
1897 .map(|n| n.text_override.clone().unwrap_or_else(|| n.initial_text.clone()))
1898 .unwrap_or_default()
1899 })
1900 }
1901 pub fn selected_option_indices(&self, idx: usize) -> Vec<usize> {
1906 let options = self.select_options(idx);
1907 let selected: Vec<usize> = options.iter().copied().filter(|&o| self.has_attr(o, "selected")).collect();
1908 if !selected.is_empty() {
1909 selected
1910 } else {
1911 options.first().copied().into_iter().collect()
1912 }
1913 }
1914 pub fn select_effective_value(&self, idx: usize) -> String {
1919 let options = self.select_options(idx);
1920 let target = options
1921 .iter()
1922 .copied()
1923 .find(|&o| self.has_attr(o, "selected"))
1924 .or_else(|| options.first().copied());
1925 match target {
1926 Some(o) => self.option_value(o),
1927 None => String::new(),
1928 }
1929 }
1930 pub fn effective_form_value(&self, idx: usize) -> String {
1939 let tag = self.nodes.get(idx).map(|n| n.tag.as_str()).unwrap_or("");
1940 if tag == "select" {
1941 self.select_effective_value(idx)
1942 } else if tag == "textarea" {
1943 self.get_attr(idx, "value").unwrap_or_else(|| {
1944 self.nodes
1945 .get(idx)
1946 .map(|n| n.initial_text.clone())
1947 .unwrap_or_default()
1948 })
1949 } else {
1950 self.get_attr(idx, "value").unwrap_or_default()
1951 }
1952 }
1953 pub fn set_select_value(&mut self, idx: usize, val: &str) {
1956 let options = self.select_options(idx);
1957 for &o in &options {
1958 if self.option_value(o) == val {
1959 self.set_attr(o, "selected", "selected");
1960 } else {
1961 self.remove_attr(o, "selected");
1962 }
1963 }
1964 self.mark_structural_dirty();
1965 }
1966 pub fn select_selected_index(&self, idx: usize) -> i32 {
1969 let options = self.select_options(idx);
1970 match options.iter().position(|&o| self.has_attr(o, "selected")) {
1971 Some(p) => p as i32,
1972 None if !options.is_empty() => 0,
1973 None => -1,
1974 }
1975 }
1976 pub fn set_select_selected_index(&mut self, idx: usize, n: i32) {
1978 let options = self.select_options(idx);
1979 for (i, &o) in options.iter().enumerate() {
1980 if i as i32 == n {
1981 self.set_attr(o, "selected", "selected");
1982 } else {
1983 self.remove_attr(o, "selected");
1984 }
1985 }
1986 self.mark_structural_dirty();
1987 }
1988 pub fn set_select_length(&mut self, idx: usize, n: usize) {
1993 let options = self.select_options(idx);
1994 if n < options.len() {
1995 for &o in &options[n..] {
1996 if let Some(parent) = self.nodes.get(o).and_then(|node| node.parent) {
1997 self.remove_child(parent, o);
1998 }
1999 }
2000 } else {
2001 for _ in options.len()..n {
2002 let opt = self.create_element("option");
2003 self.append_child(idx, opt);
2004 }
2005 }
2006 self.mark_structural_dirty();
2007 }
2008 pub fn option_index(&self, idx: usize) -> i32 {
2014 match self.closest_tag(idx, "select") {
2015 Some(sel) => self
2016 .select_options(sel)
2017 .iter()
2018 .position(|&o| o == idx)
2019 .map(|p| p as i32)
2020 .unwrap_or(0),
2021 None => 0,
2022 }
2023 }
2024
2025 pub fn get_attr(&self, idx: usize, name: &str) -> Option<String> {
2026 let n = self.nodes.get(idx)?;
2027 match name {
2028 "id" => Some(n.id.clone()),
2029 "class" => Some(n.classes.join(" ")),
2030 _ => n.attrs.get(name).cloned(),
2031 }
2032 }
2033 pub fn set_attr(&mut self, idx: usize, name: &str, val: &str) {
2034 let old_value = self.get_attr(idx, name);
2035 let unchanged = old_value.as_deref() == Some(val) && (name != "id" || !val.is_empty());
2039 if unchanged || name.starts_with('_') {
2040 if let Some(n) = self.nodes.get_mut(idx) {
2041 match name {
2042 "id" => n.id = val.to_string(),
2043 "class" => {}
2044 _ => {
2045 n.attrs.insert(name.to_string(), val.to_string());
2046 }
2047 }
2048 }
2049 self.notify_attribute(idx, name, old_value.as_deref());
2050 return;
2051 }
2052 if let Some(n) = self.nodes.get_mut(idx) {
2053 match name {
2054 "id" => n.id = val.to_string(),
2055 "class" => {
2056 n.classes = val.split_whitespace().map(String::from).collect();
2057 n.classes_dirty = true;
2058 }
2059 _ => {
2060 n.attrs.insert(name.to_string(), val.to_string());
2061 }
2062 }
2063 self.mark_structural_dirty();
2064 }
2065 self.notify_attribute(idx, name, old_value.as_deref());
2066 }
2067
2068 pub fn has_attr(&self, idx: usize, name: &str) -> bool {
2070 match self.nodes.get(idx) {
2071 Some(n) => match name {
2072 "id" => !n.id.is_empty(),
2073 "class" => !n.classes.is_empty(),
2074 _ => n.attrs.contains_key(name),
2075 },
2076 None => false,
2077 }
2078 }
2079
2080 pub fn remove_attr(&mut self, idx: usize, name: &str) {
2082 let old_value = self.get_attr(idx, name);
2086 if !self.has_attr(idx, name) || name.starts_with('_') {
2088 if let Some(n) = self.nodes.get_mut(idx) {
2089 n.attrs.remove(name);
2090 }
2091 self.notify_attribute(idx, name, old_value.as_deref());
2092 return;
2093 }
2094 if let Some(n) = self.nodes.get_mut(idx) {
2095 match name {
2096 "id" => n.id = String::new(),
2097 "class" => {
2098 n.classes.clear();
2099 n.classes_dirty = true;
2100 }
2101 _ => {
2102 n.attrs.remove(name);
2103 }
2104 }
2105 self.mark_structural_dirty();
2106 }
2107 self.notify_attribute(idx, name, old_value.as_deref());
2108 }
2109
2110 pub fn get_dataset(&self, idx: usize) -> BTreeMap<String, String> {
2112 match self.nodes.get(idx) {
2113 Some(n) => crate::os_lib::dom_dataset::extract_dataset(&n.attrs),
2114 None => BTreeMap::new(),
2115 }
2116 }
2117
2118 pub fn close_details_group_siblings(&mut self, idx: usize) -> alloc::vec::Vec<usize> {
2130 let name = match self.nodes.get(idx).and_then(|n| n.attrs.get("name")) {
2131 Some(n) if !n.is_empty() => n.clone(),
2132 _ => return alloc::vec::Vec::new(),
2133 };
2134 let siblings: alloc::vec::Vec<usize> = self
2135 .nodes
2136 .iter()
2137 .enumerate()
2138 .filter(|(i, n)| {
2139 *i != idx
2140 && n.tag == "details"
2141 && n.attrs.get("name").map(|s| s.as_str()) == Some(name.as_str())
2142 && n.attrs.contains_key("open")
2143 })
2144 .map(|(i, _)| i)
2145 .collect();
2146 for &i in &siblings {
2147 self.remove_attr(i, "open");
2148 }
2149 siblings
2150 }
2151
2152 pub fn uncheck_radio_group_siblings(&mut self, idx: usize) {
2153 let name = match self.nodes.get(idx).and_then(|n| n.attrs.get("name")) {
2154 Some(n) if !n.is_empty() => n.clone(),
2155 _ => return,
2156 };
2157 let siblings: Vec<usize> = self
2158 .nodes
2159 .iter()
2160 .enumerate()
2161 .filter(|(i, n)| {
2162 *i != idx
2163 && n.tag == "input"
2164 && n.attrs.get("type").map(|t| t.as_str()) == Some("radio")
2165 && n.attrs.get("name").map(|s| s.as_str()) == Some(name.as_str())
2166 })
2167 .map(|(i, _)| i)
2168 .collect();
2169 for i in siblings {
2170 self.remove_attr(i, "checked");
2171 self.set_attr(i, "_live_checked", "false");
2172 }
2173 }
2174
2175 pub fn node_matches(&self, idx: usize, selector: &str) -> bool {
2183 self.node_matches_scoped(idx, selector, None)
2184 }
2185
2186 pub fn node_matches_scoped(&self, idx: usize, selector: &str, scope_root: Option<usize>) -> bool {
2192 if self.nodes.get(idx).map(|n| n.is_text).unwrap_or(true) {
2193 return false;
2194 }
2195 for part in split_top_level_commas(selector) {
2196 let steps = build_steps(&tokenize_selector_group(part.trim()));
2197 if steps.is_empty() {
2198 continue;
2199 }
2200 let last = steps.len() - 1;
2201 if self.compound_matches(idx, &steps[last].compound, scope_root)
2202 && self.match_selector_chain(idx, &steps, last, scope_root)
2203 {
2204 return true;
2205 }
2206 }
2207 false
2208 }
2209
2210 fn compound_matches(&self, idx: usize, c: &CompoundSel, scope_root: Option<usize>) -> bool {
2212 if c.is_scope && Some(idx) != scope_root {
2213 return false;
2214 }
2215 let n = match self.nodes.get(idx) {
2216 Some(n) if !n.is_text => n,
2217 _ => return false,
2218 };
2219 if let Some(tag) = &c.tag {
2220 if !n.tag.eq_ignore_ascii_case(tag) {
2221 return false;
2222 }
2223 }
2224 if let Some(id) = &c.id {
2225 if &n.id != id {
2226 return false;
2227 }
2228 }
2229 if !c.classes.iter().all(|cls| n.classes.iter().any(|nc| nc == cls)) {
2230 return false;
2231 }
2232 for attr in &c.attrs {
2233 let val = match n.attrs.get(&attr.name) {
2234 Some(v) => v,
2235 None => return false,
2236 };
2237 let ok = match attr.op {
2238 AttrOp::Exists => true,
2239 AttrOp::Eq => val == &attr.value,
2240 AttrOp::Prefix => val.starts_with(attr.value.as_str()),
2241 AttrOp::Suffix => val.ends_with(attr.value.as_str()),
2242 AttrOp::Contains => !attr.value.is_empty() && val.contains(attr.value.as_str()),
2243 AttrOp::Word => val.split_whitespace().any(|w| w == attr.value),
2244 };
2245 if !ok {
2246 return false;
2247 }
2248 }
2249 for not_sel in &c.not {
2250 if self.compound_matches(idx, not_sel, scope_root) {
2251 return false;
2252 }
2253 }
2254 if !c.is_list.is_empty()
2255 && !c.is_list.iter().any(|s| self.compound_matches(idx, s, scope_root))
2256 {
2257 return false;
2258 }
2259 if !c.pos.is_empty() {
2260 let (position, count) = self.element_sibling_position(idx);
2261 for p in &c.pos {
2262 let ok = match p {
2263 PosPseudo::First => position == 1,
2264 PosPseudo::Last => position == count,
2265 PosPseudo::Only => position == 1 && count == 1,
2266 PosPseudo::Nth(f) => nth_formula_matches(*f, position),
2267 PosPseudo::NthLast(f) => nth_formula_matches(*f, count - position + 1),
2268 };
2269 if !ok {
2270 return false;
2271 }
2272 }
2273 }
2274 if !c.pos_of_type.is_empty() {
2275 let (position, count) = self.element_type_sibling_position(idx);
2276 for p in &c.pos_of_type {
2277 let ok = match p {
2278 PosPseudo::First => position == 1,
2279 PosPseudo::Last => position == count,
2280 PosPseudo::Only => position == 1 && count == 1,
2281 PosPseudo::Nth(f) => nth_formula_matches(*f, position),
2282 PosPseudo::NthLast(f) => nth_formula_matches(*f, count - position + 1),
2283 };
2284 if !ok {
2285 return false;
2286 }
2287 }
2288 }
2289 if c.is_root {
2290 let is_root_elem = n.tag == "html" || n.parent.is_none();
2292 if !is_root_elem {
2293 return false;
2294 }
2295 }
2296 if c.is_empty {
2297 let has_content = n.children.iter().any(|&cid| {
2299 if let Some(child) = self.nodes.get(cid) {
2300 !child.is_text || !child.text_override.as_deref().unwrap_or(&child.initial_text).is_empty()
2301 } else {
2302 false
2303 }
2304 });
2305 if has_content {
2306 return false;
2307 }
2308 }
2309 if c.is_checked {
2310 let checked = if let Some(lc) = n.attrs.get("_live_checked") {
2311 lc == "true"
2312 } else {
2313 n.attrs.contains_key("checked")
2314 };
2315 if !checked {
2316 return false;
2317 }
2318 }
2319 if c.is_disabled {
2320 if !n.attrs.contains_key("disabled") {
2321 return false;
2322 }
2323 }
2324 if c.is_enabled {
2325 if n.attrs.contains_key("disabled") {
2326 return false;
2327 }
2328 }
2329 true
2330 }
2331
2332 fn element_sibling_position(&self, idx: usize) -> (usize, usize) {
2335 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2336 Some(p) => p,
2337 None => return (1, 1),
2338 };
2339 let siblings = match self.nodes.get(parent) {
2340 Some(n) => &n.children,
2341 None => return (1, 1),
2342 };
2343 let elements: Vec<usize> = siblings
2344 .iter()
2345 .filter(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true))
2346 .copied()
2347 .collect();
2348 let count = elements.len();
2349 let position = elements.iter().position(|&c| c == idx).map(|p| p + 1).unwrap_or(0);
2350 (position, count)
2351 }
2352
2353 fn element_type_sibling_position(&self, idx: usize) -> (usize, usize) {
2356 let tag = match self.nodes.get(idx) {
2357 Some(n) => n.tag.clone(),
2358 None => return (1, 1),
2359 };
2360 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2361 Some(p) => p,
2362 None => return (1, 1),
2363 };
2364 let siblings = match self.nodes.get(parent) {
2365 Some(n) => &n.children,
2366 None => return (1, 1),
2367 };
2368 let same_type: Vec<usize> = siblings
2369 .iter()
2370 .filter(|&&c| self.nodes.get(c).map(|n| !n.is_text && n.tag == tag).unwrap_or(false))
2371 .copied()
2372 .collect();
2373 let count = same_type.len();
2374 let position = same_type.iter().position(|&c| c == idx).map(|p| p + 1).unwrap_or(0);
2375 (position, count)
2376 }
2377
2378 fn match_selector_chain(
2381 &self,
2382 idx: usize,
2383 steps: &[SelStep],
2384 step_i: usize,
2385 scope_root: Option<usize>,
2386 ) -> bool {
2387 if step_i == 0 {
2388 return true;
2389 }
2390 let combinator = match steps[step_i].combinator {
2391 Some(c) => c,
2392 None => return true,
2393 };
2394 match combinator {
2395 Combinator::Descendant => {
2396 let mut cur = self.nodes.get(idx).and_then(|n| n.parent);
2397 let mut guard = 0;
2398 while let Some(p) = cur {
2399 if self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2400 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2401 {
2402 return true;
2403 }
2404 cur = self.nodes.get(p).and_then(|n| n.parent);
2405 guard += 1;
2406 if guard > self.nodes.len() {
2407 break;
2408 }
2409 }
2410 false
2411 }
2412 Combinator::Child => match self.nodes.get(idx).and_then(|n| n.parent) {
2413 Some(p) => {
2414 self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2415 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2416 }
2417 None => false,
2418 },
2419 Combinator::Adjacent => match self.prev_element_sibling(idx) {
2420 Some(p) => {
2421 self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2422 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2423 }
2424 None => false,
2425 },
2426 Combinator::General => {
2427 for p in self.preceding_element_siblings(idx) {
2428 if self.compound_matches(p, &steps[step_i - 1].compound, scope_root)
2429 && self.match_selector_chain(p, steps, step_i - 1, scope_root)
2430 {
2431 return true;
2432 }
2433 }
2434 false
2435 }
2436 }
2437 }
2438
2439 fn prev_element_sibling(&self, idx: usize) -> Option<usize> {
2441 let parent = self.nodes.get(idx)?.parent?;
2442 let siblings = &self.nodes.get(parent)?.children;
2443 let pos = siblings.iter().position(|&c| c == idx)?;
2444 siblings[..pos].iter().rev().find(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true)).copied()
2445 }
2446
2447 fn preceding_element_siblings(&self, idx: usize) -> Vec<usize> {
2449 let parent = match self.nodes.get(idx).and_then(|n| n.parent) {
2450 Some(p) => p,
2451 None => return Vec::new(),
2452 };
2453 let siblings = match self.nodes.get(parent) {
2454 Some(n) => &n.children,
2455 None => return Vec::new(),
2456 };
2457 let pos = match siblings.iter().position(|&c| c == idx) {
2458 Some(p) => p,
2459 None => return Vec::new(),
2460 };
2461 siblings[..pos]
2462 .iter()
2463 .filter(|&&c| !self.nodes.get(c).map(|n| n.is_text).unwrap_or(true))
2464 .copied()
2465 .collect()
2466 }
2467
2468 pub fn is_ancestor_of(&self, anc: usize, idx: usize) -> bool {
2470 let mut cur = idx;
2471 let mut guard = 0;
2472 while let Some(p) = self.nodes.get(cur).and_then(|n| n.parent) {
2473 if p == anc {
2474 return true;
2475 }
2476 cur = p;
2477 guard += 1;
2478 if guard > self.nodes.len() {
2479 break;
2480 }
2481 }
2482 false
2483 }
2484
2485 pub fn document_position(&self, this_idx: usize, other_idx: usize) -> u32 {
2493 const DISCONNECTED: u32 = 0x01;
2494 const PRECEDING: u32 = 0x02;
2495 const FOLLOWING: u32 = 0x04;
2496 const CONTAINS: u32 = 0x08;
2497 const CONTAINED_BY: u32 = 0x10;
2498 const IMPLEMENTATION_SPECIFIC: u32 = 0x20;
2499
2500 if this_idx == other_idx {
2501 return 0;
2502 }
2503 if self.is_ancestor_of(other_idx, this_idx) {
2504 return CONTAINS | PRECEDING;
2506 }
2507 if self.is_ancestor_of(this_idx, other_idx) {
2508 return CONTAINED_BY | FOLLOWING;
2510 }
2511 let chain_of = |start: usize| -> Vec<usize> {
2512 let mut chain = alloc::vec![start];
2513 let mut cur = start;
2514 let mut guard = 0;
2515 while let Some(p) = self.nodes.get(cur).and_then(|n| n.parent) {
2516 chain.push(p);
2517 cur = p;
2518 guard += 1;
2519 if guard > self.nodes.len() {
2520 break;
2521 }
2522 }
2523 chain.reverse(); chain
2525 };
2526 let chain_a = chain_of(this_idx);
2527 let chain_b = chain_of(other_idx);
2528 if chain_a.first() != chain_b.first() {
2529 let order = if this_idx < other_idx { FOLLOWING } else { PRECEDING };
2531 return DISCONNECTED | IMPLEMENTATION_SPECIFIC | order;
2532 }
2533 let mut i = 0;
2534 while i < chain_a.len() && i < chain_b.len() && chain_a[i] == chain_b[i] {
2535 i += 1;
2536 }
2537 if i == 0 || i >= chain_a.len() || i >= chain_b.len() {
2538 return DISCONNECTED | IMPLEMENTATION_SPECIFIC;
2540 }
2541 let lca = chain_a[i - 1];
2542 let child_a = chain_a[i];
2543 let child_b = chain_b[i];
2544 let siblings = match self.nodes.get(lca) {
2545 Some(n) => &n.children,
2546 None => return DISCONNECTED | IMPLEMENTATION_SPECIFIC,
2547 };
2548 let pos_a = siblings.iter().position(|&c| c == child_a);
2549 let pos_b = siblings.iter().position(|&c| c == child_b);
2550 match (pos_a, pos_b) {
2551 (Some(pa), Some(pb)) if pa < pb => FOLLOWING,
2552 (Some(pa), Some(pb)) if pa > pb => PRECEDING,
2553 _ => DISCONNECTED | IMPLEMENTATION_SPECIFIC,
2554 }
2555 }
2556
2557 pub fn offset_parent(&self, idx: usize) -> Option<usize> {
2562 let mut cur = self.nodes.get(idx).and_then(|n| n.parent);
2563 let mut body_idx = None;
2564 let mut guard = 0;
2565 while let Some(c) = cur {
2566 if self.nodes.get(c).map(|n| !n.is_text && n.tag == "body").unwrap_or(false) {
2567 body_idx = Some(c);
2568 }
2569 let pos = self.get_computed(c, "position");
2570 if pos == "absolute" || pos == "relative" || pos == "fixed" || pos == "sticky" {
2571 return Some(c);
2572 }
2573 cur = self.nodes.get(c).and_then(|n| n.parent);
2574 guard += 1;
2575 if guard > self.nodes.len() {
2576 break;
2577 }
2578 }
2579 body_idx
2580 }
2581
2582 pub fn closest_tag(&self, idx: usize, tag: &str) -> Option<usize> {
2583 let mut cur = Some(idx);
2584 let mut guard = 0;
2585 while let Some(c) = cur {
2586 if self
2587 .nodes
2588 .get(c)
2589 .map(|n| !n.is_text && n.tag == tag)
2590 .unwrap_or(false)
2591 {
2592 return Some(c);
2593 }
2594 cur = self.nodes.get(c).and_then(|n| n.parent);
2595 guard += 1;
2596 if guard > self.nodes.len() {
2597 break;
2598 }
2599 }
2600 None
2601 }
2602
2603 pub fn associated_form(&self, idx: usize) -> Option<usize> {
2612 if let Some(form_id) = self.get_attr(idx, "form") {
2613 if let Some(fi) = self.get_element_by_id(&form_id) {
2614 if self.nodes.get(fi).map(|n| n.tag == "form").unwrap_or(false) {
2615 return Some(fi);
2616 }
2617 }
2618 }
2619 self.closest_tag(idx, "form")
2620 }
2621
2622 pub fn form_associated_controls(&self, form_idx: usize, tags: &[&str]) -> Vec<usize> {
2630 let mut out = self.descendants_by_tags(form_idx, tags);
2631 if let Some(form_id) = self.get_attr(form_idx, "id") {
2632 if !form_id.is_empty() {
2633 for (i, n) in self.nodes.iter().enumerate() {
2634 if n.is_text || out.contains(&i) {
2635 continue;
2636 }
2637 if !tags.contains(&n.tag.as_str()) {
2638 continue;
2639 }
2640 if n.attrs.get("form").map(|v| v == &form_id).unwrap_or(false) {
2641 out.push(i);
2642 }
2643 }
2644 }
2645 }
2646 out
2647 }
2648
2649 pub fn first_by_tag(&self, tag: &str) -> Option<usize> {
2651 (0..self.nodes.len())
2652 .find(|&i| !self.nodes[i].is_text && self.nodes[i].tag == tag && self.is_attached(i))
2653 }
2654
2655 pub fn element_children(&self, idx: usize) -> Vec<usize> {
2657 self.nodes
2658 .get(idx)
2659 .map(|n| {
2660 n.children
2661 .iter()
2662 .copied()
2663 .filter(|c| self.nodes.get(*c).map(|cn| !cn.is_text).unwrap_or(false))
2664 .collect()
2665 })
2666 .unwrap_or_default()
2667 }
2668
2669 pub fn descendants_by_tag_name(&self, root: usize, tag: &str) -> Vec<usize> {
2690 let want_all = tag == "*";
2691 let lower = tag.to_ascii_lowercase();
2692 let mut out = Vec::new();
2693 let mut stack: Vec<usize> = self.element_children(root);
2694 let mut i = 0;
2695 while i < stack.len() {
2696 let idx = stack[i];
2697 i += 1;
2698 if let Some(n) = self.nodes.get(idx) {
2699 if !n.is_text && (want_all || n.tag.eq_ignore_ascii_case(&lower)) {
2700 out.push(idx);
2701 }
2702 for &c in &n.children {
2703 if self.nodes.get(c).map(|cn| !cn.is_text).unwrap_or(false) {
2704 stack.push(c);
2705 }
2706 }
2707 }
2708 }
2709 out
2710 }
2711
2712 pub fn descendants_by_tags(&self, root: usize, tags: &[&str]) -> Vec<usize> {
2713 let mut out = Vec::new();
2714 let mut stack: Vec<usize> = self.element_children(root);
2715 let mut i = 0;
2717 while i < stack.len() {
2718 let idx = stack[i];
2719 i += 1;
2720 if let Some(n) = self.nodes.get(idx) {
2721 if !n.is_text && tags.iter().any(|t| n.tag == *t) {
2722 out.push(idx);
2723 }
2724 for &c in &n.children {
2726 if self.nodes.get(c).map(|cn| !cn.is_text).unwrap_or(false) {
2727 stack.push(c);
2728 }
2729 }
2730 }
2731 }
2732 out
2733 }
2734
2735 pub fn is_disabled(&self, idx: usize) -> bool {
2744 let n = match self.nodes.get(idx) {
2745 Some(n) => n,
2746 None => return false,
2747 };
2748 if n.attrs.contains_key("disabled") {
2749 return true;
2750 }
2751 let mut cur = n.parent;
2752 while let Some(i) = cur {
2753 let anc = match self.nodes.get(i) {
2754 Some(a) => a,
2755 None => break,
2756 };
2757 if anc.tag == "fieldset" && anc.attrs.contains_key("disabled") {
2758 return true;
2759 }
2760 cur = anc.parent;
2761 }
2762 false
2763 }
2764
2765 pub fn validity_flags(&self, idx: usize, value: &str) -> ValidityFlags {
2770 let mut f = ValidityFlags::default();
2771 let n = match self.nodes.get(idx) {
2772 Some(n) => n,
2773 None => return f,
2774 };
2775 if n.is_text
2783 || self.is_disabled(idx)
2784 || !matches!(n.tag.as_str(), "button" | "input" | "select" | "textarea")
2785 {
2786 return f;
2787 }
2788 let input_type = n
2789 .attrs
2790 .get("type")
2791 .map(|s| s.trim().to_lowercase())
2792 .unwrap_or_default();
2793 if matches!(
2794 input_type.as_str(),
2795 "submit" | "button" | "reset" | "hidden" | "image"
2796 ) {
2797 return f;
2798 }
2799 f.custom_error = n
2800 .attrs
2801 .get("_custom_validity")
2802 .map(|s| !s.is_empty())
2803 .unwrap_or(false);
2804 let trimmed = value.trim();
2805 f.value_missing = n.attrs.contains_key("required") && !self.required_satisfied(n, &input_type, trimmed);
2808 if matches!(input_type.as_str(), "checkbox" | "radio") || value.is_empty() {
2809 return f; }
2811 let char_len = value.chars().count();
2812 if let Some(ml) = n.attrs.get("minlength").and_then(|v| v.trim().parse::<usize>().ok()) {
2813 f.too_short = char_len < ml;
2814 }
2815 if let Some(ml) = n.attrs.get("maxlength").and_then(|v| v.trim().parse::<usize>().ok()) {
2816 f.too_long = char_len > ml;
2817 }
2818 match input_type.as_str() {
2819 "email" => f.type_mismatch = !Self::is_valid_email_value(n, value),
2820 "url" => {
2821 f.type_mismatch = !(value.starts_with("http://") || value.starts_with("https://"))
2822 }
2823 "date" | "month" | "datetime-local" => match super::builtins::input_value_as_date_ms(&input_type, value) {
2841 Some(ms) => {
2842 if let Some(min) = n
2843 .attrs
2844 .get("min")
2845 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2846 {
2847 f.range_underflow = ms < min;
2848 }
2849 if let Some(max) = n
2850 .attrs
2851 .get("max")
2852 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
2853 {
2854 f.range_overflow = ms > max;
2855 }
2856 }
2857 None => f.type_mismatch = true,
2858 },
2859 "number" | "range" => match value.trim().parse::<f64>() {
2866 Ok(num) => {
2867 if let Some(min) = n.attrs.get("min").and_then(|v| v.trim().parse::<f64>().ok()) {
2868 f.range_underflow = num < min;
2869 }
2870 if let Some(max) = n.attrs.get("max").and_then(|v| v.trim().parse::<f64>().ok()) {
2871 f.range_overflow = num > max;
2872 }
2873 let step_attr = n.attrs.get("step").map(|s| s.trim());
2874 if step_attr != Some("any") {
2875 let step = step_attr
2876 .and_then(|s| s.parse::<f64>().ok())
2877 .filter(|s| *s > 0.0)
2878 .unwrap_or(1.0);
2879 let base = n
2880 .attrs
2881 .get("min")
2882 .and_then(|v| v.trim().parse::<f64>().ok())
2883 .unwrap_or(0.0);
2884 let steps = (num - base) / step;
2885 f.step_mismatch = libm::fabs(steps - libm::round(steps)) > 1e-9;
2886 }
2887 }
2888 Err(_) => f.bad_input = true,
2889 },
2890 _ => {}
2891 }
2892 if let Some(pat) = n.attrs.get("pattern") {
2893 if !pat.is_empty() {
2894 f.pattern_mismatch = !Self::pattern_matches(pat, value);
2895 }
2896 }
2897 f
2898 }
2899
2900 fn required_satisfied(&self, n: &DomNode, input_type: &str, trimmed_value: &str) -> bool {
2905 let is_checked = |node: &DomNode| {
2906 if let Some(lc) = node.attrs.get("_live_checked") {
2907 lc == "true"
2908 } else {
2909 node.attrs.contains_key("checked")
2910 }
2911 };
2912 match input_type {
2913 "checkbox" => is_checked(n),
2914 "radio" => {
2915 let name = n.attrs.get("name").cloned().unwrap_or_default();
2916 if name.is_empty() {
2917 is_checked(n)
2918 } else {
2919 self.nodes.iter().any(|other| {
2920 other.tag == "input"
2921 && other
2922 .attrs
2923 .get("type")
2924 .map(|t| t.eq_ignore_ascii_case("radio"))
2925 .unwrap_or(false)
2926 && other.attrs.get("name").map(|nm| nm == &name).unwrap_or(false)
2927 && is_checked(other)
2928 })
2929 }
2930 }
2931 _ => !trimmed_value.is_empty(),
2932 }
2933 }
2934
2935 pub fn validate_field(&self, idx: usize, value: &str) -> Option<String> {
2941 let n = self.nodes.get(idx)?;
2942 if n.is_text {
2943 return None;
2944 }
2945 if self.is_disabled(idx) {
2948 return None;
2949 }
2950 if !matches!(n.tag.as_str(), "button" | "input" | "select" | "textarea") {
2959 return None;
2960 }
2961 let input_type = n
2962 .attrs
2963 .get("type")
2964 .map(|s| s.trim().to_lowercase())
2965 .unwrap_or_default();
2966 if matches!(
2967 input_type.as_str(),
2968 "submit" | "button" | "reset" | "hidden" | "image"
2969 ) {
2970 return None;
2971 }
2972 if let Some(cv) = n.attrs.get("_custom_validity") {
2974 if !cv.is_empty() {
2975 return Some(cv.clone());
2976 }
2977 }
2978 let trimmed = value.trim();
2979 if n.attrs.contains_key("required") && !self.required_satisfied(n, &input_type, trimmed) {
2989 return Some(match input_type.as_str() {
2990 "checkbox" => String::from("このフィールドをオンにしてください。"),
2991 "radio" => String::from("いずれかを選択してください。"),
2992 _ => String::from("このフィールドを入力してください。"),
2993 });
2994 }
2995 if matches!(input_type.as_str(), "checkbox" | "radio") {
2996 return None;
2997 }
2998 if value.is_empty() {
3000 return None;
3001 }
3002 let char_len = value.chars().count();
3004 if let Some(ml) = n
3005 .attrs
3006 .get("minlength")
3007 .and_then(|v| v.trim().parse::<usize>().ok())
3008 {
3009 if char_len < ml {
3010 return Some(alloc::format!(
3011 "{}文字以上で入力してください(現在{}文字)。",
3012 ml,
3013 char_len
3014 ));
3015 }
3016 }
3017 if let Some(ml) = n
3018 .attrs
3019 .get("maxlength")
3020 .and_then(|v| v.trim().parse::<usize>().ok())
3021 {
3022 if char_len > ml {
3023 return Some(alloc::format!(
3024 "{}文字以下で入力してください(現在{}文字)。",
3025 ml,
3026 char_len
3027 ));
3028 }
3029 }
3030 match input_type.as_str() {
3032 "email" if !Self::is_valid_email_value(n, value) => {
3033 return Some(String::from("メールアドレスを入力してください。"));
3034 }
3035 "url" if !(value.starts_with("http://") || value.starts_with("https://")) => {
3036 return Some(String::from("URL を入力してください。"));
3037 }
3038 "date" | "month" | "datetime-local" => match super::builtins::input_value_as_date_ms(&input_type, value) {
3046 Some(ms) => {
3047 if let Some(min) = n
3048 .attrs
3049 .get("min")
3050 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
3051 {
3052 if ms < min {
3053 return Some(alloc::format!(
3054 "{}以降の日付を入力してください。",
3055 n.attrs.get("min").cloned().unwrap_or_default()
3056 ));
3057 }
3058 }
3059 if let Some(max) = n
3060 .attrs
3061 .get("max")
3062 .and_then(|v| super::builtins::input_value_as_date_ms(&input_type, v))
3063 {
3064 if ms > max {
3065 return Some(alloc::format!(
3066 "{}以前の日付を入力してください。",
3067 n.attrs.get("max").cloned().unwrap_or_default()
3068 ));
3069 }
3070 }
3071 }
3072 None => return Some(String::from("正しい日付を入力してください。")),
3073 },
3074 "number" | "range" => match value.trim().parse::<f64>() {
3078 Ok(num) => {
3079 if let Some(min) = n
3080 .attrs
3081 .get("min")
3082 .and_then(|v| v.trim().parse::<f64>().ok())
3083 {
3084 if num < min {
3085 return Some(alloc::format!("{}以上の値を入力してください。", min));
3086 }
3087 }
3088 if let Some(max) = n
3089 .attrs
3090 .get("max")
3091 .and_then(|v| v.trim().parse::<f64>().ok())
3092 {
3093 if num > max {
3094 return Some(alloc::format!("{}以下の値を入力してください。", max));
3095 }
3096 }
3097 let step_attr = n.attrs.get("step").map(|s| s.trim());
3101 if step_attr != Some("any") {
3102 let step = step_attr
3103 .and_then(|s| s.parse::<f64>().ok())
3104 .filter(|s| *s > 0.0)
3105 .unwrap_or(1.0);
3106 let base = n
3107 .attrs
3108 .get("min")
3109 .and_then(|v| v.trim().parse::<f64>().ok())
3110 .unwrap_or(0.0);
3111 let steps = (num - base) / step;
3112 if libm::fabs(steps - libm::round(steps)) > 1e-9 {
3113 return Some(alloc::format!(
3114 "{} の倍数(基準値 {})で入力してください。",
3115 step,
3116 base
3117 ));
3118 }
3119 }
3120 }
3121 Err(_) => return Some(String::from("数値を入力してください。")),
3122 },
3123 _ => {}
3124 }
3125 if let Some(pat) = n.attrs.get("pattern") {
3127 if !pat.is_empty() && !Self::pattern_matches(pat, value) {
3128 return Some(String::from("要求された形式で入力してください。"));
3129 }
3130 }
3131 None
3132 }
3133
3134 fn is_valid_email(s: &str) -> bool {
3136 let (local, domain) = match s.split_once('@') {
3137 Some(v) => v,
3138 None => return false,
3139 };
3140 if local.is_empty() || domain.is_empty() {
3141 return false;
3142 }
3143 if domain.contains('@') {
3144 return false;
3145 }
3146 match domain.split_once('.') {
3148 Some((a, b)) => !a.is_empty() && !b.is_empty(),
3149 None => false,
3150 }
3151 }
3152
3153 fn is_valid_email_value(n: &DomNode, value: &str) -> bool {
3161 if n.attrs.contains_key("multiple") {
3162 value.split(',').all(|part| Self::is_valid_email(part.trim()))
3163 } else {
3164 Self::is_valid_email(value)
3165 }
3166 }
3167
3168 fn pattern_matches(pat: &str, val: &str) -> bool {
3175 let anchored = format!("^(?:{})$", pat);
3176 let re = super::regex::Regex::new(&anchored, "");
3177 let chars: alloc::vec::Vec<char> = val.chars().collect();
3178 re.find_at(&chars, 0).is_some()
3179 }
3180
3181 pub fn attr_names(&self, idx: usize) -> Vec<String> {
3183 let mut out = Vec::new();
3184 if let Some(n) = self.nodes.get(idx) {
3185 if !n.id.is_empty() {
3186 out.push(String::from("id"));
3187 }
3188 if !n.classes.is_empty() {
3189 out.push(String::from("class"));
3190 }
3191 for k in n.attrs.keys() {
3196 if k != "id" && k != "class" && !k.starts_with('_') {
3197 out.push(k.clone());
3198 }
3199 }
3200 }
3201 out
3202 }
3203
3204 pub fn add_listener(&mut self, node: usize, event: &str, func: Value) {
3205 self.add_listener_opts(node, event, func, false, false);
3206 }
3207
3208 pub fn add_listener_opts(
3211 &mut self,
3212 node: usize,
3213 event: &str,
3214 func: Value,
3215 capture: bool,
3216 once: bool,
3217 ) -> u64 {
3218 self.add_listener_opts_signal(node, event, func, capture, once, None)
3219 }
3220
3221 pub fn add_listener_opts_signal(
3224 &mut self,
3225 node: usize,
3226 event: &str,
3227 func: Value,
3228 capture: bool,
3229 once: bool,
3230 signal: Option<Value>,
3231 ) -> u64 {
3232 if signal.as_ref().is_some_and(is_signal_aborted) {
3233 return 0;
3234 }
3235 if self.listeners.iter().any(|l| {
3237 l.node == node && l.event == event && l.capture == capture && l.func.same_ref(&func)
3238 }) {
3239 return 0;
3240 }
3241 let id = self.next_listener_id;
3242 self.next_listener_id += 1;
3243 self.listeners.push(Listener {
3244 node,
3245 event: event.to_string(),
3246 func,
3247 capture,
3248 once,
3249 id,
3250 signal,
3251 });
3252 id
3253 }
3254
3255 pub fn remove_listener(&mut self, node: usize, event: &str, func: &Value, capture: bool) {
3257 self.listeners.retain(|l| {
3258 !(l.node == node && l.event == event && l.capture == capture && l.func.same_ref(func))
3259 });
3260 }
3261
3262 pub fn remove_listener_by_id(&mut self, id: u64) {
3264 self.listeners.retain(|l| l.id != id);
3265 }
3266
3267 pub fn has_listener(&self, event: &str) -> bool {
3269 self.listeners.iter().any(|l| l.event == event)
3270 }
3271
3272 pub fn listeners_for(&self, node: usize, event: &str) -> Vec<Value> {
3276 self.listeners
3277 .iter()
3278 .filter(|l| l.node == node && l.event == event && !l.capture && !is_listener_aborted(l))
3279 .map(|l| l.func.clone())
3280 .collect()
3281 }
3282
3283 pub fn listeners_phase(
3287 &self,
3288 node: usize,
3289 event: &str,
3290 want_capture: bool,
3291 ) -> Vec<(Value, bool, u64)> {
3292 self.listeners
3293 .iter()
3294 .filter(|l| l.node == node && l.event == event && l.capture == want_capture && !is_listener_aborted(l))
3295 .map(|l| (l.func.clone(), l.once, l.id))
3296 .collect()
3297 }
3298
3299 pub fn has_listener_on(&self, node: usize, event: &str) -> bool {
3301 self.listeners
3302 .iter()
3303 .any(|l| l.node == node && l.event == event)
3304 }
3305
3306 pub fn apply_overrides(&self, root: &mut Node) {
3308 let mut counter = 0usize;
3309 self.apply_node(root, &mut counter);
3310 }
3311 fn apply_node(&self, node: &mut Node, counter: &mut usize) {
3312 let idx = *counter;
3313 *counter += 1;
3314 if let Some(dn) = self.nodes.get(idx) {
3315 match &mut node.node_type {
3316 NodeType::Text(t) => {
3317 if let Some(ov) = &dn.text_override {
3318 *t = ov.clone();
3319 }
3320 }
3321 NodeType::Element {
3322 attributes,
3323 classes,
3324 ..
3325 } => {
3326 if dn.classes_dirty {
3327 *classes = dn.classes.clone();
3328 attributes.insert("class".to_string(), dn.classes.join(" "));
3329 }
3330 if dn.style_dirty {
3331 attributes.insert("style".to_string(), serialize_inline_style(&dn.style));
3332 }
3333 }
3334 }
3335 }
3336 for c in &mut node.children {
3337 self.apply_node(c, counter);
3338 }
3339 }
3340}
3341
3342fn html_escape_text(s: &str) -> String {
3344 s.replace('&', "&").replace('<', "<").replace('>', ">")
3345}
3346fn html_escape_attr(s: &str) -> String {
3348 s.replace('&', "&").replace('"', """)
3349}
3350
3351fn strip_style_priority(raw: &str) -> (&str, bool) {
3357 let trimmed = raw.trim_end();
3358 if let Some(pos) = trimmed.to_ascii_lowercase().rfind("!important") {
3359 if trimmed.get(pos..).unwrap_or("").eq_ignore_ascii_case("!important") {
3360 let before = trimmed.get(..pos).unwrap_or("").trim_end();
3361 return (before, true);
3362 }
3363 }
3364 (raw, false)
3365}
3366
3367fn parse_inline_style(s: &str) -> BTreeMap<String, String> {
3369 let mut m = BTreeMap::new();
3370 for decl in s.split(';') {
3371 let decl = decl.trim();
3372 if decl.is_empty() {
3373 continue;
3374 }
3375 if let Some(colon) = decl.find(':') {
3376 let k = decl.get(..colon).unwrap_or("").trim().to_lowercase();
3377 let v = decl.get(colon + 1..).unwrap_or("").trim().to_string();
3378 if !k.is_empty() && (!v.is_empty() || k.starts_with("--")) {
3380 m.insert(k, v);
3381 }
3382 }
3383 }
3384 m
3385}
3386
3387fn serialize_inline_style(m: &BTreeMap<String, String>) -> String {
3388 let mut parts = Vec::new();
3389 for (k, v) in m {
3390 parts.push(format!("{}: {}", k, v));
3391 }
3392 parts.join("; ")
3393}
3394
3395fn css_prop_from_camel(prop: &str) -> String {
3397 if prop.contains('-') || !prop.chars().any(|c| c.is_ascii_uppercase()) {
3398 return prop.to_lowercase();
3399 }
3400 let mut out = String::new();
3401 for c in prop.chars() {
3402 if c.is_ascii_uppercase() {
3403 out.push('-');
3404 out.push(c.to_ascii_lowercase());
3405 } else {
3406 out.push(c);
3407 }
3408 }
3409 out
3410}
3411
3412#[derive(Debug, Clone, PartialEq)]
3414pub enum CeEvent {
3415 Attr(usize, String, Option<String>, Option<String>),
3417 Inserted(usize),
3419 Removed(usize),
3421 Created(usize),
3423}
3424
3425impl DomBridge {
3426 pub fn subtree_elements(&self, root: usize) -> Vec<usize> {
3428 let mut out = Vec::new();
3429 let mut stack = alloc::vec![root];
3430 while let Some(i) = stack.pop() {
3431 let Some(n) = self.nodes.get(i) else { continue };
3432 if n.is_text {
3433 continue;
3434 }
3435 out.push(i);
3436 for &c in n.children.iter().rev() {
3437 stack.push(c);
3438 }
3439 }
3440 out
3441 }
3442}