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::node_id::NodeId;
8use blitz_traits::shell::ShellProvider;
9use euclid::{Point2D, Rect, Size2D};
10use html_escape::encode_quoted_attribute_to_string;
11use keyboard_types::Modifiers;
12use kurbo::{Affine, Rect as KurboRect};
13use markup5ever::{LocalName, local_name};
14use parley::{BreakReason, Cluster, ClusterSide, Selection};
15use selectors::matching::ElementSelectorFlags;
16use std::cell::{Cell, RefCell};
17use std::fmt::Write;
18use std::ops::{Deref, Range};
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, Ordering};
21use style::Atom;
22use style::computed_values::isolation::T as Isolation;
23use style::invalidation::element::restyle_hints::RestyleHint;
24use style::properties::ComputedValues;
25use style::properties::generated::longhands::position::computed_value::T as Position;
26use style::selector_parser::{PseudoElement, RestyleDamage};
27use style::servo_arc::Arc as ServoArc;
28use style::shared_lock::SharedRwLock;
29use style::stylesheets::UrlExtraData;
30use style::values::computed::CSSPixelLength;
31use style::values::computed::Display as StyloDisplay;
32use style::values::computed::Rotate;
33use style::values::generics::transform::{Scale, Translate};
34use style::values::specified::box_::{DisplayInside, DisplayOutside};
35use style_dom::ElementState;
36use style_traits::values::ToCss;
37use taffy::{
38 Cache,
39 prelude::{Layout, Style},
40};
41use thin_vec::ThinVec;
42
43use super::stylo_data::StyloData;
44use super::{Attribute, DocumentData, ElementData};
45
46#[derive(Clone, Copy)]
47enum OutputStyle {
48 Normal,
49 Pretty,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum DisplayOuter {
54 Block,
55 Inline,
56 None,
57}
58
59bitflags! {
60 #[derive(Clone, Copy, PartialEq)]
61 pub struct NodeFlags: u32 {
62 const IS_INLINE_ROOT = 0b00000001;
64 const IS_TABLE_ROOT = 0b00000010;
66 const IS_IN_DOCUMENT = 0b00000100;
68 }
69}
70
71impl NodeFlags {
72 #[inline(always)]
73 pub fn is_inline_root(&self) -> bool {
74 self.contains(Self::IS_INLINE_ROOT)
75 }
76
77 #[inline(always)]
78 pub fn is_table_root(&self) -> bool {
79 self.contains(Self::IS_TABLE_ROOT)
80 }
81
82 #[inline(always)]
83 pub fn is_in_document(&self) -> bool {
84 self.contains(Self::IS_IN_DOCUMENT)
85 }
86
87 #[inline(always)]
88 pub fn reset_construction_flags(&mut self) {
89 self.remove(Self::IS_INLINE_ROOT);
90 self.remove(Self::IS_TABLE_ROOT);
91 }
92}
93
94pub struct Node {
95 tree: *mut crate::NodeTree,
97
98 pub id: NodeId,
100 pub parent: Option<NodeId>,
102 pub children: ThinVec<NodeId>,
104 pub layout_parent: Cell<Option<NodeId>>,
106 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
108 pub anonymous_blocks: ThinVec<NodeId>,
114 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
116 pub stacking_context: Option<Box<HoistedPaintChildren>>,
117
118 #[cfg(feature = "shadow-dom")]
124 pub flattened_children: Option<Vec<NodeId>>,
125
126 pub flags: NodeFlags,
128
129 pub data: NodeData,
136}
137
138unsafe impl Send for Node {}
139unsafe impl Sync for Node {}
140
141macro_rules! universal_accessors {
144 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
145 impl Node {
146 $(
147 $(#[$meta])*
148 #[inline]
149 pub fn $field(&self) -> &$ty {
150 match &self.data {
151 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
152 NodeData::Document(data) => &data.$field,
153 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
154 }
155 }
156
157 $(#[$meta])*
158 #[inline]
159 pub fn $field_mut(&mut self) -> &mut $ty {
160 match &mut self.data {
161 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
162 NodeData::Document(data) => &mut data.$field,
163 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
164 }
165 }
166 )*
167 }
168 };
169}
170
171universal_accessors! {
172 stylo_element_data / stylo_element_data_mut: StyloData,
173 style / style_mut: Style<Atom>,
174 style_source / style_source_mut: Option<ServoArc<ComputedValues>>,
175 subtree_hoists / subtree_hoists_mut: bool,
176 unrounded_layout / unrounded_layout_mut: Layout,
180 final_layout / final_layout_mut: Layout,
181 scroll_offset / scroll_offset_mut: crate::Point<f64>,
182 scrollable_overflow / scrollable_overflow_mut: KurboRect,
183 transform / transform_mut: Option<Affine>,
184 display_constructed_as / display_constructed_as_mut: StyloDisplay,
185 element_state / element_state_mut: ElementState,
188 snapshot_handled / snapshot_handled_mut: AtomicBool,
189 selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
193}
194
195impl Node {
196 #[inline]
204 pub fn cache(&self) -> &Cache {
205 match &self.data {
206 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache(),
207 NodeData::Document(data) => data.cache(),
208 _ => panic!("`cache` is not available on this node kind"),
209 }
210 }
211
212 #[inline]
214 pub fn cache_mut(&mut self) -> &mut Cache {
215 match &mut self.data {
216 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_mut(),
217 NodeData::Document(data) => data.cache_mut(),
218 _ => panic!("`cache_mut` is not available on this node kind"),
219 }
220 }
221
222 #[inline]
224 pub fn cache_release(&mut self) {
225 match &mut self.data {
226 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_release(),
227 NodeData::Document(data) => data.cache_release(),
228 _ => {}
229 }
230 }
231
232 #[inline]
238 pub fn style_source_opt(&self) -> Option<&ServoArc<ComputedValues>> {
239 match &self.data {
240 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.style_source.as_ref(),
241 NodeData::Document(data) => data.style_source.as_ref(),
242 _ => None,
243 }
244 }
245
246 #[inline]
247 pub fn stylo_element_data_opt(&self) -> Option<&StyloData> {
248 match &self.data {
249 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
250 Some(&data.stylo_element_data)
251 }
252 NodeData::Document(data) => Some(&data.stylo_element_data),
253 _ => None,
254 }
255 }
256
257 #[inline]
258 pub fn stylo_element_data_opt_mut(&mut self) -> Option<&mut StyloData> {
259 match &mut self.data {
260 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
261 Some(&mut data.stylo_element_data)
262 }
263 NodeData::Document(data) => Some(&mut data.stylo_element_data),
264 _ => None,
265 }
266 }
267
268 #[inline]
271 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
272 match &self.data {
273 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
274 Some(&data.dirty_descendants)
275 }
276 NodeData::Document(data) => Some(&data.dirty_descendants),
277 _ => None,
278 }
279 }
280
281 #[inline]
284 pub fn guard(&self) -> &SharedRwLock {
285 let guard = match &self.data {
286 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
287 NodeData::Document(data) => data.guard.as_ref(),
288 _ => None,
289 };
290 guard.expect("`guard` is not available on this node kind")
291 }
292
293 #[inline]
294 pub fn has_snapshot(&self) -> bool {
295 match &self.data {
296 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
297 NodeData::Document(data) => data.has_snapshot,
298 _ => false,
299 }
300 }
301
302 #[inline]
303 pub fn set_has_snapshot(&mut self, value: bool) {
304 match &mut self.data {
305 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
306 NodeData::Document(data) => data.has_snapshot = value,
307 _ => {}
308 }
309 }
310
311 #[inline]
312 pub fn before(&self) -> Option<NodeId> {
313 self.element_data().and_then(|data| data.before)
314 }
315
316 #[inline]
317 pub fn after(&self) -> Option<NodeId> {
318 self.element_data().and_then(|data| data.after)
319 }
320}
321
322impl Node {
323 pub(crate) fn new(
324 tree: *mut crate::NodeTree,
325 id: NodeId,
326 guard: SharedRwLock,
327 mut data: NodeData,
328 ) -> Self {
329 match &mut data {
332 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
333 data.guard = Some(guard);
334 }
335 NodeData::Document(data) => data.guard = Some(guard),
336 _ => {}
337 }
338
339 Self {
340 tree,
341
342 id,
343 parent: None,
344 children: ThinVec::new(),
345 layout_parent: Cell::new(None),
346 layout_children: RefCell::new(None),
347 anonymous_blocks: ThinVec::new(),
348 paint_children: RefCell::new(None),
349 stacking_context: None,
350 #[cfg(feature = "shadow-dom")]
351 flattened_children: None,
352
353 flags: NodeFlags::empty(),
354 data,
355 }
356 }
357
358 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
359 let transform = self.primary_styles().and_then(|s| {
360 let size = self.final_layout().size;
361 let reference_box = Rect::new(
362 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
363 Size2D::new(
364 CSSPixelLength::new(size.width),
365 CSSPixelLength::new(size.height),
366 ),
367 );
368 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
371 let scale = scale as f64;
372 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
373 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
374 })
375 });
376
377 *self.transform_mut() = transform;
378 transform
379 }
380
381 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
382 match index {
383 0 => self.after(),
384 1 => self.before(),
385 _ => panic!("Invalid pseudo element index"),
386 }
387 }
388
389 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
390 let Some(data) = self.element_data_mut() else {
391 return;
392 };
393 match index {
394 0 => data.after = value,
395 1 => data.before = value,
396 _ => panic!("Invalid pseudo element index"),
397 }
398 }
399
400 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
401 Some(self.primary_styles().as_ref()?.clone_display())
402 }
403
404 pub fn computed_style_properties(&self) -> Option<Vec<(&'static str, String)>> {
421 let style = self.primary_styles()?;
422 let layout = *self.final_layout();
423 let (used_width, used_height) = if matches!(
424 style.clone_box_sizing(),
425 style::computed_values::box_sizing::T::BorderBox
426 ) {
427 (layout.size.width, layout.size.height)
428 } else {
429 (
430 (layout.size.width
431 - layout.padding.left
432 - layout.padding.right
433 - layout.border.left
434 - layout.border.right)
435 .max(0.0),
436 (layout.size.height
437 - layout.padding.top
438 - layout.padding.bottom
439 - layout.border.top
440 - layout.border.bottom)
441 .max(0.0),
442 )
443 };
444 Some(vec![
445 ("display", style.clone_display().to_css_string()),
446 ("position", style.clone_position().to_css_string()),
447 ("visibility", style.clone_visibility().to_css_string()),
448 ("opacity", style.clone_opacity().to_css_string()),
449 ("color", style.clone_color().to_css_string()),
450 (
451 "background-color",
452 style.clone_background_color().to_css_string(),
453 ),
454 (
455 "font-size",
456 format!("{}px", style.clone_font_size().computed_size().px()),
457 ),
458 ("font-weight", style.clone_font_weight().to_css_string()),
459 ("font-style", style.clone_font_style().to_css_string()),
460 ("font-family", style.clone_font_family().to_css_string()),
461 ("text-align", style.clone_text_align().to_css_string()),
462 ("overflow-x", style.clone_overflow_x().to_css_string()),
463 ("overflow-y", style.clone_overflow_y().to_css_string()),
464 ("z-index", style.clone_z_index().to_css_string()),
465 ("box-sizing", style.clone_box_sizing().to_css_string()),
466 (
467 "flex-direction",
468 style.clone_flex_direction().to_css_string(),
469 ),
470 (
471 "justify-content",
472 style.clone_justify_content().to_css_string(),
473 ),
474 ("align-items", style.clone_align_items().to_css_string()),
475 ("width", format!("{}px", used_width)),
484 ("height", format!("{}px", used_height)),
485 ("padding-top", format!("{}px", layout.padding.top)),
491 ("padding-right", format!("{}px", layout.padding.right)),
492 ("padding-bottom", format!("{}px", layout.padding.bottom)),
493 ("padding-left", format!("{}px", layout.padding.left)),
494 ("border-top-width", format!("{}px", layout.border.top)),
495 ("border-right-width", format!("{}px", layout.border.right)),
496 ("border-bottom-width", format!("{}px", layout.border.bottom)),
497 ("border-left-width", format!("{}px", layout.border.left)),
498 ("margin-top", format!("{}px", layout.margin.top)),
499 ("margin-right", format!("{}px", layout.margin.right)),
500 ("margin-bottom", format!("{}px", layout.margin.bottom)),
501 ("margin-left", format!("{}px", layout.margin.left)),
502 ])
503 }
504
505 pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
506 let style = self.primary_styles()?;
507 Some(vec![
508 ("display", style.clone_display().to_css_string()),
509 ("color", style.clone_color().to_css_string()),
510 (
511 "background-color",
512 style.clone_background_color().to_css_string(),
513 ),
514 (
515 "font-size",
516 format!("{}px", style.clone_font_size().computed_size().px()),
517 ),
518 ("width", style.clone_width().to_css_string()),
519 ])
520 }
521
522 pub fn is_display_none(&self) -> bool {
524 self.display_style()
525 .is_some_and(|display| display.is_none())
526 }
527
528 pub fn is_or_contains_block(&self) -> bool {
529 let style = self.primary_styles();
530 let style = style.as_ref();
531
532 let position = style
534 .map(|s| s.clone_position())
535 .unwrap_or(Position::Relative);
536 let is_in_flow = matches!(
537 position,
538 Position::Static | Position::Relative | Position::Sticky
539 );
540 if !is_in_flow {
541 return false;
542 }
543 let is_floating = style
546 .map(|s| s.clone_float().is_floating())
547 .unwrap_or(false);
548 if is_floating {
549 return false;
550 }
551 let display = style
552 .map(|s| s.clone_display())
553 .unwrap_or(StyloDisplay::inline());
554 match display.outside() {
555 DisplayOutside::None => false,
556 DisplayOutside::Block => true,
557 _ => {
558 if display.inside() == DisplayInside::Flow {
559 self.children
560 .iter()
561 .copied()
562 .any(|child_id| self.tree()[child_id].is_or_contains_block())
563 } else {
564 false
565 }
566 }
567 }
568 }
569
570 pub fn is_whitespace_node(&self) -> bool {
571 match &self.data {
572 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
573 _ => false,
574 }
575 }
576
577 pub fn is_focussable(&self) -> bool {
578 self.data
579 .downcast_element()
580 .map(|el| el.is_focussable)
581 .unwrap_or(false)
582 }
583
584 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
585 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
586 if let Some(mut element_data) = stylo_element_data.get_mut() {
587 element_data.hint.insert(hint);
588 }
589 }
590 self.mark_ancestors_dirty();
593 }
594
595 pub fn has_dirty_descendants(&self) -> bool {
597 self.dirty_descendants_flag()
598 .is_some_and(|flag| flag.load(Ordering::Relaxed))
599 }
600
601 pub fn set_dirty_descendants(&self) {
603 if let Some(flag) = self.dirty_descendants_flag() {
604 flag.store(true, Ordering::Relaxed);
605 }
606 }
607
608 pub fn unset_dirty_descendants(&self) {
610 if let Some(flag) = self.dirty_descendants_flag() {
611 flag.store(false, Ordering::Relaxed);
612 }
613 }
614
615 pub(crate) fn mark_style_attr_updated(&mut self) {
617 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
618 if let Some(mut data) = stylo_element_data.get_mut() {
619 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
620 }
621 }
622 self.set_dirty_descendants();
623 self.mark_ancestors_dirty();
624 }
625
626 pub fn mark_ancestors_dirty(&self) {
630 let mut current_id = self.parent;
631 while let Some(parent_id) = current_id {
632 let parent = &self.tree()[parent_id];
633 if let Some(flag) = parent.dirty_descendants_flag() {
636 if flag.swap(true, Ordering::Relaxed) {
637 break;
638 }
639 }
640 current_id = parent.parent;
641 }
642 }
643
644 pub fn damage(&self) -> Option<RestyleDamage> {
651 self.stylo_element_data_opt()
652 .and_then(|stylo| stylo.get().map(|data| data.damage))
653 }
654
655 pub fn set_damage(&mut self, damage: RestyleDamage) {
656 if let Some(stylo) = self.stylo_element_data_opt_mut() {
657 if let Some(mut data) = stylo.get_mut() {
658 data.damage = damage;
659 }
660 }
661 }
662
663 pub fn insert_damage(&mut self, damage: RestyleDamage) {
664 if let Some(stylo) = self.stylo_element_data_opt_mut() {
665 if let Some(mut data) = stylo.get_mut() {
666 data.damage |= damage;
667 }
668 }
669 }
670
671 pub fn remove_damage(&mut self, damage: RestyleDamage) {
672 if let Some(stylo) = self.stylo_element_data_opt_mut() {
673 if let Some(mut data) = stylo.get_mut() {
674 data.damage.remove(damage);
675 }
676 }
677 }
678
679 pub fn clear_damage_mut(&mut self) {
680 if let Some(stylo) = self.stylo_element_data_opt_mut() {
681 if let Some(mut data) = stylo.get_mut() {
682 data.damage = RestyleDamage::empty();
683 }
684 }
685 }
686
687 pub fn hover(&mut self) {
688 if let Some(data) = self.element_data_mut() {
689 data.element_state.insert(ElementState::HOVER);
690 }
691 self.set_restyle_hint(RestyleHint::restyle_subtree());
692 }
693
694 pub fn unhover(&mut self) {
695 if let Some(data) = self.element_data_mut() {
696 data.element_state.remove(ElementState::HOVER);
697 }
698 self.set_restyle_hint(RestyleHint::restyle_subtree());
699 }
700
701 pub fn is_hovered(&self) -> bool {
702 self.element_data()
703 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
704 }
705
706 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
707 if let Some(data) = self.element_data_mut() {
708 data.element_state
709 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
710 }
711 self.set_restyle_hint(RestyleHint::restyle_subtree());
712
713 if self
715 .element_data()
716 .and_then(|elem| elem.text_input_data())
717 .is_some()
718 {
719 shell_provider.set_ime_enabled(true);
720 let mut pos = self.absolute_position(0.0, 0.0);
721 pos.x += self.final_layout().content_box_x();
722 pos.y += self.final_layout().content_box_y();
723 let width = self.final_layout().content_box_width();
724 let height = self.final_layout().content_box_height();
725 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
726 }
727 }
728
729 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
730 if let Some(data) = self.element_data_mut() {
731 data.element_state
732 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
733 }
734 self.set_restyle_hint(RestyleHint::restyle_subtree());
735
736 if self
738 .element_data()
739 .and_then(|elem| elem.text_input_data())
740 .is_some()
741 {
742 shell_provider.set_ime_enabled(false);
743 }
744 }
745
746 pub fn is_focussed(&self) -> bool {
747 self.element_data()
748 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
749 }
750
751 pub fn active(&mut self) {
752 if let Some(data) = self.element_data_mut() {
753 data.element_state.insert(ElementState::ACTIVE);
754 }
755 self.set_restyle_hint(RestyleHint::restyle_subtree());
756 }
757
758 pub fn unactive(&mut self) {
759 if let Some(data) = self.element_data_mut() {
760 data.element_state.remove(ElementState::ACTIVE);
761 }
762 self.set_restyle_hint(RestyleHint::restyle_subtree());
763 }
764
765 pub fn is_active(&self) -> bool {
766 self.element_data()
767 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
768 }
769
770 pub fn disable(&mut self) {
773 if let Some(data) = self.element_data_mut() {
774 if data.can_be_disabled() {
775 data.element_state.insert(ElementState::DISABLED);
776 data.element_state.remove(ElementState::ENABLED);
777 }
778 }
779 self.set_restyle_hint(RestyleHint::restyle_subtree());
780 }
781
782 pub fn enable(&mut self) {
785 if let Some(data) = self.element_data_mut() {
786 if data.can_be_disabled() {
787 data.element_state.insert(ElementState::ENABLED);
788 data.element_state.remove(ElementState::DISABLED);
789 }
790 }
791 self.set_restyle_hint(RestyleHint::restyle_subtree());
792 }
793
794 pub fn subdoc(&self) -> Option<&dyn Document> {
795 self.element_data().and_then(|el| el.sub_doc_data())
796 }
797
798 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
799 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
800 }
801
802 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
803 if let Some(input_data) = self
806 .data
807 .downcast_element()
808 .and_then(|el| el.text_input_data())
809 {
810 if !input_data.is_multiline {
811 let content_box_height = self.final_layout().content_box_height();
812 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
813 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
814
815 return y_offset as f64;
816 }
817 }
818
819 0.0
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq)]
824pub enum NodeKind {
825 Document,
826 Element,
827 AnonymousBlock,
828 Text,
829 Comment,
830 DocumentFragment,
831 ShadowRoot,
832}
833
834#[derive(Debug, Clone, Copy, PartialEq, Eq)]
840pub enum TextGranularity {
841 Word,
843 Line,
845}
846
847impl TextGranularity {
848 pub fn from_click_count(count: u16) -> Option<Self> {
851 match count {
852 0 | 1 => None,
853 2 => Some(Self::Word),
854 _ => Some(Self::Line),
855 }
856 }
857}
858
859#[derive(Debug, Clone, Copy, PartialEq, Eq)]
863pub enum ShadowRootMode {
864 Open,
867 Closed,
870}
871
872#[derive(Debug, Clone)]
880pub struct ShadowRootData {
881 pub host: NodeId,
883 pub mode: ShadowRootMode,
885 pub stylesheet_nodes: Vec<NodeId>,
888}
889
890impl ShadowRootData {
891 pub fn new(host: NodeId, mode: ShadowRootMode) -> Self {
892 Self {
893 host,
894 mode,
895 stylesheet_nodes: Vec::new(),
896 }
897 }
898}
899
900#[derive(Debug, Clone)]
902pub enum NodeData {
903 Document(Box<DocumentData>),
905
906 Element(Box<ElementData>),
908
909 AnonymousBlock(Box<ElementData>),
911
912 Text(TextNodeData),
914
915 Comment {
917 contents: String,
919 },
920
921 DocumentFragment,
924
925 ShadowRoot(ShadowRootData),
927 }
934
935impl NodeData {
936 pub fn downcast_element(&self) -> Option<&ElementData> {
937 match self {
938 Self::Element(data) => Some(data),
939 Self::AnonymousBlock(data) => Some(data),
940 _ => None,
941 }
942 }
943
944 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
945 match self {
946 Self::Element(data) => Some(data),
947 Self::AnonymousBlock(data) => Some(data),
948 _ => None,
949 }
950 }
951
952 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
953 let Some(elem) = self.downcast_element() else {
954 return false;
955 };
956 *name == elem.name.local
957 }
958
959 pub fn attrs(&self) -> Option<&[Attribute]> {
960 Some(&self.downcast_element()?.attrs)
961 }
962
963 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
964 self.downcast_element()?.attr(name)
965 }
966
967 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
968 self.downcast_element()
969 .is_some_and(|elem| elem.has_attr(name))
970 }
971
972 pub fn kind(&self) -> NodeKind {
973 match self {
974 NodeData::Document(_) => NodeKind::Document,
975 NodeData::Element(_) => NodeKind::Element,
976 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
977 NodeData::Text(_) => NodeKind::Text,
978 NodeData::Comment { .. } => NodeKind::Comment,
979 NodeData::DocumentFragment => NodeKind::DocumentFragment,
980 NodeData::ShadowRoot(_) => NodeKind::ShadowRoot,
981 }
982 }
983
984 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
985 match self {
986 Self::ShadowRoot(data) => Some(data),
987 _ => None,
988 }
989 }
990
991 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
992 match self {
993 Self::ShadowRoot(data) => Some(data),
994 _ => None,
995 }
996 }
997}
998
999#[derive(Debug, Clone)]
1000pub struct TextNodeData {
1001 pub content: String,
1003}
1004
1005impl TextNodeData {
1006 pub fn new(content: String) -> Self {
1007 Self { content }
1008 }
1009}
1010
1011impl Node {
1036 pub fn tree(&self) -> &crate::NodeTree {
1037 unsafe { &*self.tree }
1038 }
1039
1040 #[track_caller]
1041 pub fn with(&self, id: NodeId) -> &Node {
1042 self.tree().get(id).unwrap()
1043 }
1044
1045 pub fn print_tree(&self, level: usize) {
1046 println!(
1047 "{} {} {:?} {} {:?}",
1048 " ".repeat(level),
1049 self.id,
1050 self.parent,
1051 self.node_debug_str().replace('\n', ""),
1052 self.children
1053 );
1054 for child_id in self.children.iter() {
1056 let child = self.with(*child_id);
1057 child.print_tree(level + 1)
1058 }
1059 }
1060
1061 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1063 self.children.iter().position(|id| *id == child_id)
1064 }
1065
1066 pub fn child_index(&self) -> Option<usize> {
1068 self.tree()[self.parent?]
1069 .children
1070 .iter()
1071 .position(|id| *id == self.id)
1072 }
1073
1074 pub fn forward(&self, n: usize) -> Option<&Node> {
1076 let child_idx = self.child_index().unwrap_or(0);
1077 self.tree()[self.parent?]
1078 .children
1079 .get(child_idx + n)
1080 .map(|id| self.with(*id))
1081 }
1082
1083 pub fn backward(&self, n: usize) -> Option<&Node> {
1084 let child_idx = self.child_index().unwrap_or(0);
1085 if child_idx < n {
1086 return None;
1087 }
1088
1089 self.tree()[self.parent?]
1090 .children
1091 .get(child_idx - n)
1092 .map(|id| self.with(*id))
1093 }
1094
1095 pub fn is_element(&self) -> bool {
1096 matches!(self.data, NodeData::Element { .. })
1097 }
1098
1099 pub fn is_anonymous(&self) -> bool {
1100 matches!(self.data, NodeData::AnonymousBlock { .. })
1101 }
1102
1103 pub fn is_shadow_root(&self) -> bool {
1104 matches!(self.data, NodeData::ShadowRoot { .. })
1105 }
1106
1107 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1108 self.data.shadow_root_data()
1109 }
1110
1111 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1112 self.data.shadow_root_data_mut()
1113 }
1114
1115 pub fn shadow_root_id(&self) -> Option<NodeId> {
1118 self.element_data().and_then(|el| el.shadow_root)
1119 }
1120
1121 #[cfg(feature = "shadow-dom")]
1125 pub fn layout_dom_children(&self) -> &[NodeId] {
1126 match &self.flattened_children {
1127 Some(children) => children,
1128 None => &self.children,
1129 }
1130 }
1131
1132 #[cfg(not(feature = "shadow-dom"))]
1134 #[inline(always)]
1135 pub fn layout_dom_children(&self) -> &[NodeId] {
1136 &self.children
1137 }
1138
1139 pub fn is_text_node(&self) -> bool {
1140 matches!(self.data, NodeData::Text { .. })
1141 }
1142
1143 pub fn element_data(&self) -> Option<&ElementData> {
1144 match self.data {
1145 NodeData::Element(ref data) => Some(data),
1146 NodeData::AnonymousBlock(ref data) => Some(data),
1147 _ => None,
1148 }
1149 }
1150
1151 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1152 match self.data {
1153 NodeData::Element(ref mut data) => Some(data),
1154 NodeData::AnonymousBlock(ref mut data) => Some(data),
1155 _ => None,
1156 }
1157 }
1158
1159 pub fn text_data(&self) -> Option<&TextNodeData> {
1160 match self.data {
1161 NodeData::Text(ref data) => Some(data),
1162 _ => None,
1163 }
1164 }
1165
1166 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1167 match self.data {
1168 NodeData::Text(ref mut data) => Some(data),
1169 _ => None,
1170 }
1171 }
1172
1173 pub fn node_debug_str(&self) -> String {
1174 let mut s = String::new();
1175
1176 match &self.data {
1177 NodeData::Document(_) => write!(s, "DOCUMENT"),
1178 NodeData::DocumentFragment => write!(s, "FRAGMENT"),
1179 NodeData::Text(data) => {
1181 let bytes = data.content.as_bytes();
1182 write!(
1183 s,
1184 "TEXT {}",
1185 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1186 .unwrap_or("INVALID UTF8")
1187 )
1188 }
1189 NodeData::Comment { .. } => write!(s, "COMMENT"),
1190 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1191 NodeData::ShadowRoot(data) => write!(s, "#shadow-root ({:?})", data.mode),
1192 NodeData::Element(data) => {
1193 let name = &data.name;
1194 let class = self.attr(local_name!("class")).unwrap_or("");
1195 let id = self.attr(local_name!("id")).unwrap_or("");
1196 let display = self.display_constructed_as().to_css_string();
1197 write!(s, "<{}", name.local).unwrap();
1198 if !id.is_empty() {
1199 write!(s, " #{id}").unwrap();
1200 }
1201 if !class.is_empty() {
1202 if class.contains(' ') {
1203 write!(s, " class=\"{class}\"").unwrap()
1204 } else {
1205 write!(s, " .{class}").unwrap()
1206 }
1207 }
1208 write!(s, "> ({display})")
1209 } }
1211 .unwrap();
1212 s
1213 }
1214
1215 pub fn outer_html(&self) -> String {
1223 let mut output = String::new();
1224 self.write_outer_html(&mut output);
1225 output
1226 }
1227
1228 pub fn outer_html_pretty(&self) -> String {
1244 let mut output = String::new();
1245 self.write_outer_html_pretty(&mut output);
1246 output
1247 }
1248
1249 pub fn write_outer_html(&self, writer: &mut String) {
1250 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
1251 }
1252
1253 #[cfg(feature = "svg")]
1254 pub(crate) fn write_outer_html_with_current_color(
1255 &self,
1256 writer: &mut String,
1257 current_color: &str,
1258 ) {
1259 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
1260 }
1261
1262 pub fn write_outer_html_pretty(&self, writer: &mut String) {
1263 self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
1264 }
1265
1266 fn write_outer_html_in_style(
1267 &self,
1268 writer: &mut String,
1269 style: OutputStyle,
1270 nesting: usize,
1271 current_color_override: Option<&str>,
1272 ) {
1273 const INDENT: &str = " ";
1274 let has_children = !self.children.is_empty();
1275 let computed_current_color = || {
1276 self.primary_styles()
1277 .map(|style| style.clone_color())
1278 .map(|color| crate::util::absolute_color_to_svg_css(&color))
1279 };
1280 let current_color = current_color_override
1281 .map(ToOwned::to_owned)
1282 .or_else(computed_current_color);
1283
1284 match &self.data {
1285 NodeData::Document(_) => {}
1286 NodeData::Comment { .. } => {}
1287 NodeData::AnonymousBlock(_) => {}
1288 NodeData::ShadowRoot(_) => {}
1289 NodeData::DocumentFragment => {}
1293 NodeData::Text(data) => {
1295 if matches!(style, OutputStyle::Pretty) {
1296 for _ in 0..nesting {
1297 writer.push_str(INDENT);
1298 }
1299 }
1300 writer.push_str(data.content.as_str());
1301 if matches!(style, OutputStyle::Pretty) {
1302 writer.push('\n');
1303 }
1304 }
1305 NodeData::Element(data) => {
1306 if matches!(style, OutputStyle::Pretty) {
1307 for _ in 0..nesting {
1308 writer.push_str(INDENT);
1309 }
1310 }
1311 writer.push('<');
1312 writer.push_str(&data.name.local);
1313
1314 for attr in data.attrs() {
1315 writer.push(' ');
1316 writer.push_str(&attr.name.local);
1317 writer.push_str("=\"");
1318 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
1320 let value = attr
1321 .value
1322 .replace("currentColor", current_color.as_ref().unwrap());
1323 encode_quoted_attribute_to_string(&value, writer);
1324 } else {
1325 encode_quoted_attribute_to_string(&attr.value, writer);
1326 }
1327 writer.push('"');
1328 }
1329 if !has_children {
1330 writer.push_str(" /");
1331 }
1332 writer.push('>');
1333 if matches!(style, OutputStyle::Pretty) {
1334 writer.push('\n');
1335 }
1336
1337 if has_children {
1338 for &child_id in &self.children {
1339 self.tree()[child_id].write_outer_html_in_style(
1340 writer,
1341 style,
1342 nesting + 1,
1343 current_color_override,
1344 );
1345 }
1346
1347 if matches!(style, OutputStyle::Pretty) {
1348 for _ in 0..nesting {
1349 writer.push_str(INDENT);
1350 }
1351 }
1352 writer.push_str("</");
1353 writer.push_str(&data.name.local);
1354 writer.push('>');
1355 if matches!(style, OutputStyle::Pretty) {
1356 writer.push('\n');
1357 }
1358 }
1359 }
1360 }
1361 }
1362
1363 pub fn attrs(&self) -> Option<&[Attribute]> {
1364 Some(&self.element_data()?.attrs)
1365 }
1366
1367 pub fn attr(&self, name: LocalName) -> Option<&str> {
1368 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1369 Some(&attr.value)
1370 }
1371
1372 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1373 self.stylo_element_data_opt()
1374 .and_then(|stylo| stylo.primary_styles())
1375 }
1376
1377 pub fn text_content(&self) -> String {
1378 let mut out = String::new();
1379 self.write_text_content(&mut out);
1380 out
1381 }
1382
1383 pub fn write_text_content<W: Write>(&self, out: &mut W) {
1396 match &self.data {
1397 NodeData::Text(data) => {
1398 let _ = out.write_str(&data.content);
1399 }
1400 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1401 for child_id in self.children.iter() {
1402 self.with(*child_id).write_text_content(out);
1403 }
1404 }
1405 _ => {}
1406 }
1407 }
1408
1409 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1410 if let NodeData::Element(ref mut elem_data) = self.data {
1411 if let Some(guard) = elem_data.guard.clone() {
1412 elem_data.flush_style_attribute(&guard, url_extra_data);
1413 }
1414 }
1415 }
1416
1417 pub fn order(&self) -> i32 {
1418 self.primary_styles()
1419 .map(|s| match s.pseudo() {
1420 Some(PseudoElement::Before) => i32::MIN,
1421 Some(PseudoElement::After) => i32::MAX,
1422 _ => s.clone_order(),
1423 })
1424 .unwrap_or(0)
1425 }
1426
1427 pub fn z_index(&self) -> i32 {
1428 self.primary_styles()
1429 .map(|s| s.clone_z_index().integer_or(0))
1430 .unwrap_or(0)
1431 }
1432
1433 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1435 let Some(style) = self.primary_styles() else {
1436 return false;
1437 };
1438
1439 let position = style.clone_position();
1440 let has_z_index = !style.clone_z_index().is_auto();
1441
1442 if style.clone_opacity() != 1.0 {
1443 return true;
1444 }
1445
1446 let position_based = match position {
1447 Position::Fixed | Position::Sticky => true,
1448 Position::Relative | Position::Absolute => has_z_index,
1449 Position::Static => has_z_index && is_flex_or_grid_item,
1450 };
1451 if position_based {
1452 return true;
1453 }
1454
1455 let box_styles = style.get_box();
1463 if !box_styles.transform.0.is_empty()
1464 || !matches!(box_styles.translate, Translate::None)
1465 || !matches!(box_styles.rotate, Rotate::None)
1466 || !matches!(box_styles.scale, Scale::None)
1467 {
1468 return true;
1469 }
1470
1471 if box_styles.isolation == Isolation::Isolate {
1476 return true;
1477 }
1478
1479 false
1486 }
1487
1488 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1497 self.hit_inner(x, y, scale, &mut None)
1498 }
1499
1500 pub(crate) fn hit_inner(
1505 &self,
1506 x: f32,
1507 y: f32,
1508 scale: f64,
1509 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1510 ) -> Option<HitResult> {
1511 use style::computed_values::pointer_events::T as PointerEvents;
1512 use style::computed_values::visibility::T as Visibility;
1513
1514 if matches!(self.style().display, taffy::Display::None) {
1523 return None;
1524 }
1525
1526 if let Some(style) = self.primary_styles() {
1528 if matches!(
1529 style.clone_visibility(),
1530 Visibility::Hidden | Visibility::Collapse
1531 ) {
1532 return None;
1533 }
1534 }
1535
1536 let pointer_events_none = self
1539 .primary_styles()
1540 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1541
1542 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1543 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1544
1545 if let Some(t) = *self.transform() {
1546 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1547 x = (p.x / scale) as f32;
1548 y = (p.y / scale) as f32;
1549 }
1550
1551 let size = self.final_layout().size;
1552 let matches_self = !(x < 0.0
1553 || x > size.width + self.scroll_offset().x as f32
1554 || y < 0.0
1555 || y > size.height + self.scroll_offset().y as f32);
1556
1557 let content_size = self.final_layout().content_size;
1558 let matches_content = !(x < 0.0
1559 || x > content_size.width + self.scroll_offset().x as f32
1560 || y < 0.0
1561 || y > content_size.height + self.scroll_offset().y as f32);
1562
1563 let matches_hoisted_content = match &self.stacking_context {
1564 Some(sc) => {
1565 let content_area = sc.content_area;
1566 x >= content_area.left + self.scroll_offset().x as f32
1567 && x <= content_area.right + self.scroll_offset().x as f32
1568 && y >= content_area.top + self.scroll_offset().y as f32
1569 && y <= content_area.bottom + self.scroll_offset().y as f32
1570 }
1571 None => false,
1572 };
1573
1574 let overflow = *self.scrollable_overflow();
1577
1578 let matches_overflow = x >= (overflow.x0 / scale) as f32
1579 && x <= (overflow.x1 / scale) as f32
1580 && y >= (overflow.y0 / scale) as f32
1581 && y <= (overflow.y1 / scale) as f32;
1582
1583 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1584 return None;
1585 }
1586
1587 if matches_self
1590 && let Some(sb) = self.scrollbar_at_local(
1591 (x - self.scroll_offset().x as f32) as f64,
1592 (y - self.scroll_offset().y as f32) as f64,
1593 )
1594 {
1595 *scrollbar = Some(sb);
1596 }
1597
1598 if self.flags.is_inline_root() {
1599 let content_box_offset = taffy::Point {
1600 x: self.final_layout().padding.left + self.final_layout().border.left,
1601 y: self.final_layout().padding.top + self.final_layout().border.top,
1602 };
1603 x -= content_box_offset.x;
1604 y -= content_box_offset.y;
1605 }
1606
1607 if matches_hoisted_content {
1609 if let Some(hoisted) = &self.stacking_context {
1610 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1611 let x = x - hoisted_child.position.x;
1612 let y = y - hoisted_child.position.y;
1613 if let Some(hit) = self
1614 .with(hoisted_child.node_id)
1615 .hit_inner(x, y, scale, scrollbar)
1616 {
1617 return Some(hit);
1618 }
1619 }
1620 }
1621 }
1622
1623 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1625 if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1626 return Some(hit);
1627 }
1628 }
1629
1630 if matches_hoisted_content {
1632 if let Some(hoisted) = &self.stacking_context {
1633 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1634 let x = x - hoisted_child.position.x;
1635 let y = y - hoisted_child.position.y;
1636 if let Some(hit) = self
1637 .with(hoisted_child.node_id)
1638 .hit_inner(x, y, scale, scrollbar)
1639 {
1640 return Some(hit);
1641 }
1642 }
1643 }
1644 }
1645
1646 if self.flags.is_inline_root() {
1648 let element_data = &self.element_data().unwrap();
1649 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1650 let layout = &ild.layout;
1651 let scale = layout.scale();
1652
1653 if let Some((cluster, _side)) =
1654 Cluster::from_point_exact(layout, x * scale, y * scale)
1655 {
1656 let style_index = cluster.glyphs().next()?.style_index();
1657 let node_id = layout.styles()[style_index].brush.id;
1658 let text_pointer_events_none = self
1659 .with(node_id)
1660 .primary_styles()
1661 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1662 if !text_pointer_events_none {
1663 return Some(HitResult {
1664 node_id,
1665 x,
1666 y,
1667 is_text: true,
1668 });
1669 }
1670 }
1671 }
1672 }
1673
1674 if matches_self && !pointer_events_none {
1676 return Some(HitResult {
1677 node_id: self.id,
1678 x,
1679 y,
1680 is_text: false,
1681 });
1682 }
1683
1684 None
1685 }
1686
1687 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1690 let mut node = self;
1691 loop {
1692 if node.flags.is_inline_root() {
1693 return Some(node);
1694 }
1695 let id = node.layout_parent.get()?;
1696 node = self.with(id);
1697 }
1698 }
1699
1700 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1704 if !self.flags.is_inline_root() {
1705 return None;
1706 }
1707
1708 let element_data = self.element_data()?;
1709 let inline_layout = element_data.inline_layout_data.as_ref()?;
1710 let layout = &inline_layout.layout;
1711 let scale = layout.scale();
1712
1713 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1715
1716 let is_leading = side == ClusterSide::Left;
1721 let offset = if cluster.is_rtl() {
1722 if is_leading {
1723 cluster.text_range().end
1724 } else {
1725 cluster.text_range().start
1726 }
1727 } else {
1728 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1730 cluster.text_range().start
1731 } else {
1732 cluster.text_range().end
1733 }
1734 };
1735
1736 Some(offset)
1737 }
1738
1739 pub fn text_range_at_point(
1749 &self,
1750 x: f32,
1751 y: f32,
1752 granularity: TextGranularity,
1753 ) -> Option<Range<usize>> {
1754 if !self.flags.is_inline_root() {
1755 return None;
1756 }
1757
1758 let element_data = self.element_data()?;
1759 let inline_layout = element_data.inline_layout_data.as_ref()?;
1760 let layout = &inline_layout.layout;
1761 let scale = layout.scale();
1762 let (x, y) = (x * scale, y * scale);
1763
1764 Cluster::from_point(layout, x, y)?;
1769
1770 let selection = match granularity {
1771 TextGranularity::Word => Selection::word_from_point(layout, x, y),
1772 TextGranularity::Line => Selection::hard_line_from_point(layout, x, y),
1776 };
1777
1778 let range = selection.text_range();
1779 if range.is_empty() { None } else { Some(range) }
1780 }
1781
1782 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1784 let x = x + self.final_layout().location.x;
1787 let y = y + self.final_layout().location.y;
1788
1789 self.layout_parent
1791 .get()
1792 .and_then(|id| self.tree().get(id))
1793 .map(|parent| {
1794 parent.absolute_position(
1795 x - parent.scroll_offset().x as f32,
1796 y - parent.scroll_offset().y as f32,
1797 )
1798 })
1799 .unwrap_or(crate::util::Point { x, y })
1800 }
1801
1802 fn is_offset_parent(&self) -> bool {
1805 let Some(styles) = self.primary_styles() else {
1806 return false;
1807 };
1808 if styles.get_box().position != Position::Static {
1809 return true;
1810 }
1811 self.data.is_element_with_tag_name(&local_name!("body"))
1812 || self.data.is_element_with_tag_name(&local_name!("td"))
1813 || self.data.is_element_with_tag_name(&local_name!("th"))
1814 }
1815
1816 pub fn offset_parent(&self) -> Option<&Node> {
1824 let mut node = self;
1825 loop {
1826 node = self.with(node.layout_parent.get()?);
1827 if node.is_offset_parent() {
1828 return Some(node);
1829 }
1830 }
1831 }
1832
1833 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1836 let mut x = 0.0;
1837 let mut y = 0.0;
1838 let mut current = self;
1839 loop {
1840 let layout = current.final_layout();
1841 x += layout.location.x;
1842 y += layout.location.y;
1843
1844 let Some(parent_id) = current.layout_parent.get() else {
1845 break;
1846 };
1847 let parent = self.with(parent_id);
1848 if parent.is_offset_parent() {
1849 let border = parent.final_layout().border;
1850 x -= border.left;
1851 y -= border.top;
1852 break;
1853 }
1854 current = parent;
1855 }
1856 crate::util::Point { x, y }
1857 }
1858
1859 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1861 DomEventData::Click(self.synthetic_click_event_data(mods))
1862 }
1863
1864 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1865 let absolute_position = self.absolute_position(0.0, 0.0);
1866 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1867 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1868
1869 BlitzPointerEvent {
1870 id: BlitzPointerId::Mouse,
1871 is_primary: true,
1872 coords: PointerCoords {
1873 page_x: x,
1874 page_y: y,
1875
1876 screen_x: x,
1878 screen_y: y,
1879 client_x: x,
1880 client_y: y,
1881 },
1882 mods,
1883 button: Default::default(),
1884 buttons: Default::default(),
1885 details: Default::default(),
1886 element: Default::default(),
1887 active_pointers: Default::default(),
1888 }
1889 }
1890}
1891
1892impl PartialEq for Node {
1894 fn eq(&self, other: &Self) -> bool {
1895 self.id == other.id
1896 }
1897}
1898
1899impl Eq for Node {}
1900
1901impl std::fmt::Debug for Node {
1902 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1903 f.debug_struct("NodeData")
1905 .field("parent", &self.parent)
1906 .field("id", &self.id)
1907 .field("is_inline_root", &self.flags.is_inline_root())
1908 .field("children", &self.children)
1909 .field("layout_children", &self.layout_children.borrow())
1910 .field("node", &self.data)
1912 .field("stylo_element_data", &self.stylo_element_data_opt())
1913 .finish()
1916 }
1917}
1918
1919#[cfg(test)]
1920mod test {
1921 use style_dom::ElementState;
1922
1923 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1924
1925 #[test]
1926 fn absolute_position_tolerates_a_detached_layout_parent() {
1927 let mut document = BaseDocument::new(DocumentConfig::default());
1928 let parent = document.create_node(NodeData::Element(Box::new(ElementData::new(
1929 qual_name!("section"),
1930 vec![],
1931 ))));
1932 let child = document.create_node(NodeData::Element(Box::new(ElementData::new(
1933 qual_name!("button"),
1934 vec![],
1935 ))));
1936 document
1937 .get_node(child)
1938 .unwrap()
1939 .layout_parent
1940 .set(Some(parent));
1941
1942 document.remove_node_from_tree(parent);
1947
1948 assert_eq!(
1949 document
1950 .get_node(child)
1951 .unwrap()
1952 .absolute_position(4.0, 7.0),
1953 crate::util::Point { x: 4.0, y: 7.0 },
1954 );
1955 }
1956
1957 #[test]
1958 fn create_node_with_disabled_attr() {
1959 let mut document = BaseDocument::new(DocumentConfig::default());
1960 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1961 qual_name!("button"),
1962 vec![Attribute {
1963 name: qual_name!("disabled"),
1964 value: "".into(),
1965 }],
1966 ))));
1967 let node = document.get_node(node).unwrap();
1968
1969 assert!(
1970 node.element_state().contains(ElementState::DISABLED),
1971 "form node is disabled"
1972 );
1973 assert!(
1974 !node.element_state().contains(ElementState::ENABLED),
1975 "form node is not enabled"
1976 );
1977 }
1978
1979 #[test]
1980 fn ignore_disabled_attr_content() {
1981 let mut document = BaseDocument::new(DocumentConfig::default());
1982 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1983 qual_name!("button"),
1984 vec![Attribute {
1985 name: qual_name!("disabled"),
1986 value: "false".into(),
1987 }],
1988 ))));
1989 let node = document.get_node(node).unwrap();
1990
1991 assert!(
1992 node.element_state().contains(ElementState::DISABLED),
1993 "form node is disabled"
1994 );
1995 assert!(
1996 !node.element_state().contains(ElementState::ENABLED),
1997 "form node is not enabled"
1998 );
1999 }
2000
2001 #[test]
2002 fn create_node_with_ignored_disable() {
2003 let mut document = BaseDocument::new(DocumentConfig::default());
2004 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2005 qual_name!("a"),
2006 vec![Attribute {
2007 name: qual_name!("disabled"),
2008 value: "".into(),
2009 }],
2010 ))));
2011 let node = document.get_node(node).unwrap();
2012
2013 assert!(
2014 !node.element_state().contains(ElementState::DISABLED),
2015 "Non form node cannot be disabled"
2016 );
2017 assert!(
2018 !node.element_state().contains(ElementState::ENABLED),
2019 "Non form node cannot be enabled"
2020 );
2021 }
2022
2023 #[test]
2024 fn create_empty_enabled_node() {
2025 let mut document = BaseDocument::new(DocumentConfig::default());
2026 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2027 qual_name!("button"),
2028 vec![],
2029 ))));
2030 let node = document.get_node(node).unwrap();
2031
2032 assert!(
2033 node.element_state().contains(ElementState::ENABLED),
2034 "Button should be enabled by default"
2035 );
2036 }
2037}