1use crate::Document;
2use crate::layout::damage::HoistedPaintChildren;
3use bitflags::bitflags;
4use blitz_traits::events::{
5 BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
6};
7use blitz_traits::node_id::NodeId;
8use blitz_traits::shell::ShellProvider;
9use euclid::{Point2D, Rect, Size2D};
10use html_escape::encode_quoted_attribute_to_string;
11use keyboard_types::Modifiers;
12use kurbo::{Affine, Rect as KurboRect};
13use markup5ever::{LocalName, local_name};
14use parley::{BreakReason, Cluster, ClusterSide, Selection};
15use selectors::matching::ElementSelectorFlags;
16use std::cell::{Cell, RefCell};
17use std::fmt::Write;
18use std::ops::{Deref, Range};
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, Ordering};
21use style::Atom;
22use style::computed_values::isolation::T as Isolation;
23use style::invalidation::element::restyle_hints::RestyleHint;
24use style::properties::ComputedValues;
25use style::properties::generated::longhands::position::computed_value::T as Position;
26use style::selector_parser::{PseudoElement, RestyleDamage};
27use style::servo_arc::Arc as ServoArc;
28use style::shared_lock::SharedRwLock;
29use style::stylesheets::UrlExtraData;
30use style::values::computed::CSSPixelLength;
31use style::values::computed::Display as StyloDisplay;
32use style::values::specified::box_::{DisplayInside, DisplayOutside};
33use style_dom::ElementState;
34use style_traits::values::ToCss;
35use taffy::{
36 Cache,
37 prelude::{Layout, Style},
38};
39use thin_vec::ThinVec;
40
41use super::stylo_data::StyloData;
42use super::{Attribute, DocumentData, ElementData};
43
44#[derive(Clone, Copy)]
45enum OutputStyle {
46 Normal,
47 Pretty,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum DisplayOuter {
52 Block,
53 Inline,
54 None,
55}
56
57bitflags! {
58 #[derive(Clone, Copy, PartialEq)]
59 pub struct NodeFlags: u32 {
60 const IS_INLINE_ROOT = 0b00000001;
62 const IS_TABLE_ROOT = 0b00000010;
64 const IS_IN_DOCUMENT = 0b00000100;
66 }
67}
68
69impl NodeFlags {
70 #[inline(always)]
71 pub fn is_inline_root(&self) -> bool {
72 self.contains(Self::IS_INLINE_ROOT)
73 }
74
75 #[inline(always)]
76 pub fn is_table_root(&self) -> bool {
77 self.contains(Self::IS_TABLE_ROOT)
78 }
79
80 #[inline(always)]
81 pub fn is_in_document(&self) -> bool {
82 self.contains(Self::IS_IN_DOCUMENT)
83 }
84
85 #[inline(always)]
86 pub fn reset_construction_flags(&mut self) {
87 self.remove(Self::IS_INLINE_ROOT);
88 self.remove(Self::IS_TABLE_ROOT);
89 }
90}
91
92pub struct Node {
93 tree: *mut crate::NodeTree,
95
96 pub id: NodeId,
98 pub parent: Option<NodeId>,
100 pub children: ThinVec<NodeId>,
102 pub layout_parent: Cell<Option<NodeId>>,
104 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
106 pub anonymous_blocks: ThinVec<NodeId>,
112 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
114 pub stacking_context: Option<Box<HoistedPaintChildren>>,
115
116 #[cfg(feature = "shadow-dom")]
122 pub flattened_children: Option<Vec<NodeId>>,
123
124 pub flags: NodeFlags,
126
127 pub data: NodeData,
134}
135
136unsafe impl Send for Node {}
137unsafe impl Sync for Node {}
138
139macro_rules! universal_accessors {
142 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
143 impl Node {
144 $(
145 $(#[$meta])*
146 #[inline]
147 pub fn $field(&self) -> &$ty {
148 match &self.data {
149 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
150 NodeData::Document(data) => &data.$field,
151 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
152 }
153 }
154
155 $(#[$meta])*
156 #[inline]
157 pub fn $field_mut(&mut self) -> &mut $ty {
158 match &mut self.data {
159 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
160 NodeData::Document(data) => &mut data.$field,
161 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
162 }
163 }
164 )*
165 }
166 };
167}
168
169universal_accessors! {
170 stylo_element_data / stylo_element_data_mut: StyloData,
171 style / style_mut: Style<Atom>,
172 style_source / style_source_mut: Option<ServoArc<ComputedValues>>,
173 subtree_hoists / subtree_hoists_mut: bool,
174 unrounded_layout / unrounded_layout_mut: Layout,
178 final_layout / final_layout_mut: Layout,
179 scroll_offset / scroll_offset_mut: crate::Point<f64>,
180 scrollable_overflow / scrollable_overflow_mut: KurboRect,
181 transform / transform_mut: Option<Affine>,
182 display_constructed_as / display_constructed_as_mut: StyloDisplay,
183 element_state / element_state_mut: ElementState,
186 snapshot_handled / snapshot_handled_mut: AtomicBool,
187 selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
191}
192
193impl Node {
194 #[inline]
202 pub fn cache(&self) -> &Cache {
203 match &self.data {
204 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache(),
205 NodeData::Document(data) => data.cache(),
206 _ => panic!("`cache` is not available on this node kind"),
207 }
208 }
209
210 #[inline]
212 pub fn cache_mut(&mut self) -> &mut Cache {
213 match &mut self.data {
214 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_mut(),
215 NodeData::Document(data) => data.cache_mut(),
216 _ => panic!("`cache_mut` is not available on this node kind"),
217 }
218 }
219
220 #[inline]
222 pub fn cache_release(&mut self) {
223 match &mut self.data {
224 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_release(),
225 NodeData::Document(data) => data.cache_release(),
226 _ => {}
227 }
228 }
229
230 #[inline]
236 pub fn style_source_opt(&self) -> Option<&ServoArc<ComputedValues>> {
237 match &self.data {
238 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.style_source.as_ref(),
239 NodeData::Document(data) => data.style_source.as_ref(),
240 _ => None,
241 }
242 }
243
244 #[inline]
245 pub fn stylo_element_data_opt(&self) -> Option<&StyloData> {
246 match &self.data {
247 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
248 Some(&data.stylo_element_data)
249 }
250 NodeData::Document(data) => Some(&data.stylo_element_data),
251 _ => None,
252 }
253 }
254
255 #[inline]
256 pub fn stylo_element_data_opt_mut(&mut self) -> Option<&mut StyloData> {
257 match &mut self.data {
258 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
259 Some(&mut data.stylo_element_data)
260 }
261 NodeData::Document(data) => Some(&mut data.stylo_element_data),
262 _ => None,
263 }
264 }
265
266 #[inline]
269 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
270 match &self.data {
271 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
272 Some(&data.dirty_descendants)
273 }
274 NodeData::Document(data) => Some(&data.dirty_descendants),
275 _ => None,
276 }
277 }
278
279 #[inline]
282 pub fn guard(&self) -> &SharedRwLock {
283 let guard = match &self.data {
284 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
285 NodeData::Document(data) => data.guard.as_ref(),
286 _ => None,
287 };
288 guard.expect("`guard` is not available on this node kind")
289 }
290
291 #[inline]
292 pub fn has_snapshot(&self) -> bool {
293 match &self.data {
294 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
295 NodeData::Document(data) => data.has_snapshot,
296 _ => false,
297 }
298 }
299
300 #[inline]
301 pub fn set_has_snapshot(&mut self, value: bool) {
302 match &mut self.data {
303 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
304 NodeData::Document(data) => data.has_snapshot = value,
305 _ => {}
306 }
307 }
308
309 #[inline]
310 pub fn before(&self) -> Option<NodeId> {
311 self.element_data().and_then(|data| data.before)
312 }
313
314 #[inline]
315 pub fn after(&self) -> Option<NodeId> {
316 self.element_data().and_then(|data| data.after)
317 }
318}
319
320impl Node {
321 pub(crate) fn new(
322 tree: *mut crate::NodeTree,
323 id: NodeId,
324 guard: SharedRwLock,
325 mut data: NodeData,
326 ) -> Self {
327 match &mut data {
330 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
331 data.guard = Some(guard);
332 }
333 NodeData::Document(data) => data.guard = Some(guard),
334 _ => {}
335 }
336
337 Self {
338 tree,
339
340 id,
341 parent: None,
342 children: ThinVec::new(),
343 layout_parent: Cell::new(None),
344 layout_children: RefCell::new(None),
345 anonymous_blocks: ThinVec::new(),
346 paint_children: RefCell::new(None),
347 stacking_context: None,
348 #[cfg(feature = "shadow-dom")]
349 flattened_children: None,
350
351 flags: NodeFlags::empty(),
352 data,
353 }
354 }
355
356 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
357 let transform = self.primary_styles().and_then(|s| {
358 let size = self.final_layout().size;
359 let reference_box = Rect::new(
360 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
361 Size2D::new(
362 CSSPixelLength::new(size.width),
363 CSSPixelLength::new(size.height),
364 ),
365 );
366 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
369 let scale = scale as f64;
370 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
371 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
372 })
373 });
374
375 *self.transform_mut() = transform;
376 transform
377 }
378
379 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
380 match index {
381 0 => self.after(),
382 1 => self.before(),
383 _ => panic!("Invalid pseudo element index"),
384 }
385 }
386
387 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
388 let Some(data) = self.element_data_mut() else {
389 return;
390 };
391 match index {
392 0 => data.after = value,
393 1 => data.before = value,
394 _ => panic!("Invalid pseudo element index"),
395 }
396 }
397
398 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
399 Some(self.primary_styles().as_ref()?.clone_display())
400 }
401
402 pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
404 let style = self.primary_styles()?;
405 Some(vec![
406 ("display", style.clone_display().to_css_string()),
407 ("color", style.clone_color().to_css_string()),
408 (
409 "background-color",
410 style.clone_background_color().to_css_string(),
411 ),
412 (
413 "font-size",
414 format!("{}px", style.clone_font_size().computed_size().px()),
415 ),
416 ("width", style.clone_width().to_css_string()),
417 ])
418 }
419
420 pub fn is_display_none(&self) -> bool {
422 self.display_style()
423 .is_some_and(|display| display.is_none())
424 }
425
426 pub fn is_or_contains_block(&self) -> bool {
427 let style = self.primary_styles();
428 let style = style.as_ref();
429
430 let position = style
432 .map(|s| s.clone_position())
433 .unwrap_or(Position::Relative);
434 let is_in_flow = matches!(
435 position,
436 Position::Static | Position::Relative | Position::Sticky
437 );
438 if !is_in_flow {
439 return false;
440 }
441 let is_floating = style
444 .map(|s| s.clone_float().is_floating())
445 .unwrap_or(false);
446 if is_floating {
447 return false;
448 }
449 let display = style
450 .map(|s| s.clone_display())
451 .unwrap_or(StyloDisplay::inline());
452 match display.outside() {
453 DisplayOutside::None => false,
454 DisplayOutside::Block => true,
455 _ => {
456 if display.inside() == DisplayInside::Flow {
457 self.children
458 .iter()
459 .copied()
460 .any(|child_id| self.tree()[child_id].is_or_contains_block())
461 } else {
462 false
463 }
464 }
465 }
466 }
467
468 pub fn is_whitespace_node(&self) -> bool {
469 match &self.data {
470 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
471 _ => false,
472 }
473 }
474
475 pub fn is_focussable(&self) -> bool {
476 self.data
477 .downcast_element()
478 .map(|el| el.is_focussable)
479 .unwrap_or(false)
480 }
481
482 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
483 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
484 if let Some(mut element_data) = stylo_element_data.get_mut() {
485 element_data.hint.insert(hint);
486 }
487 }
488 self.mark_ancestors_dirty();
491 }
492
493 pub fn has_dirty_descendants(&self) -> bool {
495 self.dirty_descendants_flag()
496 .is_some_and(|flag| flag.load(Ordering::Relaxed))
497 }
498
499 pub fn set_dirty_descendants(&self) {
501 if let Some(flag) = self.dirty_descendants_flag() {
502 flag.store(true, Ordering::Relaxed);
503 }
504 }
505
506 pub fn unset_dirty_descendants(&self) {
508 if let Some(flag) = self.dirty_descendants_flag() {
509 flag.store(false, Ordering::Relaxed);
510 }
511 }
512
513 pub(crate) fn mark_style_attr_updated(&mut self) {
515 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
516 if let Some(mut data) = stylo_element_data.get_mut() {
517 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
518 }
519 }
520 self.set_dirty_descendants();
521 self.mark_ancestors_dirty();
522 }
523
524 pub fn mark_ancestors_dirty(&self) {
528 let mut current_id = self.parent;
529 while let Some(parent_id) = current_id {
530 let parent = &self.tree()[parent_id];
531 if let Some(flag) = parent.dirty_descendants_flag() {
534 if flag.swap(true, Ordering::Relaxed) {
535 break;
536 }
537 }
538 current_id = parent.parent;
539 }
540 }
541
542 pub fn damage(&self) -> Option<RestyleDamage> {
549 self.stylo_element_data_opt()
550 .and_then(|stylo| stylo.get().map(|data| data.damage))
551 }
552
553 pub fn set_damage(&mut self, damage: RestyleDamage) {
554 if let Some(stylo) = self.stylo_element_data_opt_mut() {
555 if let Some(mut data) = stylo.get_mut() {
556 data.damage = damage;
557 }
558 }
559 }
560
561 pub fn insert_damage(&mut self, damage: RestyleDamage) {
562 if let Some(stylo) = self.stylo_element_data_opt_mut() {
563 if let Some(mut data) = stylo.get_mut() {
564 data.damage |= damage;
565 }
566 }
567 }
568
569 pub fn remove_damage(&mut self, damage: RestyleDamage) {
570 if let Some(stylo) = self.stylo_element_data_opt_mut() {
571 if let Some(mut data) = stylo.get_mut() {
572 data.damage.remove(damage);
573 }
574 }
575 }
576
577 pub fn clear_damage_mut(&mut self) {
578 if let Some(stylo) = self.stylo_element_data_opt_mut() {
579 if let Some(mut data) = stylo.get_mut() {
580 data.damage = RestyleDamage::empty();
581 }
582 }
583 }
584
585 pub fn hover(&mut self) {
586 if let Some(data) = self.element_data_mut() {
587 data.element_state.insert(ElementState::HOVER);
588 }
589 self.set_restyle_hint(RestyleHint::restyle_subtree());
590 }
591
592 pub fn unhover(&mut self) {
593 if let Some(data) = self.element_data_mut() {
594 data.element_state.remove(ElementState::HOVER);
595 }
596 self.set_restyle_hint(RestyleHint::restyle_subtree());
597 }
598
599 pub fn is_hovered(&self) -> bool {
600 self.element_data()
601 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
602 }
603
604 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
605 if let Some(data) = self.element_data_mut() {
606 data.element_state
607 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
608 }
609 self.set_restyle_hint(RestyleHint::restyle_subtree());
610
611 if self
613 .element_data()
614 .and_then(|elem| elem.text_input_data())
615 .is_some()
616 {
617 shell_provider.set_ime_enabled(true);
618 let mut pos = self.absolute_position(0.0, 0.0);
619 pos.x += self.final_layout().content_box_x();
620 pos.y += self.final_layout().content_box_y();
621 let width = self.final_layout().content_box_width();
622 let height = self.final_layout().content_box_height();
623 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
624 }
625 }
626
627 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
628 if let Some(data) = self.element_data_mut() {
629 data.element_state
630 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
631 }
632 self.set_restyle_hint(RestyleHint::restyle_subtree());
633
634 if self
636 .element_data()
637 .and_then(|elem| elem.text_input_data())
638 .is_some()
639 {
640 shell_provider.set_ime_enabled(false);
641 }
642 }
643
644 pub fn is_focussed(&self) -> bool {
645 self.element_data()
646 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
647 }
648
649 pub fn active(&mut self) {
650 if let Some(data) = self.element_data_mut() {
651 data.element_state.insert(ElementState::ACTIVE);
652 }
653 self.set_restyle_hint(RestyleHint::restyle_subtree());
654 }
655
656 pub fn unactive(&mut self) {
657 if let Some(data) = self.element_data_mut() {
658 data.element_state.remove(ElementState::ACTIVE);
659 }
660 self.set_restyle_hint(RestyleHint::restyle_subtree());
661 }
662
663 pub fn is_active(&self) -> bool {
664 self.element_data()
665 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
666 }
667
668 pub fn disable(&mut self) {
671 if let Some(data) = self.element_data_mut() {
672 if data.can_be_disabled() {
673 data.element_state.insert(ElementState::DISABLED);
674 data.element_state.remove(ElementState::ENABLED);
675 }
676 }
677 self.set_restyle_hint(RestyleHint::restyle_subtree());
678 }
679
680 pub fn enable(&mut self) {
683 if let Some(data) = self.element_data_mut() {
684 if data.can_be_disabled() {
685 data.element_state.insert(ElementState::ENABLED);
686 data.element_state.remove(ElementState::DISABLED);
687 }
688 }
689 self.set_restyle_hint(RestyleHint::restyle_subtree());
690 }
691
692 pub fn subdoc(&self) -> Option<&dyn Document> {
693 self.element_data().and_then(|el| el.sub_doc_data())
694 }
695
696 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
697 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
698 }
699
700 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
701 if let Some(input_data) = self
704 .data
705 .downcast_element()
706 .and_then(|el| el.text_input_data())
707 {
708 if !input_data.is_multiline {
709 let content_box_height = self.final_layout().content_box_height();
710 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
711 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
712
713 return y_offset as f64;
714 }
715 }
716
717 0.0
718 }
719}
720
721#[derive(Debug, Clone, Copy, PartialEq)]
722pub enum NodeKind {
723 Document,
724 Element,
725 AnonymousBlock,
726 Text,
727 Comment,
728 ShadowRoot,
729}
730
731#[derive(Debug, Clone, Copy, PartialEq, Eq)]
737pub enum TextGranularity {
738 Word,
740 Line,
742}
743
744impl TextGranularity {
745 pub fn from_click_count(count: u16) -> Option<Self> {
748 match count {
749 0 | 1 => None,
750 2 => Some(Self::Word),
751 _ => Some(Self::Line),
752 }
753 }
754}
755
756#[derive(Debug, Clone, Copy, PartialEq, Eq)]
760pub enum ShadowRootMode {
761 Open,
764 Closed,
767}
768
769#[derive(Debug, Clone)]
777pub struct ShadowRootData {
778 pub host: NodeId,
780 pub mode: ShadowRootMode,
782 pub stylesheet_nodes: Vec<NodeId>,
785}
786
787impl ShadowRootData {
788 pub fn new(host: NodeId, mode: ShadowRootMode) -> Self {
789 Self {
790 host,
791 mode,
792 stylesheet_nodes: Vec::new(),
793 }
794 }
795}
796
797#[derive(Debug, Clone)]
799pub enum NodeData {
800 Document(Box<DocumentData>),
802
803 Element(Box<ElementData>),
805
806 AnonymousBlock(Box<ElementData>),
808
809 Text(TextNodeData),
811
812 Comment {
814 contents: String,
816 },
817
818 ShadowRoot(ShadowRootData),
820 }
827
828impl NodeData {
829 pub fn downcast_element(&self) -> Option<&ElementData> {
830 match self {
831 Self::Element(data) => Some(data),
832 Self::AnonymousBlock(data) => Some(data),
833 _ => None,
834 }
835 }
836
837 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
838 match self {
839 Self::Element(data) => Some(data),
840 Self::AnonymousBlock(data) => Some(data),
841 _ => None,
842 }
843 }
844
845 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
846 let Some(elem) = self.downcast_element() else {
847 return false;
848 };
849 *name == elem.name.local
850 }
851
852 pub fn attrs(&self) -> Option<&[Attribute]> {
853 Some(&self.downcast_element()?.attrs)
854 }
855
856 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
857 self.downcast_element()?.attr(name)
858 }
859
860 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
861 self.downcast_element()
862 .is_some_and(|elem| elem.has_attr(name))
863 }
864
865 pub fn kind(&self) -> NodeKind {
866 match self {
867 NodeData::Document(_) => NodeKind::Document,
868 NodeData::Element(_) => NodeKind::Element,
869 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
870 NodeData::Text(_) => NodeKind::Text,
871 NodeData::Comment { .. } => NodeKind::Comment,
872 NodeData::ShadowRoot(_) => NodeKind::ShadowRoot,
873 }
874 }
875
876 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
877 match self {
878 Self::ShadowRoot(data) => Some(data),
879 _ => None,
880 }
881 }
882
883 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
884 match self {
885 Self::ShadowRoot(data) => Some(data),
886 _ => None,
887 }
888 }
889}
890
891#[derive(Debug, Clone)]
892pub struct TextNodeData {
893 pub content: String,
895}
896
897impl TextNodeData {
898 pub fn new(content: String) -> Self {
899 Self { content }
900 }
901}
902
903impl Node {
928 pub fn tree(&self) -> &crate::NodeTree {
929 unsafe { &*self.tree }
930 }
931
932 #[track_caller]
933 pub fn with(&self, id: NodeId) -> &Node {
934 self.tree().get(id).unwrap()
935 }
936
937 pub fn print_tree(&self, level: usize) {
938 println!(
939 "{} {} {:?} {} {:?}",
940 " ".repeat(level),
941 self.id,
942 self.parent,
943 self.node_debug_str().replace('\n', ""),
944 self.children
945 );
946 for child_id in self.children.iter() {
948 let child = self.with(*child_id);
949 child.print_tree(level + 1)
950 }
951 }
952
953 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
955 self.children.iter().position(|id| *id == child_id)
956 }
957
958 pub fn child_index(&self) -> Option<usize> {
960 self.tree()[self.parent?]
961 .children
962 .iter()
963 .position(|id| *id == self.id)
964 }
965
966 pub fn forward(&self, n: usize) -> Option<&Node> {
968 let child_idx = self.child_index().unwrap_or(0);
969 self.tree()[self.parent?]
970 .children
971 .get(child_idx + n)
972 .map(|id| self.with(*id))
973 }
974
975 pub fn backward(&self, n: usize) -> Option<&Node> {
976 let child_idx = self.child_index().unwrap_or(0);
977 if child_idx < n {
978 return None;
979 }
980
981 self.tree()[self.parent?]
982 .children
983 .get(child_idx - n)
984 .map(|id| self.with(*id))
985 }
986
987 pub fn is_element(&self) -> bool {
988 matches!(self.data, NodeData::Element { .. })
989 }
990
991 pub fn is_anonymous(&self) -> bool {
992 matches!(self.data, NodeData::AnonymousBlock { .. })
993 }
994
995 pub fn is_shadow_root(&self) -> bool {
996 matches!(self.data, NodeData::ShadowRoot { .. })
997 }
998
999 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1000 self.data.shadow_root_data()
1001 }
1002
1003 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1004 self.data.shadow_root_data_mut()
1005 }
1006
1007 pub fn shadow_root_id(&self) -> Option<NodeId> {
1010 self.element_data().and_then(|el| el.shadow_root)
1011 }
1012
1013 #[cfg(feature = "shadow-dom")]
1017 pub fn layout_dom_children(&self) -> &[NodeId] {
1018 match &self.flattened_children {
1019 Some(children) => children,
1020 None => &self.children,
1021 }
1022 }
1023
1024 #[cfg(not(feature = "shadow-dom"))]
1026 #[inline(always)]
1027 pub fn layout_dom_children(&self) -> &[NodeId] {
1028 &self.children
1029 }
1030
1031 pub fn is_text_node(&self) -> bool {
1032 matches!(self.data, NodeData::Text { .. })
1033 }
1034
1035 pub fn element_data(&self) -> Option<&ElementData> {
1036 match self.data {
1037 NodeData::Element(ref data) => Some(data),
1038 NodeData::AnonymousBlock(ref data) => Some(data),
1039 _ => None,
1040 }
1041 }
1042
1043 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1044 match self.data {
1045 NodeData::Element(ref mut data) => Some(data),
1046 NodeData::AnonymousBlock(ref mut data) => Some(data),
1047 _ => None,
1048 }
1049 }
1050
1051 pub fn text_data(&self) -> Option<&TextNodeData> {
1052 match self.data {
1053 NodeData::Text(ref data) => Some(data),
1054 _ => None,
1055 }
1056 }
1057
1058 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1059 match self.data {
1060 NodeData::Text(ref mut data) => Some(data),
1061 _ => None,
1062 }
1063 }
1064
1065 pub fn node_debug_str(&self) -> String {
1066 let mut s = String::new();
1067
1068 match &self.data {
1069 NodeData::Document(_) => write!(s, "DOCUMENT"),
1070 NodeData::Text(data) => {
1072 let bytes = data.content.as_bytes();
1073 write!(
1074 s,
1075 "TEXT {}",
1076 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1077 .unwrap_or("INVALID UTF8")
1078 )
1079 }
1080 NodeData::Comment { .. } => write!(s, "COMMENT"),
1081 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1082 NodeData::ShadowRoot(data) => write!(s, "#shadow-root ({:?})", data.mode),
1083 NodeData::Element(data) => {
1084 let name = &data.name;
1085 let class = self.attr(local_name!("class")).unwrap_or("");
1086 let id = self.attr(local_name!("id")).unwrap_or("");
1087 let display = self.display_constructed_as().to_css_string();
1088 write!(s, "<{}", name.local).unwrap();
1089 if !id.is_empty() {
1090 write!(s, " #{id}").unwrap();
1091 }
1092 if !class.is_empty() {
1093 if class.contains(' ') {
1094 write!(s, " class=\"{class}\"").unwrap()
1095 } else {
1096 write!(s, " .{class}").unwrap()
1097 }
1098 }
1099 write!(s, "> ({display})")
1100 } }
1102 .unwrap();
1103 s
1104 }
1105
1106 pub fn outer_html(&self) -> String {
1114 let mut output = String::new();
1115 self.write_outer_html(&mut output);
1116 output
1117 }
1118
1119 pub fn outer_html_pretty(&self) -> String {
1135 let mut output = String::new();
1136 self.write_outer_html_pretty(&mut output);
1137 output
1138 }
1139
1140 pub fn write_outer_html(&self, writer: &mut String) {
1141 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
1142 }
1143
1144 #[cfg(feature = "svg")]
1145 pub(crate) fn write_outer_html_with_current_color(
1146 &self,
1147 writer: &mut String,
1148 current_color: &str,
1149 ) {
1150 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
1151 }
1152
1153 pub fn write_outer_html_pretty(&self, writer: &mut String) {
1154 self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
1155 }
1156
1157 fn write_outer_html_in_style(
1158 &self,
1159 writer: &mut String,
1160 style: OutputStyle,
1161 nesting: usize,
1162 current_color_override: Option<&str>,
1163 ) {
1164 const INDENT: &str = " ";
1165 let has_children = !self.children.is_empty();
1166 let computed_current_color = || {
1167 self.primary_styles()
1168 .map(|style| style.clone_color())
1169 .map(|color| crate::util::absolute_color_to_svg_css(&color))
1170 };
1171 let current_color = current_color_override
1172 .map(ToOwned::to_owned)
1173 .or_else(computed_current_color);
1174
1175 match &self.data {
1176 NodeData::Document(_) => {}
1177 NodeData::Comment { .. } => {}
1178 NodeData::AnonymousBlock(_) => {}
1179 NodeData::ShadowRoot(_) => {}
1180 NodeData::Text(data) => {
1182 if matches!(style, OutputStyle::Pretty) {
1183 for _ in 0..nesting {
1184 writer.push_str(INDENT);
1185 }
1186 }
1187 writer.push_str(data.content.as_str());
1188 if matches!(style, OutputStyle::Pretty) {
1189 writer.push('\n');
1190 }
1191 }
1192 NodeData::Element(data) => {
1193 if matches!(style, OutputStyle::Pretty) {
1194 for _ in 0..nesting {
1195 writer.push_str(INDENT);
1196 }
1197 }
1198 writer.push('<');
1199 writer.push_str(&data.name.local);
1200
1201 for attr in data.attrs() {
1202 writer.push(' ');
1203 writer.push_str(&attr.name.local);
1204 writer.push_str("=\"");
1205 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
1207 let value = attr
1208 .value
1209 .replace("currentColor", current_color.as_ref().unwrap());
1210 encode_quoted_attribute_to_string(&value, writer);
1211 } else {
1212 encode_quoted_attribute_to_string(&attr.value, writer);
1213 }
1214 writer.push('"');
1215 }
1216 if !has_children {
1217 writer.push_str(" /");
1218 }
1219 writer.push('>');
1220 if matches!(style, OutputStyle::Pretty) {
1221 writer.push('\n');
1222 }
1223
1224 if has_children {
1225 for &child_id in &self.children {
1226 self.tree()[child_id].write_outer_html_in_style(
1227 writer,
1228 style,
1229 nesting + 1,
1230 current_color_override,
1231 );
1232 }
1233
1234 if matches!(style, OutputStyle::Pretty) {
1235 for _ in 0..nesting {
1236 writer.push_str(INDENT);
1237 }
1238 }
1239 writer.push_str("</");
1240 writer.push_str(&data.name.local);
1241 writer.push('>');
1242 if matches!(style, OutputStyle::Pretty) {
1243 writer.push('\n');
1244 }
1245 }
1246 }
1247 }
1248 }
1249
1250 pub fn attrs(&self) -> Option<&[Attribute]> {
1251 Some(&self.element_data()?.attrs)
1252 }
1253
1254 pub fn attr(&self, name: LocalName) -> Option<&str> {
1255 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1256 Some(&attr.value)
1257 }
1258
1259 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1260 self.stylo_element_data_opt()
1261 .and_then(|stylo| stylo.primary_styles())
1262 }
1263
1264 pub fn text_content(&self) -> String {
1265 let mut out = String::new();
1266 self.write_text_content(&mut out);
1267 out
1268 }
1269
1270 pub fn write_text_content<W: Write>(&self, out: &mut W) {
1283 match &self.data {
1284 NodeData::Text(data) => {
1285 let _ = out.write_str(&data.content);
1286 }
1287 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1288 for child_id in self.children.iter() {
1289 self.with(*child_id).write_text_content(out);
1290 }
1291 }
1292 _ => {}
1293 }
1294 }
1295
1296 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1297 if let NodeData::Element(ref mut elem_data) = self.data {
1298 if let Some(guard) = elem_data.guard.clone() {
1299 elem_data.flush_style_attribute(&guard, url_extra_data);
1300 }
1301 }
1302 }
1303
1304 pub fn order(&self) -> i32 {
1305 self.primary_styles()
1306 .map(|s| match s.pseudo() {
1307 Some(PseudoElement::Before) => i32::MIN,
1308 Some(PseudoElement::After) => i32::MAX,
1309 _ => s.clone_order(),
1310 })
1311 .unwrap_or(0)
1312 }
1313
1314 pub fn z_index(&self) -> i32 {
1315 self.primary_styles()
1316 .map(|s| s.clone_z_index().integer_or(0))
1317 .unwrap_or(0)
1318 }
1319
1320 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1322 let Some(style) = self.primary_styles() else {
1323 return false;
1324 };
1325
1326 let position = style.clone_position();
1327 let has_z_index = !style.clone_z_index().is_auto();
1328
1329 if style.clone_opacity() != 1.0 {
1330 return true;
1331 }
1332
1333 let position_based = match position {
1334 Position::Fixed | Position::Sticky => true,
1335 Position::Relative | Position::Absolute => has_z_index,
1336 Position::Static => has_z_index && is_flex_or_grid_item,
1337 };
1338 if position_based {
1339 return true;
1340 }
1341
1342 if self.transform().is_some() {
1343 return true;
1344 }
1345
1346 if style.get_box().isolation == Isolation::Isolate {
1351 return true;
1352 }
1353
1354 false
1361 }
1362
1363 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1372 self.hit_inner(x, y, scale, &mut None)
1373 }
1374
1375 pub(crate) fn hit_inner(
1380 &self,
1381 x: f32,
1382 y: f32,
1383 scale: f64,
1384 scrollbar: &mut Option<crate::node::ScrollbarRef>,
1385 ) -> Option<HitResult> {
1386 use style::computed_values::pointer_events::T as PointerEvents;
1387 use style::computed_values::visibility::T as Visibility;
1388
1389 if matches!(self.style().display, taffy::Display::None) {
1398 return None;
1399 }
1400
1401 if let Some(style) = self.primary_styles() {
1403 if matches!(
1404 style.clone_visibility(),
1405 Visibility::Hidden | Visibility::Collapse
1406 ) {
1407 return None;
1408 }
1409 }
1410
1411 let pointer_events_none = self
1414 .primary_styles()
1415 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1416
1417 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1418 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1419
1420 if let Some(t) = *self.transform() {
1421 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1422 x = (p.x / scale) as f32;
1423 y = (p.y / scale) as f32;
1424 }
1425
1426 let size = self.final_layout().size;
1427 let matches_self = !(x < 0.0
1428 || x > size.width + self.scroll_offset().x as f32
1429 || y < 0.0
1430 || y > size.height + self.scroll_offset().y as f32);
1431
1432 let content_size = self.final_layout().content_size;
1433 let matches_content = !(x < 0.0
1434 || x > content_size.width + self.scroll_offset().x as f32
1435 || y < 0.0
1436 || y > content_size.height + self.scroll_offset().y as f32);
1437
1438 let matches_hoisted_content = match &self.stacking_context {
1439 Some(sc) => {
1440 let content_area = sc.content_area;
1441 x >= content_area.left + self.scroll_offset().x as f32
1442 && x <= content_area.right + self.scroll_offset().x as f32
1443 && y >= content_area.top + self.scroll_offset().y as f32
1444 && y <= content_area.bottom + self.scroll_offset().y as f32
1445 }
1446 None => false,
1447 };
1448
1449 let overflow = *self.scrollable_overflow();
1452
1453 let matches_overflow = x >= (overflow.x0 / scale) as f32
1454 && x <= (overflow.x1 / scale) as f32
1455 && y >= (overflow.y0 / scale) as f32
1456 && y <= (overflow.y1 / scale) as f32;
1457
1458 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1459 return None;
1460 }
1461
1462 if matches_self
1465 && let Some(sb) = self.scrollbar_at_local(
1466 (x - self.scroll_offset().x as f32) as f64,
1467 (y - self.scroll_offset().y as f32) as f64,
1468 )
1469 {
1470 *scrollbar = Some(sb);
1471 }
1472
1473 if self.flags.is_inline_root() {
1474 let content_box_offset = taffy::Point {
1475 x: self.final_layout().padding.left + self.final_layout().border.left,
1476 y: self.final_layout().padding.top + self.final_layout().border.top,
1477 };
1478 x -= content_box_offset.x;
1479 y -= content_box_offset.y;
1480 }
1481
1482 if matches_hoisted_content {
1484 if let Some(hoisted) = &self.stacking_context {
1485 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1486 let x = x - hoisted_child.position.x;
1487 let y = y - hoisted_child.position.y;
1488 if let Some(hit) = self
1489 .with(hoisted_child.node_id)
1490 .hit_inner(x, y, scale, scrollbar)
1491 {
1492 return Some(hit);
1493 }
1494 }
1495 }
1496 }
1497
1498 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1500 if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1501 return Some(hit);
1502 }
1503 }
1504
1505 if matches_hoisted_content {
1507 if let Some(hoisted) = &self.stacking_context {
1508 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1509 let x = x - hoisted_child.position.x;
1510 let y = y - hoisted_child.position.y;
1511 if let Some(hit) = self
1512 .with(hoisted_child.node_id)
1513 .hit_inner(x, y, scale, scrollbar)
1514 {
1515 return Some(hit);
1516 }
1517 }
1518 }
1519 }
1520
1521 if self.flags.is_inline_root() {
1523 let element_data = &self.element_data().unwrap();
1524 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1525 let layout = &ild.layout;
1526 let scale = layout.scale();
1527
1528 if let Some((cluster, _side)) =
1529 Cluster::from_point_exact(layout, x * scale, y * scale)
1530 {
1531 let style_index = cluster.glyphs().next()?.style_index();
1532 let node_id = layout.styles()[style_index].brush.id;
1533 let text_pointer_events_none = self
1534 .with(node_id)
1535 .primary_styles()
1536 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1537 if !text_pointer_events_none {
1538 return Some(HitResult {
1539 node_id,
1540 x,
1541 y,
1542 is_text: true,
1543 });
1544 }
1545 }
1546 }
1547 }
1548
1549 if matches_self && !pointer_events_none {
1551 return Some(HitResult {
1552 node_id: self.id,
1553 x,
1554 y,
1555 is_text: false,
1556 });
1557 }
1558
1559 None
1560 }
1561
1562 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1565 let mut node = self;
1566 loop {
1567 if node.flags.is_inline_root() {
1568 return Some(node);
1569 }
1570 let id = node.layout_parent.get()?;
1571 node = self.with(id);
1572 }
1573 }
1574
1575 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1579 if !self.flags.is_inline_root() {
1580 return None;
1581 }
1582
1583 let element_data = self.element_data()?;
1584 let inline_layout = element_data.inline_layout_data.as_ref()?;
1585 let layout = &inline_layout.layout;
1586 let scale = layout.scale();
1587
1588 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1590
1591 let is_leading = side == ClusterSide::Left;
1596 let offset = if cluster.is_rtl() {
1597 if is_leading {
1598 cluster.text_range().end
1599 } else {
1600 cluster.text_range().start
1601 }
1602 } else {
1603 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1605 cluster.text_range().start
1606 } else {
1607 cluster.text_range().end
1608 }
1609 };
1610
1611 Some(offset)
1612 }
1613
1614 pub fn text_range_at_point(
1624 &self,
1625 x: f32,
1626 y: f32,
1627 granularity: TextGranularity,
1628 ) -> Option<Range<usize>> {
1629 if !self.flags.is_inline_root() {
1630 return None;
1631 }
1632
1633 let element_data = self.element_data()?;
1634 let inline_layout = element_data.inline_layout_data.as_ref()?;
1635 let layout = &inline_layout.layout;
1636 let scale = layout.scale();
1637 let (x, y) = (x * scale, y * scale);
1638
1639 Cluster::from_point(layout, x, y)?;
1644
1645 let selection = match granularity {
1646 TextGranularity::Word => Selection::word_from_point(layout, x, y),
1647 TextGranularity::Line => Selection::hard_line_from_point(layout, x, y),
1651 };
1652
1653 let range = selection.text_range();
1654 if range.is_empty() { None } else { Some(range) }
1655 }
1656
1657 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1659 let x = x + self.final_layout().location.x;
1662 let y = y + self.final_layout().location.y;
1663
1664 self.layout_parent
1666 .get()
1667 .and_then(|id| self.tree().get(id))
1668 .map(|parent| {
1669 parent.absolute_position(
1670 x - parent.scroll_offset().x as f32,
1671 y - parent.scroll_offset().y as f32,
1672 )
1673 })
1674 .unwrap_or(crate::util::Point { x, y })
1675 }
1676
1677 fn is_offset_parent(&self) -> bool {
1680 let Some(styles) = self.primary_styles() else {
1681 return false;
1682 };
1683 if styles.get_box().position != Position::Static {
1684 return true;
1685 }
1686 self.data.is_element_with_tag_name(&local_name!("body"))
1687 || self.data.is_element_with_tag_name(&local_name!("td"))
1688 || self.data.is_element_with_tag_name(&local_name!("th"))
1689 }
1690
1691 pub fn offset_parent(&self) -> Option<&Node> {
1694 let mut node = self;
1695 loop {
1696 node = self.with(node.layout_parent.get()?);
1697 if node.is_offset_parent() {
1698 return Some(node);
1699 }
1700 }
1701 }
1702
1703 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1706 let mut x = 0.0;
1707 let mut y = 0.0;
1708 let mut current = self;
1709 loop {
1710 let layout = current.final_layout();
1711 x += layout.location.x;
1712 y += layout.location.y;
1713
1714 let Some(parent_id) = current.layout_parent.get() else {
1715 break;
1716 };
1717 let parent = self.with(parent_id);
1718 if parent.is_offset_parent() {
1719 let border = parent.final_layout().border;
1720 x -= border.left;
1721 y -= border.top;
1722 break;
1723 }
1724 current = parent;
1725 }
1726 crate::util::Point { x, y }
1727 }
1728
1729 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1731 DomEventData::Click(self.synthetic_click_event_data(mods))
1732 }
1733
1734 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1735 let absolute_position = self.absolute_position(0.0, 0.0);
1736 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1737 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1738
1739 BlitzPointerEvent {
1740 id: BlitzPointerId::Mouse,
1741 is_primary: true,
1742 coords: PointerCoords {
1743 page_x: x,
1744 page_y: y,
1745
1746 screen_x: x,
1748 screen_y: y,
1749 client_x: x,
1750 client_y: y,
1751 },
1752 mods,
1753 button: Default::default(),
1754 buttons: Default::default(),
1755 details: Default::default(),
1756 element: Default::default(),
1757 active_pointers: Default::default(),
1758 }
1759 }
1760}
1761
1762impl PartialEq for Node {
1764 fn eq(&self, other: &Self) -> bool {
1765 self.id == other.id
1766 }
1767}
1768
1769impl Eq for Node {}
1770
1771impl std::fmt::Debug for Node {
1772 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1773 f.debug_struct("NodeData")
1775 .field("parent", &self.parent)
1776 .field("id", &self.id)
1777 .field("is_inline_root", &self.flags.is_inline_root())
1778 .field("children", &self.children)
1779 .field("layout_children", &self.layout_children.borrow())
1780 .field("node", &self.data)
1782 .field("stylo_element_data", &self.stylo_element_data_opt())
1783 .finish()
1786 }
1787}
1788
1789#[cfg(test)]
1790mod test {
1791 use style_dom::ElementState;
1792
1793 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1794
1795 #[test]
1796 fn absolute_position_tolerates_a_detached_layout_parent() {
1797 let mut document = BaseDocument::new(DocumentConfig::default());
1798 let parent = document.create_node(NodeData::Element(Box::new(ElementData::new(
1799 qual_name!("section"),
1800 vec![],
1801 ))));
1802 let child = document.create_node(NodeData::Element(Box::new(ElementData::new(
1803 qual_name!("button"),
1804 vec![],
1805 ))));
1806 document
1807 .get_node(child)
1808 .unwrap()
1809 .layout_parent
1810 .set(Some(parent));
1811
1812 document.remove_node_from_tree(parent);
1817
1818 assert_eq!(
1819 document
1820 .get_node(child)
1821 .unwrap()
1822 .absolute_position(4.0, 7.0),
1823 crate::util::Point { x: 4.0, y: 7.0 },
1824 );
1825 }
1826
1827 #[test]
1828 fn create_node_with_disabled_attr() {
1829 let mut document = BaseDocument::new(DocumentConfig::default());
1830 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1831 qual_name!("button"),
1832 vec![Attribute {
1833 name: qual_name!("disabled"),
1834 value: "".into(),
1835 }],
1836 ))));
1837 let node = document.get_node(node).unwrap();
1838
1839 assert!(
1840 node.element_state().contains(ElementState::DISABLED),
1841 "form node is disabled"
1842 );
1843 assert!(
1844 !node.element_state().contains(ElementState::ENABLED),
1845 "form node is not enabled"
1846 );
1847 }
1848
1849 #[test]
1850 fn ignore_disabled_attr_content() {
1851 let mut document = BaseDocument::new(DocumentConfig::default());
1852 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1853 qual_name!("button"),
1854 vec![Attribute {
1855 name: qual_name!("disabled"),
1856 value: "false".into(),
1857 }],
1858 ))));
1859 let node = document.get_node(node).unwrap();
1860
1861 assert!(
1862 node.element_state().contains(ElementState::DISABLED),
1863 "form node is disabled"
1864 );
1865 assert!(
1866 !node.element_state().contains(ElementState::ENABLED),
1867 "form node is not enabled"
1868 );
1869 }
1870
1871 #[test]
1872 fn create_node_with_ignored_disable() {
1873 let mut document = BaseDocument::new(DocumentConfig::default());
1874 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1875 qual_name!("a"),
1876 vec![Attribute {
1877 name: qual_name!("disabled"),
1878 value: "".into(),
1879 }],
1880 ))));
1881 let node = document.get_node(node).unwrap();
1882
1883 assert!(
1884 !node.element_state().contains(ElementState::DISABLED),
1885 "Non form node cannot be disabled"
1886 );
1887 assert!(
1888 !node.element_state().contains(ElementState::ENABLED),
1889 "Non form node cannot be enabled"
1890 );
1891 }
1892
1893 #[test]
1894 fn create_empty_enabled_node() {
1895 let mut document = BaseDocument::new(DocumentConfig::default());
1896 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1897 qual_name!("button"),
1898 vec![],
1899 ))));
1900 let node = document.get_node(node).unwrap();
1901
1902 assert!(
1903 node.element_state().contains(ElementState::ENABLED),
1904 "Button should be enabled by default"
1905 );
1906 }
1907}