1use crate::Document;
2use crate::layout::damage::HoistedPaintChildren;
3use bitflags::bitflags;
4use blitz_traits::events::{
5 BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
6};
7use blitz_traits::shell::ShellProvider;
8use euclid::{Point2D, Rect, Size2D};
9use html_escape::encode_quoted_attribute_to_string;
10use keyboard_types::Modifiers;
11use kurbo::{Affine, Rect as KurboRect};
12use markup5ever::{LocalName, local_name};
13use parley::{BreakReason, Cluster, ClusterSide};
14use selectors::matching::ElementSelectorFlags;
15use slab::Slab;
16use std::cell::{Cell, RefCell};
17use std::fmt::Write;
18use std::ops::Deref;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
21use style::Atom;
22use style::invalidation::element::restyle_hints::RestyleHint;
23use style::properties::ComputedValues;
24use style::properties::generated::longhands::position::computed_value::T as Position;
25use style::selector_parser::{PseudoElement, RestyleDamage};
26use style::servo_arc::Arc as ServoArc;
27use style::shared_lock::SharedRwLock;
28use style::stylesheets::UrlExtraData;
29use style::values::computed::CSSPixelLength;
30use style::values::computed::Display as StyloDisplay;
31use style::values::specified::box_::{DisplayInside, DisplayOutside};
32use style_dom::ElementState;
33use style_traits::values::ToCss;
34use taffy::{
35 Cache,
36 prelude::{Layout, Style},
37};
38
39use super::stylo_data::StyloData;
40use super::{Attribute, ElementData};
41
42#[derive(Clone, Copy)]
43enum OutputStyle {
44 Normal,
45 Pretty,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum DisplayOuter {
50 Block,
51 Inline,
52 None,
53}
54
55bitflags! {
56 #[derive(Clone, Copy, PartialEq)]
57 pub struct NodeFlags: u32 {
58 const IS_INLINE_ROOT = 0b00000001;
60 const IS_TABLE_ROOT = 0b00000010;
62 const IS_IN_DOCUMENT = 0b00000100;
64 }
65}
66
67impl NodeFlags {
68 #[inline(always)]
69 pub fn is_inline_root(&self) -> bool {
70 self.contains(Self::IS_INLINE_ROOT)
71 }
72
73 #[inline(always)]
74 pub fn is_table_root(&self) -> bool {
75 self.contains(Self::IS_TABLE_ROOT)
76 }
77
78 #[inline(always)]
79 pub fn is_in_document(&self) -> bool {
80 self.contains(Self::IS_IN_DOCUMENT)
81 }
82
83 #[inline(always)]
84 pub fn reset_construction_flags(&mut self) {
85 self.remove(Self::IS_INLINE_ROOT);
86 self.remove(Self::IS_TABLE_ROOT);
87 }
88}
89
90pub struct Node {
91 tree: *mut Slab<Node>,
93
94 pub id: usize,
96 pub instance_id: u64,
98 pub parent: Option<usize>,
100 pub children: Vec<usize>,
102 pub layout_parent: Cell<Option<usize>>,
104 pub layout_children: RefCell<Option<Vec<usize>>>,
106 pub paint_children: RefCell<Option<Vec<usize>>>,
108 pub stacking_context: Option<Box<HoistedPaintChildren>>,
109
110 pub flags: NodeFlags,
112
113 pub data: NodeData,
115
116 pub stylo_element_data: StyloData,
119 pub selector_flags: Cell<ElementSelectorFlags>,
120 pub guard: SharedRwLock,
121 pub element_state: ElementState,
122 pub has_snapshot: bool,
123 pub snapshot_handled: AtomicBool,
124 pub dirty_descendants: AtomicBool,
127
128 pub before: Option<usize>,
130 pub after: Option<usize>,
131
132 pub style: Style<Atom>,
134 pub display_constructed_as: StyloDisplay,
135 pub cache: Cache,
136 pub unrounded_layout: Layout,
137 pub final_layout: Layout,
138 pub scroll_offset: crate::Point<f64>,
139
140 pub scrollable_overflow: KurboRect,
141 pub transform: Option<Affine>,
142}
143
144unsafe impl Send for Node {}
145unsafe impl Sync for Node {}
146
147impl Node {
148 pub(crate) fn new(
149 tree: *mut Slab<Node>,
150 id: usize,
151 guard: SharedRwLock,
152 data: NodeData,
153 ) -> Self {
154 static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
155 let state = match &data {
157 NodeData::Element(data) => {
158 let mut state = ElementState::empty();
159 if data.can_be_disabled() {
160 state.insert(match data.has_attr(local_name!("disabled")) {
161 true => ElementState::DISABLED,
162 false => ElementState::ENABLED,
163 })
164 }
165
166 state
167 }
168 _ => ElementState::empty(),
169 };
170
171 Self {
172 tree,
173
174 id,
175 instance_id: NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed),
176 parent: None,
177 children: vec![],
178 layout_parent: Cell::new(None),
179 layout_children: RefCell::new(None),
180 paint_children: RefCell::new(None),
181 stacking_context: None,
182
183 flags: NodeFlags::empty(),
184 data,
185
186 stylo_element_data: Default::default(),
187 selector_flags: Cell::new(ElementSelectorFlags::empty()),
188 guard,
189 element_state: state,
190
191 before: None,
192 after: None,
193
194 style: Default::default(),
195 has_snapshot: false,
196 snapshot_handled: AtomicBool::new(false),
197 dirty_descendants: AtomicBool::new(true),
198 display_constructed_as: StyloDisplay::Block,
199 cache: Cache::new(),
200 unrounded_layout: Layout::new(),
201 final_layout: Layout::new(),
202 scroll_offset: crate::Point::ZERO,
203
204 scrollable_overflow: KurboRect::ZERO,
205 transform: None,
206 }
207 }
208
209 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
210 self.transform = self.primary_styles().and_then(|s| {
211 let w = self.final_layout.size.width * scale;
212 let h = self.final_layout.size.height * scale;
213 let reference_box = Rect::new(
214 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
215 Size2D::new(CSSPixelLength::new(w), CSSPixelLength::new(h)),
216 );
217 crate::resolve_2d_transform(s.get_box(), reference_box)
218 });
219
220 self.transform
221 }
222
223 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
224 match index {
225 0 => self.after,
226 1 => self.before,
227 _ => panic!("Invalid pseudo element index"),
228 }
229 }
230
231 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
232 match index {
233 0 => self.after = value,
234 1 => self.before = value,
235 _ => panic!("Invalid pseudo element index"),
236 }
237 }
238
239 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
240 Some(self.primary_styles().as_ref()?.clone_display())
241 }
242
243 pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
245 let style = self.primary_styles()?;
246 Some(vec![
247 ("display", style.clone_display().to_css_string()),
248 ("color", style.clone_color().to_css_string()),
249 (
250 "background-color",
251 style.clone_background_color().to_css_string(),
252 ),
253 (
254 "font-size",
255 format!("{}px", style.clone_font_size().computed_size().px()),
256 ),
257 ("width", style.clone_width().to_css_string()),
258 ])
259 }
260
261 pub fn is_display_none(&self) -> bool {
263 self.display_style()
264 .is_some_and(|display| display.is_none())
265 }
266
267 pub fn is_or_contains_block(&self) -> bool {
268 let style = self.primary_styles();
269 let style = style.as_ref();
270
271 let position = style
273 .map(|s| s.clone_position())
274 .unwrap_or(Position::Relative);
275 let is_in_flow = matches!(
276 position,
277 Position::Static | Position::Relative | Position::Sticky
278 );
279 if !is_in_flow {
280 return false;
281 }
282 let display = style
283 .map(|s| s.clone_display())
284 .unwrap_or(StyloDisplay::inline());
285 match display.outside() {
286 DisplayOutside::None => false,
287 DisplayOutside::Block => true,
288 _ => {
289 if display.inside() == DisplayInside::Flow {
290 self.children
291 .iter()
292 .copied()
293 .any(|child_id| self.tree()[child_id].is_or_contains_block())
294 } else {
295 false
296 }
297 }
298 }
299 }
300
301 pub fn is_whitespace_node(&self) -> bool {
302 match &self.data {
303 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
304 _ => false,
305 }
306 }
307
308 pub fn is_focussable(&self) -> bool {
309 self.data
310 .downcast_element()
311 .map(|el| el.is_focussable)
312 .unwrap_or(false)
313 }
314
315 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
316 if let Some(mut element_data) = self.stylo_element_data.get_mut() {
317 element_data.hint.insert(hint);
318 }
319 self.mark_ancestors_dirty();
322 }
323
324 pub fn has_dirty_descendants(&self) -> bool {
326 self.dirty_descendants.load(Ordering::Relaxed)
327 }
328
329 pub fn set_dirty_descendants(&self) {
331 self.dirty_descendants.store(true, Ordering::Relaxed);
332 }
333
334 pub fn unset_dirty_descendants(&self) {
336 self.dirty_descendants.store(false, Ordering::Relaxed);
337 }
338
339 pub(crate) fn mark_style_attr_updated(&mut self) {
341 if let Some(mut data) = self.stylo_element_data.get_mut() {
342 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
343 }
344 self.set_dirty_descendants();
345 }
346
347 pub fn mark_ancestors_dirty(&self) {
351 let mut current_id = self.parent;
352 while let Some(parent_id) = current_id {
353 let parent = &self.tree()[parent_id];
354 if parent.dirty_descendants.swap(true, Ordering::Relaxed) {
357 break;
358 }
359 current_id = parent.parent;
360 }
361 }
362
363 pub fn damage(&self) -> Option<RestyleDamage> {
370 self.stylo_element_data.get().map(|data| data.damage)
371 }
372
373 pub fn set_damage(&mut self, damage: RestyleDamage) {
374 if let Some(mut data) = self.stylo_element_data.get_mut() {
375 data.damage = damage;
376 }
377 }
378
379 pub fn insert_damage(&mut self, damage: RestyleDamage) {
380 if let Some(mut data) = self.stylo_element_data.get_mut() {
381 data.damage |= damage;
382 }
383 }
384
385 pub fn remove_damage(&mut self, damage: RestyleDamage) {
386 if let Some(mut data) = self.stylo_element_data.get_mut() {
387 data.damage.remove(damage);
388 }
389 }
390
391 pub fn clear_damage_mut(&mut self) {
392 if let Some(mut data) = self.stylo_element_data.get_mut() {
393 data.damage = RestyleDamage::empty();
394 }
395 }
396
397 pub fn hover(&mut self) {
398 self.element_state.insert(ElementState::HOVER);
399 self.set_restyle_hint(RestyleHint::restyle_subtree());
400 }
401
402 pub fn unhover(&mut self) {
403 self.element_state.remove(ElementState::HOVER);
404 self.set_restyle_hint(RestyleHint::restyle_subtree());
405 }
406
407 pub fn is_hovered(&self) -> bool {
408 self.element_state.contains(ElementState::HOVER)
409 }
410
411 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
412 self.element_state
413 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
414 self.set_restyle_hint(RestyleHint::restyle_subtree());
415
416 if self
418 .element_data()
419 .and_then(|elem| elem.text_input_data())
420 .is_some()
421 {
422 shell_provider.set_ime_enabled(true);
423 let mut pos = self.absolute_position(0.0, 0.0);
424 pos.x += self.final_layout.content_box_x();
425 pos.y += self.final_layout.content_box_y();
426 let width = self.final_layout.content_box_width();
427 let height = self.final_layout.content_box_height();
428 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
429 }
430 }
431
432 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
433 self.element_state
434 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
435 self.set_restyle_hint(RestyleHint::restyle_subtree());
436
437 if self
439 .element_data()
440 .and_then(|elem| elem.text_input_data())
441 .is_some()
442 {
443 shell_provider.set_ime_enabled(false);
444 }
445 }
446
447 pub fn is_focussed(&self) -> bool {
448 self.element_state.contains(ElementState::FOCUS)
449 }
450
451 pub fn active(&mut self) {
452 self.element_state.insert(ElementState::ACTIVE);
453 self.set_restyle_hint(RestyleHint::restyle_subtree());
454 }
455
456 pub fn unactive(&mut self) {
457 self.element_state.remove(ElementState::ACTIVE);
458 self.set_restyle_hint(RestyleHint::restyle_subtree());
459 }
460
461 pub fn is_active(&self) -> bool {
462 self.element_state.contains(ElementState::ACTIVE)
463 }
464
465 pub fn disable(&mut self) {
468 if self
469 .data
470 .downcast_element()
471 .is_some_and(|data| data.can_be_disabled())
472 {
473 self.element_state.insert(ElementState::DISABLED);
474 self.element_state.remove(ElementState::ENABLED);
475 }
476 self.set_restyle_hint(RestyleHint::restyle_subtree());
477 }
478
479 pub fn enable(&mut self) {
482 if self
483 .data
484 .downcast_element()
485 .is_some_and(|data| data.can_be_disabled())
486 {
487 self.element_state.insert(ElementState::ENABLED);
488 self.element_state.remove(ElementState::DISABLED);
489 }
490 self.set_restyle_hint(RestyleHint::restyle_subtree());
491 }
492
493 pub fn subdoc(&self) -> Option<&dyn Document> {
494 self.element_data().and_then(|el| el.sub_doc_data())
495 }
496
497 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
498 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
499 }
500
501 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
502 if let Some(input_data) = self
505 .data
506 .downcast_element()
507 .and_then(|el| el.text_input_data())
508 {
509 if !input_data.is_multiline {
510 let content_box_height = self.final_layout.content_box_height();
511 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
512 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
513
514 return y_offset as f64;
515 }
516 }
517
518 0.0
519 }
520}
521
522#[derive(Debug, Clone, Copy, PartialEq)]
523pub enum NodeKind {
524 Document,
525 Element,
526 AnonymousBlock,
527 Text,
528 Comment,
529}
530
531#[derive(Debug, Clone)]
533pub enum NodeData {
534 Document,
536
537 Element(ElementData),
539
540 AnonymousBlock(ElementData),
542
543 Text(TextNodeData),
545
546 Comment,
548 }
557
558impl NodeData {
559 pub fn downcast_element(&self) -> Option<&ElementData> {
560 match self {
561 Self::Element(data) => Some(data),
562 Self::AnonymousBlock(data) => Some(data),
563 _ => None,
564 }
565 }
566
567 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
568 match self {
569 Self::Element(data) => Some(data),
570 Self::AnonymousBlock(data) => Some(data),
571 _ => None,
572 }
573 }
574
575 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
576 let Some(elem) = self.downcast_element() else {
577 return false;
578 };
579 *name == elem.name.local
580 }
581
582 pub fn attrs(&self) -> Option<&[Attribute]> {
583 Some(&self.downcast_element()?.attrs)
584 }
585
586 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
587 self.downcast_element()?.attr(name)
588 }
589
590 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
591 self.downcast_element()
592 .is_some_and(|elem| elem.has_attr(name))
593 }
594
595 pub fn kind(&self) -> NodeKind {
596 match self {
597 NodeData::Document => NodeKind::Document,
598 NodeData::Element(_) => NodeKind::Element,
599 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
600 NodeData::Text(_) => NodeKind::Text,
601 NodeData::Comment => NodeKind::Comment,
602 }
603 }
604}
605
606#[derive(Debug, Clone)]
607pub struct TextNodeData {
608 pub content: String,
610}
611
612impl TextNodeData {
613 pub fn new(content: String) -> Self {
614 Self { content }
615 }
616}
617
618impl Node {
643 pub fn tree(&self) -> &Slab<Node> {
644 unsafe { &*self.tree }
645 }
646
647 #[track_caller]
648 pub fn with(&self, id: usize) -> &Node {
649 self.tree().get(id).unwrap()
650 }
651
652 pub fn print_tree(&self, level: usize) {
653 println!(
654 "{} {} {:?} {} {:?}",
655 " ".repeat(level),
656 self.id,
657 self.parent,
658 self.node_debug_str().replace('\n', ""),
659 self.children
660 );
661 for child_id in self.children.iter() {
663 let child = self.with(*child_id);
664 child.print_tree(level + 1)
665 }
666 }
667
668 pub fn index_of_child(&self, child_id: usize) -> Option<usize> {
670 self.children.iter().position(|id| *id == child_id)
671 }
672
673 pub fn child_index(&self) -> Option<usize> {
675 self.tree()[self.parent?]
676 .children
677 .iter()
678 .position(|id| *id == self.id)
679 }
680
681 pub fn forward(&self, n: usize) -> Option<&Node> {
683 let child_idx = self.child_index().unwrap_or(0);
684 self.tree()[self.parent?]
685 .children
686 .get(child_idx + n)
687 .map(|id| self.with(*id))
688 }
689
690 pub fn backward(&self, n: usize) -> Option<&Node> {
691 let child_idx = self.child_index().unwrap_or(0);
692 if child_idx < n {
693 return None;
694 }
695
696 self.tree()[self.parent?]
697 .children
698 .get(child_idx - n)
699 .map(|id| self.with(*id))
700 }
701
702 pub fn is_element(&self) -> bool {
703 matches!(self.data, NodeData::Element { .. })
704 }
705
706 pub fn is_anonymous(&self) -> bool {
707 matches!(self.data, NodeData::AnonymousBlock { .. })
708 }
709
710 pub fn is_text_node(&self) -> bool {
711 matches!(self.data, NodeData::Text { .. })
712 }
713
714 pub fn element_data(&self) -> Option<&ElementData> {
715 match self.data {
716 NodeData::Element(ref data) => Some(data),
717 NodeData::AnonymousBlock(ref data) => Some(data),
718 _ => None,
719 }
720 }
721
722 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
723 match self.data {
724 NodeData::Element(ref mut data) => Some(data),
725 NodeData::AnonymousBlock(ref mut data) => Some(data),
726 _ => None,
727 }
728 }
729
730 pub fn text_data(&self) -> Option<&TextNodeData> {
731 match self.data {
732 NodeData::Text(ref data) => Some(data),
733 _ => None,
734 }
735 }
736
737 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
738 match self.data {
739 NodeData::Text(ref mut data) => Some(data),
740 _ => None,
741 }
742 }
743
744 pub fn node_debug_str(&self) -> String {
745 let mut s = String::new();
746
747 match &self.data {
748 NodeData::Document => write!(s, "DOCUMENT"),
749 NodeData::Text(data) => {
751 let bytes = data.content.as_bytes();
752 write!(
753 s,
754 "TEXT {}",
755 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
756 .unwrap_or("INVALID UTF8")
757 )
758 }
759 NodeData::Comment => write!(
760 s,
761 "COMMENT",
762 ),
764 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
765 NodeData::Element(data) => {
766 let name = &data.name;
767 let class = self.attr(local_name!("class")).unwrap_or("");
768 let id = self.attr(local_name!("id")).unwrap_or("");
769 let display = self.display_constructed_as.to_css_string();
770 write!(s, "<{}", name.local).unwrap();
771 if !id.is_empty() {
772 write!(s, " #{id}").unwrap();
773 }
774 if !class.is_empty() {
775 if class.contains(' ') {
776 write!(s, " class=\"{class}\"").unwrap()
777 } else {
778 write!(s, " .{class}").unwrap()
779 }
780 }
781 write!(s, "> ({display})")
782 } }
784 .unwrap();
785 s
786 }
787
788 pub fn outer_html(&self) -> String {
796 let mut output = String::new();
797 self.write_outer_html(&mut output);
798 output
799 }
800
801 pub fn outer_html_pretty(&self) -> String {
817 let mut output = String::new();
818 self.write_outer_html_pretty(&mut output);
819 output
820 }
821
822 pub fn write_outer_html(&self, writer: &mut String) {
823 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
824 }
825
826 #[cfg(feature = "svg")]
827 pub(crate) fn write_outer_html_with_current_color(
828 &self,
829 writer: &mut String,
830 current_color: &str,
831 ) {
832 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
833 }
834
835 pub fn write_outer_html_pretty(&self, writer: &mut String) {
836 self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
837 }
838
839 fn write_outer_html_in_style(
840 &self,
841 writer: &mut String,
842 style: OutputStyle,
843 nesting: usize,
844 current_color_override: Option<&str>,
845 ) {
846 const INDENT: &str = " ";
847 let has_children = !self.children.is_empty();
848 let computed_current_color = || {
849 self.primary_styles()
850 .map(|style| style.clone_color())
851 .map(|color| crate::util::absolute_color_to_svg_css(&color))
852 };
853 let current_color = current_color_override
854 .map(ToOwned::to_owned)
855 .or_else(computed_current_color);
856
857 match &self.data {
858 NodeData::Document => {}
859 NodeData::Comment => {}
860 NodeData::AnonymousBlock(_) => {}
861 NodeData::Text(data) => {
863 if matches!(style, OutputStyle::Pretty) {
864 for _ in 0..nesting {
865 writer.push_str(INDENT);
866 }
867 }
868 writer.push_str(data.content.as_str());
869 if matches!(style, OutputStyle::Pretty) {
870 writer.push('\n');
871 }
872 }
873 NodeData::Element(data) => {
874 if matches!(style, OutputStyle::Pretty) {
875 for _ in 0..nesting {
876 writer.push_str(INDENT);
877 }
878 }
879 writer.push('<');
880 writer.push_str(&data.name.local);
881
882 for attr in data.attrs() {
883 writer.push(' ');
884 writer.push_str(&attr.name.local);
885 writer.push_str("=\"");
886 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
888 let value = attr
889 .value
890 .replace("currentColor", current_color.as_ref().unwrap());
891 encode_quoted_attribute_to_string(&value, writer);
892 } else {
893 encode_quoted_attribute_to_string(&attr.value, writer);
894 }
895 writer.push('"');
896 }
897 if !has_children {
898 writer.push_str(" /");
899 }
900 writer.push('>');
901 if matches!(style, OutputStyle::Pretty) {
902 writer.push('\n');
903 }
904
905 if has_children {
906 for &child_id in &self.children {
907 self.tree()[child_id].write_outer_html_in_style(
908 writer,
909 style,
910 nesting + 1,
911 current_color_override,
912 );
913 }
914
915 if matches!(style, OutputStyle::Pretty) {
916 for _ in 0..nesting {
917 writer.push_str(INDENT);
918 }
919 }
920 writer.push_str("</");
921 writer.push_str(&data.name.local);
922 writer.push('>');
923 if matches!(style, OutputStyle::Pretty) {
924 writer.push('\n');
925 }
926 }
927 }
928 }
929 }
930
931 pub fn attrs(&self) -> Option<&[Attribute]> {
932 Some(&self.element_data()?.attrs)
933 }
934
935 pub fn attr(&self, name: LocalName) -> Option<&str> {
936 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
937 Some(&attr.value)
938 }
939
940 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
941 self.stylo_element_data.primary_styles()
942 }
943
944 pub fn text_content(&self) -> String {
945 let mut out = String::new();
946 self.write_text_content(&mut out);
947 out
948 }
949
950 fn write_text_content(&self, out: &mut String) {
951 match &self.data {
952 NodeData::Text(data) => {
953 out.push_str(&data.content);
954 }
955 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
956 for child_id in self.children.iter() {
957 self.with(*child_id).write_text_content(out);
958 }
959 }
960 _ => {}
961 }
962 }
963
964 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
965 if let NodeData::Element(ref mut elem_data) = self.data {
966 elem_data.flush_style_attribute(&self.guard, url_extra_data);
967 }
968 }
969
970 pub fn order(&self) -> i32 {
971 self.primary_styles()
972 .map(|s| match s.pseudo() {
973 Some(PseudoElement::Before) => i32::MIN,
974 Some(PseudoElement::After) => i32::MAX,
975 _ => s.clone_order(),
976 })
977 .unwrap_or(0)
978 }
979
980 pub fn z_index(&self) -> i32 {
981 self.primary_styles()
982 .map(|s| s.clone_z_index().integer_or(0))
983 .unwrap_or(0)
984 }
985
986 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
988 let Some(style) = self.primary_styles() else {
989 return false;
990 };
991
992 let position = style.clone_position();
993 let has_z_index = !style.clone_z_index().is_auto();
994
995 if style.clone_opacity() != 1.0 {
996 return true;
997 }
998
999 let position_based = match position {
1000 Position::Fixed | Position::Sticky => true,
1001 Position::Relative | Position::Absolute => has_z_index,
1002 Position::Static => has_z_index && is_flex_or_grid_item,
1003 };
1004 if position_based {
1005 return true;
1006 }
1007
1008 if self.transform.is_some() {
1009 return true;
1010 }
1011
1012 false
1020 }
1021
1022 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1031 self.hit_inner(x, y, scale, &mut None)
1032 }
1033
1034 pub(crate) fn hit_inner(
1039 &self,
1040 x: f32,
1041 y: f32,
1042 scale: f64,
1043 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1044 ) -> Option<HitResult> {
1045 use style::computed_values::pointer_events::T as PointerEvents;
1046 use style::computed_values::visibility::T as Visibility;
1047
1048 if let Some(style) = self.primary_styles() {
1050 if matches!(
1051 style.clone_visibility(),
1052 Visibility::Hidden | Visibility::Collapse
1053 ) {
1054 return None;
1055 }
1056 }
1057
1058 let pointer_events_none = self
1061 .primary_styles()
1062 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1063
1064 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
1065 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
1066
1067 if let Some(t) = self.transform {
1068 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1069 x = (p.x / scale) as f32;
1070 y = (p.y / scale) as f32;
1071 }
1072
1073 let size = self.final_layout.size;
1074 let matches_self = !(x < 0.0
1075 || x > size.width + self.scroll_offset.x as f32
1076 || y < 0.0
1077 || y > size.height + self.scroll_offset.y as f32);
1078
1079 let content_size = self.final_layout.content_size;
1080 let matches_content = !(x < 0.0
1081 || x > content_size.width + self.scroll_offset.x as f32
1082 || y < 0.0
1083 || y > content_size.height + self.scroll_offset.y as f32);
1084
1085 let matches_hoisted_content = match &self.stacking_context {
1086 Some(sc) => {
1087 let content_area = sc.content_area;
1088 x >= content_area.left + self.scroll_offset.x as f32
1089 && x <= content_area.right + self.scroll_offset.x as f32
1090 && y >= content_area.top + self.scroll_offset.y as f32
1091 && y <= content_area.bottom + self.scroll_offset.y as f32
1092 }
1093 None => false,
1094 };
1095
1096 let overflow = self.scrollable_overflow;
1099
1100 let matches_overflow = x >= (overflow.x0 / scale) as f32
1101 && x <= (overflow.x1 / scale) as f32
1102 && y >= (overflow.y0 / scale) as f32
1103 && y <= (overflow.y1 / scale) as f32;
1104
1105 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1106 return None;
1107 }
1108
1109 if matches_self
1112 && let Some(sb) = self.scrollbar_at_local(
1113 (x - self.scroll_offset.x as f32) as f64,
1114 (y - self.scroll_offset.y as f32) as f64,
1115 )
1116 {
1117 *scrollbar = Some(sb);
1118 }
1119
1120 if self.flags.is_inline_root() {
1121 let content_box_offset = taffy::Point {
1122 x: self.final_layout.padding.left + self.final_layout.border.left,
1123 y: self.final_layout.padding.top + self.final_layout.border.top,
1124 };
1125 x -= content_box_offset.x;
1126 y -= content_box_offset.y;
1127 }
1128
1129 if matches_hoisted_content {
1131 if let Some(hoisted) = &self.stacking_context {
1132 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1133 let x = x - hoisted_child.position.x;
1134 let y = y - hoisted_child.position.y;
1135 if let Some(hit) = self
1136 .with(hoisted_child.node_id)
1137 .hit_inner(x, y, scale, scrollbar)
1138 {
1139 return Some(hit);
1140 }
1141 }
1142 }
1143 }
1144
1145 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1147 if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1148 return Some(hit);
1149 }
1150 }
1151
1152 if matches_hoisted_content {
1154 if let Some(hoisted) = &self.stacking_context {
1155 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1156 let x = x - hoisted_child.position.x;
1157 let y = y - hoisted_child.position.y;
1158 if let Some(hit) = self
1159 .with(hoisted_child.node_id)
1160 .hit_inner(x, y, scale, scrollbar)
1161 {
1162 return Some(hit);
1163 }
1164 }
1165 }
1166 }
1167
1168 if self.flags.is_inline_root() {
1170 let element_data = &self.element_data().unwrap();
1171 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1172 let layout = &ild.layout;
1173 let scale = layout.scale();
1174
1175 if let Some((cluster, _side)) =
1176 Cluster::from_point_exact(layout, x * scale, y * scale)
1177 {
1178 let style_index = cluster.glyphs().next()?.style_index();
1179 let node_id = layout.styles()[style_index].brush.id;
1180 let text_pointer_events_none = self
1181 .with(node_id)
1182 .primary_styles()
1183 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1184 if !text_pointer_events_none {
1185 return Some(HitResult {
1186 node_id,
1187 x,
1188 y,
1189 is_text: true,
1190 });
1191 }
1192 }
1193 }
1194 }
1195
1196 if matches_self && !pointer_events_none {
1198 return Some(HitResult {
1199 node_id: self.id,
1200 x,
1201 y,
1202 is_text: false,
1203 });
1204 }
1205
1206 None
1207 }
1208
1209 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1212 let mut node = self;
1213 loop {
1214 if node.flags.is_inline_root() {
1215 return Some(node);
1216 }
1217 let id = node.layout_parent.get()?;
1218 node = self.with(id);
1219 }
1220 }
1221
1222 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1226 if !self.flags.is_inline_root() {
1227 return None;
1228 }
1229
1230 let element_data = self.element_data()?;
1231 let inline_layout = element_data.inline_layout_data.as_ref()?;
1232 let layout = &inline_layout.layout;
1233 let scale = layout.scale();
1234
1235 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1237
1238 let is_leading = side == ClusterSide::Left;
1243 let offset = if cluster.is_rtl() {
1244 if is_leading {
1245 cluster.text_range().end
1246 } else {
1247 cluster.text_range().start
1248 }
1249 } else {
1250 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1252 cluster.text_range().start
1253 } else {
1254 cluster.text_range().end
1255 }
1256 };
1257
1258 Some(offset)
1259 }
1260
1261 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1263 let x = x + self.final_layout.location.x;
1266 let y = y + self.final_layout.location.y;
1267
1268 self.layout_parent
1270 .get()
1271 .map(|i| {
1272 let parent = self.with(i);
1273 parent.absolute_position(
1274 x - parent.scroll_offset.x as f32,
1275 y - parent.scroll_offset.y as f32,
1276 )
1277 })
1278 .unwrap_or(crate::util::Point { x, y })
1279 }
1280
1281 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1283 DomEventData::Click(self.synthetic_click_event_data(mods))
1284 }
1285
1286 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1287 let absolute_position = self.absolute_position(0.0, 0.0);
1288 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1289 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1290
1291 BlitzPointerEvent {
1292 id: BlitzPointerId::Mouse,
1293 is_primary: true,
1294 coords: PointerCoords {
1295 page_x: x,
1296 page_y: y,
1297
1298 screen_x: x,
1300 screen_y: y,
1301 client_x: x,
1302 client_y: y,
1303 },
1304 mods,
1305 button: Default::default(),
1306 buttons: Default::default(),
1307 details: Default::default(),
1308 element: Default::default(),
1309 active_pointers: Default::default(),
1310 }
1311 }
1312}
1313
1314impl PartialEq for Node {
1316 fn eq(&self, other: &Self) -> bool {
1317 self.id == other.id
1318 }
1319}
1320
1321impl Eq for Node {}
1322
1323impl std::fmt::Debug for Node {
1324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1325 f.debug_struct("NodeData")
1327 .field("parent", &self.parent)
1328 .field("id", &self.id)
1329 .field("is_inline_root", &self.flags.is_inline_root())
1330 .field("children", &self.children)
1331 .field("layout_children", &self.layout_children.borrow())
1332 .field("node", &self.data)
1334 .field("stylo_element_data", &self.stylo_element_data)
1335 .finish()
1338 }
1339}
1340
1341#[cfg(test)]
1342mod test {
1343 use style_dom::ElementState;
1344
1345 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1346
1347 #[test]
1348 fn create_node_with_disabled_attr() {
1349 let mut document = BaseDocument::new(DocumentConfig::default());
1350 let node = document.create_node(NodeData::Element(ElementData::new(
1351 qual_name!("button"),
1352 vec![Attribute {
1353 name: qual_name!("disabled"),
1354 value: "".into(),
1355 }],
1356 )));
1357 let node = document.get_node(node).unwrap();
1358
1359 assert!(
1360 node.element_state.contains(ElementState::DISABLED),
1361 "form node is disabled"
1362 );
1363 assert!(
1364 !node.element_state.contains(ElementState::ENABLED),
1365 "form node is not enabled"
1366 );
1367 }
1368
1369 #[test]
1370 fn ignore_disabled_attr_content() {
1371 let mut document = BaseDocument::new(DocumentConfig::default());
1372 let node = document.create_node(NodeData::Element(ElementData::new(
1373 qual_name!("button"),
1374 vec![Attribute {
1375 name: qual_name!("disabled"),
1376 value: "false".into(),
1377 }],
1378 )));
1379 let node = document.get_node(node).unwrap();
1380
1381 assert!(
1382 node.element_state.contains(ElementState::DISABLED),
1383 "form node is disabled"
1384 );
1385 assert!(
1386 !node.element_state.contains(ElementState::ENABLED),
1387 "form node is not enabled"
1388 );
1389 }
1390
1391 #[test]
1392 fn create_node_with_ignored_disable() {
1393 let mut document = BaseDocument::new(DocumentConfig::default());
1394 let node = document.create_node(NodeData::Element(ElementData::new(
1395 qual_name!("a"),
1396 vec![Attribute {
1397 name: qual_name!("disabled"),
1398 value: "".into(),
1399 }],
1400 )));
1401 let node = document.get_node(node).unwrap();
1402
1403 assert!(
1404 !node.element_state.contains(ElementState::DISABLED),
1405 "Non form node cannot be disabled"
1406 );
1407 assert!(
1408 !node.element_state.contains(ElementState::ENABLED),
1409 "Non form node cannot be enabled"
1410 );
1411 }
1412
1413 #[test]
1414 fn create_empty_enabled_node() {
1415 let mut document = BaseDocument::new(DocumentConfig::default());
1416 let node = document.create_node(NodeData::Element(ElementData::new(
1417 qual_name!("button"),
1418 vec![],
1419 )));
1420 let node = document.get_node(node).unwrap();
1421
1422 assert!(
1423 node.element_state.contains(ElementState::ENABLED),
1424 "Button should be enabled by default"
1425 );
1426 }
1427}