1use bitflags::bitflags;
2use blitz_traits::events::{
3 BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
4};
5use blitz_traits::shell::ShellProvider;
6use html_escape::encode_quoted_attribute_to_string;
7use keyboard_types::Modifiers;
8use kurbo::Affine;
9use markup5ever::{LocalName, local_name};
10use parley::{BreakReason, Cluster, ClusterSide};
11use selectors::matching::ElementSelectorFlags;
12use slab::Slab;
13use std::cell::{Cell, RefCell};
14use std::fmt::Write;
15use std::ops::Deref;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicBool, Ordering};
18use style::Atom;
19use style::invalidation::element::restyle_hints::RestyleHint;
20use style::properties::ComputedValues;
21use style::properties::generated::longhands::position::computed_value::T as Position;
22use style::selector_parser::{PseudoElement, RestyleDamage};
23use style::servo_arc::Arc as ServoArc;
24use style::shared_lock::SharedRwLock;
25use style::stylesheets::UrlExtraData;
26use style::values::computed::Display as StyloDisplay;
27use style::values::specified::box_::{DisplayInside, DisplayOutside};
28use style_dom::ElementState;
29use style_traits::values::ToCss;
30use taffy::{
31 Cache,
32 prelude::{Layout, Style},
33};
34
35use crate::Document;
36use crate::layout::damage::HoistedPaintChildren;
37
38use super::stylo_data::StyloData;
39use super::{Attribute, ElementData};
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum DisplayOuter {
43 Block,
44 Inline,
45 None,
46}
47
48bitflags! {
49 #[derive(Clone, Copy, PartialEq)]
50 pub struct NodeFlags: u32 {
51 const IS_INLINE_ROOT = 0b00000001;
53 const IS_TABLE_ROOT = 0b00000010;
55 const IS_IN_DOCUMENT = 0b00000100;
57 }
58}
59
60impl NodeFlags {
61 #[inline(always)]
62 pub fn is_inline_root(&self) -> bool {
63 self.contains(Self::IS_INLINE_ROOT)
64 }
65
66 #[inline(always)]
67 pub fn is_table_root(&self) -> bool {
68 self.contains(Self::IS_TABLE_ROOT)
69 }
70
71 #[inline(always)]
72 pub fn is_in_document(&self) -> bool {
73 self.contains(Self::IS_IN_DOCUMENT)
74 }
75
76 #[inline(always)]
77 pub fn reset_construction_flags(&mut self) {
78 self.remove(Self::IS_INLINE_ROOT);
79 self.remove(Self::IS_TABLE_ROOT);
80 }
81}
82
83pub struct Node {
84 tree: *mut Slab<Node>,
86
87 pub id: usize,
89 pub parent: Option<usize>,
91 pub children: Vec<usize>,
93 pub layout_parent: Cell<Option<usize>>,
95 pub layout_children: RefCell<Option<Vec<usize>>>,
97 pub paint_children: RefCell<Option<Vec<usize>>>,
99 pub stacking_context: Option<Box<HoistedPaintChildren>>,
100
101 pub flags: NodeFlags,
103
104 pub data: NodeData,
106
107 pub stylo_element_data: StyloData,
110 pub selector_flags: Cell<ElementSelectorFlags>,
111 pub guard: SharedRwLock,
112 pub element_state: ElementState,
113 pub has_snapshot: bool,
114 pub snapshot_handled: AtomicBool,
115 pub dirty_descendants: AtomicBool,
118
119 pub before: Option<usize>,
121 pub after: Option<usize>,
122
123 pub style: Style<Atom>,
125 pub display_constructed_as: StyloDisplay,
126 pub cache: Cache,
127 pub unrounded_layout: Layout,
128 pub final_layout: Layout,
129 pub scroll_offset: crate::Point<f64>,
130
131 pub transform: Option<Affine>,
132}
133
134unsafe impl Send for Node {}
135unsafe impl Sync for Node {}
136
137impl Node {
138 pub(crate) fn new(
139 tree: *mut Slab<Node>,
140 id: usize,
141 guard: SharedRwLock,
142 data: NodeData,
143 ) -> Self {
144 let state = match &data {
146 NodeData::Element(data) => {
147 let mut state = ElementState::empty();
148 if data.can_be_disabled() {
149 state.insert(match data.has_attr(local_name!("disabled")) {
150 true => ElementState::DISABLED,
151 false => ElementState::ENABLED,
152 })
153 }
154
155 state
156 }
157 _ => ElementState::empty(),
158 };
159
160 Self {
161 tree,
162
163 id,
164 parent: None,
165 children: vec![],
166 layout_parent: Cell::new(None),
167 layout_children: RefCell::new(None),
168 paint_children: RefCell::new(None),
169 stacking_context: None,
170
171 flags: NodeFlags::empty(),
172 data,
173
174 stylo_element_data: Default::default(),
175 selector_flags: Cell::new(ElementSelectorFlags::empty()),
176 guard,
177 element_state: state,
178
179 before: None,
180 after: None,
181
182 style: Default::default(),
183 has_snapshot: false,
184 snapshot_handled: AtomicBool::new(false),
185 dirty_descendants: AtomicBool::new(true),
186 display_constructed_as: StyloDisplay::Block,
187 cache: Cache::new(),
188 unrounded_layout: Layout::new(),
189 final_layout: Layout::new(),
190 scroll_offset: crate::Point::ZERO,
191
192 transform: None,
193 }
194 }
195
196 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
197 match index {
198 0 => self.after,
199 1 => self.before,
200 _ => panic!("Invalid pseudo element index"),
201 }
202 }
203
204 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
205 match index {
206 0 => self.after = value,
207 1 => self.before = value,
208 _ => panic!("Invalid pseudo element index"),
209 }
210 }
211
212 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
213 Some(self.primary_styles().as_ref()?.clone_display())
214 }
215
216 pub fn is_or_contains_block(&self) -> bool {
217 let style = self.primary_styles();
218 let style = style.as_ref();
219
220 let position = style
222 .map(|s| s.clone_position())
223 .unwrap_or(Position::Relative);
224 let is_in_flow = matches!(
225 position,
226 Position::Static | Position::Relative | Position::Sticky
227 );
228 if !is_in_flow {
229 return false;
230 }
231 let display = style
232 .map(|s| s.clone_display())
233 .unwrap_or(StyloDisplay::inline());
234 match display.outside() {
235 DisplayOutside::None => false,
236 DisplayOutside::Block => true,
237 _ => {
238 if display.inside() == DisplayInside::Flow {
239 self.children
240 .iter()
241 .copied()
242 .any(|child_id| self.tree()[child_id].is_or_contains_block())
243 } else {
244 false
245 }
246 }
247 }
248 }
249
250 pub fn is_whitespace_node(&self) -> bool {
251 match &self.data {
252 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
253 _ => false,
254 }
255 }
256
257 pub fn is_focussable(&self) -> bool {
258 self.data
259 .downcast_element()
260 .map(|el| el.is_focussable)
261 .unwrap_or(false)
262 }
263
264 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
265 if let Some(mut element_data) = self.stylo_element_data.get_mut() {
266 element_data.hint.insert(hint);
267 }
268 self.mark_ancestors_dirty();
271 }
272
273 pub fn has_dirty_descendants(&self) -> bool {
275 self.dirty_descendants.load(Ordering::Relaxed)
276 }
277
278 pub fn set_dirty_descendants(&self) {
280 self.dirty_descendants.store(true, Ordering::Relaxed);
281 }
282
283 pub fn unset_dirty_descendants(&self) {
285 self.dirty_descendants.store(false, Ordering::Relaxed);
286 }
287
288 pub(crate) fn mark_style_attr_updated(&mut self) {
290 if let Some(mut data) = self.stylo_element_data.get_mut() {
291 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
292 }
293 self.set_dirty_descendants();
294 }
295
296 pub fn mark_ancestors_dirty(&self) {
300 let mut current_id = self.parent;
301 while let Some(parent_id) = current_id {
302 let parent = &self.tree()[parent_id];
303 if parent.dirty_descendants.swap(true, Ordering::Relaxed) {
306 break;
307 }
308 current_id = parent.parent;
309 }
310 }
311
312 pub fn damage(&self) -> Option<RestyleDamage> {
319 self.stylo_element_data.get().map(|data| data.damage)
320 }
321
322 pub fn set_damage(&mut self, damage: RestyleDamage) {
323 if let Some(mut data) = self.stylo_element_data.get_mut() {
324 data.damage = damage;
325 }
326 }
327
328 pub fn insert_damage(&mut self, damage: RestyleDamage) {
329 if let Some(mut data) = self.stylo_element_data.get_mut() {
330 data.damage |= damage;
331 }
332 }
333
334 pub fn remove_damage(&mut self, damage: RestyleDamage) {
335 if let Some(mut data) = self.stylo_element_data.get_mut() {
336 data.damage.remove(damage);
337 }
338 }
339
340 pub fn clear_damage_mut(&mut self) {
341 if let Some(mut data) = self.stylo_element_data.get_mut() {
342 data.damage = RestyleDamage::empty();
343 }
344 }
345
346 pub fn hover(&mut self) {
347 self.element_state.insert(ElementState::HOVER);
348 self.set_restyle_hint(RestyleHint::restyle_subtree());
349 }
350
351 pub fn unhover(&mut self) {
352 self.element_state.remove(ElementState::HOVER);
353 self.set_restyle_hint(RestyleHint::restyle_subtree());
354 }
355
356 pub fn is_hovered(&self) -> bool {
357 self.element_state.contains(ElementState::HOVER)
358 }
359
360 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
361 self.element_state
362 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
363 self.set_restyle_hint(RestyleHint::restyle_subtree());
364
365 if self
367 .element_data()
368 .and_then(|elem| elem.text_input_data())
369 .is_some()
370 {
371 shell_provider.set_ime_enabled(true);
372 let mut pos = self.absolute_position(0.0, 0.0);
373 pos.x += self.final_layout.content_box_x();
374 pos.y += self.final_layout.content_box_y();
375 let width = self.final_layout.content_box_width();
376 let height = self.final_layout.content_box_height();
377 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
378 }
379 }
380
381 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
382 self.element_state
383 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
384 self.set_restyle_hint(RestyleHint::restyle_subtree());
385
386 if self
388 .element_data()
389 .and_then(|elem| elem.text_input_data())
390 .is_some()
391 {
392 shell_provider.set_ime_enabled(false);
393 }
394 }
395
396 pub fn is_focussed(&self) -> bool {
397 self.element_state.contains(ElementState::FOCUS)
398 }
399
400 pub fn active(&mut self) {
401 self.element_state.insert(ElementState::ACTIVE);
402 self.set_restyle_hint(RestyleHint::restyle_subtree());
403 }
404
405 pub fn unactive(&mut self) {
406 self.element_state.remove(ElementState::ACTIVE);
407 self.set_restyle_hint(RestyleHint::restyle_subtree());
408 }
409
410 pub fn is_active(&self) -> bool {
411 self.element_state.contains(ElementState::ACTIVE)
412 }
413
414 pub fn disable(&mut self) {
417 if self
418 .data
419 .downcast_element()
420 .is_some_and(|data| data.can_be_disabled())
421 {
422 self.element_state.insert(ElementState::DISABLED);
423 self.element_state.remove(ElementState::ENABLED);
424 }
425 self.set_restyle_hint(RestyleHint::restyle_subtree());
426 }
427
428 pub fn enable(&mut self) {
431 if self
432 .data
433 .downcast_element()
434 .is_some_and(|data| data.can_be_disabled())
435 {
436 self.element_state.insert(ElementState::ENABLED);
437 self.element_state.remove(ElementState::DISABLED);
438 }
439 self.set_restyle_hint(RestyleHint::restyle_subtree());
440 }
441
442 pub fn subdoc(&self) -> Option<&dyn Document> {
443 self.element_data().and_then(|el| el.sub_doc_data())
444 }
445
446 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
447 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
448 }
449
450 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
451 if let Some(input_data) = self
454 .data
455 .downcast_element()
456 .and_then(|el| el.text_input_data())
457 {
458 if !input_data.is_multiline {
459 let content_box_height = self.final_layout.content_box_height();
460 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
461 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
462
463 return y_offset as f64;
464 }
465 }
466
467 0.0
468 }
469}
470
471#[derive(Debug, Clone, Copy, PartialEq)]
472pub enum NodeKind {
473 Document,
474 Element,
475 AnonymousBlock,
476 Text,
477 Comment,
478}
479
480#[derive(Debug, Clone)]
482pub enum NodeData {
483 Document,
485
486 Element(ElementData),
488
489 AnonymousBlock(ElementData),
491
492 Text(TextNodeData),
494
495 Comment,
497 }
506
507impl NodeData {
508 pub fn downcast_element(&self) -> Option<&ElementData> {
509 match self {
510 Self::Element(data) => Some(data),
511 Self::AnonymousBlock(data) => Some(data),
512 _ => None,
513 }
514 }
515
516 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
517 match self {
518 Self::Element(data) => Some(data),
519 Self::AnonymousBlock(data) => Some(data),
520 _ => None,
521 }
522 }
523
524 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
525 let Some(elem) = self.downcast_element() else {
526 return false;
527 };
528 *name == elem.name.local
529 }
530
531 pub fn attrs(&self) -> Option<&[Attribute]> {
532 Some(&self.downcast_element()?.attrs)
533 }
534
535 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
536 self.downcast_element()?.attr(name)
537 }
538
539 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
540 self.downcast_element()
541 .is_some_and(|elem| elem.has_attr(name))
542 }
543
544 pub fn kind(&self) -> NodeKind {
545 match self {
546 NodeData::Document => NodeKind::Document,
547 NodeData::Element(_) => NodeKind::Element,
548 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
549 NodeData::Text(_) => NodeKind::Text,
550 NodeData::Comment => NodeKind::Comment,
551 }
552 }
553}
554
555#[derive(Debug, Clone)]
556pub struct TextNodeData {
557 pub content: String,
559}
560
561impl TextNodeData {
562 pub fn new(content: String) -> Self {
563 Self { content }
564 }
565}
566
567impl Node {
592 pub fn tree(&self) -> &Slab<Node> {
593 unsafe { &*self.tree }
594 }
595
596 #[track_caller]
597 pub fn with(&self, id: usize) -> &Node {
598 self.tree().get(id).unwrap()
599 }
600
601 pub fn print_tree(&self, level: usize) {
602 println!(
603 "{} {} {:?} {} {:?}",
604 " ".repeat(level),
605 self.id,
606 self.parent,
607 self.node_debug_str().replace('\n', ""),
608 self.children
609 );
610 for child_id in self.children.iter() {
612 let child = self.with(*child_id);
613 child.print_tree(level + 1)
614 }
615 }
616
617 pub fn index_of_child(&self, child_id: usize) -> Option<usize> {
619 self.children.iter().position(|id| *id == child_id)
620 }
621
622 pub fn child_index(&self) -> Option<usize> {
624 self.tree()[self.parent?]
625 .children
626 .iter()
627 .position(|id| *id == self.id)
628 }
629
630 pub fn forward(&self, n: usize) -> Option<&Node> {
632 let child_idx = self.child_index().unwrap_or(0);
633 self.tree()[self.parent?]
634 .children
635 .get(child_idx + n)
636 .map(|id| self.with(*id))
637 }
638
639 pub fn backward(&self, n: usize) -> Option<&Node> {
640 let child_idx = self.child_index().unwrap_or(0);
641 if child_idx < n {
642 return None;
643 }
644
645 self.tree()[self.parent?]
646 .children
647 .get(child_idx - n)
648 .map(|id| self.with(*id))
649 }
650
651 pub fn is_element(&self) -> bool {
652 matches!(self.data, NodeData::Element { .. })
653 }
654
655 pub fn is_anonymous(&self) -> bool {
656 matches!(self.data, NodeData::AnonymousBlock { .. })
657 }
658
659 pub fn is_text_node(&self) -> bool {
660 matches!(self.data, NodeData::Text { .. })
661 }
662
663 pub fn element_data(&self) -> Option<&ElementData> {
664 match self.data {
665 NodeData::Element(ref data) => Some(data),
666 NodeData::AnonymousBlock(ref data) => Some(data),
667 _ => None,
668 }
669 }
670
671 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
672 match self.data {
673 NodeData::Element(ref mut data) => Some(data),
674 NodeData::AnonymousBlock(ref mut data) => Some(data),
675 _ => None,
676 }
677 }
678
679 pub fn text_data(&self) -> Option<&TextNodeData> {
680 match self.data {
681 NodeData::Text(ref data) => Some(data),
682 _ => None,
683 }
684 }
685
686 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
687 match self.data {
688 NodeData::Text(ref mut data) => Some(data),
689 _ => None,
690 }
691 }
692
693 pub fn node_debug_str(&self) -> String {
694 let mut s = String::new();
695
696 match &self.data {
697 NodeData::Document => write!(s, "DOCUMENT"),
698 NodeData::Text(data) => {
700 let bytes = data.content.as_bytes();
701 write!(
702 s,
703 "TEXT {}",
704 &std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
705 .unwrap_or("INVALID UTF8")
706 )
707 }
708 NodeData::Comment => write!(
709 s,
710 "COMMENT",
711 ),
713 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
714 NodeData::Element(data) => {
715 let name = &data.name;
716 let class = self.attr(local_name!("class")).unwrap_or("");
717 let id = self.attr(local_name!("id")).unwrap_or("");
718 let display = self.display_constructed_as.to_css_string();
719 write!(s, "<{}", name.local).unwrap();
720 if !id.is_empty() {
721 write!(s, " #{id}").unwrap();
722 }
723 if !class.is_empty() {
724 if class.contains(' ') {
725 write!(s, " class=\"{class}\"").unwrap()
726 } else {
727 write!(s, " .{class}").unwrap()
728 }
729 }
730 write!(s, "> ({display})")
731 } }
733 .unwrap();
734 s
735 }
736
737 pub fn outer_html(&self) -> String {
738 let mut output = String::new();
739 self.write_outer_html(&mut output);
740 output
741 }
742
743 pub fn write_outer_html(&self, writer: &mut String) {
744 let has_children = !self.children.is_empty();
745 let current_color = self
746 .primary_styles()
747 .map(|style| style.clone_color())
748 .map(|color| color.to_css_string());
749
750 match &self.data {
751 NodeData::Document => {}
752 NodeData::Comment => {}
753 NodeData::AnonymousBlock(_) => {}
754 NodeData::Text(data) => {
756 writer.push_str(data.content.as_str());
757 }
758 NodeData::Element(data) => {
759 writer.push('<');
760 writer.push_str(&data.name.local);
761
762 for attr in data.attrs() {
763 writer.push(' ');
764 writer.push_str(&attr.name.local);
765 writer.push_str("=\"");
766 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
768 let value = attr
769 .value
770 .replace("currentColor", current_color.as_ref().unwrap());
771 encode_quoted_attribute_to_string(&value, writer);
772 } else {
773 encode_quoted_attribute_to_string(&attr.value, writer);
774 }
775 writer.push('"');
776 }
777 if !has_children {
778 writer.push_str(" /");
779 }
780 writer.push('>');
781
782 if has_children {
783 for &child_id in &self.children {
784 self.tree()[child_id].write_outer_html(writer);
785 }
786
787 writer.push_str("</");
788 writer.push_str(&data.name.local);
789 writer.push('>');
790 }
791 }
792 }
793 }
794
795 pub fn attrs(&self) -> Option<&[Attribute]> {
796 Some(&self.element_data()?.attrs)
797 }
798
799 pub fn attr(&self, name: LocalName) -> Option<&str> {
800 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
801 Some(&attr.value)
802 }
803
804 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
805 self.stylo_element_data.primary_styles()
806 }
807
808 pub fn text_content(&self) -> String {
809 let mut out = String::new();
810 self.write_text_content(&mut out);
811 out
812 }
813
814 fn write_text_content(&self, out: &mut String) {
815 match &self.data {
816 NodeData::Text(data) => {
817 out.push_str(&data.content);
818 }
819 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
820 for child_id in self.children.iter() {
821 self.with(*child_id).write_text_content(out);
822 }
823 }
824 _ => {}
825 }
826 }
827
828 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
829 if let NodeData::Element(ref mut elem_data) = self.data {
830 elem_data.flush_style_attribute(&self.guard, url_extra_data);
831 }
832 }
833
834 pub fn order(&self) -> i32 {
835 self.primary_styles()
836 .map(|s| match s.pseudo() {
837 Some(PseudoElement::Before) => i32::MIN,
838 Some(PseudoElement::After) => i32::MAX,
839 _ => s.clone_order(),
840 })
841 .unwrap_or(0)
842 }
843
844 pub fn z_index(&self) -> i32 {
845 self.primary_styles()
846 .map(|s| s.clone_z_index().integer_or(0))
847 .unwrap_or(0)
848 }
849
850 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
852 let Some(style) = self.primary_styles() else {
853 return false;
854 };
855
856 let position = style.clone_position();
857 let has_z_index = !style.clone_z_index().is_auto();
858
859 if style.clone_opacity() != 1.0 {
860 return true;
861 }
862
863 let position_based = match position {
864 Position::Fixed | Position::Sticky => true,
865 Position::Relative | Position::Absolute => has_z_index,
866 Position::Static => has_z_index && is_flex_or_grid_item,
867 };
868 if position_based {
869 return true;
870 }
871
872 false
881 }
882
883 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
892 use style::computed_values::visibility::T as Visibility;
893
894 if let Some(style) = self.primary_styles() {
896 if matches!(
897 style.clone_visibility(),
898 Visibility::Hidden | Visibility::Collapse
899 ) {
900 return None;
901 }
902 }
903
904 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
905 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
906
907 let size = self.final_layout.size;
908 let matches_self = !(x < 0.0
909 || x > size.width + self.scroll_offset.x as f32
910 || y < 0.0
911 || y > size.height + self.scroll_offset.y as f32);
912
913 let content_size = self.final_layout.content_size;
914 let matches_content = !(x < 0.0
915 || x > content_size.width + self.scroll_offset.x as f32
916 || y < 0.0
917 || y > content_size.height + self.scroll_offset.y as f32);
918
919 let matches_hoisted_content = match &self.stacking_context {
920 Some(sc) => {
921 let content_area = sc.content_area;
922 x >= content_area.left + self.scroll_offset.x as f32
923 && x <= content_area.right + self.scroll_offset.x as f32
924 && y >= content_area.top + self.scroll_offset.y as f32
925 && y <= content_area.bottom + self.scroll_offset.y as f32
926 }
927 None => false,
928 };
929
930 if !matches_self && !matches_content && !matches_hoisted_content {
931 return None;
932 }
933
934 if self.flags.is_inline_root() {
935 let content_box_offset = taffy::Point {
936 x: self.final_layout.padding.left + self.final_layout.border.left,
937 y: self.final_layout.padding.top + self.final_layout.border.top,
938 };
939 x -= content_box_offset.x;
940 y -= content_box_offset.y;
941 }
942
943 if matches_hoisted_content {
945 if let Some(hoisted) = &self.stacking_context {
946 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
947 let x = x - hoisted_child.position.x;
948 let y = y - hoisted_child.position.y;
949 if let Some(hit) = self.with(hoisted_child.node_id).hit(x, y) {
950 return Some(hit);
951 }
952 }
953 }
954 }
955
956 for child_id in self.paint_children.borrow().iter().flatten().rev() {
958 if let Some(hit) = self.with(*child_id).hit(x, y) {
959 return Some(hit);
960 }
961 }
962
963 if matches_hoisted_content {
965 if let Some(hoisted) = &self.stacking_context {
966 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
967 let x = x - hoisted_child.position.x;
968 let y = y - hoisted_child.position.y;
969 if let Some(hit) = self.with(hoisted_child.node_id).hit(x, y) {
970 return Some(hit);
971 }
972 }
973 }
974 }
975
976 if self.flags.is_inline_root() {
978 let element_data = &self.element_data().unwrap();
979 if let Some(ild) = element_data.inline_layout_data.as_ref() {
980 let layout = &ild.layout;
981 let scale = layout.scale();
982
983 if let Some((cluster, _side)) =
984 Cluster::from_point_exact(layout, x * scale, y * scale)
985 {
986 let style_index = cluster.glyphs().next()?.style_index();
987 let node_id = layout.styles()[style_index].brush.id;
988 return Some(HitResult {
989 node_id,
990 x,
991 y,
992 is_text: true,
993 });
994 }
995 }
996 }
997
998 if matches_self {
1000 return Some(HitResult {
1001 node_id: self.id,
1002 x,
1003 y,
1004 is_text: false,
1005 });
1006 }
1007
1008 None
1009 }
1010
1011 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1014 let mut node = self;
1015 loop {
1016 if node.flags.is_inline_root() {
1017 return Some(node);
1018 }
1019 match node.layout_parent.get() {
1020 Some(id) => node = self.with(id),
1021 None => return None,
1022 }
1023 }
1024 }
1025
1026 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1030 if !self.flags.is_inline_root() {
1031 return None;
1032 }
1033
1034 let element_data = self.element_data()?;
1035 let inline_layout = element_data.inline_layout_data.as_ref()?;
1036 let layout = &inline_layout.layout;
1037 let scale = layout.scale();
1038
1039 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1041
1042 let is_leading = side == ClusterSide::Left;
1047 let offset = if cluster.is_rtl() {
1048 if is_leading {
1049 cluster.text_range().end
1050 } else {
1051 cluster.text_range().start
1052 }
1053 } else {
1054 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1056 cluster.text_range().start
1057 } else {
1058 cluster.text_range().end
1059 }
1060 };
1061
1062 Some(offset)
1063 }
1064
1065 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1067 let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
1068 let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
1069
1070 self.layout_parent
1072 .get()
1073 .map(|i| self.with(i).absolute_position(x, y))
1074 .unwrap_or(crate::util::Point { x, y })
1075 }
1076
1077 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1079 DomEventData::Click(self.synthetic_click_event_data(mods))
1080 }
1081
1082 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1083 let absolute_position = self.absolute_position(0.0, 0.0);
1084 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1085 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1086
1087 BlitzPointerEvent {
1088 id: BlitzPointerId::Mouse,
1089 is_primary: true,
1090 coords: PointerCoords {
1091 page_x: x,
1092 page_y: y,
1093
1094 screen_x: x,
1096 screen_y: y,
1097 client_x: x,
1098 client_y: y,
1099 },
1100 mods,
1101 button: Default::default(),
1102 buttons: Default::default(),
1103 details: Default::default(),
1104 }
1105 }
1106}
1107
1108impl PartialEq for Node {
1110 fn eq(&self, other: &Self) -> bool {
1111 self.id == other.id
1112 }
1113}
1114
1115impl Eq for Node {}
1116
1117impl std::fmt::Debug for Node {
1118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1119 f.debug_struct("NodeData")
1121 .field("parent", &self.parent)
1122 .field("id", &self.id)
1123 .field("is_inline_root", &self.flags.is_inline_root())
1124 .field("children", &self.children)
1125 .field("layout_children", &self.layout_children.borrow())
1126 .field("node", &self.data)
1128 .field("stylo_element_data", &self.stylo_element_data)
1129 .finish()
1132 }
1133}
1134
1135#[cfg(test)]
1136mod test {
1137 use style_dom::ElementState;
1138
1139 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1140
1141 #[test]
1142 fn create_node_with_disabled_attr() {
1143 let mut document = BaseDocument::new(DocumentConfig::default());
1144 let node = document.create_node(NodeData::Element(ElementData::new(
1145 qual_name!("button"),
1146 vec![Attribute {
1147 name: qual_name!("disabled"),
1148 value: "".into(),
1149 }],
1150 )));
1151 let node = document.get_node(node).unwrap();
1152
1153 assert!(
1154 node.element_state.contains(ElementState::DISABLED),
1155 "form node is disabled"
1156 );
1157 assert!(
1158 !node.element_state.contains(ElementState::ENABLED),
1159 "form node is not enabled"
1160 );
1161 }
1162
1163 #[test]
1164 fn ignore_disabled_attr_content() {
1165 let mut document = BaseDocument::new(DocumentConfig::default());
1166 let node = document.create_node(NodeData::Element(ElementData::new(
1167 qual_name!("button"),
1168 vec![Attribute {
1169 name: qual_name!("disabled"),
1170 value: "false".into(),
1171 }],
1172 )));
1173 let node = document.get_node(node).unwrap();
1174
1175 assert!(
1176 node.element_state.contains(ElementState::DISABLED),
1177 "form node is disabled"
1178 );
1179 assert!(
1180 !node.element_state.contains(ElementState::ENABLED),
1181 "form node is not enabled"
1182 );
1183 }
1184
1185 #[test]
1186 fn create_node_with_ignored_disable() {
1187 let mut document = BaseDocument::new(DocumentConfig::default());
1188 let node = document.create_node(NodeData::Element(ElementData::new(
1189 qual_name!("a"),
1190 vec![Attribute {
1191 name: qual_name!("disabled"),
1192 value: "".into(),
1193 }],
1194 )));
1195 let node = document.get_node(node).unwrap();
1196
1197 assert!(
1198 !node.element_state.contains(ElementState::DISABLED),
1199 "Non form node cannot be disabled"
1200 );
1201 assert!(
1202 !node.element_state.contains(ElementState::ENABLED),
1203 "Non form node cannot be enabled"
1204 );
1205 }
1206
1207 #[test]
1208 fn create_empty_enabled_node() {
1209 let mut document = BaseDocument::new(DocumentConfig::default());
1210 let node = document.create_node(NodeData::Element(ElementData::new(
1211 qual_name!("button"),
1212 vec![],
1213 )));
1214 let node = document.get_node(node).unwrap();
1215
1216 assert!(
1217 node.element_state.contains(ElementState::ENABLED),
1218 "Button should be enabled by default"
1219 );
1220 }
1221}