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, 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, Debug, PartialEq, Eq)]
43pub enum DisplayOuter {
44 Block,
45 Inline,
46 None,
47}
48
49bitflags! {
50 #[derive(Clone, Copy, PartialEq)]
51 pub struct NodeFlags: u32 {
52 const IS_INLINE_ROOT = 0b00000001;
54 const IS_TABLE_ROOT = 0b00000010;
56 const IS_IN_DOCUMENT = 0b00000100;
58 }
59}
60
61impl NodeFlags {
62 #[inline(always)]
63 pub fn is_inline_root(&self) -> bool {
64 self.contains(Self::IS_INLINE_ROOT)
65 }
66
67 #[inline(always)]
68 pub fn is_table_root(&self) -> bool {
69 self.contains(Self::IS_TABLE_ROOT)
70 }
71
72 #[inline(always)]
73 pub fn is_in_document(&self) -> bool {
74 self.contains(Self::IS_IN_DOCUMENT)
75 }
76
77 #[inline(always)]
78 pub fn reset_construction_flags(&mut self) {
79 self.remove(Self::IS_INLINE_ROOT);
80 self.remove(Self::IS_TABLE_ROOT);
81 }
82}
83
84pub struct Node {
85 tree: *mut Slab<Node>,
87
88 pub id: usize,
90 pub parent: Option<usize>,
92 pub children: Vec<usize>,
94 pub layout_parent: Cell<Option<usize>>,
96 pub layout_children: RefCell<Option<Vec<usize>>>,
98 pub paint_children: RefCell<Option<Vec<usize>>>,
100 pub stacking_context: Option<Box<HoistedPaintChildren>>,
101
102 pub flags: NodeFlags,
104
105 pub data: NodeData,
107
108 pub stylo_element_data: StyloData,
111 pub selector_flags: Cell<ElementSelectorFlags>,
112 pub guard: SharedRwLock,
113 pub element_state: ElementState,
114 pub has_snapshot: bool,
115 pub snapshot_handled: AtomicBool,
116 pub dirty_descendants: AtomicBool,
119
120 pub before: Option<usize>,
122 pub after: Option<usize>,
123
124 pub style: Style<Atom>,
126 pub display_constructed_as: StyloDisplay,
127 pub cache: Cache,
128 pub unrounded_layout: Layout,
129 pub final_layout: Layout,
130 pub scroll_offset: crate::Point<f64>,
131
132 pub scrollable_overflow: KurboRect,
133 pub transform: Option<Affine>,
134}
135
136unsafe impl Send for Node {}
137unsafe impl Sync for Node {}
138
139impl Node {
140 pub(crate) fn new(
141 tree: *mut Slab<Node>,
142 id: usize,
143 guard: SharedRwLock,
144 data: NodeData,
145 ) -> Self {
146 let state = match &data {
148 NodeData::Element(data) => {
149 let mut state = ElementState::empty();
150 if data.can_be_disabled() {
151 state.insert(match data.has_attr(local_name!("disabled")) {
152 true => ElementState::DISABLED,
153 false => ElementState::ENABLED,
154 })
155 }
156
157 state
158 }
159 _ => ElementState::empty(),
160 };
161
162 Self {
163 tree,
164
165 id,
166 parent: None,
167 children: vec![],
168 layout_parent: Cell::new(None),
169 layout_children: RefCell::new(None),
170 paint_children: RefCell::new(None),
171 stacking_context: None,
172
173 flags: NodeFlags::empty(),
174 data,
175
176 stylo_element_data: Default::default(),
177 selector_flags: Cell::new(ElementSelectorFlags::empty()),
178 guard,
179 element_state: state,
180
181 before: None,
182 after: None,
183
184 style: Default::default(),
185 has_snapshot: false,
186 snapshot_handled: AtomicBool::new(false),
187 dirty_descendants: AtomicBool::new(true),
188 display_constructed_as: StyloDisplay::Block,
189 cache: Cache::new(),
190 unrounded_layout: Layout::new(),
191 final_layout: Layout::new(),
192 scroll_offset: crate::Point::ZERO,
193
194 scrollable_overflow: KurboRect::ZERO,
195 transform: None,
196 }
197 }
198
199 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
200 self.transform = self.primary_styles().and_then(|s| {
201 let w = self.final_layout.size.width * scale;
202 let h = self.final_layout.size.height * scale;
203 let reference_box = Rect::new(
204 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
205 Size2D::new(CSSPixelLength::new(w), CSSPixelLength::new(h)),
206 );
207 crate::resolve_2d_transform(s.get_box(), reference_box)
208 });
209
210 self.transform
211 }
212
213 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
214 match index {
215 0 => self.after,
216 1 => self.before,
217 _ => panic!("Invalid pseudo element index"),
218 }
219 }
220
221 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
222 match index {
223 0 => self.after = value,
224 1 => self.before = value,
225 _ => panic!("Invalid pseudo element index"),
226 }
227 }
228
229 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
230 Some(self.primary_styles().as_ref()?.clone_display())
231 }
232
233 pub fn is_or_contains_block(&self) -> bool {
234 let style = self.primary_styles();
235 let style = style.as_ref();
236
237 let position = style
239 .map(|s| s.clone_position())
240 .unwrap_or(Position::Relative);
241 let is_in_flow = matches!(
242 position,
243 Position::Static | Position::Relative | Position::Sticky
244 );
245 if !is_in_flow {
246 return false;
247 }
248 let display = style
249 .map(|s| s.clone_display())
250 .unwrap_or(StyloDisplay::inline());
251 match display.outside() {
252 DisplayOutside::None => false,
253 DisplayOutside::Block => true,
254 _ => {
255 if display.inside() == DisplayInside::Flow {
256 self.children
257 .iter()
258 .copied()
259 .any(|child_id| self.tree()[child_id].is_or_contains_block())
260 } else {
261 false
262 }
263 }
264 }
265 }
266
267 pub fn is_whitespace_node(&self) -> bool {
268 match &self.data {
269 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
270 _ => false,
271 }
272 }
273
274 pub fn is_focussable(&self) -> bool {
275 self.data
276 .downcast_element()
277 .map(|el| el.is_focussable)
278 .unwrap_or(false)
279 }
280
281 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
282 if let Some(mut element_data) = self.stylo_element_data.get_mut() {
283 element_data.hint.insert(hint);
284 }
285 self.mark_ancestors_dirty();
288 }
289
290 pub fn has_dirty_descendants(&self) -> bool {
292 self.dirty_descendants.load(Ordering::Relaxed)
293 }
294
295 pub fn set_dirty_descendants(&self) {
297 self.dirty_descendants.store(true, Ordering::Relaxed);
298 }
299
300 pub fn unset_dirty_descendants(&self) {
302 self.dirty_descendants.store(false, Ordering::Relaxed);
303 }
304
305 pub(crate) fn mark_style_attr_updated(&mut self) {
307 if let Some(mut data) = self.stylo_element_data.get_mut() {
308 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
309 }
310 self.set_dirty_descendants();
311 }
312
313 pub fn mark_ancestors_dirty(&self) {
317 let mut current_id = self.parent;
318 while let Some(parent_id) = current_id {
319 let parent = &self.tree()[parent_id];
320 if parent.dirty_descendants.swap(true, Ordering::Relaxed) {
323 break;
324 }
325 current_id = parent.parent;
326 }
327 }
328
329 pub fn damage(&self) -> Option<RestyleDamage> {
336 self.stylo_element_data.get().map(|data| data.damage)
337 }
338
339 pub fn set_damage(&mut self, damage: RestyleDamage) {
340 if let Some(mut data) = self.stylo_element_data.get_mut() {
341 data.damage = damage;
342 }
343 }
344
345 pub fn insert_damage(&mut self, damage: RestyleDamage) {
346 if let Some(mut data) = self.stylo_element_data.get_mut() {
347 data.damage |= damage;
348 }
349 }
350
351 pub fn remove_damage(&mut self, damage: RestyleDamage) {
352 if let Some(mut data) = self.stylo_element_data.get_mut() {
353 data.damage.remove(damage);
354 }
355 }
356
357 pub fn clear_damage_mut(&mut self) {
358 if let Some(mut data) = self.stylo_element_data.get_mut() {
359 data.damage = RestyleDamage::empty();
360 }
361 }
362
363 pub fn hover(&mut self) {
364 self.element_state.insert(ElementState::HOVER);
365 self.set_restyle_hint(RestyleHint::restyle_subtree());
366 }
367
368 pub fn unhover(&mut self) {
369 self.element_state.remove(ElementState::HOVER);
370 self.set_restyle_hint(RestyleHint::restyle_subtree());
371 }
372
373 pub fn is_hovered(&self) -> bool {
374 self.element_state.contains(ElementState::HOVER)
375 }
376
377 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
378 self.element_state
379 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
380 self.set_restyle_hint(RestyleHint::restyle_subtree());
381
382 if self
384 .element_data()
385 .and_then(|elem| elem.text_input_data())
386 .is_some()
387 {
388 shell_provider.set_ime_enabled(true);
389 let mut pos = self.absolute_position(0.0, 0.0);
390 pos.x += self.final_layout.content_box_x();
391 pos.y += self.final_layout.content_box_y();
392 let width = self.final_layout.content_box_width();
393 let height = self.final_layout.content_box_height();
394 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
395 }
396 }
397
398 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
399 self.element_state
400 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
401 self.set_restyle_hint(RestyleHint::restyle_subtree());
402
403 if self
405 .element_data()
406 .and_then(|elem| elem.text_input_data())
407 .is_some()
408 {
409 shell_provider.set_ime_enabled(false);
410 }
411 }
412
413 pub fn is_focussed(&self) -> bool {
414 self.element_state.contains(ElementState::FOCUS)
415 }
416
417 pub fn active(&mut self) {
418 self.element_state.insert(ElementState::ACTIVE);
419 self.set_restyle_hint(RestyleHint::restyle_subtree());
420 }
421
422 pub fn unactive(&mut self) {
423 self.element_state.remove(ElementState::ACTIVE);
424 self.set_restyle_hint(RestyleHint::restyle_subtree());
425 }
426
427 pub fn is_active(&self) -> bool {
428 self.element_state.contains(ElementState::ACTIVE)
429 }
430
431 pub fn disable(&mut self) {
434 if self
435 .data
436 .downcast_element()
437 .is_some_and(|data| data.can_be_disabled())
438 {
439 self.element_state.insert(ElementState::DISABLED);
440 self.element_state.remove(ElementState::ENABLED);
441 }
442 self.set_restyle_hint(RestyleHint::restyle_subtree());
443 }
444
445 pub fn enable(&mut self) {
448 if self
449 .data
450 .downcast_element()
451 .is_some_and(|data| data.can_be_disabled())
452 {
453 self.element_state.insert(ElementState::ENABLED);
454 self.element_state.remove(ElementState::DISABLED);
455 }
456 self.set_restyle_hint(RestyleHint::restyle_subtree());
457 }
458
459 pub fn subdoc(&self) -> Option<&dyn Document> {
460 self.element_data().and_then(|el| el.sub_doc_data())
461 }
462
463 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
464 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
465 }
466
467 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
468 if let Some(input_data) = self
471 .data
472 .downcast_element()
473 .and_then(|el| el.text_input_data())
474 {
475 if !input_data.is_multiline {
476 let content_box_height = self.final_layout.content_box_height();
477 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
478 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
479
480 return y_offset as f64;
481 }
482 }
483
484 0.0
485 }
486}
487
488#[derive(Debug, Clone, Copy, PartialEq)]
489pub enum NodeKind {
490 Document,
491 Element,
492 AnonymousBlock,
493 Text,
494 Comment,
495}
496
497#[derive(Debug, Clone)]
499pub enum NodeData {
500 Document,
502
503 Element(ElementData),
505
506 AnonymousBlock(ElementData),
508
509 Text(TextNodeData),
511
512 Comment,
514 }
523
524impl NodeData {
525 pub fn downcast_element(&self) -> Option<&ElementData> {
526 match self {
527 Self::Element(data) => Some(data),
528 Self::AnonymousBlock(data) => Some(data),
529 _ => None,
530 }
531 }
532
533 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
534 match self {
535 Self::Element(data) => Some(data),
536 Self::AnonymousBlock(data) => Some(data),
537 _ => None,
538 }
539 }
540
541 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
542 let Some(elem) = self.downcast_element() else {
543 return false;
544 };
545 *name == elem.name.local
546 }
547
548 pub fn attrs(&self) -> Option<&[Attribute]> {
549 Some(&self.downcast_element()?.attrs)
550 }
551
552 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
553 self.downcast_element()?.attr(name)
554 }
555
556 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
557 self.downcast_element()
558 .is_some_and(|elem| elem.has_attr(name))
559 }
560
561 pub fn kind(&self) -> NodeKind {
562 match self {
563 NodeData::Document => NodeKind::Document,
564 NodeData::Element(_) => NodeKind::Element,
565 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
566 NodeData::Text(_) => NodeKind::Text,
567 NodeData::Comment => NodeKind::Comment,
568 }
569 }
570}
571
572#[derive(Debug, Clone)]
573pub struct TextNodeData {
574 pub content: String,
576}
577
578impl TextNodeData {
579 pub fn new(content: String) -> Self {
580 Self { content }
581 }
582}
583
584impl Node {
609 pub fn tree(&self) -> &Slab<Node> {
610 unsafe { &*self.tree }
611 }
612
613 #[track_caller]
614 pub fn with(&self, id: usize) -> &Node {
615 self.tree().get(id).unwrap()
616 }
617
618 pub fn print_tree(&self, level: usize) {
619 println!(
620 "{} {} {:?} {} {:?}",
621 " ".repeat(level),
622 self.id,
623 self.parent,
624 self.node_debug_str().replace('\n', ""),
625 self.children
626 );
627 for child_id in self.children.iter() {
629 let child = self.with(*child_id);
630 child.print_tree(level + 1)
631 }
632 }
633
634 pub fn index_of_child(&self, child_id: usize) -> Option<usize> {
636 self.children.iter().position(|id| *id == child_id)
637 }
638
639 pub fn child_index(&self) -> Option<usize> {
641 self.tree()[self.parent?]
642 .children
643 .iter()
644 .position(|id| *id == self.id)
645 }
646
647 pub fn forward(&self, n: usize) -> Option<&Node> {
649 let child_idx = self.child_index().unwrap_or(0);
650 self.tree()[self.parent?]
651 .children
652 .get(child_idx + n)
653 .map(|id| self.with(*id))
654 }
655
656 pub fn backward(&self, n: usize) -> Option<&Node> {
657 let child_idx = self.child_index().unwrap_or(0);
658 if child_idx < n {
659 return None;
660 }
661
662 self.tree()[self.parent?]
663 .children
664 .get(child_idx - n)
665 .map(|id| self.with(*id))
666 }
667
668 pub fn is_element(&self) -> bool {
669 matches!(self.data, NodeData::Element { .. })
670 }
671
672 pub fn is_anonymous(&self) -> bool {
673 matches!(self.data, NodeData::AnonymousBlock { .. })
674 }
675
676 pub fn is_text_node(&self) -> bool {
677 matches!(self.data, NodeData::Text { .. })
678 }
679
680 pub fn element_data(&self) -> Option<&ElementData> {
681 match self.data {
682 NodeData::Element(ref data) => Some(data),
683 NodeData::AnonymousBlock(ref data) => Some(data),
684 _ => None,
685 }
686 }
687
688 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
689 match self.data {
690 NodeData::Element(ref mut data) => Some(data),
691 NodeData::AnonymousBlock(ref mut data) => Some(data),
692 _ => None,
693 }
694 }
695
696 pub fn text_data(&self) -> Option<&TextNodeData> {
697 match self.data {
698 NodeData::Text(ref data) => Some(data),
699 _ => None,
700 }
701 }
702
703 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
704 match self.data {
705 NodeData::Text(ref mut data) => Some(data),
706 _ => None,
707 }
708 }
709
710 pub fn node_debug_str(&self) -> String {
711 let mut s = String::new();
712
713 match &self.data {
714 NodeData::Document => write!(s, "DOCUMENT"),
715 NodeData::Text(data) => {
717 let bytes = data.content.as_bytes();
718 write!(
719 s,
720 "TEXT {}",
721 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
722 .unwrap_or("INVALID UTF8")
723 )
724 }
725 NodeData::Comment => write!(
726 s,
727 "COMMENT",
728 ),
730 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
731 NodeData::Element(data) => {
732 let name = &data.name;
733 let class = self.attr(local_name!("class")).unwrap_or("");
734 let id = self.attr(local_name!("id")).unwrap_or("");
735 let display = self.display_constructed_as.to_css_string();
736 write!(s, "<{}", name.local).unwrap();
737 if !id.is_empty() {
738 write!(s, " #{id}").unwrap();
739 }
740 if !class.is_empty() {
741 if class.contains(' ') {
742 write!(s, " class=\"{class}\"").unwrap()
743 } else {
744 write!(s, " .{class}").unwrap()
745 }
746 }
747 write!(s, "> ({display})")
748 } }
750 .unwrap();
751 s
752 }
753
754 pub fn outer_html(&self) -> String {
755 let mut output = String::new();
756 self.write_outer_html(&mut output);
757 output
758 }
759
760 pub fn write_outer_html(&self, writer: &mut String) {
761 let has_children = !self.children.is_empty();
762 let current_color = self
763 .primary_styles()
764 .map(|style| style.clone_color())
765 .map(|color| color.to_css_string());
766
767 match &self.data {
768 NodeData::Document => {}
769 NodeData::Comment => {}
770 NodeData::AnonymousBlock(_) => {}
771 NodeData::Text(data) => {
773 writer.push_str(data.content.as_str());
774 }
775 NodeData::Element(data) => {
776 writer.push('<');
777 writer.push_str(&data.name.local);
778
779 for attr in data.attrs() {
780 writer.push(' ');
781 writer.push_str(&attr.name.local);
782 writer.push_str("=\"");
783 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
785 let value = attr
786 .value
787 .replace("currentColor", current_color.as_ref().unwrap());
788 encode_quoted_attribute_to_string(&value, writer);
789 } else {
790 encode_quoted_attribute_to_string(&attr.value, writer);
791 }
792 writer.push('"');
793 }
794 if !has_children {
795 writer.push_str(" /");
796 }
797 writer.push('>');
798
799 if has_children {
800 for &child_id in &self.children {
801 self.tree()[child_id].write_outer_html(writer);
802 }
803
804 writer.push_str("</");
805 writer.push_str(&data.name.local);
806 writer.push('>');
807 }
808 }
809 }
810 }
811
812 pub fn attrs(&self) -> Option<&[Attribute]> {
813 Some(&self.element_data()?.attrs)
814 }
815
816 pub fn attr(&self, name: LocalName) -> Option<&str> {
817 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
818 Some(&attr.value)
819 }
820
821 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
822 self.stylo_element_data.primary_styles()
823 }
824
825 pub fn text_content(&self) -> String {
826 let mut out = String::new();
827 self.write_text_content(&mut out);
828 out
829 }
830
831 fn write_text_content(&self, out: &mut String) {
832 match &self.data {
833 NodeData::Text(data) => {
834 out.push_str(&data.content);
835 }
836 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
837 for child_id in self.children.iter() {
838 self.with(*child_id).write_text_content(out);
839 }
840 }
841 _ => {}
842 }
843 }
844
845 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
846 if let NodeData::Element(ref mut elem_data) = self.data {
847 elem_data.flush_style_attribute(&self.guard, url_extra_data);
848 }
849 }
850
851 pub fn order(&self) -> i32 {
852 self.primary_styles()
853 .map(|s| match s.pseudo() {
854 Some(PseudoElement::Before) => i32::MIN,
855 Some(PseudoElement::After) => i32::MAX,
856 _ => s.clone_order(),
857 })
858 .unwrap_or(0)
859 }
860
861 pub fn z_index(&self) -> i32 {
862 self.primary_styles()
863 .map(|s| s.clone_z_index().integer_or(0))
864 .unwrap_or(0)
865 }
866
867 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
869 let Some(style) = self.primary_styles() else {
870 return false;
871 };
872
873 let position = style.clone_position();
874 let has_z_index = !style.clone_z_index().is_auto();
875
876 if style.clone_opacity() != 1.0 {
877 return true;
878 }
879
880 let position_based = match position {
881 Position::Fixed | Position::Sticky => true,
882 Position::Relative | Position::Absolute => has_z_index,
883 Position::Static => has_z_index && is_flex_or_grid_item,
884 };
885 if position_based {
886 return true;
887 }
888
889 if self.transform.is_some() {
890 return true;
891 }
892
893 false
901 }
902
903 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
912 self.hit_inner(x, y, scale, &mut None)
913 }
914
915 pub(crate) fn hit_inner(
920 &self,
921 x: f32,
922 y: f32,
923 scale: f64,
924 scrollbar: &mut Option<crate::node::ScrollbarRef>,
925 ) -> Option<HitResult> {
926 use style::computed_values::pointer_events::T as PointerEvents;
927 use style::computed_values::visibility::T as Visibility;
928
929 if let Some(style) = self.primary_styles() {
931 if matches!(
932 style.clone_visibility(),
933 Visibility::Hidden | Visibility::Collapse
934 ) {
935 return None;
936 }
937 }
938
939 let pointer_events_none = self
942 .primary_styles()
943 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
944
945 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
946 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
947
948 if let Some(t) = self.transform {
949 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
950 x = (p.x / scale) as f32;
951 y = (p.y / scale) as f32;
952 }
953
954 let size = self.final_layout.size;
955 let matches_self = !(x < 0.0
956 || x > size.width + self.scroll_offset.x as f32
957 || y < 0.0
958 || y > size.height + self.scroll_offset.y as f32);
959
960 let content_size = self.final_layout.content_size;
961 let matches_content = !(x < 0.0
962 || x > content_size.width + self.scroll_offset.x as f32
963 || y < 0.0
964 || y > content_size.height + self.scroll_offset.y as f32);
965
966 let matches_hoisted_content = match &self.stacking_context {
967 Some(sc) => {
968 let content_area = sc.content_area;
969 x >= content_area.left + self.scroll_offset.x as f32
970 && x <= content_area.right + self.scroll_offset.x as f32
971 && y >= content_area.top + self.scroll_offset.y as f32
972 && y <= content_area.bottom + self.scroll_offset.y as f32
973 }
974 None => false,
975 };
976
977 let overflow = self.scrollable_overflow;
980
981 let matches_overflow = x >= (overflow.x0 / scale) as f32
982 && x <= (overflow.x1 / scale) as f32
983 && y >= (overflow.y0 / scale) as f32
984 && y <= (overflow.y1 / scale) as f32;
985
986 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
987 return None;
988 }
989
990 if matches_self
993 && let Some(sb) = self.scrollbar_at_local(
994 (x - self.scroll_offset.x as f32) as f64,
995 (y - self.scroll_offset.y as f32) as f64,
996 )
997 {
998 *scrollbar = Some(sb);
999 }
1000
1001 if self.flags.is_inline_root() {
1002 let content_box_offset = taffy::Point {
1003 x: self.final_layout.padding.left + self.final_layout.border.left,
1004 y: self.final_layout.padding.top + self.final_layout.border.top,
1005 };
1006 x -= content_box_offset.x;
1007 y -= content_box_offset.y;
1008 }
1009
1010 if matches_hoisted_content {
1012 if let Some(hoisted) = &self.stacking_context {
1013 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1014 let x = x - hoisted_child.position.x;
1015 let y = y - hoisted_child.position.y;
1016 if let Some(hit) = self
1017 .with(hoisted_child.node_id)
1018 .hit_inner(x, y, scale, scrollbar)
1019 {
1020 return Some(hit);
1021 }
1022 }
1023 }
1024 }
1025
1026 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1028 if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1029 return Some(hit);
1030 }
1031 }
1032
1033 if matches_hoisted_content {
1035 if let Some(hoisted) = &self.stacking_context {
1036 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1037 let x = x - hoisted_child.position.x;
1038 let y = y - hoisted_child.position.y;
1039 if let Some(hit) = self
1040 .with(hoisted_child.node_id)
1041 .hit_inner(x, y, scale, scrollbar)
1042 {
1043 return Some(hit);
1044 }
1045 }
1046 }
1047 }
1048
1049 if self.flags.is_inline_root() {
1051 let element_data = &self.element_data().unwrap();
1052 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1053 let layout = &ild.layout;
1054 let scale = layout.scale();
1055
1056 if let Some((cluster, _side)) =
1057 Cluster::from_point_exact(layout, x * scale, y * scale)
1058 {
1059 let style_index = cluster.glyphs().next()?.style_index();
1060 let node_id = layout.styles()[style_index].brush.id;
1061 let text_pointer_events_none = self
1062 .with(node_id)
1063 .primary_styles()
1064 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1065 if !text_pointer_events_none {
1066 return Some(HitResult {
1067 node_id,
1068 x,
1069 y,
1070 is_text: true,
1071 });
1072 }
1073 }
1074 }
1075 }
1076
1077 if matches_self && !pointer_events_none {
1079 return Some(HitResult {
1080 node_id: self.id,
1081 x,
1082 y,
1083 is_text: false,
1084 });
1085 }
1086
1087 None
1088 }
1089
1090 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1093 let mut node = self;
1094 loop {
1095 if node.flags.is_inline_root() {
1096 return Some(node);
1097 }
1098 let id = node.layout_parent.get()?;
1099 node = self.with(id);
1100 }
1101 }
1102
1103 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1107 if !self.flags.is_inline_root() {
1108 return None;
1109 }
1110
1111 let element_data = self.element_data()?;
1112 let inline_layout = element_data.inline_layout_data.as_ref()?;
1113 let layout = &inline_layout.layout;
1114 let scale = layout.scale();
1115
1116 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1118
1119 let is_leading = side == ClusterSide::Left;
1124 let offset = if cluster.is_rtl() {
1125 if is_leading {
1126 cluster.text_range().end
1127 } else {
1128 cluster.text_range().start
1129 }
1130 } else {
1131 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1133 cluster.text_range().start
1134 } else {
1135 cluster.text_range().end
1136 }
1137 };
1138
1139 Some(offset)
1140 }
1141
1142 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1144 let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
1145 let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
1146
1147 self.layout_parent
1149 .get()
1150 .map(|i| self.with(i).absolute_position(x, y))
1151 .unwrap_or(crate::util::Point { x, y })
1152 }
1153
1154 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1156 DomEventData::Click(self.synthetic_click_event_data(mods))
1157 }
1158
1159 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1160 let absolute_position = self.absolute_position(0.0, 0.0);
1161 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1162 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1163
1164 BlitzPointerEvent {
1165 id: BlitzPointerId::Mouse,
1166 is_primary: true,
1167 coords: PointerCoords {
1168 page_x: x,
1169 page_y: y,
1170
1171 screen_x: x,
1173 screen_y: y,
1174 client_x: x,
1175 client_y: y,
1176 },
1177 mods,
1178 button: Default::default(),
1179 buttons: Default::default(),
1180 details: Default::default(),
1181 element: Default::default(),
1182 active_pointers: Default::default(),
1183 }
1184 }
1185}
1186
1187impl PartialEq for Node {
1189 fn eq(&self, other: &Self) -> bool {
1190 self.id == other.id
1191 }
1192}
1193
1194impl Eq for Node {}
1195
1196impl std::fmt::Debug for Node {
1197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1198 f.debug_struct("NodeData")
1200 .field("parent", &self.parent)
1201 .field("id", &self.id)
1202 .field("is_inline_root", &self.flags.is_inline_root())
1203 .field("children", &self.children)
1204 .field("layout_children", &self.layout_children.borrow())
1205 .field("node", &self.data)
1207 .field("stylo_element_data", &self.stylo_element_data)
1208 .finish()
1211 }
1212}
1213
1214#[cfg(test)]
1215mod test {
1216 use style_dom::ElementState;
1217
1218 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1219
1220 #[test]
1221 fn create_node_with_disabled_attr() {
1222 let mut document = BaseDocument::new(DocumentConfig::default());
1223 let node = document.create_node(NodeData::Element(ElementData::new(
1224 qual_name!("button"),
1225 vec![Attribute {
1226 name: qual_name!("disabled"),
1227 value: "".into(),
1228 }],
1229 )));
1230 let node = document.get_node(node).unwrap();
1231
1232 assert!(
1233 node.element_state.contains(ElementState::DISABLED),
1234 "form node is disabled"
1235 );
1236 assert!(
1237 !node.element_state.contains(ElementState::ENABLED),
1238 "form node is not enabled"
1239 );
1240 }
1241
1242 #[test]
1243 fn ignore_disabled_attr_content() {
1244 let mut document = BaseDocument::new(DocumentConfig::default());
1245 let node = document.create_node(NodeData::Element(ElementData::new(
1246 qual_name!("button"),
1247 vec![Attribute {
1248 name: qual_name!("disabled"),
1249 value: "false".into(),
1250 }],
1251 )));
1252 let node = document.get_node(node).unwrap();
1253
1254 assert!(
1255 node.element_state.contains(ElementState::DISABLED),
1256 "form node is disabled"
1257 );
1258 assert!(
1259 !node.element_state.contains(ElementState::ENABLED),
1260 "form node is not enabled"
1261 );
1262 }
1263
1264 #[test]
1265 fn create_node_with_ignored_disable() {
1266 let mut document = BaseDocument::new(DocumentConfig::default());
1267 let node = document.create_node(NodeData::Element(ElementData::new(
1268 qual_name!("a"),
1269 vec![Attribute {
1270 name: qual_name!("disabled"),
1271 value: "".into(),
1272 }],
1273 )));
1274 let node = document.get_node(node).unwrap();
1275
1276 assert!(
1277 !node.element_state.contains(ElementState::DISABLED),
1278 "Non form node cannot be disabled"
1279 );
1280 assert!(
1281 !node.element_state.contains(ElementState::ENABLED),
1282 "Non form node cannot be enabled"
1283 );
1284 }
1285
1286 #[test]
1287 fn create_empty_enabled_node() {
1288 let mut document = BaseDocument::new(DocumentConfig::default());
1289 let node = document.create_node(NodeData::Element(ElementData::new(
1290 qual_name!("button"),
1291 vec![],
1292 )));
1293 let node = document.get_node(node).unwrap();
1294
1295 assert!(
1296 node.element_state.contains(ElementState::ENABLED),
1297 "Button should be enabled by default"
1298 );
1299 }
1300}