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};
20use style::computed_values::flex_direction::T as FlexDirection;
21use style::computed_values::isolation::T as Isolation;
22use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
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::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 stylo_takia::CopyStyleGetters;
38use stylo_takia::WritingModeExt;
39use takia::{Cache, prelude::Layout};
40use thin_vec::ThinVec;
41
42use super::stylo_data::{ComputedStyleRef, StyloData};
43use super::{Attribute, DocumentData, ElementData, LayoutData};
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum DisplayOuter {
47 Block,
48 Inline,
49 None,
50}
51
52bitflags! {
53 #[derive(Clone, Copy, PartialEq)]
54 pub struct NodeFlags: u32 {
55 const IS_INLINE_ROOT = 0b00000001;
57 const IS_TABLE_ROOT = 0b00000010;
59 const IS_IN_DOCUMENT = 0b00000100;
61 const IS_TOP_LAYER = 0b00001000;
63 const CONTAIN_TOP_LAYER_SCROLL = 0b00010000;
64 const IS_INLINE_WITH_ONLY_BLOCK_CHILDREN = 0b00100000;
66 }
67}
68
69impl NodeFlags {
70 pub fn is_top_layer(&self) -> bool {
71 self.contains(Self::IS_TOP_LAYER)
72 }
73 #[inline(always)]
74 pub fn is_inline_root(&self) -> bool {
75 self.contains(Self::IS_INLINE_ROOT)
76 }
77
78 #[inline(always)]
79 pub fn is_table_root(&self) -> bool {
80 self.contains(Self::IS_TABLE_ROOT)
81 }
82
83 #[inline(always)]
84 pub fn is_in_document(&self) -> bool {
85 self.contains(Self::IS_IN_DOCUMENT)
86 }
87
88 #[inline(always)]
89 pub fn reset_construction_flags(&mut self) {
90 self.remove(Self::IS_INLINE_ROOT);
91 self.remove(Self::IS_TABLE_ROOT);
92 self.remove(Self::IS_INLINE_WITH_ONLY_BLOCK_CHILDREN);
93 }
94}
95
96pub struct Node {
97 tree: *mut crate::NodeTree,
99
100 pub id: NodeId,
102 pub parent: Option<NodeId>,
104 pub children: ThinVec<NodeId>,
106 pub layout_parent: Cell<Option<NodeId>>,
108 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
110 pub hoisted_children: RefCell<ThinVec<NodeId>>,
114 pub oof_containing_block: Cell<Option<NodeId>>,
119 pub anonymous_blocks: ThinVec<NodeId>,
125 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
127 pub stacking_context: Option<Box<StackingContext>>,
128 pub sc_contribution_cache: RefCell<ThinVec<HoistedPaintChild>>,
132
133 pub flags: NodeFlags,
135 pub out_of_flow: AtomicBool,
139
140 pub data: NodeData,
147}
148
149unsafe impl Send for Node {}
150unsafe impl Sync for Node {}
151
152macro_rules! universal_accessors {
155 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
156 impl Node {
157 $(
158 $(#[$meta])*
159 #[inline]
160 pub fn $field(&self) -> &$ty {
161 match &self.data {
162 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
163 NodeData::Document(data) => &data.$field,
164 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
165 }
166 }
167
168 $(#[$meta])*
169 #[inline]
170 pub fn $field_mut(&mut self) -> &mut $ty {
171 match &mut self.data {
172 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
173 NodeData::Document(data) => &mut data.$field,
174 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
175 }
176 }
177 )*
178 }
179 };
180}
181
182universal_accessors! {
183 stylo_element_data / stylo_element_data_mut: StyloData,
184 transform / transform_mut: Option<Box<Affine>>,
185 display_constructed_as / display_constructed_as_mut: StyloDisplay,
186 element_state / element_state_mut: ElementState,
189 snapshot_handled / snapshot_handled_mut: AtomicBool,
190 selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
194}
195
196impl Node {
197 #[inline]
203 pub fn layout_data(&self) -> &LayoutData {
204 match &self.data {
205 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.layout_data(),
206 NodeData::Document(data) => data.layout_data(),
207 _ => panic!("`layout_data` is not available on this node kind"),
208 }
209 }
210
211 #[inline]
217 pub fn layout_data_mut(&mut self) -> &mut LayoutData {
218 match &mut self.data {
219 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.layout_data_mut(),
220 NodeData::Document(data) => data.layout_data_mut(),
221 _ => panic!("`layout_data` is not available on this node kind"),
222 }
223 }
224
225 #[inline]
229 pub fn try_layout_data_mut(&mut self) -> Option<&mut LayoutData> {
230 match &mut self.data {
231 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
232 data.layout_data.as_deref_mut()
233 }
234 NodeData::Document(data) => data.layout_data.as_deref_mut(),
235 _ => None,
236 }
237 }
238
239 #[inline]
242 pub fn clear_layout_cache(&mut self) {
243 if let Some(layout_data) = self.try_layout_data_mut() {
244 layout_data.cache.clear();
245 }
246 }
247
248 pub fn invalidate_layout_cache(&mut self) {
251 self.clear_layout_cache();
252 if let Some(inline_layout) = self
253 .data
254 .downcast_element_mut()
255 .and_then(|el| el.inline_layout_data.as_mut())
256 {
257 inline_layout.content_widths = None;
258 }
259 }
260
261 #[inline]
262 pub fn cache(&self) -> &Cache {
263 &self.layout_data().cache
264 }
265
266 #[inline]
267 pub fn cache_mut(&mut self) -> &mut Cache {
268 &mut self.layout_data_mut().cache
269 }
270
271 #[inline]
272 pub fn unrounded_layout(&self) -> &Layout {
273 &self.layout_data().unrounded_layout
274 }
275
276 #[inline]
277 pub fn unrounded_layout_mut(&mut self) -> &mut Layout {
278 &mut self.layout_data_mut().unrounded_layout
279 }
280
281 #[inline]
282 pub fn final_layout(&self) -> &Layout {
283 &self.layout_data().final_layout
284 }
285
286 #[inline]
287 pub fn final_layout_mut(&mut self) -> &mut Layout {
288 &mut self.layout_data_mut().final_layout
289 }
290
291 #[inline]
292 pub fn scroll_offset(&self) -> &crate::Point<f64> {
293 &self.layout_data().scroll_offset
294 }
295
296 #[inline]
297 pub fn scroll_offset_mut(&mut self) -> &mut crate::Point<f64> {
298 &mut self.layout_data_mut().scroll_offset
299 }
300
301 #[inline]
302 pub fn scrollable_overflow(&self) -> &KurboRect {
303 &self.layout_data().scrollable_overflow
304 }
305
306 #[inline]
307 pub fn scrollable_overflow_mut(&mut self) -> &mut KurboRect {
308 &mut self.layout_data_mut().scrollable_overflow
309 }
310
311 #[inline]
314 pub fn try_stylo_element_data(&self) -> Option<&StyloData> {
315 match &self.data {
316 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
317 Some(&data.stylo_element_data)
318 }
319 NodeData::Document(data) => Some(&data.stylo_element_data),
320 _ => None,
321 }
322 }
323
324 #[inline]
325 pub fn try_stylo_element_data_mut(&mut self) -> Option<&mut StyloData> {
326 match &mut self.data {
327 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
328 Some(&mut data.stylo_element_data)
329 }
330 NodeData::Document(data) => Some(&mut data.stylo_element_data),
331 _ => None,
332 }
333 }
334
335 #[inline]
338 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
339 match &self.data {
340 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
341 Some(&data.dirty_descendants)
342 }
343 NodeData::Document(data) => Some(&data.dirty_descendants),
344 _ => None,
345 }
346 }
347
348 #[inline]
351 fn damaged_descendants_flag(&self) -> Option<&AtomicBool> {
352 match &self.data {
353 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
354 Some(&data.damaged_descendants)
355 }
356 NodeData::Document(data) => Some(&data.damaged_descendants),
357 _ => None,
358 }
359 }
360
361 #[inline]
364 pub fn guard(&self) -> &SharedRwLock {
365 let guard = match &self.data {
366 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
367 NodeData::Document(data) => data.guard.as_ref(),
368 _ => None,
369 };
370 guard.expect("`guard` is not available on this node kind")
371 }
372
373 #[inline]
374 pub fn has_snapshot(&self) -> bool {
375 match &self.data {
376 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
377 NodeData::Document(data) => data.has_snapshot,
378 _ => false,
379 }
380 }
381
382 #[inline]
383 pub fn set_has_snapshot(&mut self, value: bool) {
384 match &mut self.data {
385 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
386 NodeData::Document(data) => data.has_snapshot = value,
387 _ => {}
388 }
389 }
390
391 #[inline]
392 pub fn before(&self) -> Option<NodeId> {
393 self.element_data().and_then(|data| data.before)
394 }
395
396 #[inline]
397 pub fn after(&self) -> Option<NodeId> {
398 self.element_data().and_then(|data| data.after)
399 }
400}
401
402impl Node {
403 pub(crate) fn new(
404 tree: *mut crate::NodeTree,
405 id: NodeId,
406 guard: SharedRwLock,
407 mut data: NodeData,
408 ) -> Self {
409 match &mut data {
412 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
413 data.guard = Some(guard);
414 }
415 NodeData::Document(data) => data.guard = Some(guard),
416 _ => {}
417 }
418
419 Self {
420 tree,
421
422 id,
423 parent: None,
424 children: ThinVec::new(),
425 layout_parent: Cell::new(None),
426 oof_containing_block: Cell::new(None),
427 layout_children: RefCell::new(None),
428 hoisted_children: RefCell::new(ThinVec::new()),
429 anonymous_blocks: ThinVec::new(),
430 paint_children: RefCell::new(None),
431 stacking_context: None,
432 sc_contribution_cache: RefCell::new(ThinVec::new()),
433
434 flags: NodeFlags::empty(),
435 out_of_flow: AtomicBool::new(false),
436 data,
437 }
438 }
439
440 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
441 let transform = self.primary_styles().and_then(|s| {
442 let size = self.final_layout().size;
443 let reference_box = Rect::new(
444 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
445 Size2D::new(
446 CSSPixelLength::new(size.width),
447 CSSPixelLength::new(size.height),
448 ),
449 );
450 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
453 let scale = scale as f64;
454 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
455 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
456 })
457 });
458
459 let slot = self.transform_mut();
460 match (slot.as_deref_mut(), transform) {
461 (Some(existing), Some(new)) => *existing = new,
462 (None, Some(new)) => *slot = Some(Box::new(new)),
463 (_, None) => *slot = None,
464 }
465 transform
466 }
467
468 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
469 match index {
470 0 => self.after(),
471 1 => self.before(),
472 _ => panic!("Invalid pseudo element index"),
473 }
474 }
475
476 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
477 let Some(data) = self.element_data_mut() else {
478 return;
479 };
480 match index {
481 0 => data.after = value,
482 1 => data.before = value,
483 _ => panic!("Invalid pseudo element index"),
484 }
485 }
486
487 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
488 let display = self.primary_styles().as_ref()?.clone_display();
489 Some(
490 if self.element_data().is_some_and(|el| el.is_button_input()) {
491 stylo_takia::convert::button_input_display(display)
492 } else {
493 display
494 },
495 )
496 }
497
498 pub fn is_or_contains_block(&self) -> bool {
499 let style = self.primary_styles();
500 let style = style.as_ref();
501
502 let position = style
504 .map(|s| s.clone_position())
505 .unwrap_or(Position::Relative);
506 let is_in_flow = matches!(
507 position,
508 Position::Static | Position::Relative | Position::Sticky
509 );
510 if !is_in_flow {
511 return false;
512 }
513 let is_floating = style
516 .map(|s| s.clone_float().is_floating())
517 .unwrap_or(false);
518 if is_floating {
519 return false;
520 }
521 let display = self.display_style().unwrap_or(StyloDisplay::inline());
522 match display.outside() {
523 DisplayOutside::None => false,
524 DisplayOutside::Block => true,
525 _ => {
526 if display.inside() == DisplayInside::Flow {
527 self.children
528 .iter()
529 .copied()
530 .any(|child_id| self.tree()[child_id].is_or_contains_block())
531 } else {
532 false
533 }
534 }
535 }
536 }
537
538 pub fn is_inline_with_only_block_children(&self) -> bool {
542 self.flags
543 .contains(NodeFlags::IS_INLINE_WITH_ONLY_BLOCK_CHILDREN)
544 }
545
546 pub fn is_whitespace_node(&self) -> bool {
547 match &self.data {
548 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
549 _ => false,
550 }
551 }
552
553 pub fn is_collapsible_whitespace_node(&self) -> bool {
556 let NodeData::Text(data) = &self.data else {
557 return false;
558 };
559 if data.content.is_empty() {
560 return true;
561 }
562 if !data.content.chars().all(|c| c.is_ascii_whitespace()) {
563 return false;
564 }
565 let white_space_collapse = self
566 .parent
567 .and_then(|parent_id| self.with(parent_id).primary_styles())
568 .map(|style| style.clone_white_space_collapse());
569 match white_space_collapse {
570 Some(
571 WhiteSpaceCollapse::Preserve
572 | WhiteSpaceCollapse::BreakSpaces
573 | WhiteSpaceCollapse::PreserveSpaces,
574 ) => false,
575 Some(WhiteSpaceCollapse::PreserveBreaks) => !data.content.contains('\n'),
576 Some(WhiteSpaceCollapse::Collapse) | None => true,
577 }
578 }
579
580 pub fn is_focussable(&self) -> bool {
581 self.data
582 .downcast_element()
583 .map(|el| el.is_focussable)
584 .unwrap_or(false)
585 }
586
587 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
588 if let Some(stylo_element_data) = self.try_stylo_element_data_mut() {
589 if let Some(mut element_data) = stylo_element_data.get_mut() {
590 element_data.hint.insert(hint);
591 }
592 }
593 self.mark_ancestors_dirty();
596 }
597
598 pub fn has_dirty_descendants(&self) -> bool {
600 self.dirty_descendants_flag()
601 .is_some_and(|flag| flag.load(Ordering::Relaxed))
602 }
603
604 pub fn set_dirty_descendants(&self) {
606 if let Some(flag) = self.dirty_descendants_flag() {
607 flag.store(true, Ordering::Relaxed);
608 }
609 }
610
611 pub fn unset_dirty_descendants(&self) {
613 if let Some(flag) = self.dirty_descendants_flag() {
614 flag.store(false, Ordering::Relaxed);
615 }
616 }
617
618 pub fn mark_ancestors_dirty(&self) {
622 let mut current_id = self.parent;
623 while let Some(parent_id) = current_id {
624 let parent = &self.tree()[parent_id];
625 if let Some(flag) = parent.dirty_descendants_flag() {
628 if flag.swap(true, Ordering::Relaxed) {
629 break;
630 }
631 }
632 current_id = parent.parent;
633 }
634 }
635
636 pub fn has_damaged_descendants(&self) -> bool {
638 self.damaged_descendants_flag()
639 .is_some_and(|flag| flag.load(Ordering::Relaxed))
640 }
641
642 pub fn unset_damaged_descendants(&self) {
644 if let Some(flag) = self.damaged_descendants_flag() {
645 flag.store(false, Ordering::Relaxed);
646 }
647 }
648
649 pub fn mark_damaged(&self) {
656 if let Some(flag) = self.damaged_descendants_flag() {
657 if flag.swap(true, Ordering::Relaxed) {
658 return;
659 }
660 }
661 let mut current_id = self.parent;
662 while let Some(parent_id) = current_id {
663 let parent = &self.tree()[parent_id];
664 if let Some(flag) = parent.damaged_descendants_flag() {
667 if flag.swap(true, Ordering::Relaxed) {
668 break;
669 }
670 }
671 current_id = parent.parent;
672 }
673 }
674
675 pub fn damage(&self) -> Option<RestyleDamage> {
682 self.try_stylo_element_data()
683 .and_then(|stylo| stylo.get().map(|data| data.damage))
684 }
685
686 pub fn set_damage(&mut self, damage: RestyleDamage) {
687 if let Some(stylo) = self.try_stylo_element_data_mut() {
688 if let Some(mut data) = stylo.get_mut() {
689 data.damage = damage;
690 }
691 }
692 }
693
694 pub fn insert_damage(&mut self, damage: RestyleDamage) {
695 if let Some(stylo) = self.try_stylo_element_data_mut() {
696 if let Some(mut data) = stylo.get_mut() {
697 data.damage |= damage;
698 }
699 }
700 if !damage.is_empty() {
701 self.mark_damaged();
702 }
703 }
704
705 pub fn remove_damage(&mut self, damage: RestyleDamage) {
706 if let Some(stylo) = self.try_stylo_element_data_mut() {
707 if let Some(mut data) = stylo.get_mut() {
708 data.damage.remove(damage);
709 }
710 }
711 }
712
713 pub fn clear_damage_mut(&mut self) {
714 if let Some(stylo) = self.try_stylo_element_data_mut() {
715 if let Some(mut data) = stylo.get_mut() {
716 data.damage = RestyleDamage::empty();
717 }
718 }
719 }
720
721 pub fn hover(&mut self) {
727 if let Some(data) = self.element_data_mut() {
728 data.element_state.insert(ElementState::HOVER);
729 }
730 self.mark_ancestors_dirty();
731 }
732
733 pub fn unhover(&mut self) {
734 if let Some(data) = self.element_data_mut() {
735 data.element_state.remove(ElementState::HOVER);
736 }
737 self.mark_ancestors_dirty();
738 }
739
740 pub fn is_hovered(&self) -> bool {
741 self.element_data()
742 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
743 }
744
745 pub fn add_focus_within(&mut self) {
748 if let Some(data) = self.element_data_mut() {
749 data.element_state.insert(ElementState::FOCUS_WITHIN);
750 }
751 self.mark_ancestors_dirty();
752 }
753
754 pub fn remove_focus_within(&mut self) {
756 if let Some(data) = self.element_data_mut() {
757 data.element_state.remove(ElementState::FOCUS_WITHIN);
758 }
759 self.mark_ancestors_dirty();
760 }
761
762 pub fn contains_focus(&self) -> bool {
764 self.element_data()
765 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS_WITHIN))
766 }
767
768 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
769 if let Some(data) = self.element_data_mut() {
770 data.element_state
771 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
772 }
773 self.mark_ancestors_dirty();
774
775 if self
777 .element_data()
778 .and_then(|elem| elem.text_input_data())
779 .is_some()
780 {
781 let password = self
782 .element_data()
783 .and_then(|element| element.text_input_data())
784 .is_some_and(|input| input.is_password());
785 shell_provider.set_ime_purpose(if password {
786 apace_traits::shell::ImePurpose::Password
787 } else {
788 apace_traits::shell::ImePurpose::Normal
789 });
790 shell_provider.set_ime_enabled(true);
791 if let Some(input) = self.element_data().and_then(|e| e.text_input_data()) {
792 input.sync_ime_context(shell_provider.as_ref());
793 }
794 if let Some((x, y, width, height)) = self.text_input_ime_cursor_area() {
795 shell_provider.set_ime_cursor_area(x, y, width, height);
796 }
797 }
798 }
799
800 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
801 if let Some(data) = self.element_data_mut() {
802 data.element_state
803 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
804 }
805 self.mark_ancestors_dirty();
806
807 if self
809 .element_data()
810 .and_then(|elem| elem.text_input_data())
811 .is_some()
812 {
813 shell_provider.set_ime_enabled(false);
814 }
815 }
816
817 pub fn is_focussed(&self) -> bool {
818 self.element_data()
819 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
820 }
821
822 pub fn active(&mut self) {
823 if let Some(data) = self.element_data_mut() {
824 data.element_state.insert(ElementState::ACTIVE);
825 }
826 self.mark_ancestors_dirty();
827 }
828
829 pub fn unactive(&mut self) {
830 if let Some(data) = self.element_data_mut() {
831 data.element_state.remove(ElementState::ACTIVE);
832 }
833 self.mark_ancestors_dirty();
834 }
835
836 pub fn is_active(&self) -> bool {
837 self.element_data()
838 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
839 }
840
841 pub fn disable(&mut self) {
844 if let Some(data) = self.element_data_mut() {
845 if data.can_be_disabled() {
846 data.element_state.insert(ElementState::DISABLED);
847 data.element_state.remove(ElementState::ENABLED);
848 }
849 }
850 self.mark_ancestors_dirty();
851 }
852
853 pub fn enable(&mut self) {
856 if let Some(data) = self.element_data_mut() {
857 if data.can_be_disabled() {
858 data.element_state.insert(ElementState::ENABLED);
859 data.element_state.remove(ElementState::DISABLED);
860 }
861 }
862 self.mark_ancestors_dirty();
863 }
864
865 pub fn subdoc(&self) -> Option<&dyn Document> {
866 self.element_data().and_then(|el| el.sub_doc_data())
867 }
868
869 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
870 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
871 }
872
873 pub(crate) fn text_input_ime_cursor_area(&self) -> Option<(f32, f32, f32, f32)> {
874 let input = self.element_data()?.text_input_data()?;
875 let scale = f64::from(input.display_editor().try_layout()?.scale());
876 let rect = input.ime_cursor_area();
877 let pos = self.absolute_position(0.0, 0.0);
878 let x = pos.x + self.final_layout().content_box_x() + (rect.x0 / scale) as f32
879 - if input.is_multiline {
880 0.0
881 } else {
882 input.scroll_offset
883 };
884 let y = pos.y
885 + self.final_layout().content_box_y()
886 + self.text_input_v_centering_offset(scale) as f32
887 + (rect.y0 / scale) as f32
888 - if input.is_multiline {
889 input.scroll_offset
890 } else {
891 0.0
892 };
893 Some((
894 x,
895 y,
896 ((rect.x1 - rect.x0) / scale) as f32,
897 ((rect.y1 - rect.y0) / scale) as f32,
898 ))
899 }
900
901 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
902 if let Some(input_data) = self
905 .data
906 .downcast_element()
907 .and_then(|el| el.text_input_data())
908 {
909 if !input_data.is_multiline {
910 let content_box_height = self.final_layout().content_box_height();
911 let input_height =
912 input_data.display_editor().try_layout().unwrap().height() / scale as f32;
913 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
914
915 return y_offset as f64;
916 }
917 }
918
919 0.0
920 }
921}
922
923#[derive(Debug, Clone, Copy, PartialEq)]
924pub enum NodeKind {
925 Document,
926 Element,
927 AnonymousBlock,
928 Text,
929 Comment,
930}
931
932#[derive(Debug, Clone)]
934pub enum NodeData {
935 Document(Box<DocumentData>),
937
938 Element(Box<ElementData>),
940
941 AnonymousBlock(Box<ElementData>),
943
944 Text(TextNodeData),
946
947 Comment {
949 contents: String,
951 },
952 }
959
960impl NodeData {
961 pub fn downcast_element(&self) -> Option<&ElementData> {
962 match self {
963 Self::Element(data) => Some(data),
964 Self::AnonymousBlock(data) => Some(data),
965 _ => None,
966 }
967 }
968
969 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
970 match self {
971 Self::Element(data) => Some(data),
972 Self::AnonymousBlock(data) => Some(data),
973 _ => None,
974 }
975 }
976
977 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
978 let Some(elem) = self.downcast_element() else {
979 return false;
980 };
981 *name == elem.name.local
982 }
983
984 pub fn attrs(&self) -> Option<&[Attribute]> {
985 Some(&self.downcast_element()?.attrs)
986 }
987
988 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
989 self.downcast_element()?.attr(name)
990 }
991
992 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
993 self.downcast_element()
994 .is_some_and(|elem| elem.has_attr(name))
995 }
996
997 pub fn kind(&self) -> NodeKind {
998 match self {
999 NodeData::Document(_) => NodeKind::Document,
1000 NodeData::Element(_) => NodeKind::Element,
1001 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
1002 NodeData::Text(_) => NodeKind::Text,
1003 NodeData::Comment { .. } => NodeKind::Comment,
1004 }
1005 }
1006}
1007
1008#[derive(Debug, Clone)]
1009pub struct TextNodeData {
1010 pub content: String,
1012}
1013
1014impl TextNodeData {
1015 pub fn new(content: String) -> Self {
1016 Self { content }
1017 }
1018}
1019
1020impl Node {
1045 pub fn tree(&self) -> &crate::NodeTree {
1046 unsafe { &*self.tree }
1047 }
1048
1049 #[track_caller]
1050 pub fn with(&self, id: NodeId) -> &Node {
1051 self.tree().get(id).unwrap()
1052 }
1053
1054 pub fn print_tree(&self, level: usize) {
1055 println!(
1056 "{} {} {:?} {} {:?}",
1057 " ".repeat(level),
1058 self.id,
1059 self.parent,
1060 self.node_debug_str().replace('\n', ""),
1061 self.children
1062 );
1063 for child_id in self.children.iter() {
1065 let child = self.with(*child_id);
1066 child.print_tree(level + 1)
1067 }
1068 }
1069
1070 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1072 self.children.iter().position(|id| *id == child_id)
1073 }
1074
1075 pub fn child_index(&self) -> Option<usize> {
1077 self.tree()[self.parent?]
1078 .children
1079 .iter()
1080 .position(|id| *id == self.id)
1081 }
1082
1083 pub fn forward(&self, n: usize) -> Option<&Node> {
1085 let child_idx = self.child_index().unwrap_or(0);
1086 self.tree()[self.parent?]
1087 .children
1088 .get(child_idx + n)
1089 .map(|id| self.with(*id))
1090 }
1091
1092 pub fn backward(&self, n: usize) -> Option<&Node> {
1093 let child_idx = self.child_index().unwrap_or(0);
1094 if child_idx < n {
1095 return None;
1096 }
1097
1098 self.tree()[self.parent?]
1099 .children
1100 .get(child_idx - n)
1101 .map(|id| self.with(*id))
1102 }
1103
1104 pub fn is_element(&self) -> bool {
1105 matches!(self.data, NodeData::Element { .. })
1106 }
1107
1108 pub fn is_anonymous(&self) -> bool {
1109 matches!(self.data, NodeData::AnonymousBlock { .. })
1110 }
1111
1112 pub fn is_text_node(&self) -> bool {
1113 matches!(self.data, NodeData::Text { .. })
1114 }
1115
1116 pub fn element_data(&self) -> Option<&ElementData> {
1117 match self.data {
1118 NodeData::Element(ref data) => Some(data),
1119 NodeData::AnonymousBlock(ref data) => Some(data),
1120 _ => None,
1121 }
1122 }
1123
1124 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1125 match self.data {
1126 NodeData::Element(ref mut data) => Some(data),
1127 NodeData::AnonymousBlock(ref mut data) => Some(data),
1128 _ => None,
1129 }
1130 }
1131
1132 pub fn text_data(&self) -> Option<&TextNodeData> {
1133 match self.data {
1134 NodeData::Text(ref data) => Some(data),
1135 _ => None,
1136 }
1137 }
1138
1139 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1140 match self.data {
1141 NodeData::Text(ref mut data) => Some(data),
1142 _ => None,
1143 }
1144 }
1145
1146 pub fn node_debug_str(&self) -> String {
1147 let mut s = String::new();
1148
1149 match &self.data {
1150 NodeData::Document(_) => write!(s, "DOCUMENT"),
1151 NodeData::Text(data) => {
1153 let bytes = data.content.as_bytes();
1154 write!(
1155 s,
1156 "TEXT {}",
1157 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1158 .unwrap_or("INVALID UTF8")
1159 )
1160 }
1161 NodeData::Comment { .. } => write!(s, "COMMENT"),
1162 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1163 NodeData::Element(data) => {
1164 let name = &data.name;
1165 let class = self.attr(local_name!("class")).unwrap_or("");
1166 let id = self.attr(local_name!("id")).unwrap_or("");
1167 let display = self.display_constructed_as().to_css_string();
1168 write!(s, "<{}", name.local).unwrap();
1169 if !id.is_empty() {
1170 write!(s, " #{id}").unwrap();
1171 }
1172 if !class.is_empty() {
1173 if class.contains(' ') {
1174 write!(s, " class=\"{class}\"").unwrap()
1175 } else {
1176 write!(s, " .{class}").unwrap()
1177 }
1178 }
1179 write!(s, "> ({display})")
1180 } }
1182 .unwrap();
1183 s
1184 }
1185
1186 pub fn attrs(&self) -> Option<&[Attribute]> {
1187 Some(&self.element_data()?.attrs)
1188 }
1189
1190 pub fn attr(&self, name: LocalName) -> Option<&str> {
1191 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1192 Some(&attr.value)
1193 }
1194
1195 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1196 self.try_stylo_element_data()
1197 .and_then(|stylo| stylo.primary_styles())
1198 }
1199
1200 pub fn layout_style(&self) -> stylo_takia::TakiaStyloStyle<ComputedStyleRef<'_>> {
1206 let styles = self
1207 .try_stylo_element_data()
1208 .and_then(|stylo| stylo.computed_styles())
1209 .expect("layout_style() called on a node without computed styles");
1210
1211 let mut flags = stylo_takia::StyleFlags::empty();
1212 if let Some(el) = self.data.downcast_element() {
1213 if crate::layout::replaced::is_replaced_element(&el.name.local) {
1214 flags |= stylo_takia::StyleFlags::IS_REPLACED;
1215 }
1216 if el.name.local == local_name!("button") {
1217 flags |= stylo_takia::StyleFlags::IS_BUTTON;
1218 }
1219 if el.is_button_input() {
1220 flags |= stylo_takia::StyleFlags::IS_BUTTON_INPUT;
1221 }
1222 if let Some(inline_layout) = el.inline_layout_data.as_deref() {
1223 flags |= inline_layout.span_cb_flags;
1224 }
1225 }
1226
1227 #[cfg(feature = "writing-mode")]
1228 {
1229 let layout_wm = styles.writing_mode;
1231 stylo_takia::TakiaStyloStyle::new_in(styles, flags, layout_wm)
1232 }
1233 #[cfg(not(feature = "writing-mode"))]
1234 {
1235 stylo_takia::TakiaStyloStyle::new(styles, flags)
1236 }
1237 }
1238
1239 #[cfg(feature = "writing-mode")]
1242 pub fn layout_style_in(
1243 &self,
1244 layout_wm: stylo_takia::WritingMode,
1245 ) -> stylo_takia::TakiaStyloStyle<ComputedStyleRef<'_>> {
1246 let mut style = self.layout_style();
1247 style.layout_wm = layout_wm;
1248 style
1249 }
1250
1251 #[cfg(feature = "writing-mode")]
1254 pub(crate) fn has_containment(&self) -> bool {
1255 self.primary_styles()
1256 .is_some_and(|style| !style.clone_contain().is_empty())
1257 }
1258
1259 #[cfg(feature = "writing-mode")]
1262 pub(crate) fn is_column_flex_container(&self) -> bool {
1263 self.primary_styles().is_some_and(|s| {
1264 s.clone_display().inside() == DisplayInside::Flex
1265 && !self.element_data().is_some_and(|el| el.is_button_input())
1266 && matches!(
1267 s.get_position().flex_direction,
1268 FlexDirection::Column | FlexDirection::ColumnReverse
1269 )
1270 })
1271 }
1272
1273 #[cfg(feature = "writing-mode")]
1275 pub fn writing_mode(&self) -> stylo_takia::WritingMode {
1276 self.primary_styles()
1277 .map(|s| s.writing_mode)
1278 .unwrap_or(stylo_takia::WritingMode::empty())
1279 }
1280
1281 pub fn takia_display(&self) -> takia::Display {
1284 self.primary_styles()
1285 .map(|style| {
1286 let display = style.clone_display();
1287 if self.element_data().is_some_and(|el| el.is_button_input())
1288 && !matches!(
1289 display.inside(),
1290 DisplayInside::None | DisplayInside::Contents
1291 )
1292 {
1293 takia::Display::FlowRoot
1294 } else {
1295 stylo_takia::convert::display(display)
1296 }
1297 })
1298 .unwrap_or(takia::Display::Block)
1299 }
1300
1301 pub fn takia_position(&self) -> takia::Position {
1304 self.primary_styles()
1305 .map(|s| stylo_takia::convert::position(s.get_box().position))
1306 .unwrap_or(takia::Position::Static)
1307 }
1308
1309 pub fn is_out_of_flow(&self) -> bool {
1313 self.primary_styles()
1314 .is_some_and(|s| style_is_out_of_flow(&s))
1315 }
1316
1317 pub(crate) fn record_out_of_flow(&self, style: &ComputedValues) {
1320 self.out_of_flow
1321 .store(style_is_out_of_flow(style), Ordering::Relaxed);
1322 }
1323
1324 pub fn text_content(&self) -> String {
1325 let mut out = String::new();
1326 self.write_text_content(&mut out);
1327 out
1328 }
1329
1330 fn write_text_content(&self, out: &mut String) {
1331 match &self.data {
1332 NodeData::Text(data) => {
1333 out.push_str(&data.content);
1334 }
1335 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1336 for child_id in self.children.iter() {
1337 self.with(*child_id).write_text_content(out);
1338 }
1339 }
1340 _ => {}
1341 }
1342 }
1343
1344 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1345 if let NodeData::Element(ref mut elem_data) = self.data {
1346 if let Some(guard) = elem_data.guard.clone() {
1347 elem_data.flush_style_attribute(&guard, url_extra_data);
1348 }
1349 }
1350 }
1351
1352 pub fn order(&self) -> i32 {
1362 self.primary_styles()
1363 .filter(|s| !matches!(s.get_box().position, Position::Absolute | Position::Fixed))
1364 .map(|s| s.clone_order())
1365 .unwrap_or(0)
1366 }
1367
1368 pub fn z_index(&self) -> i32 {
1369 self.primary_styles()
1370 .map(|s| s.clone_z_index().integer_or(0))
1371 .unwrap_or(0)
1372 }
1373
1374 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1376 let Some(style) = self.primary_styles() else {
1377 return false;
1378 };
1379
1380 let position = style.clone_position();
1381 let has_z_index = !style.clone_z_index().is_auto();
1382
1383 if style.clone_opacity() != 1.0 {
1384 return true;
1385 }
1386
1387 let position_based = match position {
1388 Position::Fixed | Position::Sticky => true,
1389 Position::Relative | Position::Absolute => has_z_index,
1390 Position::Static => has_z_index && is_flex_or_grid_item,
1391 };
1392 if position_based {
1393 return true;
1394 }
1395
1396 let box_style = style.get_box();
1397 if !box_style.transform.0.is_empty()
1398 || !matches!(box_style.rotate, Rotate::None)
1399 || !matches!(box_style.scale, Scale::None)
1400 || !matches!(box_style.translate, Translate::None)
1401 {
1402 return true;
1403 }
1404
1405 if self.applies_atomic_paint_effect() {
1406 return true;
1407 }
1408
1409 if style.get_box().isolation == Isolation::Isolate {
1410 return true;
1411 }
1412
1413 if style.get_effects().mix_blend_mode != style::computed_values::mix_blend_mode::T::Normal {
1414 return true;
1415 }
1416 false
1419 }
1420
1421 pub(crate) fn applies_atomic_paint_effect(&self) -> bool {
1424 use style::values::computed::basic_shape::ClipPath;
1425 use style::values::generics::image::GenericImage;
1426
1427 let Some(style) = self.primary_styles() else {
1428 return false;
1429 };
1430
1431 if style.clone_opacity() != 1.0 {
1432 return true;
1433 }
1434
1435 let effects = style.get_effects();
1436 if !effects.filter.0.is_empty() {
1437 return true;
1438 }
1439
1440 if !matches!(style.get_clip_path(), ClipPath::None) {
1441 return true;
1442 }
1443
1444 style
1445 .get_svg()
1446 .mask_image
1447 .0
1448 .iter()
1449 .any(|image| !matches!(image, GenericImage::None))
1450 }
1451
1452 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1465 self.hit_inner(x, y, scale, &mut None)
1466 }
1467
1468 pub(crate) fn hit_inner(
1473 &self,
1474 x: f32,
1475 y: f32,
1476 scale: f64,
1477 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1478 ) -> Option<HitResult> {
1479 use style::computed_values::pointer_events::T as PointerEvents;
1480 use style::computed_values::visibility::T as Visibility;
1481
1482 if let Some(style) = self.primary_styles() {
1484 if matches!(
1485 style.clone_visibility(),
1486 Visibility::Hidden | Visibility::Collapse
1487 ) {
1488 return None;
1489 }
1490 }
1491
1492 let pointer_events_none = self
1495 .primary_styles()
1496 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1497
1498 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1499 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1500
1501 if let Some(t) = self.transform().as_deref() {
1502 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1503 x = (p.x / scale) as f32;
1504 y = (p.y / scale) as f32;
1505 }
1506
1507 let size = self.final_layout().size;
1508 let matches_self = !(x < self.scroll_offset().x as f32
1509 || x > size.width + self.scroll_offset().x as f32
1510 || y < self.scroll_offset().y as f32
1511 || y > size.height + self.scroll_offset().y as f32);
1512
1513 let layout = self.final_layout();
1514 let overflow_rect = layout.scrollable_overflow_rect;
1515 let is_rtl = self.primary_styles().is_some_and(|style| {
1518 style.clone_direction() == style::computed_values::direction::T::Rtl
1519 });
1520 let (gutter_left, gutter_top) = (is_rtl, false);
1521 #[cfg(feature = "writing-mode")]
1522 let (gutter_left, gutter_top) = {
1523 let wm = self.layout_data().writing_mode;
1524 if wm.swaps_axes() {
1525 (wm.block_left_is_end(), wm.direction().is_rtl())
1526 } else {
1527 (gutter_left, gutter_top)
1528 }
1529 };
1530 let client_x = layout.border.left
1531 + if gutter_left {
1532 layout.scrollbar_size.width
1533 } else {
1534 0.0
1535 };
1536 let client_y = layout.border.top
1537 + if gutter_top {
1538 layout.scrollbar_size.height
1539 } else {
1540 0.0
1541 };
1542 let matches_content = !(x < overflow_rect.left() + client_x
1543 || x > overflow_rect.right() + client_x
1544 || y < overflow_rect.top() + client_y
1545 || y > overflow_rect.bottom() + client_y);
1546
1547 let matches_hoisted_content = match &self.stacking_context {
1548 Some(sc) => {
1549 let bbox = sc.hoisted_content_bbox(self.tree(), self.id, scale);
1552 x >= bbox.left && x <= bbox.right && y >= bbox.top && y <= bbox.bottom
1553 }
1554 None => false,
1555 };
1556
1557 let overflow = *self.scrollable_overflow();
1560
1561 let matches_overflow = x >= (overflow.x0 / scale) as f32
1562 && x <= (overflow.x1 / scale) as f32
1563 && y >= (overflow.y0 / scale) as f32
1564 && y <= (overflow.y1 / scale) as f32;
1565
1566 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1567 return None;
1568 }
1569
1570 if matches_self
1573 && let Some(sb) = self.scrollbar_at_local(
1574 (x - self.scroll_offset().x as f32) as f64,
1575 (y - self.scroll_offset().y as f32) as f64,
1576 )
1577 {
1578 *scrollbar = Some(sb);
1579 }
1580
1581 let content_box_offset = takia::Point {
1582 x: self.final_layout().padding.left + self.final_layout().border.left,
1583 y: self.final_layout().padding.top + self.final_layout().border.top,
1584 };
1585 if self.flags.is_inline_root() {
1586 x -= content_box_offset.x;
1587 y -= content_box_offset.y;
1588 }
1589
1590 if matches_hoisted_content {
1592 if let Some(hoisted) = &self.stacking_context {
1593 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1594 let Some(child) = self.tree().get(hoisted_child.node_id) else {
1595 continue;
1596 };
1597 let pos = hoisted_child.position(self.tree(), self.id);
1598 let x = x - pos.x;
1599 let y = y - pos.y;
1600 if let Some(hit) = child.hit_inner(x, y, scale, scrollbar) {
1601 return Some(hit);
1602 }
1603 }
1604 }
1605 }
1606
1607 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1609 let Some(child) = self.tree().get(*child_id) else {
1610 continue;
1611 };
1612 let child_position = child.takia_position();
1613 let mut child_x = x;
1614 let mut child_y = y;
1615 if child_position.is_out_of_flow() {
1616 if self.flags.is_inline_root() {
1619 child_x += content_box_offset.x;
1620 child_y += content_box_offset.y;
1621 }
1622 if child_position == takia::Position::Fixed
1627 && self.containing_block().is_none()
1628 && !self.flags.is_top_layer()
1629 {
1630 child_x -= self.scroll_offset().x as f32;
1631 child_y -= self.scroll_offset().y as f32;
1632 let viewport_scroll = self.tree().viewport_scroll();
1633 child_x -= viewport_scroll.x as f32;
1634 child_y -= viewport_scroll.y as f32;
1635 }
1636 }
1637 if let Some(hit) = child.hit_inner(child_x, child_y, scale, scrollbar) {
1638 return Some(hit);
1639 }
1640 }
1641
1642 if matches_hoisted_content {
1644 if let Some(hoisted) = &self.stacking_context {
1645 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1646 let Some(child) = self.tree().get(hoisted_child.node_id) else {
1647 continue;
1648 };
1649 let pos = hoisted_child.position(self.tree(), self.id);
1650 let x = x - pos.x;
1651 let y = y - pos.y;
1652 if let Some(hit) = child.hit_inner(x, y, scale, scrollbar) {
1653 return Some(hit);
1654 }
1655 }
1656 }
1657 }
1658
1659 if self.flags.is_inline_root() {
1661 let element_data = &self.element_data().unwrap();
1662 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1663 let layout = &ild.layout;
1664 let scale = layout.scale();
1665 let y = y - ild.block_offset;
1666
1667 if let Some((cluster, _side)) =
1668 Cluster::from_point_exact(layout, x * scale, y * scale)
1669 {
1670 let node_id = cluster.style().brush.id;
1671 if let Some(text_node) = self.tree().get(node_id)
1672 && !text_node.primary_styles().is_some_and(|style| {
1673 style.clone_pointer_events() == PointerEvents::None
1674 })
1675 {
1676 return Some(HitResult {
1677 node_id,
1678 x,
1679 y,
1680 is_text: true,
1681 });
1682 }
1683 }
1684 }
1685 }
1686
1687 if matches_self && !pointer_events_none {
1689 return Some(HitResult {
1690 node_id: self.id,
1691 x,
1692 y,
1693 is_text: false,
1694 });
1695 }
1696
1697 None
1698 }
1699
1700 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1703 let mut node = self;
1704 loop {
1705 if node.flags.is_inline_root() {
1706 return Some(node);
1707 }
1708 let id = node.layout_parent.get()?;
1709 node = self.with(id);
1710 }
1711 }
1712
1713 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1717 if !self.flags.is_inline_root() {
1718 return None;
1719 }
1720
1721 let element_data = self.element_data()?;
1722 let inline_layout = element_data.inline_layout_data.as_ref()?;
1723 let layout = &inline_layout.layout;
1724 let scale = layout.scale();
1725 let y = y - inline_layout.block_offset;
1726
1727 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1729
1730 let is_leading = side == ClusterSide::Left;
1735 let offset = if cluster.is_rtl() {
1736 if is_leading {
1737 cluster.text_range().end
1738 } else {
1739 cluster.text_range().start
1740 }
1741 } else {
1742 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1744 cluster.text_range().start
1745 } else {
1746 cluster.text_range().end
1747 }
1748 };
1749
1750 Some(offset)
1751 }
1752
1753 pub fn is_non_atomic_inline(&self) -> bool {
1757 let Some(element) = self.element_data() else {
1758 return false;
1759 };
1760 if self.flags.is_inline_root()
1761 || crate::layout::replaced::is_inline_box_element(&element.name.local)
1762 {
1763 return false;
1764 }
1765 self.primary_styles().is_some_and(|styles| {
1766 let display = styles.clone_display();
1767 display.outside() == DisplayOutside::Inline && display.inside() == DisplayInside::Flow
1768 })
1769 }
1770
1771 pub fn containing_block(&self) -> Option<NodeId> {
1775 if self.flags.is_top_layer() {
1776 return None;
1777 }
1778 self.oof_containing_block
1779 .get()
1780 .or_else(|| self.layout_parent.get())
1781 }
1782
1783 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1785 let x = x + self.final_layout().location.x - self.scroll_offset().x as f32;
1786 let y = y + self.final_layout().location.y - self.scroll_offset().y as f32;
1787 if self.flags.is_top_layer() {
1788 let scroll = self.tree().viewport_scroll();
1789 return crate::util::Point {
1790 x: x + scroll.x as f32,
1791 y: y + scroll.y as f32,
1792 };
1793 }
1794
1795 self.containing_block()
1797 .map(|i| self.with(i).absolute_position(x, y))
1798 .unwrap_or(crate::util::Point { x, y })
1799 }
1800
1801 pub(crate) fn is_offset_parent(&self) -> bool {
1804 let Some(styles) = self.primary_styles() else {
1805 return false;
1806 };
1807 if styles.get_box().position != Position::Static {
1808 return true;
1809 }
1810 self.data.is_element_with_tag_name(&local_name!("body"))
1811 || self.data.is_element_with_tag_name(&local_name!("td"))
1812 || self.data.is_element_with_tag_name(&local_name!("th"))
1813 }
1814
1815 pub(crate) fn is_static_body(&self) -> bool {
1819 self.data.is_element_with_tag_name(&local_name!("body"))
1820 && self
1821 .primary_styles()
1822 .is_some_and(|styles| styles.get_box().position == Position::Static)
1823 }
1824
1825 pub fn offset_parent(&self) -> Option<&Node> {
1828 let mut node = self;
1829 loop {
1830 node = self.with(node.containing_block()?);
1831 if node.is_offset_parent() {
1832 return Some(node);
1833 }
1834 }
1835 }
1836
1837 pub fn inline_fragment_boxes(&self) -> Option<impl Iterator<Item = takia::Rect<f32>> + '_> {
1842 use parley::PositionedLayoutItem;
1843
1844 if !self.is_non_atomic_inline() {
1845 return None;
1846 }
1847
1848 let inline_root = self.inline_root_ancestor()?;
1849 let inline_layout = inline_root.element_data()?.inline_layout_data.as_ref()?;
1850 let layout = &inline_layout.layout;
1851 let scale = layout.scale();
1852
1853 let inline_root_id = inline_root.id;
1856 let is_in_target = move |mut id: NodeId| -> bool {
1857 loop {
1858 if id == self.id {
1859 return true;
1860 }
1861 if id == inline_root_id {
1862 return false;
1863 }
1864 match self.with(id).parent {
1865 Some(parent) => id = parent,
1866 None => return false,
1867 }
1868 }
1869 };
1870
1871 let root_layout = inline_root.unrounded_layout();
1872 let content_box_inset = root_layout.padding + root_layout.border;
1873 let origin_x = content_box_inset.left;
1874 let origin_y = content_box_inset.top + inline_layout.block_offset;
1875
1876 fn union(acc: &mut Option<takia::Rect<f32>>, left: f32, top: f32, right: f32, bottom: f32) {
1877 *acc = Some(match *acc {
1878 Some(rect) => takia::Rect {
1879 left: rect.left.min(left),
1880 top: rect.top.min(top),
1881 right: rect.right.max(right),
1882 bottom: rect.bottom.max(bottom),
1883 },
1884 None => takia::Rect {
1885 left,
1886 top,
1887 right,
1888 bottom,
1889 },
1890 });
1891 }
1892
1893 Some(layout.lines().filter_map(move |line| {
1895 let line_metrics = line.metrics();
1896 let mut line_rect: Option<takia::Rect<f32>> = None;
1897
1898 for item in line.items() {
1899 match item {
1900 PositionedLayoutItem::GlyphRun(glyph_run) => {
1901 if !is_in_target(glyph_run.style().brush.id) {
1902 continue;
1903 }
1904 let x0 = glyph_run.offset();
1905 let x1 = x0 + glyph_run.advance();
1906 let y0 = line_metrics.block_min_coord;
1912 let y1 = line_metrics.block_max_coord;
1913 union(&mut line_rect, x0, y0, x1, y1);
1914 }
1915 PositionedLayoutItem::InlineBox(inline_box) => {
1916 if !is_in_target(NodeId::from_u64(inline_box.id)) {
1917 continue;
1918 }
1919 let x0 = inline_box.x;
1920 let y0 = inline_box.y;
1921 union(
1922 &mut line_rect,
1923 x0,
1924 y0,
1925 x0 + inline_box.width,
1926 y0 + inline_box.height,
1927 );
1928 }
1929 }
1930 }
1931
1932 line_rect.map(|rect| takia::Rect {
1933 left: origin_x + rect.left / scale,
1934 top: origin_y + rect.top / scale,
1935 right: origin_x + rect.right / scale,
1936 bottom: origin_y + rect.bottom / scale,
1937 })
1938 }))
1939 }
1940
1941 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1944 let mut x = 0.0;
1945 let mut y = 0.0;
1946 let mut current = self;
1947
1948 if let Some(mut fragments) = self.inline_fragment_boxes() {
1952 if let Some(first) = fragments.next() {
1953 x += first.left;
1954 y += first.top;
1955 }
1956 let Some(inline_root) = self.inline_root_ancestor() else {
1957 return crate::util::Point { x, y };
1958 };
1959 if inline_root.is_offset_parent() && !inline_root.is_static_body() {
1960 let border = inline_root.final_layout().border;
1961 return crate::util::Point {
1962 x: x - border.left,
1963 y: y - border.top,
1964 };
1965 }
1966 current = inline_root;
1967 }
1968
1969 loop {
1970 let layout = current.final_layout();
1971 x += layout.location.x;
1972 y += layout.location.y;
1973
1974 let Some(parent_id) = current.containing_block() else {
1975 break;
1976 };
1977 let parent = self.with(parent_id);
1978 if parent.is_offset_parent() && !parent.is_static_body() {
1979 let border = parent.final_layout().border;
1980 x -= border.left;
1981 y -= border.top;
1982 break;
1983 }
1984 current = parent;
1985 }
1986 crate::util::Point { x, y }
1987 }
1988
1989 pub fn client_width(&self) -> f32 {
1992 let layout = self.final_layout();
1993 layout.size.width - layout.border.left - layout.border.right - layout.scrollbar_size.width
1994 }
1995
1996 pub fn client_height(&self) -> f32 {
1999 let layout = self.final_layout();
2000 layout.size.height - layout.border.top - layout.border.bottom - layout.scrollbar_size.height
2001 }
2002
2003 pub fn scroll_width(&self) -> f32 {
2006 self.cssom_scroll_size().width
2007 }
2008
2009 pub fn scroll_height(&self) -> f32 {
2012 self.cssom_scroll_size().height
2013 }
2014
2015 fn cssom_scroll_size(&self) -> takia::Size<f32> {
2016 let layout = self.final_layout();
2017 if self.primary_styles().is_none() {
2018 return takia::Size {
2019 width: self.client_width() + layout.scroll_width(),
2020 height: self.client_height() + layout.scroll_height(),
2021 };
2022 }
2023 let overflow = takia::CoreStyle::overflow(&self.layout_style());
2024 if overflow.x.is_scroll_container() || overflow.y.is_scroll_container() {
2025 return takia::Size {
2026 width: self.client_width() + layout.scroll_width(),
2027 height: self.client_height() + layout.scroll_height(),
2028 };
2029 }
2030
2031 let mut inline = false;
2034 let mut block = false;
2035 let mut vertical = false;
2036 if let Some(style) = self.primary_styles() {
2037 let wm = style.writing_mode;
2038 inline = wm.direction().is_rtl();
2039 block = wm.block_left_is_end();
2040 vertical = wm.swaps_axes();
2041 if self.takia_display() == takia::Display::Flex {
2042 let position = style.get_position();
2043 let row = matches!(
2044 position.flex_direction,
2045 FlexDirection::Row | FlexDirection::RowReverse
2046 );
2047 let reverse = matches!(
2048 position.flex_direction,
2049 FlexDirection::RowReverse | FlexDirection::ColumnReverse
2050 );
2051 let wrap_reverse = position
2052 .flex_wrap
2053 .contains(style::values::computed::FlexWrap::WRAP_REVERSE);
2054 inline ^= if row { reverse } else { wrap_reverse };
2055 block ^= if row { wrap_reverse } else { reverse };
2056 }
2057 }
2058 let (reverse_x, reverse_y) = if vertical {
2059 (block, inline)
2060 } else {
2061 (inline, block)
2062 };
2063 let overflow = layout.scrollable_overflow_rect;
2064 takia::Size {
2065 width: if reverse_x {
2066 self.client_width() - overflow.left()
2067 } else {
2068 self.client_width().max(overflow.right())
2069 },
2070 height: if reverse_y {
2071 self.client_height() - overflow.top()
2072 } else {
2073 self.client_height().max(overflow.bottom())
2074 },
2075 }
2076 }
2077
2078 pub(crate) fn scroll_range(&self) -> (crate::Point<f64>, crate::Point<f64>) {
2080 let layout = self.final_layout();
2081 let reversed = layout.scrollable_overflow_rect.is_reversed();
2082 let width = layout.scroll_width() as f64;
2083 let height = layout.scroll_height() as f64;
2084 (
2085 crate::Point {
2086 x: if reversed.x { -width } else { 0.0 },
2087 y: if reversed.y { -height } else { 0.0 },
2088 },
2089 crate::Point {
2090 x: if reversed.x { 0.0 } else { width },
2091 y: if reversed.y { 0.0 } else { height },
2092 },
2093 )
2094 }
2095
2096 pub fn has_boxes(&self) -> bool {
2100 self.flags.is_in_document()
2101 && !self.display_style().is_some_and(|display| {
2102 matches!(
2103 display.inside(),
2104 style::values::specified::box_::DisplayInside::None
2105 | style::values::specified::box_::DisplayInside::Contents
2106 )
2107 })
2108 }
2109
2110 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
2112 DomEventData::Click(self.synthetic_click_event_data(mods))
2113 }
2114
2115 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> ApacePointerEvent {
2116 let absolute_position = self.absolute_position(0.0, 0.0);
2117 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
2118 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
2119
2120 ApacePointerEvent {
2121 id: ApacePointerId::Mouse,
2122 is_primary: true,
2123 coords: PointerCoords {
2124 page_x: x,
2125 page_y: y,
2126
2127 screen_x: x,
2129 screen_y: y,
2130 client_x: x,
2131 client_y: y,
2132 },
2133 mods,
2134 button: Default::default(),
2135 buttons: Default::default(),
2136 details: Default::default(),
2137 element: Default::default(),
2138 active_pointers: Default::default(),
2139 }
2140 }
2141}
2142
2143impl PartialEq for Node {
2145 fn eq(&self, other: &Self) -> bool {
2146 self.id == other.id
2147 }
2148}
2149
2150impl Eq for Node {}
2151
2152impl std::fmt::Debug for Node {
2153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2154 f.debug_struct("NodeData")
2156 .field("parent", &self.parent)
2157 .field("id", &self.id)
2158 .field("is_inline_root", &self.flags.is_inline_root())
2159 .field("children", &self.children)
2160 .field("layout_children", &self.layout_children.borrow())
2161 .field("node", &self.data)
2163 .field("stylo_element_data", &self.try_stylo_element_data())
2164 .finish()
2167 }
2168}
2169
2170pub(crate) fn style_is_out_of_flow(style: &ComputedValues) -> bool {
2174 let box_style = style.get_box();
2175 matches!(box_style.position, Position::Absolute | Position::Fixed)
2176 && !box_style.display.is_none()
2177}
2178
2179#[cfg(test)]
2180mod test {
2181 use style_dom::ElementState;
2182
2183 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
2184
2185 #[test]
2186 fn create_node_with_disabled_attr() {
2187 let mut document = BaseDocument::new(DocumentConfig::default());
2188 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2189 qual_name!("button"),
2190 vec![Attribute {
2191 name: qual_name!("disabled"),
2192 value: "".into(),
2193 }],
2194 ))));
2195 let node = document.get_node(node).unwrap();
2196
2197 assert!(
2198 node.element_state().contains(ElementState::DISABLED),
2199 "form node is disabled"
2200 );
2201 assert!(
2202 !node.element_state().contains(ElementState::ENABLED),
2203 "form node is not enabled"
2204 );
2205 }
2206
2207 #[test]
2208 fn ignore_disabled_attr_content() {
2209 let mut document = BaseDocument::new(DocumentConfig::default());
2210 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2211 qual_name!("button"),
2212 vec![Attribute {
2213 name: qual_name!("disabled"),
2214 value: "false".into(),
2215 }],
2216 ))));
2217 let node = document.get_node(node).unwrap();
2218
2219 assert!(
2220 node.element_state().contains(ElementState::DISABLED),
2221 "form node is disabled"
2222 );
2223 assert!(
2224 !node.element_state().contains(ElementState::ENABLED),
2225 "form node is not enabled"
2226 );
2227 }
2228
2229 #[test]
2230 fn create_node_with_ignored_disable() {
2231 let mut document = BaseDocument::new(DocumentConfig::default());
2232 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2233 qual_name!("a"),
2234 vec![Attribute {
2235 name: qual_name!("disabled"),
2236 value: "".into(),
2237 }],
2238 ))));
2239 let node = document.get_node(node).unwrap();
2240
2241 assert!(
2242 !node.element_state().contains(ElementState::DISABLED),
2243 "Non form node cannot be disabled"
2244 );
2245 assert!(
2246 !node.element_state().contains(ElementState::ENABLED),
2247 "Non form node cannot be enabled"
2248 );
2249 }
2250
2251 #[test]
2252 fn create_empty_enabled_node() {
2253 let mut document = BaseDocument::new(DocumentConfig::default());
2254 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2255 qual_name!("button"),
2256 vec![],
2257 ))));
2258 let node = document.get_node(node).unwrap();
2259
2260 assert!(
2261 node.element_state().contains(ElementState::ENABLED),
2262 "Button should be enabled by default"
2263 );
2264 }
2265}