1use crate::Document;
2use crate::layout::paint_tree::{HoistedPaintChild, StackingContext};
3use apace_traits::events::{
4 ApacePointerEvent, ApacePointerId, DomEventData, HitResult, PointerCoords,
5};
6use apace_traits::node_id::NodeId;
7use apace_traits::shell::ShellProvider;
8use bitflags::bitflags;
9use euclid::{Point2D, Rect, Size2D};
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 std::cell::{Cell, RefCell};
16use std::fmt::Write;
17use std::ops::Deref;
18use std::sync::Arc;
19use std::sync::atomic::{AtomicBool, Ordering};
20#[cfg(feature = "writing-mode")]
21use style::computed_values::flex_direction::T as FlexDirection;
22use style::computed_values::isolation::T as Isolation;
23use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
24use style::invalidation::element::restyle_hints::RestyleHint;
25use style::properties::ComputedValues;
26use style::properties::generated::longhands::position::computed_value::T as Position;
27use style::selector_parser::RestyleDamage;
28use style::servo_arc::Arc as ServoArc;
29use style::shared_lock::SharedRwLock;
30use style::stylesheets::UrlExtraData;
31use style::values::computed::CSSPixelLength;
32use style::values::computed::Display as StyloDisplay;
33use style::values::computed::Rotate;
34use style::values::generics::transform::{Scale, Translate};
35use style::values::specified::box_::{DisplayInside, DisplayOutside};
36use style_dom::ElementState;
37use style_traits::values::ToCss;
38use takia::{Cache, prelude::Layout};
39use thin_vec::ThinVec;
40
41use super::stylo_data::{ComputedStyleRef, StyloData};
42use super::{Attribute, DocumentData, ElementData, LayoutData};
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum DisplayOuter {
46 Block,
47 Inline,
48 None,
49}
50
51bitflags! {
52 #[derive(Clone, Copy, PartialEq)]
53 pub struct NodeFlags: u32 {
54 const IS_INLINE_ROOT = 0b00000001;
56 const IS_TABLE_ROOT = 0b00000010;
58 const IS_IN_DOCUMENT = 0b00000100;
60 const IS_TOP_LAYER = 0b00001000;
62 const CONTAIN_TOP_LAYER_SCROLL = 0b00010000;
63 }
64}
65
66impl NodeFlags {
67 pub fn is_top_layer(&self) -> bool {
68 self.contains(Self::IS_TOP_LAYER)
69 }
70 #[inline(always)]
71 pub fn is_inline_root(&self) -> bool {
72 self.contains(Self::IS_INLINE_ROOT)
73 }
74
75 #[inline(always)]
76 pub fn is_table_root(&self) -> bool {
77 self.contains(Self::IS_TABLE_ROOT)
78 }
79
80 #[inline(always)]
81 pub fn is_in_document(&self) -> bool {
82 self.contains(Self::IS_IN_DOCUMENT)
83 }
84
85 #[inline(always)]
86 pub fn reset_construction_flags(&mut self) {
87 self.remove(Self::IS_INLINE_ROOT);
88 self.remove(Self::IS_TABLE_ROOT);
89 }
90}
91
92pub struct Node {
93 tree: *mut crate::NodeTree,
95
96 pub id: NodeId,
98 pub parent: Option<NodeId>,
100 pub children: ThinVec<NodeId>,
102 pub layout_parent: Cell<Option<NodeId>>,
104 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
106 pub hoisted_children: RefCell<ThinVec<NodeId>>,
110 pub oof_containing_block: Cell<Option<NodeId>>,
115 pub anonymous_blocks: ThinVec<NodeId>,
121 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
123 pub stacking_context: Option<Box<StackingContext>>,
124 pub sc_contribution_cache: RefCell<ThinVec<HoistedPaintChild>>,
128
129 pub flags: NodeFlags,
131
132 pub data: NodeData,
139}
140
141unsafe impl Send for Node {}
142unsafe impl Sync for Node {}
143
144macro_rules! universal_accessors {
147 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
148 impl Node {
149 $(
150 $(#[$meta])*
151 #[inline]
152 pub fn $field(&self) -> &$ty {
153 match &self.data {
154 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
155 NodeData::Document(data) => &data.$field,
156 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
157 }
158 }
159
160 $(#[$meta])*
161 #[inline]
162 pub fn $field_mut(&mut self) -> &mut $ty {
163 match &mut self.data {
164 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
165 NodeData::Document(data) => &mut data.$field,
166 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
167 }
168 }
169 )*
170 }
171 };
172}
173
174universal_accessors! {
175 stylo_element_data / stylo_element_data_mut: StyloData,
176 transform / transform_mut: Option<Box<Affine>>,
177 display_constructed_as / display_constructed_as_mut: StyloDisplay,
178 element_state / element_state_mut: ElementState,
181 snapshot_handled / snapshot_handled_mut: AtomicBool,
182 selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
186}
187
188impl Node {
189 #[inline]
195 pub fn layout_data(&self) -> &LayoutData {
196 match &self.data {
197 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.layout_data(),
198 NodeData::Document(data) => data.layout_data(),
199 _ => panic!("`layout_data` is not available on this node kind"),
200 }
201 }
202
203 #[inline]
209 pub fn layout_data_mut(&mut self) -> &mut LayoutData {
210 match &mut self.data {
211 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.layout_data_mut(),
212 NodeData::Document(data) => data.layout_data_mut(),
213 _ => panic!("`layout_data` is not available on this node kind"),
214 }
215 }
216
217 #[inline]
221 pub fn try_layout_data_mut(&mut self) -> Option<&mut LayoutData> {
222 match &mut self.data {
223 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
224 data.layout_data.as_deref_mut()
225 }
226 NodeData::Document(data) => data.layout_data.as_deref_mut(),
227 _ => None,
228 }
229 }
230
231 #[inline]
234 pub fn clear_layout_cache(&mut self) {
235 if let Some(layout_data) = self.try_layout_data_mut() {
236 layout_data.cache.clear();
237 }
238 }
239
240 pub fn invalidate_layout_cache(&mut self) {
243 self.clear_layout_cache();
244 if let Some(inline_layout) = self
245 .data
246 .downcast_element_mut()
247 .and_then(|el| el.inline_layout_data.as_mut())
248 {
249 inline_layout.content_widths = None;
250 }
251 }
252
253 #[inline]
254 pub fn cache(&self) -> &Cache {
255 &self.layout_data().cache
256 }
257
258 #[inline]
259 pub fn cache_mut(&mut self) -> &mut Cache {
260 &mut self.layout_data_mut().cache
261 }
262
263 #[inline]
264 pub fn unrounded_layout(&self) -> &Layout {
265 &self.layout_data().unrounded_layout
266 }
267
268 #[inline]
269 pub fn unrounded_layout_mut(&mut self) -> &mut Layout {
270 &mut self.layout_data_mut().unrounded_layout
271 }
272
273 #[inline]
274 pub fn final_layout(&self) -> &Layout {
275 &self.layout_data().final_layout
276 }
277
278 #[inline]
279 pub fn final_layout_mut(&mut self) -> &mut Layout {
280 &mut self.layout_data_mut().final_layout
281 }
282
283 #[inline]
284 pub fn scroll_offset(&self) -> &crate::Point<f64> {
285 &self.layout_data().scroll_offset
286 }
287
288 #[inline]
289 pub fn scroll_offset_mut(&mut self) -> &mut crate::Point<f64> {
290 &mut self.layout_data_mut().scroll_offset
291 }
292
293 #[inline]
294 pub fn scrollable_overflow(&self) -> &KurboRect {
295 &self.layout_data().scrollable_overflow
296 }
297
298 #[inline]
299 pub fn scrollable_overflow_mut(&mut self) -> &mut KurboRect {
300 &mut self.layout_data_mut().scrollable_overflow
301 }
302
303 #[inline]
306 pub fn try_stylo_element_data(&self) -> Option<&StyloData> {
307 match &self.data {
308 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
309 Some(&data.stylo_element_data)
310 }
311 NodeData::Document(data) => Some(&data.stylo_element_data),
312 _ => None,
313 }
314 }
315
316 #[inline]
317 pub fn try_stylo_element_data_mut(&mut self) -> Option<&mut StyloData> {
318 match &mut self.data {
319 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
320 Some(&mut data.stylo_element_data)
321 }
322 NodeData::Document(data) => Some(&mut data.stylo_element_data),
323 _ => None,
324 }
325 }
326
327 #[inline]
330 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
331 match &self.data {
332 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
333 Some(&data.dirty_descendants)
334 }
335 NodeData::Document(data) => Some(&data.dirty_descendants),
336 _ => None,
337 }
338 }
339
340 #[inline]
343 fn damaged_descendants_flag(&self) -> Option<&AtomicBool> {
344 match &self.data {
345 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
346 Some(&data.damaged_descendants)
347 }
348 NodeData::Document(data) => Some(&data.damaged_descendants),
349 _ => None,
350 }
351 }
352
353 #[inline]
356 pub fn guard(&self) -> &SharedRwLock {
357 let guard = match &self.data {
358 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
359 NodeData::Document(data) => data.guard.as_ref(),
360 _ => None,
361 };
362 guard.expect("`guard` is not available on this node kind")
363 }
364
365 #[inline]
366 pub fn has_snapshot(&self) -> bool {
367 match &self.data {
368 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
369 NodeData::Document(data) => data.has_snapshot,
370 _ => false,
371 }
372 }
373
374 #[inline]
375 pub fn set_has_snapshot(&mut self, value: bool) {
376 match &mut self.data {
377 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
378 NodeData::Document(data) => data.has_snapshot = value,
379 _ => {}
380 }
381 }
382
383 #[inline]
384 pub fn before(&self) -> Option<NodeId> {
385 self.element_data().and_then(|data| data.before)
386 }
387
388 #[inline]
389 pub fn after(&self) -> Option<NodeId> {
390 self.element_data().and_then(|data| data.after)
391 }
392}
393
394impl Node {
395 pub(crate) fn new(
396 tree: *mut crate::NodeTree,
397 id: NodeId,
398 guard: SharedRwLock,
399 mut data: NodeData,
400 ) -> Self {
401 match &mut data {
404 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
405 data.guard = Some(guard);
406 }
407 NodeData::Document(data) => data.guard = Some(guard),
408 _ => {}
409 }
410
411 Self {
412 tree,
413
414 id,
415 parent: None,
416 children: ThinVec::new(),
417 layout_parent: Cell::new(None),
418 oof_containing_block: Cell::new(None),
419 layout_children: RefCell::new(None),
420 hoisted_children: RefCell::new(ThinVec::new()),
421 anonymous_blocks: ThinVec::new(),
422 paint_children: RefCell::new(None),
423 stacking_context: None,
424 sc_contribution_cache: RefCell::new(ThinVec::new()),
425
426 flags: NodeFlags::empty(),
427 data,
428 }
429 }
430
431 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
432 let transform = self.primary_styles().and_then(|s| {
433 let size = self.final_layout().size;
434 let reference_box = Rect::new(
435 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
436 Size2D::new(
437 CSSPixelLength::new(size.width),
438 CSSPixelLength::new(size.height),
439 ),
440 );
441 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
444 let scale = scale as f64;
445 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
446 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
447 })
448 });
449
450 let slot = self.transform_mut();
451 match (slot.as_deref_mut(), transform) {
452 (Some(existing), Some(new)) => *existing = new,
453 (None, Some(new)) => *slot = Some(Box::new(new)),
454 (_, None) => *slot = None,
455 }
456 transform
457 }
458
459 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
460 match index {
461 0 => self.after(),
462 1 => self.before(),
463 _ => panic!("Invalid pseudo element index"),
464 }
465 }
466
467 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
468 let Some(data) = self.element_data_mut() else {
469 return;
470 };
471 match index {
472 0 => data.after = value,
473 1 => data.before = value,
474 _ => panic!("Invalid pseudo element index"),
475 }
476 }
477
478 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
479 let display = self.primary_styles().as_ref()?.clone_display();
480 Some(
481 if self.element_data().is_some_and(|el| el.is_button_input()) {
482 stylo_takia::convert::button_input_display(display)
483 } else {
484 display
485 },
486 )
487 }
488
489 pub fn is_or_contains_block(&self) -> bool {
490 let style = self.primary_styles();
491 let style = style.as_ref();
492
493 let position = style
495 .map(|s| s.clone_position())
496 .unwrap_or(Position::Relative);
497 let is_in_flow = matches!(
498 position,
499 Position::Static | Position::Relative | Position::Sticky
500 );
501 if !is_in_flow {
502 return false;
503 }
504 let is_floating = style
507 .map(|s| s.clone_float().is_floating())
508 .unwrap_or(false);
509 if is_floating {
510 return false;
511 }
512 let display = self.display_style().unwrap_or(StyloDisplay::inline());
513 match display.outside() {
514 DisplayOutside::None => false,
515 DisplayOutside::Block => true,
516 _ => {
517 if display.inside() == DisplayInside::Flow {
518 self.children
519 .iter()
520 .copied()
521 .any(|child_id| self.tree()[child_id].is_or_contains_block())
522 } else {
523 false
524 }
525 }
526 }
527 }
528
529 pub fn is_whitespace_node(&self) -> bool {
530 match &self.data {
531 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
532 _ => false,
533 }
534 }
535
536 pub fn is_collapsible_whitespace_node(&self) -> bool {
539 let NodeData::Text(data) = &self.data else {
540 return false;
541 };
542 if data.content.is_empty() {
543 return true;
544 }
545 if !data.content.chars().all(|c| c.is_ascii_whitespace()) {
546 return false;
547 }
548 let white_space_collapse = self
549 .parent
550 .and_then(|parent_id| self.with(parent_id).primary_styles())
551 .map(|style| style.clone_white_space_collapse());
552 match white_space_collapse {
553 Some(WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces) => false,
554 Some(WhiteSpaceCollapse::PreserveBreaks) => !data.content.contains('\n'),
555 Some(WhiteSpaceCollapse::Collapse) | None => true,
556 }
557 }
558
559 pub fn is_focussable(&self) -> bool {
560 self.data
561 .downcast_element()
562 .map(|el| el.is_focussable)
563 .unwrap_or(false)
564 }
565
566 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
567 if let Some(stylo_element_data) = self.try_stylo_element_data_mut() {
568 if let Some(mut element_data) = stylo_element_data.get_mut() {
569 element_data.hint.insert(hint);
570 }
571 }
572 self.mark_ancestors_dirty();
575 }
576
577 pub fn has_dirty_descendants(&self) -> bool {
579 self.dirty_descendants_flag()
580 .is_some_and(|flag| flag.load(Ordering::Relaxed))
581 }
582
583 pub fn set_dirty_descendants(&self) {
585 if let Some(flag) = self.dirty_descendants_flag() {
586 flag.store(true, Ordering::Relaxed);
587 }
588 }
589
590 pub fn unset_dirty_descendants(&self) {
592 if let Some(flag) = self.dirty_descendants_flag() {
593 flag.store(false, Ordering::Relaxed);
594 }
595 }
596
597 pub fn mark_ancestors_dirty(&self) {
601 let mut current_id = self.parent;
602 while let Some(parent_id) = current_id {
603 let parent = &self.tree()[parent_id];
604 if let Some(flag) = parent.dirty_descendants_flag() {
607 if flag.swap(true, Ordering::Relaxed) {
608 break;
609 }
610 }
611 current_id = parent.parent;
612 }
613 }
614
615 pub fn has_damaged_descendants(&self) -> bool {
617 self.damaged_descendants_flag()
618 .is_some_and(|flag| flag.load(Ordering::Relaxed))
619 }
620
621 pub fn unset_damaged_descendants(&self) {
623 if let Some(flag) = self.damaged_descendants_flag() {
624 flag.store(false, Ordering::Relaxed);
625 }
626 }
627
628 pub fn mark_damaged(&self) {
635 if let Some(flag) = self.damaged_descendants_flag() {
636 if flag.swap(true, Ordering::Relaxed) {
637 return;
638 }
639 }
640 let mut current_id = self.parent;
641 while let Some(parent_id) = current_id {
642 let parent = &self.tree()[parent_id];
643 if let Some(flag) = parent.damaged_descendants_flag() {
646 if flag.swap(true, Ordering::Relaxed) {
647 break;
648 }
649 }
650 current_id = parent.parent;
651 }
652 }
653
654 pub fn damage(&self) -> Option<RestyleDamage> {
661 self.try_stylo_element_data()
662 .and_then(|stylo| stylo.get().map(|data| data.damage))
663 }
664
665 pub fn set_damage(&mut self, damage: RestyleDamage) {
666 if let Some(stylo) = self.try_stylo_element_data_mut() {
667 if let Some(mut data) = stylo.get_mut() {
668 data.damage = damage;
669 }
670 }
671 }
672
673 pub fn insert_damage(&mut self, damage: RestyleDamage) {
674 if let Some(stylo) = self.try_stylo_element_data_mut() {
675 if let Some(mut data) = stylo.get_mut() {
676 data.damage |= damage;
677 }
678 }
679 if !damage.is_empty() {
680 self.mark_damaged();
681 }
682 }
683
684 pub fn remove_damage(&mut self, damage: RestyleDamage) {
685 if let Some(stylo) = self.try_stylo_element_data_mut() {
686 if let Some(mut data) = stylo.get_mut() {
687 data.damage.remove(damage);
688 }
689 }
690 }
691
692 pub fn clear_damage_mut(&mut self) {
693 if let Some(stylo) = self.try_stylo_element_data_mut() {
694 if let Some(mut data) = stylo.get_mut() {
695 data.damage = RestyleDamage::empty();
696 }
697 }
698 }
699
700 pub fn hover(&mut self) {
706 if let Some(data) = self.element_data_mut() {
707 data.element_state.insert(ElementState::HOVER);
708 }
709 self.mark_ancestors_dirty();
710 }
711
712 pub fn unhover(&mut self) {
713 if let Some(data) = self.element_data_mut() {
714 data.element_state.remove(ElementState::HOVER);
715 }
716 self.mark_ancestors_dirty();
717 }
718
719 pub fn is_hovered(&self) -> bool {
720 self.element_data()
721 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
722 }
723
724 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
725 if let Some(data) = self.element_data_mut() {
726 data.element_state
727 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
728 }
729 self.mark_ancestors_dirty();
730
731 if self
733 .element_data()
734 .and_then(|elem| elem.text_input_data())
735 .is_some()
736 {
737 let password = self
738 .element_data()
739 .and_then(|element| element.text_input_data())
740 .is_some_and(|input| input.is_password());
741 shell_provider.set_ime_purpose(if password {
742 apace_traits::shell::ImePurpose::Password
743 } else {
744 apace_traits::shell::ImePurpose::Normal
745 });
746 shell_provider.set_ime_enabled(true);
747 if let Some(input) = self.element_data().and_then(|e| e.text_input_data()) {
748 input.sync_ime_context(shell_provider.as_ref());
749 }
750 if let Some((x, y, width, height)) = self.text_input_ime_cursor_area() {
751 shell_provider.set_ime_cursor_area(x, y, width, height);
752 }
753 }
754 }
755
756 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
757 if let Some(data) = self.element_data_mut() {
758 data.element_state
759 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
760 }
761 self.mark_ancestors_dirty();
762
763 if self
765 .element_data()
766 .and_then(|elem| elem.text_input_data())
767 .is_some()
768 {
769 shell_provider.set_ime_enabled(false);
770 }
771 }
772
773 pub fn is_focussed(&self) -> bool {
774 self.element_data()
775 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
776 }
777
778 pub fn active(&mut self) {
779 if let Some(data) = self.element_data_mut() {
780 data.element_state.insert(ElementState::ACTIVE);
781 }
782 self.mark_ancestors_dirty();
783 }
784
785 pub fn unactive(&mut self) {
786 if let Some(data) = self.element_data_mut() {
787 data.element_state.remove(ElementState::ACTIVE);
788 }
789 self.mark_ancestors_dirty();
790 }
791
792 pub fn is_active(&self) -> bool {
793 self.element_data()
794 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
795 }
796
797 pub fn disable(&mut self) {
800 if let Some(data) = self.element_data_mut() {
801 if data.can_be_disabled() {
802 data.element_state.insert(ElementState::DISABLED);
803 data.element_state.remove(ElementState::ENABLED);
804 }
805 }
806 self.mark_ancestors_dirty();
807 }
808
809 pub fn enable(&mut self) {
812 if let Some(data) = self.element_data_mut() {
813 if data.can_be_disabled() {
814 data.element_state.insert(ElementState::ENABLED);
815 data.element_state.remove(ElementState::DISABLED);
816 }
817 }
818 self.mark_ancestors_dirty();
819 }
820
821 pub fn subdoc(&self) -> Option<&dyn Document> {
822 self.element_data().and_then(|el| el.sub_doc_data())
823 }
824
825 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
826 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
827 }
828
829 pub(crate) fn text_input_ime_cursor_area(&self) -> Option<(f32, f32, f32, f32)> {
830 let input = self.element_data()?.text_input_data()?;
831 let scale = f64::from(input.display_editor().try_layout()?.scale());
832 let rect = input.ime_cursor_area();
833 let pos = self.absolute_position(0.0, 0.0);
834 let x = pos.x + self.final_layout().content_box_x() + (rect.x0 / scale) as f32
835 - if input.is_multiline {
836 0.0
837 } else {
838 input.scroll_offset
839 };
840 let y = pos.y
841 + self.final_layout().content_box_y()
842 + self.text_input_v_centering_offset(scale) as f32
843 + (rect.y0 / scale) as f32
844 - if input.is_multiline {
845 input.scroll_offset
846 } else {
847 0.0
848 };
849 Some((
850 x,
851 y,
852 ((rect.x1 - rect.x0) / scale) as f32,
853 ((rect.y1 - rect.y0) / scale) as f32,
854 ))
855 }
856
857 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
858 if let Some(input_data) = self
861 .data
862 .downcast_element()
863 .and_then(|el| el.text_input_data())
864 {
865 if !input_data.is_multiline {
866 let content_box_height = self.final_layout().content_box_height();
867 let input_height =
868 input_data.display_editor().try_layout().unwrap().height() / scale as f32;
869 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
870
871 return y_offset as f64;
872 }
873 }
874
875 0.0
876 }
877}
878
879#[derive(Debug, Clone, Copy, PartialEq)]
880pub enum NodeKind {
881 Document,
882 Element,
883 AnonymousBlock,
884 Text,
885 Comment,
886}
887
888#[derive(Debug, Clone)]
890pub enum NodeData {
891 Document(Box<DocumentData>),
893
894 Element(Box<ElementData>),
896
897 AnonymousBlock(Box<ElementData>),
899
900 Text(TextNodeData),
902
903 Comment {
905 contents: String,
907 },
908 }
915
916impl NodeData {
917 pub fn downcast_element(&self) -> Option<&ElementData> {
918 match self {
919 Self::Element(data) => Some(data),
920 Self::AnonymousBlock(data) => Some(data),
921 _ => None,
922 }
923 }
924
925 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
926 match self {
927 Self::Element(data) => Some(data),
928 Self::AnonymousBlock(data) => Some(data),
929 _ => None,
930 }
931 }
932
933 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
934 let Some(elem) = self.downcast_element() else {
935 return false;
936 };
937 *name == elem.name.local
938 }
939
940 pub fn attrs(&self) -> Option<&[Attribute]> {
941 Some(&self.downcast_element()?.attrs)
942 }
943
944 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
945 self.downcast_element()?.attr(name)
946 }
947
948 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
949 self.downcast_element()
950 .is_some_and(|elem| elem.has_attr(name))
951 }
952
953 pub fn kind(&self) -> NodeKind {
954 match self {
955 NodeData::Document(_) => NodeKind::Document,
956 NodeData::Element(_) => NodeKind::Element,
957 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
958 NodeData::Text(_) => NodeKind::Text,
959 NodeData::Comment { .. } => NodeKind::Comment,
960 }
961 }
962}
963
964#[derive(Debug, Clone)]
965pub struct TextNodeData {
966 pub content: String,
968}
969
970impl TextNodeData {
971 pub fn new(content: String) -> Self {
972 Self { content }
973 }
974}
975
976impl Node {
1001 pub fn tree(&self) -> &crate::NodeTree {
1002 unsafe { &*self.tree }
1003 }
1004
1005 #[track_caller]
1006 pub fn with(&self, id: NodeId) -> &Node {
1007 self.tree().get(id).unwrap()
1008 }
1009
1010 pub fn print_tree(&self, level: usize) {
1011 println!(
1012 "{} {} {:?} {} {:?}",
1013 " ".repeat(level),
1014 self.id,
1015 self.parent,
1016 self.node_debug_str().replace('\n', ""),
1017 self.children
1018 );
1019 for child_id in self.children.iter() {
1021 let child = self.with(*child_id);
1022 child.print_tree(level + 1)
1023 }
1024 }
1025
1026 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1028 self.children.iter().position(|id| *id == child_id)
1029 }
1030
1031 pub fn child_index(&self) -> Option<usize> {
1033 self.tree()[self.parent?]
1034 .children
1035 .iter()
1036 .position(|id| *id == self.id)
1037 }
1038
1039 pub fn forward(&self, n: usize) -> Option<&Node> {
1041 let child_idx = self.child_index().unwrap_or(0);
1042 self.tree()[self.parent?]
1043 .children
1044 .get(child_idx + n)
1045 .map(|id| self.with(*id))
1046 }
1047
1048 pub fn backward(&self, n: usize) -> Option<&Node> {
1049 let child_idx = self.child_index().unwrap_or(0);
1050 if child_idx < n {
1051 return None;
1052 }
1053
1054 self.tree()[self.parent?]
1055 .children
1056 .get(child_idx - n)
1057 .map(|id| self.with(*id))
1058 }
1059
1060 pub fn is_element(&self) -> bool {
1061 matches!(self.data, NodeData::Element { .. })
1062 }
1063
1064 pub fn is_anonymous(&self) -> bool {
1065 matches!(self.data, NodeData::AnonymousBlock { .. })
1066 }
1067
1068 pub fn is_text_node(&self) -> bool {
1069 matches!(self.data, NodeData::Text { .. })
1070 }
1071
1072 pub fn element_data(&self) -> Option<&ElementData> {
1073 match self.data {
1074 NodeData::Element(ref data) => Some(data),
1075 NodeData::AnonymousBlock(ref data) => Some(data),
1076 _ => None,
1077 }
1078 }
1079
1080 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1081 match self.data {
1082 NodeData::Element(ref mut data) => Some(data),
1083 NodeData::AnonymousBlock(ref mut data) => Some(data),
1084 _ => None,
1085 }
1086 }
1087
1088 pub fn text_data(&self) -> Option<&TextNodeData> {
1089 match self.data {
1090 NodeData::Text(ref data) => Some(data),
1091 _ => None,
1092 }
1093 }
1094
1095 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1096 match self.data {
1097 NodeData::Text(ref mut data) => Some(data),
1098 _ => None,
1099 }
1100 }
1101
1102 pub fn node_debug_str(&self) -> String {
1103 let mut s = String::new();
1104
1105 match &self.data {
1106 NodeData::Document(_) => write!(s, "DOCUMENT"),
1107 NodeData::Text(data) => {
1109 let bytes = data.content.as_bytes();
1110 write!(
1111 s,
1112 "TEXT {}",
1113 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1114 .unwrap_or("INVALID UTF8")
1115 )
1116 }
1117 NodeData::Comment { .. } => write!(s, "COMMENT"),
1118 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1119 NodeData::Element(data) => {
1120 let name = &data.name;
1121 let class = self.attr(local_name!("class")).unwrap_or("");
1122 let id = self.attr(local_name!("id")).unwrap_or("");
1123 let display = self.display_constructed_as().to_css_string();
1124 write!(s, "<{}", name.local).unwrap();
1125 if !id.is_empty() {
1126 write!(s, " #{id}").unwrap();
1127 }
1128 if !class.is_empty() {
1129 if class.contains(' ') {
1130 write!(s, " class=\"{class}\"").unwrap()
1131 } else {
1132 write!(s, " .{class}").unwrap()
1133 }
1134 }
1135 write!(s, "> ({display})")
1136 } }
1138 .unwrap();
1139 s
1140 }
1141
1142 pub fn attrs(&self) -> Option<&[Attribute]> {
1143 Some(&self.element_data()?.attrs)
1144 }
1145
1146 pub fn attr(&self, name: LocalName) -> Option<&str> {
1147 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1148 Some(&attr.value)
1149 }
1150
1151 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1152 self.try_stylo_element_data()
1153 .and_then(|stylo| stylo.primary_styles())
1154 }
1155
1156 pub fn layout_style(&self) -> stylo_takia::TakiaStyloStyle<ComputedStyleRef<'_>> {
1162 let styles = self
1163 .try_stylo_element_data()
1164 .and_then(|stylo| stylo.computed_styles())
1165 .expect("layout_style() called on a node without computed styles");
1166
1167 let mut flags = stylo_takia::StyleFlags::empty();
1168 if let Some(el) = self.data.downcast_element() {
1169 if crate::layout::replaced::is_replaced_element(&el.name.local) {
1170 flags |= stylo_takia::StyleFlags::IS_REPLACED;
1171 }
1172 if el.name.local == local_name!("button") {
1173 flags |= stylo_takia::StyleFlags::IS_BUTTON;
1174 }
1175 if el.is_button_input() {
1176 flags |= stylo_takia::StyleFlags::IS_BUTTON_INPUT;
1177 }
1178 if let Some(inline_layout) = el.inline_layout_data.as_deref() {
1179 flags |= inline_layout.span_cb_flags;
1180 }
1181 }
1182
1183 #[cfg(feature = "writing-mode")]
1184 {
1185 let layout_wm = styles.writing_mode;
1187 stylo_takia::TakiaStyloStyle::new_in(styles, flags, layout_wm)
1188 }
1189 #[cfg(not(feature = "writing-mode"))]
1190 {
1191 stylo_takia::TakiaStyloStyle::new(styles, flags)
1192 }
1193 }
1194
1195 #[cfg(feature = "writing-mode")]
1198 pub fn layout_style_in(
1199 &self,
1200 layout_wm: stylo_takia::WritingMode,
1201 ) -> stylo_takia::TakiaStyloStyle<ComputedStyleRef<'_>> {
1202 let mut style = self.layout_style();
1203 style.layout_wm = layout_wm;
1204 style
1205 }
1206
1207 #[cfg(feature = "writing-mode")]
1210 pub(crate) fn has_containment(&self) -> bool {
1211 self.primary_styles()
1212 .is_some_and(|style| !style.clone_contain().is_empty())
1213 }
1214
1215 #[cfg(feature = "writing-mode")]
1218 pub(crate) fn is_column_flex_container(&self) -> bool {
1219 self.primary_styles().is_some_and(|s| {
1220 s.clone_display().inside() == DisplayInside::Flex
1221 && !self.element_data().is_some_and(|el| el.is_button_input())
1222 && matches!(
1223 s.get_position().flex_direction,
1224 FlexDirection::Column | FlexDirection::ColumnReverse
1225 )
1226 })
1227 }
1228
1229 #[cfg(feature = "writing-mode")]
1231 pub fn writing_mode(&self) -> stylo_takia::WritingMode {
1232 self.primary_styles()
1233 .map(|s| s.writing_mode)
1234 .unwrap_or(stylo_takia::WritingMode::empty())
1235 }
1236
1237 pub fn takia_display(&self) -> takia::Display {
1240 self.primary_styles()
1241 .map(|style| {
1242 let display = style.clone_display();
1243 if self.element_data().is_some_and(|el| el.is_button_input())
1244 && !matches!(
1245 display.inside(),
1246 DisplayInside::None | DisplayInside::Contents
1247 )
1248 {
1249 takia::Display::FlowRoot
1250 } else {
1251 stylo_takia::convert::display(display)
1252 }
1253 })
1254 .unwrap_or(takia::Display::Block)
1255 }
1256
1257 pub fn takia_position(&self) -> takia::Position {
1260 self.primary_styles()
1261 .map(|s| stylo_takia::convert::position(s.get_box().position))
1262 .unwrap_or(takia::Position::Static)
1263 }
1264
1265 pub fn is_out_of_flow(&self) -> bool {
1269 self.primary_styles().is_some_and(|s| {
1270 let box_style = s.get_box();
1271 stylo_takia::convert::position(box_style.position).is_out_of_flow()
1272 && stylo_takia::convert::display(box_style.display) != takia::Display::None
1273 })
1274 }
1275
1276 pub fn text_content(&self) -> String {
1277 let mut out = String::new();
1278 self.write_text_content(&mut out);
1279 out
1280 }
1281
1282 fn write_text_content(&self, out: &mut String) {
1283 match &self.data {
1284 NodeData::Text(data) => {
1285 out.push_str(&data.content);
1286 }
1287 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1288 for child_id in self.children.iter() {
1289 self.with(*child_id).write_text_content(out);
1290 }
1291 }
1292 _ => {}
1293 }
1294 }
1295
1296 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1297 if let NodeData::Element(ref mut elem_data) = self.data {
1298 if let Some(guard) = elem_data.guard.clone() {
1299 elem_data.flush_style_attribute(&guard, url_extra_data);
1300 }
1301 }
1302 }
1303
1304 pub fn order(&self) -> i32 {
1314 self.primary_styles()
1315 .filter(|s| !matches!(s.get_box().position, Position::Absolute | Position::Fixed))
1316 .map(|s| s.clone_order())
1317 .unwrap_or(0)
1318 }
1319
1320 pub fn z_index(&self) -> i32 {
1321 self.primary_styles()
1322 .map(|s| s.clone_z_index().integer_or(0))
1323 .unwrap_or(0)
1324 }
1325
1326 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1328 let Some(style) = self.primary_styles() else {
1329 return false;
1330 };
1331
1332 let position = style.clone_position();
1333 let has_z_index = !style.clone_z_index().is_auto();
1334
1335 if style.clone_opacity() != 1.0 {
1336 return true;
1337 }
1338
1339 let position_based = match position {
1340 Position::Fixed | Position::Sticky => true,
1341 Position::Relative | Position::Absolute => has_z_index,
1342 Position::Static => has_z_index && is_flex_or_grid_item,
1343 };
1344 if position_based {
1345 return true;
1346 }
1347
1348 let box_style = style.get_box();
1349 if !box_style.transform.0.is_empty()
1350 || !matches!(box_style.rotate, Rotate::None)
1351 || !matches!(box_style.scale, Scale::None)
1352 || !matches!(box_style.translate, Translate::None)
1353 {
1354 return true;
1355 }
1356
1357 if self.applies_atomic_paint_effect() {
1358 return true;
1359 }
1360
1361 if style.get_box().isolation == Isolation::Isolate {
1362 return true;
1363 }
1364
1365 false
1369 }
1370
1371 pub(crate) fn applies_atomic_paint_effect(&self) -> bool {
1374 use style::values::computed::basic_shape::ClipPath;
1375 use style::values::generics::image::GenericImage;
1376
1377 let Some(style) = self.primary_styles() else {
1378 return false;
1379 };
1380
1381 if style.clone_opacity() != 1.0 {
1382 return true;
1383 }
1384
1385 let effects = style.get_effects();
1386 if !effects.filter.0.is_empty() {
1387 return true;
1388 }
1389
1390 if !matches!(style.clone_clip_path(), ClipPath::None) {
1391 return true;
1392 }
1393
1394 style
1395 .get_svg()
1396 .mask_image
1397 .0
1398 .iter()
1399 .any(|image| !matches!(image, GenericImage::None))
1400 }
1401
1402 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1411 self.hit_inner(x, y, scale, &mut None)
1412 }
1413
1414 pub(crate) fn hit_inner(
1419 &self,
1420 x: f32,
1421 y: f32,
1422 scale: f64,
1423 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1424 ) -> Option<HitResult> {
1425 use style::computed_values::pointer_events::T as PointerEvents;
1426 use style::computed_values::visibility::T as Visibility;
1427
1428 if let Some(style) = self.primary_styles() {
1430 if matches!(
1431 style.clone_visibility(),
1432 Visibility::Hidden | Visibility::Collapse
1433 ) {
1434 return None;
1435 }
1436 }
1437
1438 let pointer_events_none = self
1441 .primary_styles()
1442 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1443
1444 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1445 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1446
1447 if let Some(t) = self.transform().as_deref() {
1448 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1449 x = (p.x / scale) as f32;
1450 y = (p.y / scale) as f32;
1451 }
1452
1453 let size = self.final_layout().size;
1454 let matches_self = !(x < 0.0
1455 || x > size.width + self.scroll_offset().x as f32
1456 || y < 0.0
1457 || y > size.height + self.scroll_offset().y as f32);
1458
1459 let overflow_rect = self.final_layout().scrollable_overflow_rect;
1460 let matches_content = !(x < 0.0
1461 || x > overflow_rect.right + self.scroll_offset().x as f32
1462 || y < 0.0
1463 || y > overflow_rect.bottom + self.scroll_offset().y as f32);
1464
1465 let matches_hoisted_content = match &self.stacking_context {
1466 Some(sc) => {
1467 let bbox = sc.hoisted_content_bbox(self.tree(), self.id, scale);
1470 x >= bbox.left && x <= bbox.right && y >= bbox.top && y <= bbox.bottom
1471 }
1472 None => false,
1473 };
1474
1475 let overflow = *self.scrollable_overflow();
1478
1479 let matches_overflow = x >= (overflow.x0 / scale) as f32
1480 && x <= (overflow.x1 / scale) as f32
1481 && y >= (overflow.y0 / scale) as f32
1482 && y <= (overflow.y1 / scale) as f32;
1483
1484 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1485 return None;
1486 }
1487
1488 if matches_self
1491 && let Some(sb) = self.scrollbar_at_local(
1492 (x - self.scroll_offset().x as f32) as f64,
1493 (y - self.scroll_offset().y as f32) as f64,
1494 )
1495 {
1496 *scrollbar = Some(sb);
1497 }
1498
1499 let content_box_offset = takia::Point {
1500 x: self.final_layout().padding.left + self.final_layout().border.left,
1501 y: self.final_layout().padding.top + self.final_layout().border.top,
1502 };
1503 if self.flags.is_inline_root() {
1504 x -= content_box_offset.x;
1505 y -= content_box_offset.y;
1506 }
1507
1508 if matches_hoisted_content {
1510 if let Some(hoisted) = &self.stacking_context {
1511 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1512 let pos = hoisted_child.position(self.tree(), self.id);
1513 let x = x - pos.x;
1514 let y = y - pos.y;
1515 if let Some(hit) = self
1516 .with(hoisted_child.node_id)
1517 .hit_inner(x, y, scale, scrollbar)
1518 {
1519 return Some(hit);
1520 }
1521 }
1522 }
1523 }
1524
1525 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1527 let child = self.with(*child_id);
1528 let child_position = child.takia_position();
1529 let mut child_x = x;
1530 let mut child_y = y;
1531 if child_position.is_out_of_flow() {
1532 if self.flags.is_inline_root() {
1535 child_x += content_box_offset.x;
1536 child_y += content_box_offset.y;
1537 }
1538 if child_position == takia::Position::Fixed
1543 && self.containing_block().is_none()
1544 && !self.flags.is_top_layer()
1545 {
1546 child_x -= self.scroll_offset().x as f32;
1547 child_y -= self.scroll_offset().y as f32;
1548 let viewport_scroll = self.tree().viewport_scroll();
1549 child_x -= viewport_scroll.x as f32;
1550 child_y -= viewport_scroll.y as f32;
1551 }
1552 }
1553 if let Some(hit) = child.hit_inner(child_x, child_y, scale, scrollbar) {
1554 return Some(hit);
1555 }
1556 }
1557
1558 if matches_hoisted_content {
1560 if let Some(hoisted) = &self.stacking_context {
1561 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1562 let pos = hoisted_child.position(self.tree(), self.id);
1563 let x = x - pos.x;
1564 let y = y - pos.y;
1565 if let Some(hit) = self
1566 .with(hoisted_child.node_id)
1567 .hit_inner(x, y, scale, scrollbar)
1568 {
1569 return Some(hit);
1570 }
1571 }
1572 }
1573 }
1574
1575 if self.flags.is_inline_root() {
1577 let element_data = &self.element_data().unwrap();
1578 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1579 let layout = &ild.layout;
1580 let scale = layout.scale();
1581 let y = y - ild.block_offset;
1582
1583 if let Some((cluster, _side)) =
1584 Cluster::from_point_exact(layout, x * scale, y * scale)
1585 {
1586 let node_id = cluster.style().brush.id;
1587 let text_pointer_events_none = self
1588 .with(node_id)
1589 .primary_styles()
1590 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1591 if !text_pointer_events_none {
1592 return Some(HitResult {
1593 node_id,
1594 x,
1595 y,
1596 is_text: true,
1597 });
1598 }
1599 }
1600 }
1601 }
1602
1603 if matches_self && !pointer_events_none {
1605 return Some(HitResult {
1606 node_id: self.id,
1607 x,
1608 y,
1609 is_text: false,
1610 });
1611 }
1612
1613 None
1614 }
1615
1616 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1619 let mut node = self;
1620 loop {
1621 if node.flags.is_inline_root() {
1622 return Some(node);
1623 }
1624 let id = node.layout_parent.get()?;
1625 node = self.with(id);
1626 }
1627 }
1628
1629 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1633 if !self.flags.is_inline_root() {
1634 return None;
1635 }
1636
1637 let element_data = self.element_data()?;
1638 let inline_layout = element_data.inline_layout_data.as_ref()?;
1639 let layout = &inline_layout.layout;
1640 let scale = layout.scale();
1641 let y = y - inline_layout.block_offset;
1642
1643 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1645
1646 let is_leading = side == ClusterSide::Left;
1651 let offset = if cluster.is_rtl() {
1652 if is_leading {
1653 cluster.text_range().end
1654 } else {
1655 cluster.text_range().start
1656 }
1657 } else {
1658 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1660 cluster.text_range().start
1661 } else {
1662 cluster.text_range().end
1663 }
1664 };
1665
1666 Some(offset)
1667 }
1668
1669 pub fn is_non_atomic_inline(&self) -> bool {
1673 let Some(element) = self.element_data() else {
1674 return false;
1675 };
1676 if self.flags.is_inline_root()
1677 || crate::layout::replaced::is_inline_box_element(&element.name.local)
1678 {
1679 return false;
1680 }
1681 self.primary_styles().is_some_and(|styles| {
1682 let display = styles.clone_display();
1683 display.outside() == DisplayOutside::Inline && display.inside() == DisplayInside::Flow
1684 })
1685 }
1686
1687 pub fn containing_block(&self) -> Option<NodeId> {
1691 if self.flags.is_top_layer() {
1692 return None;
1693 }
1694 self.oof_containing_block
1695 .get()
1696 .or_else(|| self.layout_parent.get())
1697 }
1698
1699 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1701 let x = x + self.final_layout().location.x - self.scroll_offset().x as f32;
1702 let y = y + self.final_layout().location.y - self.scroll_offset().y as f32;
1703 if self.flags.is_top_layer() {
1704 let scroll = self.tree().viewport_scroll();
1705 return crate::util::Point {
1706 x: x + scroll.x as f32,
1707 y: y + scroll.y as f32,
1708 };
1709 }
1710
1711 self.containing_block()
1713 .map(|i| self.with(i).absolute_position(x, y))
1714 .unwrap_or(crate::util::Point { x, y })
1715 }
1716
1717 pub(crate) fn is_offset_parent(&self) -> bool {
1720 let Some(styles) = self.primary_styles() else {
1721 return false;
1722 };
1723 if styles.get_box().position != Position::Static {
1724 return true;
1725 }
1726 self.data.is_element_with_tag_name(&local_name!("body"))
1727 || self.data.is_element_with_tag_name(&local_name!("td"))
1728 || self.data.is_element_with_tag_name(&local_name!("th"))
1729 }
1730
1731 pub(crate) fn is_static_body(&self) -> bool {
1735 self.data.is_element_with_tag_name(&local_name!("body"))
1736 && self
1737 .primary_styles()
1738 .is_some_and(|styles| styles.get_box().position == Position::Static)
1739 }
1740
1741 pub fn offset_parent(&self) -> Option<&Node> {
1744 let mut node = self;
1745 loop {
1746 node = self.with(node.containing_block()?);
1747 if node.is_offset_parent() {
1748 return Some(node);
1749 }
1750 }
1751 }
1752
1753 pub fn inline_fragment_boxes(&self) -> Option<impl Iterator<Item = takia::Rect<f32>> + '_> {
1758 use parley::PositionedLayoutItem;
1759
1760 if !self.is_non_atomic_inline() {
1761 return None;
1762 }
1763
1764 let inline_root = self.inline_root_ancestor()?;
1765 let inline_layout = inline_root.element_data()?.inline_layout_data.as_ref()?;
1766 let layout = &inline_layout.layout;
1767 let scale = layout.scale();
1768
1769 let inline_root_id = inline_root.id;
1772 let is_in_target = move |mut id: NodeId| -> bool {
1773 loop {
1774 if id == self.id {
1775 return true;
1776 }
1777 if id == inline_root_id {
1778 return false;
1779 }
1780 match self.with(id).parent {
1781 Some(parent) => id = parent,
1782 None => return false,
1783 }
1784 }
1785 };
1786
1787 let root_layout = inline_root.unrounded_layout();
1788 let content_box_inset = root_layout.padding + root_layout.border;
1789 let origin_x = content_box_inset.left;
1790 let origin_y = content_box_inset.top + inline_layout.block_offset;
1791
1792 fn union(acc: &mut Option<takia::Rect<f32>>, left: f32, top: f32, right: f32, bottom: f32) {
1793 *acc = Some(match *acc {
1794 Some(rect) => takia::Rect {
1795 left: rect.left.min(left),
1796 top: rect.top.min(top),
1797 right: rect.right.max(right),
1798 bottom: rect.bottom.max(bottom),
1799 },
1800 None => takia::Rect {
1801 left,
1802 top,
1803 right,
1804 bottom,
1805 },
1806 });
1807 }
1808
1809 Some(layout.lines().filter_map(move |line| {
1811 let line_metrics = line.metrics();
1812 let mut line_rect: Option<takia::Rect<f32>> = None;
1813
1814 for item in line.items() {
1815 match item {
1816 PositionedLayoutItem::GlyphRun(glyph_run) => {
1817 if !is_in_target(glyph_run.style().brush.id) {
1818 continue;
1819 }
1820 let x0 = glyph_run.offset();
1821 let x1 = x0 + glyph_run.advance();
1822 let y0 = line_metrics.block_min_coord;
1828 let y1 = line_metrics.block_max_coord;
1829 union(&mut line_rect, x0, y0, x1, y1);
1830 }
1831 PositionedLayoutItem::InlineBox(inline_box) => {
1832 if !is_in_target(NodeId::from_u64(inline_box.id)) {
1833 continue;
1834 }
1835 let x0 = inline_box.x;
1836 let y0 = inline_box.y;
1837 union(
1838 &mut line_rect,
1839 x0,
1840 y0,
1841 x0 + inline_box.width,
1842 y0 + inline_box.height,
1843 );
1844 }
1845 }
1846 }
1847
1848 line_rect.map(|rect| takia::Rect {
1849 left: origin_x + rect.left / scale,
1850 top: origin_y + rect.top / scale,
1851 right: origin_x + rect.right / scale,
1852 bottom: origin_y + rect.bottom / scale,
1853 })
1854 }))
1855 }
1856
1857 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1860 let mut x = 0.0;
1861 let mut y = 0.0;
1862 let mut current = self;
1863
1864 if let Some(mut fragments) = self.inline_fragment_boxes() {
1868 if let Some(first) = fragments.next() {
1869 x += first.left;
1870 y += first.top;
1871 }
1872 let Some(inline_root) = self.inline_root_ancestor() else {
1873 return crate::util::Point { x, y };
1874 };
1875 if inline_root.is_offset_parent() && !inline_root.is_static_body() {
1876 let border = inline_root.final_layout().border;
1877 return crate::util::Point {
1878 x: x - border.left,
1879 y: y - border.top,
1880 };
1881 }
1882 current = inline_root;
1883 }
1884
1885 loop {
1886 let layout = current.final_layout();
1887 x += layout.location.x;
1888 y += layout.location.y;
1889
1890 let Some(parent_id) = current.containing_block() else {
1891 break;
1892 };
1893 let parent = self.with(parent_id);
1894 if parent.is_offset_parent() && !parent.is_static_body() {
1895 let border = parent.final_layout().border;
1896 x -= border.left;
1897 y -= border.top;
1898 break;
1899 }
1900 current = parent;
1901 }
1902 crate::util::Point { x, y }
1903 }
1904
1905 pub fn client_width(&self) -> f32 {
1908 let layout = self.final_layout();
1909 layout.size.width - layout.border.left - layout.border.right - layout.scrollbar_size.width
1910 }
1911
1912 pub fn client_height(&self) -> f32 {
1915 let layout = self.final_layout();
1916 layout.size.height - layout.border.top - layout.border.bottom - layout.scrollbar_size.height
1917 }
1918
1919 pub fn scroll_width(&self) -> f32 {
1922 self.client_width()
1923 .max(self.final_layout().scrollable_overflow_rect.right)
1924 }
1925
1926 pub fn scroll_height(&self) -> f32 {
1929 self.client_height()
1930 .max(self.final_layout().scrollable_overflow_rect.bottom)
1931 }
1932
1933 pub fn has_boxes(&self) -> bool {
1937 self.flags.is_in_document()
1938 && !self.display_style().is_some_and(|display| {
1939 matches!(
1940 display.inside(),
1941 style::values::specified::box_::DisplayInside::None
1942 | style::values::specified::box_::DisplayInside::Contents
1943 )
1944 })
1945 }
1946
1947 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1949 DomEventData::Click(self.synthetic_click_event_data(mods))
1950 }
1951
1952 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> ApacePointerEvent {
1953 let absolute_position = self.absolute_position(0.0, 0.0);
1954 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1955 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1956
1957 ApacePointerEvent {
1958 id: ApacePointerId::Mouse,
1959 is_primary: true,
1960 coords: PointerCoords {
1961 page_x: x,
1962 page_y: y,
1963
1964 screen_x: x,
1966 screen_y: y,
1967 client_x: x,
1968 client_y: y,
1969 },
1970 mods,
1971 button: Default::default(),
1972 buttons: Default::default(),
1973 details: Default::default(),
1974 element: Default::default(),
1975 active_pointers: Default::default(),
1976 }
1977 }
1978}
1979
1980impl PartialEq for Node {
1982 fn eq(&self, other: &Self) -> bool {
1983 self.id == other.id
1984 }
1985}
1986
1987impl Eq for Node {}
1988
1989impl std::fmt::Debug for Node {
1990 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1991 f.debug_struct("NodeData")
1993 .field("parent", &self.parent)
1994 .field("id", &self.id)
1995 .field("is_inline_root", &self.flags.is_inline_root())
1996 .field("children", &self.children)
1997 .field("layout_children", &self.layout_children.borrow())
1998 .field("node", &self.data)
2000 .field("stylo_element_data", &self.try_stylo_element_data())
2001 .finish()
2004 }
2005}
2006
2007#[cfg(test)]
2008mod test {
2009 use style_dom::ElementState;
2010
2011 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
2012
2013 #[test]
2014 fn create_node_with_disabled_attr() {
2015 let mut document = BaseDocument::new(DocumentConfig::default());
2016 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2017 qual_name!("button"),
2018 vec![Attribute {
2019 name: qual_name!("disabled"),
2020 value: "".into(),
2021 }],
2022 ))));
2023 let node = document.get_node(node).unwrap();
2024
2025 assert!(
2026 node.element_state().contains(ElementState::DISABLED),
2027 "form node is disabled"
2028 );
2029 assert!(
2030 !node.element_state().contains(ElementState::ENABLED),
2031 "form node is not enabled"
2032 );
2033 }
2034
2035 #[test]
2036 fn ignore_disabled_attr_content() {
2037 let mut document = BaseDocument::new(DocumentConfig::default());
2038 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2039 qual_name!("button"),
2040 vec![Attribute {
2041 name: qual_name!("disabled"),
2042 value: "false".into(),
2043 }],
2044 ))));
2045 let node = document.get_node(node).unwrap();
2046
2047 assert!(
2048 node.element_state().contains(ElementState::DISABLED),
2049 "form node is disabled"
2050 );
2051 assert!(
2052 !node.element_state().contains(ElementState::ENABLED),
2053 "form node is not enabled"
2054 );
2055 }
2056
2057 #[test]
2058 fn create_node_with_ignored_disable() {
2059 let mut document = BaseDocument::new(DocumentConfig::default());
2060 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2061 qual_name!("a"),
2062 vec![Attribute {
2063 name: qual_name!("disabled"),
2064 value: "".into(),
2065 }],
2066 ))));
2067 let node = document.get_node(node).unwrap();
2068
2069 assert!(
2070 !node.element_state().contains(ElementState::DISABLED),
2071 "Non form node cannot be disabled"
2072 );
2073 assert!(
2074 !node.element_state().contains(ElementState::ENABLED),
2075 "Non form node cannot be enabled"
2076 );
2077 }
2078
2079 #[test]
2080 fn create_empty_enabled_node() {
2081 let mut document = BaseDocument::new(DocumentConfig::default());
2082 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2083 qual_name!("button"),
2084 vec![],
2085 ))));
2086 let node = document.get_node(node).unwrap();
2087
2088 assert!(
2089 node.element_state().contains(ElementState::ENABLED),
2090 "Button should be enabled by default"
2091 );
2092 }
2093}