1use crate::Document;
2use crate::layout::damage::HoistedPaintChildren;
3use bitflags::bitflags;
4use blitz_traits::events::{
5 BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
6};
7use blitz_traits::node_id::NodeId;
8use blitz_traits::shell::ShellProvider;
9use euclid::{Point2D, Rect, Size2D};
10use html_escape::encode_quoted_attribute_to_string;
11use keyboard_types::Modifiers;
12use kurbo::{Affine, Rect as KurboRect};
13use markup5ever::{LocalName, local_name};
14use parley::{BreakReason, Cluster, ClusterSide};
15use selectors::matching::ElementSelectorFlags;
16use std::cell::{Cell, RefCell};
17use std::fmt::Write;
18use std::ops::Deref;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, Ordering};
21use style::Atom;
22use style::invalidation::element::restyle_hints::RestyleHint;
23use style::properties::ComputedValues;
24use style::properties::generated::longhands::position::computed_value::T as Position;
25use style::selector_parser::RestyleDamage;
26use style::servo_arc::Arc as ServoArc;
27use style::shared_lock::SharedRwLock;
28use style::stylesheets::UrlExtraData;
29use style::values::computed::CSSPixelLength;
30use style::values::computed::Display as StyloDisplay;
31use style::values::specified::box_::{DisplayInside, DisplayOutside};
32use style_dom::ElementState;
33use style_traits::values::ToCss;
34use taffy::{
35 Cache,
36 prelude::{Layout, Style},
37};
38use thin_vec::ThinVec;
39
40use super::stylo_data::StyloData;
41use super::{Attribute, DocumentData, ElementData};
42
43#[derive(Clone, Copy)]
44enum OutputStyle {
45 Normal,
46 Pretty,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum DisplayOuter {
51 Block,
52 Inline,
53 None,
54}
55
56bitflags! {
57 #[derive(Clone, Copy, PartialEq)]
58 pub struct NodeFlags: u32 {
59 const IS_INLINE_ROOT = 0b00000001;
61 const IS_TABLE_ROOT = 0b00000010;
63 const IS_IN_DOCUMENT = 0b00000100;
65 }
66}
67
68impl NodeFlags {
69 #[inline(always)]
70 pub fn is_inline_root(&self) -> bool {
71 self.contains(Self::IS_INLINE_ROOT)
72 }
73
74 #[inline(always)]
75 pub fn is_table_root(&self) -> bool {
76 self.contains(Self::IS_TABLE_ROOT)
77 }
78
79 #[inline(always)]
80 pub fn is_in_document(&self) -> bool {
81 self.contains(Self::IS_IN_DOCUMENT)
82 }
83
84 #[inline(always)]
85 pub fn reset_construction_flags(&mut self) {
86 self.remove(Self::IS_INLINE_ROOT);
87 self.remove(Self::IS_TABLE_ROOT);
88 }
89}
90
91pub struct Node {
92 tree: *mut crate::NodeTree,
94
95 pub id: NodeId,
97 pub parent: Option<NodeId>,
99 pub children: ThinVec<NodeId>,
101 pub layout_parent: Cell<Option<NodeId>>,
103 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
105 pub anonymous_blocks: ThinVec<NodeId>,
111 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
113 pub stacking_context: Option<Box<HoistedPaintChildren>>,
114
115 pub flags: NodeFlags,
117
118 pub data: NodeData,
125}
126
127unsafe impl Send for Node {}
128unsafe impl Sync for Node {}
129
130macro_rules! universal_accessors {
133 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
134 impl Node {
135 $(
136 $(#[$meta])*
137 #[inline]
138 pub fn $field(&self) -> &$ty {
139 match &self.data {
140 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
141 NodeData::Document(data) => &data.$field,
142 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
143 }
144 }
145
146 $(#[$meta])*
147 #[inline]
148 pub fn $field_mut(&mut self) -> &mut $ty {
149 match &mut self.data {
150 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
151 NodeData::Document(data) => &mut data.$field,
152 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
153 }
154 }
155 )*
156 }
157 };
158}
159
160universal_accessors! {
161 stylo_element_data / stylo_element_data_mut: StyloData,
162 style / style_mut: Style<Atom>,
163 cache / cache_mut: Cache,
164 unrounded_layout / unrounded_layout_mut: Layout,
165 final_layout / final_layout_mut: Layout,
166 scroll_offset / scroll_offset_mut: crate::Point<f64>,
167 scrollable_overflow / scrollable_overflow_mut: KurboRect,
168 transform / transform_mut: Option<Affine>,
169 display_constructed_as / display_constructed_as_mut: StyloDisplay,
170 element_state / element_state_mut: ElementState,
173 snapshot_handled / snapshot_handled_mut: AtomicBool,
174 selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
178}
179
180impl Node {
181 #[inline]
184 pub fn stylo_element_data_opt(&self) -> Option<&StyloData> {
185 match &self.data {
186 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
187 Some(&data.stylo_element_data)
188 }
189 NodeData::Document(data) => Some(&data.stylo_element_data),
190 _ => None,
191 }
192 }
193
194 #[inline]
195 pub fn stylo_element_data_opt_mut(&mut self) -> Option<&mut StyloData> {
196 match &mut self.data {
197 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
198 Some(&mut data.stylo_element_data)
199 }
200 NodeData::Document(data) => Some(&mut data.stylo_element_data),
201 _ => None,
202 }
203 }
204
205 #[inline]
208 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
209 match &self.data {
210 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
211 Some(&data.dirty_descendants)
212 }
213 NodeData::Document(data) => Some(&data.dirty_descendants),
214 _ => None,
215 }
216 }
217
218 #[inline]
221 fn damaged_descendants_flag(&self) -> Option<&AtomicBool> {
222 match &self.data {
223 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
224 Some(&data.damaged_descendants)
225 }
226 NodeData::Document(data) => Some(&data.damaged_descendants),
227 _ => None,
228 }
229 }
230
231 #[inline]
234 pub fn guard(&self) -> &SharedRwLock {
235 let guard = match &self.data {
236 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
237 NodeData::Document(data) => data.guard.as_ref(),
238 _ => None,
239 };
240 guard.expect("`guard` is not available on this node kind")
241 }
242
243 #[inline]
244 pub fn has_snapshot(&self) -> bool {
245 match &self.data {
246 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
247 NodeData::Document(data) => data.has_snapshot,
248 _ => false,
249 }
250 }
251
252 #[inline]
253 pub fn set_has_snapshot(&mut self, value: bool) {
254 match &mut self.data {
255 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
256 NodeData::Document(data) => data.has_snapshot = value,
257 _ => {}
258 }
259 }
260
261 #[inline]
262 pub fn before(&self) -> Option<NodeId> {
263 self.element_data().and_then(|data| data.before)
264 }
265
266 #[inline]
267 pub fn after(&self) -> Option<NodeId> {
268 self.element_data().and_then(|data| data.after)
269 }
270}
271
272impl Node {
273 pub(crate) fn new(
274 tree: *mut crate::NodeTree,
275 id: NodeId,
276 guard: SharedRwLock,
277 mut data: NodeData,
278 ) -> Self {
279 match &mut data {
282 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
283 data.guard = Some(guard);
284 }
285 NodeData::Document(data) => data.guard = Some(guard),
286 _ => {}
287 }
288
289 Self {
290 tree,
291
292 id,
293 parent: None,
294 children: ThinVec::new(),
295 layout_parent: Cell::new(None),
296 layout_children: RefCell::new(None),
297 anonymous_blocks: ThinVec::new(),
298 paint_children: RefCell::new(None),
299 stacking_context: None,
300
301 flags: NodeFlags::empty(),
302 data,
303 }
304 }
305
306 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
307 let transform = self.primary_styles().and_then(|s| {
308 let size = self.final_layout().size;
309 let reference_box = Rect::new(
310 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
311 Size2D::new(
312 CSSPixelLength::new(size.width),
313 CSSPixelLength::new(size.height),
314 ),
315 );
316 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
319 let scale = scale as f64;
320 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
321 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
322 })
323 });
324
325 *self.transform_mut() = transform;
326 transform
327 }
328
329 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
330 match index {
331 0 => self.after(),
332 1 => self.before(),
333 _ => panic!("Invalid pseudo element index"),
334 }
335 }
336
337 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
338 let Some(data) = self.element_data_mut() else {
339 return;
340 };
341 match index {
342 0 => data.after = value,
343 1 => data.before = value,
344 _ => panic!("Invalid pseudo element index"),
345 }
346 }
347
348 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
349 Some(self.primary_styles().as_ref()?.clone_display())
350 }
351
352 pub fn is_or_contains_block(&self) -> bool {
353 let style = self.primary_styles();
354 let style = style.as_ref();
355
356 let position = style
358 .map(|s| s.clone_position())
359 .unwrap_or(Position::Relative);
360 let is_in_flow = matches!(
361 position,
362 Position::Static | Position::Relative | Position::Sticky
363 );
364 if !is_in_flow {
365 return false;
366 }
367 let is_floating = style
370 .map(|s| s.clone_float().is_floating())
371 .unwrap_or(false);
372 if is_floating {
373 return false;
374 }
375 let display = style
376 .map(|s| s.clone_display())
377 .unwrap_or(StyloDisplay::inline());
378 match display.outside() {
379 DisplayOutside::None => false,
380 DisplayOutside::Block => true,
381 _ => {
382 if display.inside() == DisplayInside::Flow {
383 self.children
384 .iter()
385 .copied()
386 .any(|child_id| self.tree()[child_id].is_or_contains_block())
387 } else {
388 false
389 }
390 }
391 }
392 }
393
394 pub fn is_whitespace_node(&self) -> bool {
395 match &self.data {
396 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
397 _ => false,
398 }
399 }
400
401 pub fn is_focussable(&self) -> bool {
402 self.data
403 .downcast_element()
404 .map(|el| el.is_focussable)
405 .unwrap_or(false)
406 }
407
408 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
409 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
410 if let Some(mut element_data) = stylo_element_data.get_mut() {
411 element_data.hint.insert(hint);
412 }
413 }
414 self.mark_ancestors_dirty();
417 }
418
419 pub fn has_dirty_descendants(&self) -> bool {
421 self.dirty_descendants_flag()
422 .is_some_and(|flag| flag.load(Ordering::Relaxed))
423 }
424
425 pub fn set_dirty_descendants(&self) {
427 if let Some(flag) = self.dirty_descendants_flag() {
428 flag.store(true, Ordering::Relaxed);
429 }
430 }
431
432 pub fn unset_dirty_descendants(&self) {
434 if let Some(flag) = self.dirty_descendants_flag() {
435 flag.store(false, Ordering::Relaxed);
436 }
437 }
438
439 pub(crate) fn mark_style_attr_updated(&mut self) {
441 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
442 if let Some(mut data) = stylo_element_data.get_mut() {
443 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
444 }
445 }
446 self.set_dirty_descendants();
447 self.mark_ancestors_dirty();
448 }
449
450 pub fn mark_ancestors_dirty(&self) {
454 let mut current_id = self.parent;
455 while let Some(parent_id) = current_id {
456 let parent = &self.tree()[parent_id];
457 if let Some(flag) = parent.dirty_descendants_flag() {
460 if flag.swap(true, Ordering::Relaxed) {
461 break;
462 }
463 }
464 current_id = parent.parent;
465 }
466 }
467
468 pub fn has_damaged_descendants(&self) -> bool {
470 self.damaged_descendants_flag()
471 .is_some_and(|flag| flag.load(Ordering::Relaxed))
472 }
473
474 pub fn unset_damaged_descendants(&self) {
476 if let Some(flag) = self.damaged_descendants_flag() {
477 flag.store(false, Ordering::Relaxed);
478 }
479 }
480
481 pub fn mark_damaged(&self) {
488 if let Some(flag) = self.damaged_descendants_flag() {
489 if flag.swap(true, Ordering::Relaxed) {
490 return;
491 }
492 }
493 let mut current_id = self.parent;
494 while let Some(parent_id) = current_id {
495 let parent = &self.tree()[parent_id];
496 if let Some(flag) = parent.damaged_descendants_flag() {
499 if flag.swap(true, Ordering::Relaxed) {
500 break;
501 }
502 }
503 current_id = parent.parent;
504 }
505 }
506
507 pub fn damage(&self) -> Option<RestyleDamage> {
514 self.stylo_element_data_opt()
515 .and_then(|stylo| stylo.get().map(|data| data.damage))
516 }
517
518 pub fn set_damage(&mut self, damage: RestyleDamage) {
519 if let Some(stylo) = self.stylo_element_data_opt_mut() {
520 if let Some(mut data) = stylo.get_mut() {
521 data.damage = damage;
522 }
523 }
524 }
525
526 pub fn insert_damage(&mut self, damage: RestyleDamage) {
527 if let Some(stylo) = self.stylo_element_data_opt_mut() {
528 if let Some(mut data) = stylo.get_mut() {
529 data.damage |= damage;
530 }
531 }
532 if !damage.is_empty() {
533 self.mark_damaged();
534 }
535 }
536
537 pub fn remove_damage(&mut self, damage: RestyleDamage) {
538 if let Some(stylo) = self.stylo_element_data_opt_mut() {
539 if let Some(mut data) = stylo.get_mut() {
540 data.damage.remove(damage);
541 }
542 }
543 }
544
545 pub fn clear_damage_mut(&mut self) {
546 if let Some(stylo) = self.stylo_element_data_opt_mut() {
547 if let Some(mut data) = stylo.get_mut() {
548 data.damage = RestyleDamage::empty();
549 }
550 }
551 }
552
553 pub fn hover(&mut self) {
559 if let Some(data) = self.element_data_mut() {
560 data.element_state.insert(ElementState::HOVER);
561 }
562 self.mark_ancestors_dirty();
563 }
564
565 pub fn unhover(&mut self) {
566 if let Some(data) = self.element_data_mut() {
567 data.element_state.remove(ElementState::HOVER);
568 }
569 self.mark_ancestors_dirty();
570 }
571
572 pub fn is_hovered(&self) -> bool {
573 self.element_data()
574 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
575 }
576
577 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
578 if let Some(data) = self.element_data_mut() {
579 data.element_state
580 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
581 }
582 self.mark_ancestors_dirty();
583
584 if self
586 .element_data()
587 .and_then(|elem| elem.text_input_data())
588 .is_some()
589 {
590 shell_provider.set_ime_enabled(true);
591 let mut pos = self.absolute_position(0.0, 0.0);
592 pos.x += self.final_layout().content_box_x();
593 pos.y += self.final_layout().content_box_y();
594 let width = self.final_layout().content_box_width();
595 let height = self.final_layout().content_box_height();
596 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
597 }
598 }
599
600 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
601 if let Some(data) = self.element_data_mut() {
602 data.element_state
603 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
604 }
605 self.mark_ancestors_dirty();
606
607 if self
609 .element_data()
610 .and_then(|elem| elem.text_input_data())
611 .is_some()
612 {
613 shell_provider.set_ime_enabled(false);
614 }
615 }
616
617 pub fn is_focussed(&self) -> bool {
618 self.element_data()
619 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
620 }
621
622 pub fn active(&mut self) {
623 if let Some(data) = self.element_data_mut() {
624 data.element_state.insert(ElementState::ACTIVE);
625 }
626 self.mark_ancestors_dirty();
627 }
628
629 pub fn unactive(&mut self) {
630 if let Some(data) = self.element_data_mut() {
631 data.element_state.remove(ElementState::ACTIVE);
632 }
633 self.mark_ancestors_dirty();
634 }
635
636 pub fn is_active(&self) -> bool {
637 self.element_data()
638 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
639 }
640
641 pub fn disable(&mut self) {
644 if let Some(data) = self.element_data_mut() {
645 if data.can_be_disabled() {
646 data.element_state.insert(ElementState::DISABLED);
647 data.element_state.remove(ElementState::ENABLED);
648 }
649 }
650 self.mark_ancestors_dirty();
651 }
652
653 pub fn enable(&mut self) {
656 if let Some(data) = self.element_data_mut() {
657 if data.can_be_disabled() {
658 data.element_state.insert(ElementState::ENABLED);
659 data.element_state.remove(ElementState::DISABLED);
660 }
661 }
662 self.mark_ancestors_dirty();
663 }
664
665 pub fn subdoc(&self) -> Option<&dyn Document> {
666 self.element_data().and_then(|el| el.sub_doc_data())
667 }
668
669 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
670 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
671 }
672
673 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
674 if let Some(input_data) = self
677 .data
678 .downcast_element()
679 .and_then(|el| el.text_input_data())
680 {
681 if !input_data.is_multiline {
682 let content_box_height = self.final_layout().content_box_height();
683 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
684 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
685
686 return y_offset as f64;
687 }
688 }
689
690 0.0
691 }
692}
693
694#[derive(Debug, Clone, Copy, PartialEq)]
695pub enum NodeKind {
696 Document,
697 Element,
698 AnonymousBlock,
699 Text,
700 Comment,
701}
702
703#[derive(Debug, Clone)]
705pub enum NodeData {
706 Document(Box<DocumentData>),
708
709 Element(Box<ElementData>),
711
712 AnonymousBlock(Box<ElementData>),
714
715 Text(TextNodeData),
717
718 Comment {
720 contents: String,
722 },
723 }
730
731impl NodeData {
732 pub fn downcast_element(&self) -> Option<&ElementData> {
733 match self {
734 Self::Element(data) => Some(data),
735 Self::AnonymousBlock(data) => Some(data),
736 _ => None,
737 }
738 }
739
740 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
741 match self {
742 Self::Element(data) => Some(data),
743 Self::AnonymousBlock(data) => Some(data),
744 _ => None,
745 }
746 }
747
748 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
749 let Some(elem) = self.downcast_element() else {
750 return false;
751 };
752 *name == elem.name.local
753 }
754
755 pub fn attrs(&self) -> Option<&[Attribute]> {
756 Some(&self.downcast_element()?.attrs)
757 }
758
759 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
760 self.downcast_element()?.attr(name)
761 }
762
763 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
764 self.downcast_element()
765 .is_some_and(|elem| elem.has_attr(name))
766 }
767
768 pub fn kind(&self) -> NodeKind {
769 match self {
770 NodeData::Document(_) => NodeKind::Document,
771 NodeData::Element(_) => NodeKind::Element,
772 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
773 NodeData::Text(_) => NodeKind::Text,
774 NodeData::Comment { .. } => NodeKind::Comment,
775 }
776 }
777}
778
779#[derive(Debug, Clone)]
780pub struct TextNodeData {
781 pub content: String,
783}
784
785impl TextNodeData {
786 pub fn new(content: String) -> Self {
787 Self { content }
788 }
789}
790
791impl Node {
816 pub fn tree(&self) -> &crate::NodeTree {
817 unsafe { &*self.tree }
818 }
819
820 #[track_caller]
821 pub fn with(&self, id: NodeId) -> &Node {
822 self.tree().get(id).unwrap()
823 }
824
825 pub fn print_tree(&self, level: usize) {
826 println!(
827 "{} {} {:?} {} {:?}",
828 " ".repeat(level),
829 self.id,
830 self.parent,
831 self.node_debug_str().replace('\n', ""),
832 self.children
833 );
834 for child_id in self.children.iter() {
836 let child = self.with(*child_id);
837 child.print_tree(level + 1)
838 }
839 }
840
841 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
843 self.children.iter().position(|id| *id == child_id)
844 }
845
846 pub fn child_index(&self) -> Option<usize> {
848 self.tree()[self.parent?]
849 .children
850 .iter()
851 .position(|id| *id == self.id)
852 }
853
854 pub fn forward(&self, n: usize) -> Option<&Node> {
856 let child_idx = self.child_index().unwrap_or(0);
857 self.tree()[self.parent?]
858 .children
859 .get(child_idx + n)
860 .map(|id| self.with(*id))
861 }
862
863 pub fn backward(&self, n: usize) -> Option<&Node> {
864 let child_idx = self.child_index().unwrap_or(0);
865 if child_idx < n {
866 return None;
867 }
868
869 self.tree()[self.parent?]
870 .children
871 .get(child_idx - n)
872 .map(|id| self.with(*id))
873 }
874
875 pub fn is_element(&self) -> bool {
876 matches!(self.data, NodeData::Element { .. })
877 }
878
879 pub fn is_anonymous(&self) -> bool {
880 matches!(self.data, NodeData::AnonymousBlock { .. })
881 }
882
883 pub fn is_text_node(&self) -> bool {
884 matches!(self.data, NodeData::Text { .. })
885 }
886
887 pub fn element_data(&self) -> Option<&ElementData> {
888 match self.data {
889 NodeData::Element(ref data) => Some(data),
890 NodeData::AnonymousBlock(ref data) => Some(data),
891 _ => None,
892 }
893 }
894
895 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
896 match self.data {
897 NodeData::Element(ref mut data) => Some(data),
898 NodeData::AnonymousBlock(ref mut data) => Some(data),
899 _ => None,
900 }
901 }
902
903 pub fn text_data(&self) -> Option<&TextNodeData> {
904 match self.data {
905 NodeData::Text(ref data) => Some(data),
906 _ => None,
907 }
908 }
909
910 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
911 match self.data {
912 NodeData::Text(ref mut data) => Some(data),
913 _ => None,
914 }
915 }
916
917 pub fn node_debug_str(&self) -> String {
918 let mut s = String::new();
919
920 match &self.data {
921 NodeData::Document(_) => write!(s, "DOCUMENT"),
922 NodeData::Text(data) => {
924 let bytes = data.content.as_bytes();
925 write!(
926 s,
927 "TEXT {}",
928 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
929 .unwrap_or("INVALID UTF8")
930 )
931 }
932 NodeData::Comment { .. } => write!(s, "COMMENT"),
933 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
934 NodeData::Element(data) => {
935 let name = &data.name;
936 let class = self.attr(local_name!("class")).unwrap_or("");
937 let id = self.attr(local_name!("id")).unwrap_or("");
938 let display = self.display_constructed_as().to_css_string();
939 write!(s, "<{}", name.local).unwrap();
940 if !id.is_empty() {
941 write!(s, " #{id}").unwrap();
942 }
943 if !class.is_empty() {
944 if class.contains(' ') {
945 write!(s, " class=\"{class}\"").unwrap()
946 } else {
947 write!(s, " .{class}").unwrap()
948 }
949 }
950 write!(s, "> ({display})")
951 } }
953 .unwrap();
954 s
955 }
956
957 pub fn outer_html(&self) -> String {
965 let mut output = String::new();
966 self.write_outer_html(&mut output);
967 output
968 }
969
970 pub fn outer_html_pretty(&self) -> String {
986 let mut output = String::new();
987 self.write_outer_html_pretty(&mut output);
988 output
989 }
990
991 pub fn write_outer_html(&self, writer: &mut String) {
992 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0);
993 }
994
995 pub fn write_outer_html_pretty(&self, writer: &mut String) {
996 self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0);
997 }
998
999 fn write_outer_html_in_style(&self, writer: &mut String, style: OutputStyle, nesting: usize) {
1000 const INDENT: &str = " ";
1001 let has_children = !self.children.is_empty();
1002 let current_color = self
1003 .primary_styles()
1004 .map(|style| style.clone_color())
1005 .map(|color| color.to_css_string());
1006
1007 match &self.data {
1008 NodeData::Document(_) => {}
1009 NodeData::Comment { .. } => {}
1010 NodeData::AnonymousBlock(_) => {}
1011 NodeData::Text(data) => {
1013 if matches!(style, OutputStyle::Pretty) {
1014 for _ in 0..nesting {
1015 writer.push_str(INDENT);
1016 }
1017 }
1018 writer.push_str(data.content.as_str());
1019 if matches!(style, OutputStyle::Pretty) {
1020 writer.push('\n');
1021 }
1022 }
1023 NodeData::Element(data) => {
1024 if matches!(style, OutputStyle::Pretty) {
1025 for _ in 0..nesting {
1026 writer.push_str(INDENT);
1027 }
1028 }
1029 writer.push('<');
1030 writer.push_str(&data.name.local);
1031
1032 for attr in data.attrs() {
1033 writer.push(' ');
1034 writer.push_str(&attr.name.local);
1035 writer.push_str("=\"");
1036 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
1038 let value = attr
1039 .value
1040 .replace("currentColor", current_color.as_ref().unwrap());
1041 encode_quoted_attribute_to_string(&value, writer);
1042 } else {
1043 encode_quoted_attribute_to_string(&attr.value, writer);
1044 }
1045 writer.push('"');
1046 }
1047 if !has_children {
1048 writer.push_str(" /");
1049 }
1050 writer.push('>');
1051 if matches!(style, OutputStyle::Pretty) {
1052 writer.push('\n');
1053 }
1054
1055 if has_children {
1056 for &child_id in &self.children {
1057 self.tree()[child_id].write_outer_html_in_style(writer, style, nesting + 1);
1058 }
1059
1060 if matches!(style, OutputStyle::Pretty) {
1061 for _ in 0..nesting {
1062 writer.push_str(INDENT);
1063 }
1064 }
1065 writer.push_str("</");
1066 writer.push_str(&data.name.local);
1067 writer.push('>');
1068 if matches!(style, OutputStyle::Pretty) {
1069 writer.push('\n');
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1076 pub fn attrs(&self) -> Option<&[Attribute]> {
1077 Some(&self.element_data()?.attrs)
1078 }
1079
1080 pub fn attr(&self, name: LocalName) -> Option<&str> {
1081 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1082 Some(&attr.value)
1083 }
1084
1085 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1086 self.stylo_element_data_opt()
1087 .and_then(|stylo| stylo.primary_styles())
1088 }
1089
1090 pub fn text_content(&self) -> String {
1091 let mut out = String::new();
1092 self.write_text_content(&mut out);
1093 out
1094 }
1095
1096 fn write_text_content(&self, out: &mut String) {
1097 match &self.data {
1098 NodeData::Text(data) => {
1099 out.push_str(&data.content);
1100 }
1101 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1102 for child_id in self.children.iter() {
1103 self.with(*child_id).write_text_content(out);
1104 }
1105 }
1106 _ => {}
1107 }
1108 }
1109
1110 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1111 if let NodeData::Element(ref mut elem_data) = self.data {
1112 if let Some(guard) = elem_data.guard.clone() {
1113 elem_data.flush_style_attribute(&guard, url_extra_data);
1114 }
1115 }
1116 }
1117
1118 pub fn order(&self) -> i32 {
1119 self.primary_styles().map(|s| s.clone_order()).unwrap_or(0)
1123 }
1124
1125 pub fn z_index(&self) -> i32 {
1126 self.primary_styles()
1127 .map(|s| s.clone_z_index().integer_or(0))
1128 .unwrap_or(0)
1129 }
1130
1131 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1133 let Some(style) = self.primary_styles() else {
1134 return false;
1135 };
1136
1137 let position = style.clone_position();
1138 let has_z_index = !style.clone_z_index().is_auto();
1139
1140 if style.clone_opacity() != 1.0 {
1141 return true;
1142 }
1143
1144 let position_based = match position {
1145 Position::Fixed | Position::Sticky => true,
1146 Position::Relative | Position::Absolute => has_z_index,
1147 Position::Static => has_z_index && is_flex_or_grid_item,
1148 };
1149 if position_based {
1150 return true;
1151 }
1152
1153 if self.transform().is_some() {
1154 return true;
1155 }
1156
1157 false
1165 }
1166
1167 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1176 self.hit_inner(x, y, scale, &mut None)
1177 }
1178
1179 pub(crate) fn hit_inner(
1184 &self,
1185 x: f32,
1186 y: f32,
1187 scale: f64,
1188 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1189 ) -> Option<HitResult> {
1190 use style::computed_values::pointer_events::T as PointerEvents;
1191 use style::computed_values::visibility::T as Visibility;
1192
1193 if let Some(style) = self.primary_styles() {
1195 if matches!(
1196 style.clone_visibility(),
1197 Visibility::Hidden | Visibility::Collapse
1198 ) {
1199 return None;
1200 }
1201 }
1202
1203 let pointer_events_none = self
1206 .primary_styles()
1207 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1208
1209 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1210 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1211
1212 if let Some(t) = *self.transform() {
1213 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1214 x = (p.x / scale) as f32;
1215 y = (p.y / scale) as f32;
1216 }
1217
1218 let size = self.final_layout().size;
1219 let matches_self = !(x < 0.0
1220 || x > size.width + self.scroll_offset().x as f32
1221 || y < 0.0
1222 || y > size.height + self.scroll_offset().y as f32);
1223
1224 let overflow_rect = self.final_layout().scrollable_overflow_rect;
1225 let matches_content = !(x < 0.0
1226 || x > overflow_rect.right + self.scroll_offset().x as f32
1227 || y < 0.0
1228 || y > overflow_rect.bottom + self.scroll_offset().y as f32);
1229
1230 let matches_hoisted_content = match &self.stacking_context {
1231 Some(sc) => {
1232 let content_area = sc.content_area;
1233 x >= content_area.left + self.scroll_offset().x as f32
1234 && x <= content_area.right + self.scroll_offset().x as f32
1235 && y >= content_area.top + self.scroll_offset().y as f32
1236 && y <= content_area.bottom + self.scroll_offset().y as f32
1237 }
1238 None => false,
1239 };
1240
1241 let overflow = *self.scrollable_overflow();
1244
1245 let matches_overflow = x >= (overflow.x0 / scale) as f32
1246 && x <= (overflow.x1 / scale) as f32
1247 && y >= (overflow.y0 / scale) as f32
1248 && y <= (overflow.y1 / scale) as f32;
1249
1250 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1251 return None;
1252 }
1253
1254 if matches_self
1257 && let Some(sb) = self.scrollbar_at_local(
1258 (x - self.scroll_offset().x as f32) as f64,
1259 (y - self.scroll_offset().y as f32) as f64,
1260 )
1261 {
1262 *scrollbar = Some(sb);
1263 }
1264
1265 if self.flags.is_inline_root() {
1266 let content_box_offset = taffy::Point {
1267 x: self.final_layout().padding.left + self.final_layout().border.left,
1268 y: self.final_layout().padding.top + self.final_layout().border.top,
1269 };
1270 x -= content_box_offset.x;
1271 y -= content_box_offset.y;
1272 }
1273
1274 if matches_hoisted_content {
1276 if let Some(hoisted) = &self.stacking_context {
1277 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1278 let x = x - hoisted_child.position.x;
1279 let y = y - hoisted_child.position.y;
1280 if let Some(hit) = self
1281 .with(hoisted_child.node_id)
1282 .hit_inner(x, y, scale, scrollbar)
1283 {
1284 return Some(hit);
1285 }
1286 }
1287 }
1288 }
1289
1290 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1292 if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1293 return Some(hit);
1294 }
1295 }
1296
1297 if matches_hoisted_content {
1299 if let Some(hoisted) = &self.stacking_context {
1300 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1301 let x = x - hoisted_child.position.x;
1302 let y = y - hoisted_child.position.y;
1303 if let Some(hit) = self
1304 .with(hoisted_child.node_id)
1305 .hit_inner(x, y, scale, scrollbar)
1306 {
1307 return Some(hit);
1308 }
1309 }
1310 }
1311 }
1312
1313 if self.flags.is_inline_root() {
1315 let element_data = &self.element_data().unwrap();
1316 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1317 let layout = &ild.layout;
1318 let scale = layout.scale();
1319
1320 if let Some((cluster, _side)) =
1321 Cluster::from_point_exact(layout, x * scale, y * scale)
1322 {
1323 let style_index = cluster.glyphs().next()?.style_index();
1324 let node_id = layout.styles()[style_index].brush.id;
1325 let text_pointer_events_none = self
1326 .with(node_id)
1327 .primary_styles()
1328 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1329 if !text_pointer_events_none {
1330 return Some(HitResult {
1331 node_id,
1332 x,
1333 y,
1334 is_text: true,
1335 });
1336 }
1337 }
1338 }
1339 }
1340
1341 if matches_self && !pointer_events_none {
1343 return Some(HitResult {
1344 node_id: self.id,
1345 x,
1346 y,
1347 is_text: false,
1348 });
1349 }
1350
1351 None
1352 }
1353
1354 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1357 let mut node = self;
1358 loop {
1359 if node.flags.is_inline_root() {
1360 return Some(node);
1361 }
1362 let id = node.layout_parent.get()?;
1363 node = self.with(id);
1364 }
1365 }
1366
1367 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1371 if !self.flags.is_inline_root() {
1372 return None;
1373 }
1374
1375 let element_data = self.element_data()?;
1376 let inline_layout = element_data.inline_layout_data.as_ref()?;
1377 let layout = &inline_layout.layout;
1378 let scale = layout.scale();
1379
1380 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1382
1383 let is_leading = side == ClusterSide::Left;
1388 let offset = if cluster.is_rtl() {
1389 if is_leading {
1390 cluster.text_range().end
1391 } else {
1392 cluster.text_range().start
1393 }
1394 } else {
1395 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1397 cluster.text_range().start
1398 } else {
1399 cluster.text_range().end
1400 }
1401 };
1402
1403 Some(offset)
1404 }
1405
1406 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1408 let x = x + self.final_layout().location.x - self.scroll_offset().x as f32;
1409 let y = y + self.final_layout().location.y - self.scroll_offset().y as f32;
1410
1411 self.layout_parent
1413 .get()
1414 .map(|i| self.with(i).absolute_position(x, y))
1415 .unwrap_or(crate::util::Point { x, y })
1416 }
1417
1418 fn is_offset_parent(&self) -> bool {
1421 let Some(styles) = self.primary_styles() else {
1422 return false;
1423 };
1424 if styles.get_box().position != Position::Static {
1425 return true;
1426 }
1427 self.data.is_element_with_tag_name(&local_name!("body"))
1428 || self.data.is_element_with_tag_name(&local_name!("td"))
1429 || self.data.is_element_with_tag_name(&local_name!("th"))
1430 }
1431
1432 fn is_static_body(&self) -> bool {
1436 self.data.is_element_with_tag_name(&local_name!("body"))
1437 && self
1438 .primary_styles()
1439 .is_some_and(|styles| styles.get_box().position == Position::Static)
1440 }
1441
1442 pub fn offset_parent(&self) -> Option<&Node> {
1445 let mut node = self;
1446 loop {
1447 node = self.with(node.layout_parent.get()?);
1448 if node.is_offset_parent() {
1449 return Some(node);
1450 }
1451 }
1452 }
1453
1454 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1457 let mut x = 0.0;
1458 let mut y = 0.0;
1459 let mut current = self;
1460 loop {
1461 let layout = current.final_layout();
1462 x += layout.location.x;
1463 y += layout.location.y;
1464
1465 let Some(parent_id) = current.layout_parent.get() else {
1466 break;
1467 };
1468 let parent = self.with(parent_id);
1469 if parent.is_offset_parent() && !parent.is_static_body() {
1470 let border = parent.final_layout().border;
1471 x -= border.left;
1472 y -= border.top;
1473 break;
1474 }
1475 current = parent;
1476 }
1477 crate::util::Point { x, y }
1478 }
1479
1480 pub fn client_width(&self) -> f32 {
1483 let layout = self.final_layout();
1484 layout.size.width - layout.border.left - layout.border.right - layout.scrollbar_size.width
1485 }
1486
1487 pub fn client_height(&self) -> f32 {
1490 let layout = self.final_layout();
1491 layout.size.height - layout.border.top - layout.border.bottom - layout.scrollbar_size.height
1492 }
1493
1494 pub fn scroll_width(&self) -> f32 {
1497 self.client_width()
1498 .max(self.final_layout().scrollable_overflow_rect.right)
1499 }
1500
1501 pub fn scroll_height(&self) -> f32 {
1504 self.client_height()
1505 .max(self.final_layout().scrollable_overflow_rect.bottom)
1506 }
1507
1508 pub fn has_boxes(&self) -> bool {
1512 self.flags.is_in_document()
1513 && !self.display_style().is_some_and(|display| {
1514 matches!(
1515 display.inside(),
1516 style::values::specified::box_::DisplayInside::None
1517 | style::values::specified::box_::DisplayInside::Contents
1518 )
1519 })
1520 }
1521
1522 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1524 DomEventData::Click(self.synthetic_click_event_data(mods))
1525 }
1526
1527 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1528 let absolute_position = self.absolute_position(0.0, 0.0);
1529 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1530 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1531
1532 BlitzPointerEvent {
1533 id: BlitzPointerId::Mouse,
1534 is_primary: true,
1535 coords: PointerCoords {
1536 page_x: x,
1537 page_y: y,
1538
1539 screen_x: x,
1541 screen_y: y,
1542 client_x: x,
1543 client_y: y,
1544 },
1545 mods,
1546 button: Default::default(),
1547 buttons: Default::default(),
1548 details: Default::default(),
1549 element: Default::default(),
1550 active_pointers: Default::default(),
1551 }
1552 }
1553}
1554
1555impl PartialEq for Node {
1557 fn eq(&self, other: &Self) -> bool {
1558 self.id == other.id
1559 }
1560}
1561
1562impl Eq for Node {}
1563
1564impl std::fmt::Debug for Node {
1565 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1566 f.debug_struct("NodeData")
1568 .field("parent", &self.parent)
1569 .field("id", &self.id)
1570 .field("is_inline_root", &self.flags.is_inline_root())
1571 .field("children", &self.children)
1572 .field("layout_children", &self.layout_children.borrow())
1573 .field("node", &self.data)
1575 .field("stylo_element_data", &self.stylo_element_data_opt())
1576 .finish()
1579 }
1580}
1581
1582#[cfg(test)]
1583mod test {
1584 use style_dom::ElementState;
1585
1586 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1587
1588 #[test]
1589 fn create_node_with_disabled_attr() {
1590 let mut document = BaseDocument::new(DocumentConfig::default());
1591 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1592 qual_name!("button"),
1593 vec![Attribute {
1594 name: qual_name!("disabled"),
1595 value: "".into(),
1596 }],
1597 ))));
1598 let node = document.get_node(node).unwrap();
1599
1600 assert!(
1601 node.element_state().contains(ElementState::DISABLED),
1602 "form node is disabled"
1603 );
1604 assert!(
1605 !node.element_state().contains(ElementState::ENABLED),
1606 "form node is not enabled"
1607 );
1608 }
1609
1610 #[test]
1611 fn ignore_disabled_attr_content() {
1612 let mut document = BaseDocument::new(DocumentConfig::default());
1613 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1614 qual_name!("button"),
1615 vec![Attribute {
1616 name: qual_name!("disabled"),
1617 value: "false".into(),
1618 }],
1619 ))));
1620 let node = document.get_node(node).unwrap();
1621
1622 assert!(
1623 node.element_state().contains(ElementState::DISABLED),
1624 "form node is disabled"
1625 );
1626 assert!(
1627 !node.element_state().contains(ElementState::ENABLED),
1628 "form node is not enabled"
1629 );
1630 }
1631
1632 #[test]
1633 fn create_node_with_ignored_disable() {
1634 let mut document = BaseDocument::new(DocumentConfig::default());
1635 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1636 qual_name!("a"),
1637 vec![Attribute {
1638 name: qual_name!("disabled"),
1639 value: "".into(),
1640 }],
1641 ))));
1642 let node = document.get_node(node).unwrap();
1643
1644 assert!(
1645 !node.element_state().contains(ElementState::DISABLED),
1646 "Non form node cannot be disabled"
1647 );
1648 assert!(
1649 !node.element_state().contains(ElementState::ENABLED),
1650 "Non form node cannot be enabled"
1651 );
1652 }
1653
1654 #[test]
1655 fn create_empty_enabled_node() {
1656 let mut document = BaseDocument::new(DocumentConfig::default());
1657 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1658 qual_name!("button"),
1659 vec![],
1660 ))));
1661 let node = document.get_node(node).unwrap();
1662
1663 assert!(
1664 node.element_state().contains(ElementState::ENABLED),
1665 "Button should be enabled by default"
1666 );
1667 }
1668}