1use crate::Document;
2use crate::layout::cache::Cache;
3use crate::layout::damage::HoistedPaintChildren;
4use bitflags::bitflags;
5use blitz_traits::events::{
6 BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
7};
8use blitz_traits::node_id::NodeId;
9use blitz_traits::shell::ShellProvider;
10use euclid::{Point2D, Rect, Size2D};
11use html_escape::encode_quoted_attribute_to_string;
12use keyboard_types::Modifiers;
13use kurbo::{Affine, Rect as KurboRect};
14use markup5ever::{LocalName, local_name};
15use parley::{BreakReason, Cluster, ClusterSide, Selection};
16use selectors::matching::ElementSelectorFlags;
17use std::cell::{Cell, RefCell};
18use std::fmt::Write;
19use std::ops::{Deref, Range};
20use std::sync::Arc;
21use std::sync::atomic::{AtomicBool, Ordering};
22use style::Atom;
23use style::computed_values::isolation::T as Isolation;
24use style::invalidation::element::restyle_hints::RestyleHint;
25use style::properties::ComputedValues;
26use style::properties::generated::longhands::position::computed_value::T as Position;
27use style::selector_parser::{PseudoElement, RestyleDamage};
28use style::servo_arc::Arc as ServoArc;
29use style::shared_lock::SharedRwLock;
30use style::stylesheets::UrlExtraData;
31use style::values::computed::CSSPixelLength;
32use style::values::computed::Display as StyloDisplay;
33use style::values::computed::Rotate;
34use style::values::generics::transform::{Scale, Translate};
35use style::values::specified::box_::{DisplayInside, DisplayOutside};
36use style_dom::ElementState;
37use style_traits::values::ToCss;
38use taffy::prelude::{Layout, Style};
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 const IS_PARSER_INERT_SCRIPT = 0b00001000;
68 }
69}
70
71impl NodeFlags {
72 #[inline(always)]
73 pub fn is_inline_root(&self) -> bool {
74 self.contains(Self::IS_INLINE_ROOT)
75 }
76
77 #[inline(always)]
78 pub fn is_table_root(&self) -> bool {
79 self.contains(Self::IS_TABLE_ROOT)
80 }
81
82 #[inline(always)]
83 pub fn is_in_document(&self) -> bool {
84 self.contains(Self::IS_IN_DOCUMENT)
85 }
86
87 #[inline(always)]
88 pub fn reset_construction_flags(&mut self) {
89 self.remove(Self::IS_INLINE_ROOT);
90 self.remove(Self::IS_TABLE_ROOT);
91 }
92}
93
94pub struct Node {
95 tree: *mut crate::NodeTree,
97
98 pub id: NodeId,
100 pub parent: Option<NodeId>,
102 child_index_hint: Cell<usize>,
108 pub owner_document: Option<NodeId>,
111 pub markup: Option<Arc<MarkupNode>>,
113 pub custom_element_subtree_count: usize,
115 pub children: ThinVec<NodeId>,
117 pub layout_parent: Cell<Option<NodeId>>,
119 pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
121 pub anonymous_blocks: ThinVec<NodeId>,
127 pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
129 pub stacking_context: Option<Box<HoistedPaintChildren>>,
130
131 #[cfg(feature = "shadow-dom")]
137 pub flattened_children: Option<Vec<NodeId>>,
138 #[cfg(feature = "shadow-dom")]
140 pub assigned_slot: Option<NodeId>,
141 #[cfg(feature = "shadow-dom")]
143 pub assigned_nodes: Option<Vec<NodeId>>,
144 pub(crate) children_to_process: std::sync::atomic::AtomicIsize,
145
146 pub flags: NodeFlags,
148
149 pub data: NodeData,
156}
157
158unsafe impl Send for Node {}
159unsafe impl Sync for Node {}
160
161macro_rules! universal_accessors {
164 ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
165 impl Node {
166 $(
167 $(#[$meta])*
168 #[inline]
169 pub fn $field(&self) -> &$ty {
170 match &self.data {
171 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
172 NodeData::Document(data) => &data.$field,
173 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
174 }
175 }
176
177 $(#[$meta])*
178 #[inline]
179 pub fn $field_mut(&mut self) -> &mut $ty {
180 match &mut self.data {
181 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
182 NodeData::Document(data) => &mut data.$field,
183 _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
184 }
185 }
186 )*
187 }
188 };
189}
190
191universal_accessors! {
192 stylo_element_data / stylo_element_data_mut: StyloData,
193 style / style_mut: Style<Atom>,
194 style_source / style_source_mut: Option<ServoArc<ComputedValues>>,
195 subtree_hoists / subtree_hoists_mut: bool,
196 unrounded_layout / unrounded_layout_mut: Layout,
200 final_layout / final_layout_mut: Layout,
201 scroll_offset / scroll_offset_mut: crate::Point<f64>,
202 scrollable_overflow / scrollable_overflow_mut: KurboRect,
203 transform / transform_mut: Option<Affine>,
204 display_constructed_as / display_constructed_as_mut: StyloDisplay,
205 element_state / element_state_mut: ElementState,
208 snapshot_handled / snapshot_handled_mut: AtomicBool,
209 }
214
215impl Node {
216 pub fn selector_flags(&self) -> &Cell<ElementSelectorFlags> {
217 match &self.data {
218 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.selector_flags,
219 NodeData::Document(data) => &data.selector_flags,
220 NodeData::ShadowRoot(data) => &data.selector_flags,
221 _ => panic!("selector flags require a parent node"),
222 }
223 }
224
225 pub fn selector_flags_mut(&mut self) -> &mut Cell<ElementSelectorFlags> {
226 match &mut self.data {
227 NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.selector_flags,
228 NodeData::Document(data) => &mut data.selector_flags,
229 NodeData::ShadowRoot(data) => &mut data.selector_flags,
230 _ => panic!("selector flags require a parent node"),
231 }
232 }
233
234 #[inline]
242 pub fn cache(&self) -> &Cache {
243 match &self.data {
244 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache(),
245 NodeData::Document(data) => data.cache(),
246 _ => panic!("`cache` is not available on this node kind"),
247 }
248 }
249
250 #[inline]
252 pub fn cache_mut(&mut self) -> &mut Cache {
253 match &mut self.data {
254 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_mut(),
255 NodeData::Document(data) => data.cache_mut(),
256 _ => panic!("`cache_mut` is not available on this node kind"),
257 }
258 }
259
260 #[inline]
262 pub fn cache_release(&mut self) {
263 match &mut self.data {
264 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_release(),
265 NodeData::Document(data) => data.cache_release(),
266 _ => {}
267 }
268 }
269
270 #[inline]
276 pub fn style_source_opt(&self) -> Option<&ServoArc<ComputedValues>> {
277 match &self.data {
278 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.style_source.as_ref(),
279 NodeData::Document(data) => data.style_source.as_ref(),
280 _ => None,
281 }
282 }
283
284 #[inline]
285 pub fn stylo_element_data_opt(&self) -> Option<&StyloData> {
286 match &self.data {
287 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
288 Some(&data.stylo_element_data)
289 }
290 NodeData::Document(data) => Some(&data.stylo_element_data),
291 _ => None,
292 }
293 }
294
295 #[inline]
296 pub fn stylo_element_data_opt_mut(&mut self) -> Option<&mut StyloData> {
297 match &mut self.data {
298 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
299 Some(&mut data.stylo_element_data)
300 }
301 NodeData::Document(data) => Some(&mut data.stylo_element_data),
302 _ => None,
303 }
304 }
305
306 #[inline]
309 fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
310 match &self.data {
311 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
312 Some(&data.dirty_descendants)
313 }
314 NodeData::Document(data) => Some(&data.dirty_descendants),
315 _ => None,
316 }
317 }
318
319 #[inline]
322 pub fn guard(&self) -> &SharedRwLock {
323 let guard = match &self.data {
324 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
325 NodeData::Document(data) => data.guard.as_ref(),
326 _ => None,
327 };
328 guard.expect("`guard` is not available on this node kind")
329 }
330
331 #[inline]
332 pub fn has_snapshot(&self) -> bool {
333 match &self.data {
334 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
335 NodeData::Document(data) => data.has_snapshot,
336 _ => false,
337 }
338 }
339
340 #[inline]
341 pub fn set_has_snapshot(&mut self, value: bool) {
342 match &mut self.data {
343 NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
344 NodeData::Document(data) => data.has_snapshot = value,
345 _ => {}
346 }
347 }
348
349 #[inline]
350 pub fn before(&self) -> Option<NodeId> {
351 self.element_data().and_then(|data| data.before)
352 }
353
354 #[inline]
355 pub fn after(&self) -> Option<NodeId> {
356 self.element_data().and_then(|data| data.after)
357 }
358}
359
360impl Node {
361 pub(crate) fn new(
362 tree: *mut crate::NodeTree,
363 id: NodeId,
364 guard: SharedRwLock,
365 mut data: NodeData,
366 ) -> Self {
367 match &mut data {
370 NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
371 data.guard = Some(guard);
372 }
373 NodeData::Document(data) => data.guard = Some(guard),
374 _ => {}
375 }
376
377 Self {
378 tree,
379
380 id,
381 parent: None,
382 child_index_hint: Cell::new(0),
383 owner_document: None,
384 markup: None,
385 custom_element_subtree_count: 0,
386 children: ThinVec::new(),
387 layout_parent: Cell::new(None),
388 layout_children: RefCell::new(None),
389 anonymous_blocks: ThinVec::new(),
390 paint_children: RefCell::new(None),
391 stacking_context: None,
392 #[cfg(feature = "shadow-dom")]
393 flattened_children: None,
394 #[cfg(feature = "shadow-dom")]
395 assigned_slot: None,
396 #[cfg(feature = "shadow-dom")]
397 assigned_nodes: None,
398 children_to_process: std::sync::atomic::AtomicIsize::new(0),
399
400 flags: NodeFlags::empty(),
401 data,
402 }
403 }
404
405 pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
406 let transform = self.primary_styles().and_then(|s| {
407 let size = self.final_layout().size;
408 let reference_box = Rect::new(
409 Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
410 Size2D::new(
411 CSSPixelLength::new(size.width),
412 CSSPixelLength::new(size.height),
413 ),
414 );
415 crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
418 let scale = scale as f64;
419 let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
420 Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
421 })
422 });
423
424 *self.transform_mut() = transform;
425 transform
426 }
427
428 pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
429 match index {
430 0 => self.after(),
431 1 => self.before(),
432 _ => panic!("Invalid pseudo element index"),
433 }
434 }
435
436 pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
437 let Some(data) = self.element_data_mut() else {
438 return;
439 };
440 match index {
441 0 => data.after = value,
442 1 => data.before = value,
443 _ => panic!("Invalid pseudo element index"),
444 }
445 }
446
447 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
448 Some(self.primary_styles().as_ref()?.clone_display())
449 }
450
451 pub fn computed_style_properties(&self) -> Option<Vec<(&'static str, String)>> {
468 let style = self.primary_styles()?;
469 let layout = *self.final_layout();
470 let (used_width, used_height) = if matches!(
471 style.clone_box_sizing(),
472 style::computed_values::box_sizing::T::BorderBox
473 ) {
474 (layout.size.width, layout.size.height)
475 } else {
476 (
477 (layout.size.width
478 - layout.padding.left
479 - layout.padding.right
480 - layout.border.left
481 - layout.border.right)
482 .max(0.0),
483 (layout.size.height
484 - layout.padding.top
485 - layout.padding.bottom
486 - layout.border.top
487 - layout.border.bottom)
488 .max(0.0),
489 )
490 };
491 Some(vec![
492 ("display", style.clone_display().to_css_string()),
493 ("position", style.clone_position().to_css_string()),
494 ("visibility", style.clone_visibility().to_css_string()),
495 ("opacity", style.clone_opacity().to_css_string()),
496 ("color", style.clone_color().to_css_string()),
497 (
498 "background-color",
499 style.clone_background_color().to_css_string(),
500 ),
501 (
502 "font-size",
503 format!("{}px", style.clone_font_size().computed_size().px()),
504 ),
505 ("font-weight", style.clone_font_weight().to_css_string()),
506 ("font-style", style.clone_font_style().to_css_string()),
507 ("font-family", style.clone_font_family().to_css_string()),
508 ("text-align", style.clone_text_align().to_css_string()),
509 ("overflow-x", style.clone_overflow_x().to_css_string()),
510 ("overflow-y", style.clone_overflow_y().to_css_string()),
511 ("z-index", style.clone_z_index().to_css_string()),
512 ("box-sizing", style.clone_box_sizing().to_css_string()),
513 (
514 "flex-direction",
515 style.clone_flex_direction().to_css_string(),
516 ),
517 (
518 "justify-content",
519 style.clone_justify_content().to_css_string(),
520 ),
521 ("align-items", style.clone_align_items().to_css_string()),
522 ("width", format!("{}px", used_width)),
531 ("height", format!("{}px", used_height)),
532 ("padding-top", format!("{}px", layout.padding.top)),
538 ("padding-right", format!("{}px", layout.padding.right)),
539 ("padding-bottom", format!("{}px", layout.padding.bottom)),
540 ("padding-left", format!("{}px", layout.padding.left)),
541 ("border-top-width", format!("{}px", layout.border.top)),
542 ("border-right-width", format!("{}px", layout.border.right)),
543 ("border-bottom-width", format!("{}px", layout.border.bottom)),
544 ("border-left-width", format!("{}px", layout.border.left)),
545 ("margin-top", format!("{}px", layout.margin.top)),
546 ("margin-right", format!("{}px", layout.margin.right)),
547 ("margin-bottom", format!("{}px", layout.margin.bottom)),
548 ("margin-left", format!("{}px", layout.margin.left)),
549 ])
550 }
551
552 pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
553 let style = self.primary_styles()?;
554 Some(vec![
555 ("display", style.clone_display().to_css_string()),
556 ("color", style.clone_color().to_css_string()),
557 (
558 "background-color",
559 style.clone_background_color().to_css_string(),
560 ),
561 (
562 "font-size",
563 format!("{}px", style.clone_font_size().computed_size().px()),
564 ),
565 ("width", style.clone_width().to_css_string()),
566 ])
567 }
568
569 pub fn is_display_none(&self) -> bool {
571 self.display_style()
572 .is_some_and(|display| display.is_none())
573 }
574
575 pub fn is_or_contains_block(&self) -> bool {
576 let style = self.primary_styles();
577 let style = style.as_ref();
578
579 let position = style
581 .map(|s| s.clone_position())
582 .unwrap_or(Position::Relative);
583 let is_in_flow = matches!(
584 position,
585 Position::Static | Position::Relative | Position::Sticky
586 );
587 if !is_in_flow {
588 return false;
589 }
590 let is_floating = style
593 .map(|s| s.clone_float().is_floating())
594 .unwrap_or(false);
595 if is_floating {
596 return false;
597 }
598 let display = style
599 .map(|s| s.clone_display())
600 .unwrap_or(StyloDisplay::inline());
601 match display.outside() {
602 DisplayOutside::None => false,
603 DisplayOutside::Block => true,
604 _ => {
605 if display.inside() == DisplayInside::Flow {
606 self.children
607 .iter()
608 .copied()
609 .any(|child_id| self.tree()[child_id].is_or_contains_block())
610 } else {
611 false
612 }
613 }
614 }
615 }
616
617 pub fn is_whitespace_node(&self) -> bool {
618 match &self.data {
619 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
620 _ => false,
621 }
622 }
623
624 pub fn is_focussable(&self) -> bool {
625 self.data
626 .downcast_element()
627 .map(|el| el.is_focussable)
628 .unwrap_or(false)
629 }
630
631 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
632 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
633 let mut element_data = stylo_element_data.ensure_init_mut();
637 element_data.hint.insert(hint);
638 }
639 self.mark_ancestors_dirty();
642 }
643
644 pub fn has_dirty_descendants(&self) -> bool {
646 self.dirty_descendants_flag()
647 .is_some_and(|flag| flag.load(Ordering::Relaxed))
648 }
649
650 pub fn set_dirty_descendants(&self) {
652 if let Some(flag) = self.dirty_descendants_flag() {
653 flag.store(true, Ordering::Relaxed);
654 }
655 }
656
657 pub fn unset_dirty_descendants(&self) {
659 if let Some(flag) = self.dirty_descendants_flag() {
660 flag.store(false, Ordering::Relaxed);
661 }
662 }
663
664 pub(crate) fn mark_style_attr_updated(&mut self) {
666 if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
667 if let Some(mut data) = stylo_element_data.get_mut() {
668 data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
669 }
670 }
671 self.set_dirty_descendants();
672 self.mark_ancestors_dirty();
673 }
674
675 pub fn mark_ancestors_dirty(&self) {
679 let mut current = self
682 .shadow_root_data()
683 .map(|root| self.with(root.host))
684 .or_else(|| self.flattened_parent());
685
686 while let Some(parent) = current {
687 if let Some(flag) = parent.dirty_descendants_flag() {
688 flag.store(true, Ordering::Relaxed);
692 }
693 current = parent.flattened_parent();
694 }
695 }
696
697 pub fn damage(&self) -> Option<RestyleDamage> {
704 self.stylo_element_data_opt()
705 .and_then(|stylo| stylo.get().map(|data| data.damage))
706 }
707
708 pub fn set_damage(&mut self, damage: RestyleDamage) {
709 if let Some(stylo) = self.stylo_element_data_opt_mut() {
710 if let Some(mut data) = stylo.get_mut() {
711 data.damage = damage;
712 }
713 }
714 }
715
716 pub fn insert_damage(&mut self, damage: RestyleDamage) {
717 if let Some(stylo) = self.stylo_element_data_opt_mut() {
718 if let Some(mut data) = stylo.get_mut() {
719 data.damage |= damage;
720 }
721 }
722 }
723
724 pub fn remove_damage(&mut self, damage: RestyleDamage) {
725 if let Some(stylo) = self.stylo_element_data_opt_mut() {
726 if let Some(mut data) = stylo.get_mut() {
727 data.damage.remove(damage);
728 }
729 }
730 }
731
732 pub fn clear_damage_mut(&mut self) {
733 if let Some(stylo) = self.stylo_element_data_opt_mut() {
734 if let Some(mut data) = stylo.get_mut() {
735 data.damage = RestyleDamage::empty();
736 }
737 }
738 }
739
740 pub fn hover(&mut self) {
741 if let Some(data) = self.element_data_mut() {
742 data.element_state.insert(ElementState::HOVER);
743 }
744 self.set_restyle_hint(RestyleHint::restyle_subtree());
745 }
746
747 pub fn unhover(&mut self) {
748 if let Some(data) = self.element_data_mut() {
749 data.element_state.remove(ElementState::HOVER);
750 }
751 self.set_restyle_hint(RestyleHint::restyle_subtree());
752 }
753
754 pub fn is_hovered(&self) -> bool {
755 self.element_data()
756 .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
757 }
758
759 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
760 if let Some(data) = self.element_data_mut() {
761 data.element_state
762 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
763 }
764 self.set_restyle_hint(RestyleHint::restyle_subtree());
765
766 if self
768 .element_data()
769 .and_then(|elem| elem.text_input_data())
770 .is_some()
771 {
772 shell_provider.set_ime_enabled(true);
773 let mut pos = self.absolute_position(0.0, 0.0);
774 pos.x += self.final_layout().content_box_x();
775 pos.y += self.final_layout().content_box_y();
776 let width = self.final_layout().content_box_width();
777 let height = self.final_layout().content_box_height();
778 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
779 }
780 }
781
782 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
783 if let Some(data) = self.element_data_mut() {
784 data.element_state
785 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
786 }
787 self.set_restyle_hint(RestyleHint::restyle_subtree());
788
789 if self
791 .element_data()
792 .and_then(|elem| elem.text_input_data())
793 .is_some()
794 {
795 shell_provider.set_ime_enabled(false);
796 }
797 }
798
799 pub fn is_focussed(&self) -> bool {
800 self.element_data()
801 .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
802 }
803
804 pub fn active(&mut self) {
805 if let Some(data) = self.element_data_mut() {
806 data.element_state.insert(ElementState::ACTIVE);
807 }
808 self.set_restyle_hint(RestyleHint::restyle_subtree());
809 }
810
811 pub fn unactive(&mut self) {
812 if let Some(data) = self.element_data_mut() {
813 data.element_state.remove(ElementState::ACTIVE);
814 }
815 self.set_restyle_hint(RestyleHint::restyle_subtree());
816 }
817
818 pub fn is_active(&self) -> bool {
819 self.element_data()
820 .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
821 }
822
823 pub fn disable(&mut self) {
826 if let Some(data) = self.element_data_mut() {
827 if data.can_be_disabled() {
828 data.element_state.insert(ElementState::DISABLED);
829 data.element_state.remove(ElementState::ENABLED);
830 }
831 }
832 self.set_restyle_hint(RestyleHint::restyle_subtree());
833 }
834
835 pub fn enable(&mut self) {
838 if let Some(data) = self.element_data_mut() {
839 if data.can_be_disabled() {
840 data.element_state.insert(ElementState::ENABLED);
841 data.element_state.remove(ElementState::DISABLED);
842 }
843 }
844 self.set_restyle_hint(RestyleHint::restyle_subtree());
845 }
846
847 pub fn subdoc(&self) -> Option<&dyn Document> {
848 self.element_data().and_then(|el| el.sub_doc_data())
849 }
850
851 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
852 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
853 }
854
855 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
856 if let Some(input_data) = self
859 .data
860 .downcast_element()
861 .and_then(|el| el.text_input_data())
862 {
863 if !input_data.is_multiline {
864 let content_box_height = self.final_layout().content_box_height();
865 let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
866 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
867
868 return y_offset as f64;
869 }
870 }
871
872 0.0
873 }
874}
875
876#[derive(Debug, Clone, Copy, PartialEq)]
877pub enum NodeKind {
878 Document,
879 Element,
880 AnonymousBlock,
881 Text,
882 Comment,
883 DocumentFragment,
884 ShadowRoot,
885}
886
887#[derive(Debug, Clone, Copy, PartialEq, Eq)]
893pub enum TextGranularity {
894 Word,
896 Line,
898}
899
900impl TextGranularity {
901 pub fn from_click_count(count: u16) -> Option<Self> {
904 match count {
905 0 | 1 => None,
906 2 => Some(Self::Word),
907 _ => Some(Self::Line),
908 }
909 }
910}
911
912#[derive(Debug, Clone, Copy, PartialEq, Eq)]
916pub enum ShadowRootMode {
917 Open,
920 Closed,
923}
924
925#[derive(Clone)]
933pub struct ShadowRootData {
934 pub host: NodeId,
936 pub mode: ShadowRootMode,
938 pub declarative: bool,
940 pub clonable: bool,
942 pub serializable: bool,
944 pub delegates_focus: bool,
946 pub stylesheet_nodes: Vec<NodeId>,
948 pub(crate) cascade_data: Option<ServoArc<style::stylist::CascadeData>>,
949 pub(crate) cascade_dirty: bool,
950 pub(crate) slots: Vec<NodeId>,
951 #[cfg(feature = "shadow-dom")]
952 pub(crate) slottables: Vec<NodeId>,
953 pub(crate) selector_flags: Cell<ElementSelectorFlags>,
954}
955
956impl std::fmt::Debug for ShadowRootData {
957 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
958 formatter
959 .debug_struct("ShadowRootData")
960 .field("host", &self.host)
961 .field("mode", &self.mode)
962 .field("declarative", &self.declarative)
963 .field("clonable", &self.clonable)
964 .field("serializable", &self.serializable)
965 .field("delegates_focus", &self.delegates_focus)
966 .field("stylesheet_nodes", &self.stylesheet_nodes)
967 .field("cascade_dirty", &self.cascade_dirty)
968 .field("slots", &self.slots)
969 .finish()
970 }
971}
972
973impl ShadowRootData {
974 pub fn new(host: NodeId, mode: ShadowRootMode) -> Self {
975 Self {
976 host,
977 mode,
978 declarative: false,
979 clonable: false,
980 serializable: false,
981 delegates_focus: false,
982 stylesheet_nodes: Vec::new(),
983 cascade_data: None,
984 cascade_dirty: true,
985 slots: Vec::new(),
986 #[cfg(feature = "shadow-dom")]
987 slottables: Vec::new(),
988 selector_flags: Cell::new(ElementSelectorFlags::empty()),
989 }
990 }
991}
992
993#[derive(Debug, PartialEq, Eq)]
995pub enum MarkupNode {
996 ProcessingInstruction {
997 target: String,
998 },
999 Doctype {
1000 name: String,
1001 public_id: String,
1002 system_id: String,
1003 },
1004}
1005
1006#[derive(Debug, Clone)]
1008pub enum NodeData {
1009 Document(Box<DocumentData>),
1011
1012 Element(Box<ElementData>),
1014
1015 AnonymousBlock(Box<ElementData>),
1017
1018 Text(TextNodeData),
1020
1021 Comment {
1023 contents: String,
1025 },
1026
1027 DocumentFragment,
1030
1031 ShadowRoot(ShadowRootData),
1033 }
1040
1041impl NodeData {
1042 pub fn downcast_element(&self) -> Option<&ElementData> {
1043 match self {
1044 Self::Element(data) => Some(data),
1045 Self::AnonymousBlock(data) => Some(data),
1046 _ => None,
1047 }
1048 }
1049
1050 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
1051 match self {
1052 Self::Element(data) => Some(data),
1053 Self::AnonymousBlock(data) => Some(data),
1054 _ => None,
1055 }
1056 }
1057
1058 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
1059 let Some(elem) = self.downcast_element() else {
1060 return false;
1061 };
1062 *name == elem.name.local
1063 }
1064
1065 pub fn attrs(&self) -> Option<&[Attribute]> {
1066 Some(&self.downcast_element()?.attrs)
1067 }
1068
1069 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
1070 self.downcast_element()?.attr(name)
1071 }
1072
1073 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
1074 self.downcast_element()
1075 .is_some_and(|elem| elem.has_attr(name))
1076 }
1077
1078 pub fn kind(&self) -> NodeKind {
1079 match self {
1080 NodeData::Document(_) => NodeKind::Document,
1081 NodeData::Element(_) => NodeKind::Element,
1082 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
1083 NodeData::Text(_) => NodeKind::Text,
1084 NodeData::Comment { .. } => NodeKind::Comment,
1085 NodeData::DocumentFragment => NodeKind::DocumentFragment,
1086 NodeData::ShadowRoot(_) => NodeKind::ShadowRoot,
1087 }
1088 }
1089
1090 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1091 match self {
1092 Self::ShadowRoot(data) => Some(data),
1093 _ => None,
1094 }
1095 }
1096
1097 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1098 match self {
1099 Self::ShadowRoot(data) => Some(data),
1100 _ => None,
1101 }
1102 }
1103}
1104
1105#[derive(Debug, Clone)]
1106pub struct TextNodeData {
1107 pub content: String,
1109}
1110
1111impl TextNodeData {
1112 pub fn new(content: String) -> Self {
1113 Self { content }
1114 }
1115}
1116
1117impl Node {
1142 pub fn tree(&self) -> &crate::NodeTree {
1143 unsafe { &*self.tree }
1144 }
1145
1146 pub fn is_html_document(&self) -> bool {
1147 let document = match &self.data {
1148 NodeData::Document(data) => return data.content_type == "text/html",
1149 _ => self.owner_document.and_then(|id| self.tree().get(id)),
1150 };
1151 match document.map(|node| &node.data) {
1152 Some(NodeData::Document(data)) => data.content_type == "text/html",
1153 _ => true,
1154 }
1155 }
1156
1157 pub fn qualified_element_name(&self) -> Option<String> {
1158 let element = self.element_data()?;
1159 let name = match &element.name.prefix {
1160 Some(prefix) => format!("{prefix}:{}", element.name.local),
1161 None => element.name.local.to_string(),
1162 };
1163 Some(
1164 if self.is_html_document() && element.name.ns == markup5ever::ns!(html) {
1165 name.to_ascii_uppercase()
1166 } else {
1167 name
1168 },
1169 )
1170 }
1171
1172 #[track_caller]
1173 pub fn with(&self, id: NodeId) -> &Node {
1174 self.tree().get(id).unwrap()
1175 }
1176
1177 pub fn print_tree(&self, level: usize) {
1178 println!(
1179 "{} {} {:?} {} {:?}",
1180 " ".repeat(level),
1181 self.id,
1182 self.parent,
1183 self.node_debug_str().replace('\n', ""),
1184 self.children
1185 );
1186 for child_id in self.children.iter() {
1188 let child = self.with(*child_id);
1189 child.print_tree(level + 1)
1190 }
1191 }
1192
1193 pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1195 self.children.iter().position(|id| *id == child_id)
1196 }
1197
1198 pub fn child_index(&self) -> Option<usize> {
1200 let children = &self.tree()[self.parent?].children;
1201 let hint = self.child_index_hint.get();
1202 if children.get(hint) == Some(&self.id) {
1203 return Some(hint);
1204 }
1205 let index = children.iter().position(|id| *id == self.id)?;
1206 self.child_index_hint.set(index);
1207 Some(index)
1208 }
1209
1210 pub fn dom_child_at(&self, index: usize) -> Option<NodeId> {
1212 let id = *self.children.get(index)?;
1213 self.with(id).child_index_hint.set(index);
1214 Some(id)
1215 }
1216
1217 pub fn forward(&self, n: usize) -> Option<&Node> {
1219 let index = self.child_index()?.checked_add(n)?;
1220 let id = *self.tree()[self.parent?].children.get(index)?;
1221 let node = self.with(id);
1222 node.child_index_hint.set(index);
1223 Some(node)
1224 }
1225
1226 pub fn backward(&self, n: usize) -> Option<&Node> {
1227 let index = self.child_index()?.checked_sub(n)?;
1228 let id = *self.tree()[self.parent?].children.get(index)?;
1229 let node = self.with(id);
1230 node.child_index_hint.set(index);
1231 Some(node)
1232 }
1233
1234 pub fn is_element(&self) -> bool {
1235 matches!(self.data, NodeData::Element { .. })
1236 }
1237
1238 pub fn is_anonymous(&self) -> bool {
1239 matches!(self.data, NodeData::AnonymousBlock { .. })
1240 }
1241
1242 pub fn is_shadow_root(&self) -> bool {
1243 matches!(self.data, NodeData::ShadowRoot { .. })
1244 }
1245
1246 pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1247 self.data.shadow_root_data()
1248 }
1249
1250 pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1251 self.data.shadow_root_data_mut()
1252 }
1253
1254 pub fn shadow_root_id(&self) -> Option<NodeId> {
1257 self.element_data().and_then(|el| el.shadow_root)
1258 }
1259
1260 #[inline]
1266 pub(crate) fn flattened_parent(&self) -> Option<&Node> {
1267 #[cfg(feature = "shadow-dom")]
1268 {
1269 if self.is_shadow_root() {
1270 return None;
1271 }
1272 if let Some(slot_id) = self.assigned_slot {
1273 return Some(self.with(slot_id));
1274 }
1275 let parent = self.with(self.parent?);
1276 if let Some(root) = parent.shadow_root_data() {
1277 return Some(self.with(root.host));
1278 }
1279 if parent.shadow_root_id().is_some()
1280 || parent
1281 .assigned_nodes
1282 .as_ref()
1283 .is_some_and(|nodes| !nodes.is_empty())
1284 {
1285 return None;
1286 }
1287 Some(parent)
1288 }
1289 #[cfg(not(feature = "shadow-dom"))]
1290 {
1291 self.parent.map(|id| self.with(id))
1292 }
1293 }
1294
1295 #[cfg(feature = "shadow-dom")]
1299 pub fn layout_dom_children(&self) -> &[NodeId] {
1300 match &self.flattened_children {
1301 Some(children) => children,
1302 None => &self.children,
1303 }
1304 }
1305
1306 #[cfg(not(feature = "shadow-dom"))]
1308 #[inline(always)]
1309 pub fn layout_dom_children(&self) -> &[NodeId] {
1310 &self.children
1311 }
1312
1313 pub fn is_text_node(&self) -> bool {
1314 matches!(self.data, NodeData::Text { .. })
1315 }
1316
1317 pub fn element_data(&self) -> Option<&ElementData> {
1318 match self.data {
1319 NodeData::Element(ref data) => Some(data),
1320 NodeData::AnonymousBlock(ref data) => Some(data),
1321 _ => None,
1322 }
1323 }
1324
1325 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1326 match self.data {
1327 NodeData::Element(ref mut data) => Some(data),
1328 NodeData::AnonymousBlock(ref mut data) => Some(data),
1329 _ => None,
1330 }
1331 }
1332
1333 pub fn text_data(&self) -> Option<&TextNodeData> {
1334 match self.data {
1335 NodeData::Text(ref data) => Some(data),
1336 _ => None,
1337 }
1338 }
1339
1340 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1341 match self.data {
1342 NodeData::Text(ref mut data) => Some(data),
1343 _ => None,
1344 }
1345 }
1346
1347 pub fn node_debug_str(&self) -> String {
1348 let mut s = String::new();
1349
1350 match &self.data {
1351 NodeData::Document(_) => write!(s, "DOCUMENT"),
1352 NodeData::DocumentFragment => write!(s, "FRAGMENT"),
1353 NodeData::Text(data) => {
1355 let bytes = data.content.as_bytes();
1356 write!(
1357 s,
1358 "TEXT {}",
1359 std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1360 .unwrap_or("INVALID UTF8")
1361 )
1362 }
1363 NodeData::Comment { .. } => write!(s, "COMMENT"),
1364 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1365 NodeData::ShadowRoot(data) => write!(s, "#shadow-root ({:?})", data.mode),
1366 NodeData::Element(data) => {
1367 let name = &data.name;
1368 let class = self.attr(local_name!("class")).unwrap_or("");
1369 let id = self.attr(local_name!("id")).unwrap_or("");
1370 let display = self.display_constructed_as().to_css_string();
1371 write!(s, "<{}", name.local).unwrap();
1372 if !id.is_empty() {
1373 write!(s, " #{id}").unwrap();
1374 }
1375 if !class.is_empty() {
1376 if class.contains(' ') {
1377 write!(s, " class=\"{class}\"").unwrap()
1378 } else {
1379 write!(s, " .{class}").unwrap()
1380 }
1381 }
1382 write!(s, "> ({display})")
1383 } }
1385 .unwrap();
1386 s
1387 }
1388
1389 pub fn outer_html(&self) -> String {
1397 let mut output = String::new();
1398 self.write_outer_html(&mut output);
1399 output
1400 }
1401
1402 pub fn outer_html_pretty(&self) -> String {
1418 let mut output = String::new();
1419 self.write_outer_html_pretty(&mut output);
1420 output
1421 }
1422
1423 pub fn write_outer_html(&self, writer: &mut String) {
1424 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
1425 }
1426
1427 #[cfg(feature = "svg")]
1428 pub(crate) fn write_outer_html_with_current_color(
1429 &self,
1430 writer: &mut String,
1431 current_color: &str,
1432 ) {
1433 self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
1434 }
1435
1436 #[cfg(feature = "svg")]
1437 pub(crate) fn write_outer_html_with_svg_ids(
1438 &self,
1439 writer: &mut String,
1440 ids: &std::collections::HashMap<NodeId, String>,
1441 ) {
1442 self.write_outer_html_in_style_with_svg_ids(
1443 writer,
1444 OutputStyle::Normal,
1445 0,
1446 None,
1447 Some(ids),
1448 );
1449 }
1450
1451 pub fn write_outer_html_pretty(&self, writer: &mut String) {
1452 self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
1453 }
1454
1455 fn write_outer_html_in_style(
1456 &self,
1457 writer: &mut String,
1458 style: OutputStyle,
1459 nesting: usize,
1460 current_color_override: Option<&str>,
1461 ) {
1462 self.write_outer_html_in_style_with_svg_ids(
1463 writer,
1464 style,
1465 nesting,
1466 current_color_override,
1467 None,
1468 );
1469 }
1470
1471 fn write_outer_html_in_style_with_svg_ids(
1472 &self,
1473 writer: &mut String,
1474 style: OutputStyle,
1475 nesting: usize,
1476 current_color_override: Option<&str>,
1477 svg_ids: Option<&std::collections::HashMap<NodeId, String>>,
1478 ) {
1479 const INDENT: &str = " ";
1480 let has_children = !self.children.is_empty();
1481 let computed_current_color = || {
1482 self.primary_styles()
1483 .map(|style| style.clone_color())
1484 .map(|color| crate::util::absolute_color_to_svg_css(&color))
1485 };
1486 let current_color = current_color_override
1487 .map(ToOwned::to_owned)
1488 .or_else(computed_current_color);
1489
1490 match &self.data {
1491 NodeData::Document(_) => {}
1492 NodeData::Comment { .. } => {}
1493 NodeData::AnonymousBlock(_) => {}
1494 NodeData::ShadowRoot(_) => {}
1495 NodeData::DocumentFragment => {}
1499 NodeData::Text(data) => {
1501 if matches!(style, OutputStyle::Pretty) {
1502 for _ in 0..nesting {
1503 writer.push_str(INDENT);
1504 }
1505 }
1506 writer.push_str(data.content.as_str());
1507 if matches!(style, OutputStyle::Pretty) {
1508 writer.push('\n');
1509 }
1510 }
1511 NodeData::Element(data) => {
1512 if matches!(style, OutputStyle::Pretty) {
1513 for _ in 0..nesting {
1514 writer.push_str(INDENT);
1515 }
1516 }
1517 let tag_name = if svg_ids.is_some()
1520 && data.name.ns.as_ref() == "http://www.w3.org/2000/svg"
1521 && data.name.local == local_name!("a")
1522 {
1523 "g"
1524 } else {
1525 data.name.local.as_ref()
1526 };
1527 let svg_id = svg_ids.and_then(|ids| ids.get(&self.id));
1528 writer.push('<');
1529 writer.push_str(tag_name);
1530 if let Some(id) = svg_id {
1531 writer.push_str(" id=\"");
1532 encode_quoted_attribute_to_string(id, writer);
1533 writer.push('"');
1534 }
1535
1536 for attr in data.attrs() {
1537 if svg_id.is_some()
1538 && attr.name.ns.as_ref().is_empty()
1539 && attr.name.local == local_name!("id")
1540 {
1541 continue;
1542 }
1543 writer.push(' ');
1544 if let Some(prefix) = attr
1545 .name
1546 .prefix
1547 .as_ref()
1548 .filter(|prefix| !prefix.is_empty())
1549 {
1550 writer.push_str(prefix);
1551 writer.push(':');
1552 }
1553 writer.push_str(&attr.name.local);
1554 writer.push_str("=\"");
1555 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
1557 let value = attr
1558 .value
1559 .replace("currentColor", current_color.as_ref().unwrap());
1560 encode_quoted_attribute_to_string(&value, writer);
1561 } else {
1562 encode_quoted_attribute_to_string(&attr.value, writer);
1563 }
1564 writer.push('"');
1565 }
1566 if !has_children {
1567 writer.push_str(" /");
1568 }
1569 writer.push('>');
1570 if matches!(style, OutputStyle::Pretty) {
1571 writer.push('\n');
1572 }
1573
1574 if has_children {
1575 for &child_id in &self.children {
1576 self.tree()[child_id].write_outer_html_in_style_with_svg_ids(
1577 writer,
1578 style,
1579 nesting + 1,
1580 current_color_override,
1581 svg_ids,
1582 );
1583 }
1584
1585 if matches!(style, OutputStyle::Pretty) {
1586 for _ in 0..nesting {
1587 writer.push_str(INDENT);
1588 }
1589 }
1590 writer.push_str("</");
1591 writer.push_str(tag_name);
1592 writer.push('>');
1593 if matches!(style, OutputStyle::Pretty) {
1594 writer.push('\n');
1595 }
1596 }
1597 }
1598 }
1599 }
1600
1601 pub fn write_html(&self, writer: &mut String) {
1609 match &self.data {
1610 NodeData::Text(data) => {
1611 let raw = self
1612 .parent
1613 .and_then(|parent| self.tree()[parent].element_data())
1614 .is_some_and(|parent| is_raw_text_element(&parent.name.local));
1615 if raw {
1616 writer.push_str(&data.content);
1617 } else {
1618 escape_html(&data.content, false, writer);
1619 }
1620 }
1621 NodeData::Comment { contents } => {
1622 writer.push_str("<!--");
1623 writer.push_str(contents);
1624 writer.push_str("-->");
1625 }
1626 NodeData::Element(data) => {
1627 writer.push('<');
1628 writer.push_str(&data.name.local);
1629 for attr in data.attrs() {
1630 writer.push(' ');
1631 if let Some(prefix) = attr
1632 .name
1633 .prefix
1634 .as_ref()
1635 .filter(|prefix| !prefix.is_empty())
1636 {
1637 writer.push_str(prefix);
1638 writer.push(':');
1639 }
1640 writer.push_str(&attr.name.local);
1641 writer.push_str("=\"");
1642 escape_html(&attr.value, true, writer);
1643 writer.push('"');
1644 }
1645 writer.push('>');
1646 if is_void_element(&data.name.local) {
1647 return;
1648 }
1649 self.write_children_html(writer);
1650 writer.push_str("</");
1651 writer.push_str(&data.name.local);
1652 writer.push('>');
1653 }
1654 NodeData::Document(_)
1655 | NodeData::DocumentFragment
1656 | NodeData::ShadowRoot(_)
1657 | NodeData::AnonymousBlock(_) => self.write_children_html(writer),
1658 }
1659 }
1660
1661 pub fn write_children_html(&self, writer: &mut String) {
1663 if let Some(contents) = self
1664 .element_data()
1665 .and_then(|element| element.template_contents)
1666 {
1667 self.tree()[contents].write_children_html(writer);
1668 return;
1669 }
1670 for &child_id in &self.children {
1671 self.tree()[child_id].write_html(writer);
1672 }
1673 }
1674
1675 pub fn attrs(&self) -> Option<&[Attribute]> {
1676 Some(&self.element_data()?.attrs)
1677 }
1678
1679 pub fn attr(&self, name: LocalName) -> Option<&str> {
1680 let attr = self
1681 .attrs()?
1682 .iter()
1683 .find(|attr| attr.name.ns == markup5ever::ns!() && attr.name.local == name)?;
1684 Some(&attr.value)
1685 }
1686
1687 pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1688 self.stylo_element_data_opt()
1689 .and_then(|stylo| stylo.primary_styles())
1690 }
1691
1692 pub fn text_content(&self) -> String {
1693 let mut out = String::new();
1694 self.write_text_content(&mut out);
1695 out
1696 }
1697
1698 pub fn rendered_inner_text(&self) -> String {
1702 use style::computed_values::visibility::T as Visibility;
1703
1704 enum Piece {
1705 Text(String),
1706 RequiredBreak(usize),
1707 }
1708
1709 fn text(node: &Node) -> String {
1710 let NodeData::Text(data) = &node.data else {
1711 return String::new();
1712 };
1713 if node.inline_root_ancestor().is_none() {
1715 return String::new();
1716 }
1717 let preserves = node
1718 .parent
1719 .map(|id| node.with(id))
1720 .and_then(Node::primary_styles)
1721 .is_some_and(|style| {
1722 style.get_inherited_text().white_space_collapse
1723 != style::computed_values::white_space_collapse::T::Collapse
1724 });
1725 if preserves {
1726 return data.content.to_string();
1727 }
1728 let mut result = String::with_capacity(data.content.len());
1731 let mut space = false;
1732 for ch in data.content.chars() {
1733 if matches!(ch, ' ' | '\t' | '\n' | '\r' | '\u{c}') {
1734 space = true;
1735 } else {
1736 if space {
1737 result.push(' ');
1738 space = false;
1739 }
1740 result.push(ch);
1741 }
1742 }
1743 if space {
1744 result.push(' ');
1745 }
1746 result
1747 }
1748 fn collect(node: &Node, pieces: &mut Vec<Piece>) {
1749 if node.is_element() && node.is_display_none() {
1750 return;
1751 }
1752 let visible = node
1753 .primary_styles()
1754 .is_none_or(|style| style.clone_visibility() == Visibility::Visible);
1755 if node.is_text_node() {
1756 let visible = node
1757 .parent
1758 .map(|id| node.with(id))
1759 .and_then(Node::primary_styles)
1760 .is_none_or(|style| style.clone_visibility() == Visibility::Visible);
1761 if visible {
1762 let value = text(node);
1763 if !value.is_empty() {
1764 pieces.push(Piece::Text(value));
1765 }
1766 }
1767 return;
1768 }
1769 if !node.is_element() {
1770 return;
1771 }
1772 let tag = node.element_data().expect("element").name.local.as_ref();
1773 if tag == "br" {
1774 if visible {
1775 pieces.push(Piece::Text("\n".to_owned()));
1776 }
1777 return;
1778 }
1779 let display = node.primary_styles().map(|style| style.clone_display());
1780 let block = display.is_some_and(|display| {
1781 display.outside() == DisplayOutside::Block && !display.is_contents()
1782 });
1783 let breaks = if tag == "p" { 2 } else { usize::from(block) };
1784 if breaks != 0 {
1785 pieces.push(Piece::RequiredBreak(breaks));
1786 }
1787 for child in &node.children {
1788 collect(node.with(*child), pieces);
1789 }
1790 if visible {
1791 if matches!(tag, "td" | "th") {
1792 let next_cell = node
1793 .parent
1794 .map(|parent| node.with(parent))
1795 .and_then(|parent| {
1796 parent.index_of_child(node.id).map(|index| (parent, index))
1797 })
1798 .is_some_and(|(parent, index)| {
1799 parent.children[index + 1..].iter().any(|id| {
1800 let child = parent.with(*id);
1801 child.data.is_element_with_tag_name(&local_name!("td"))
1802 || child.data.is_element_with_tag_name(&local_name!("th"))
1803 })
1804 });
1805 if next_cell {
1806 pieces.push(Piece::Text("\t".to_owned()));
1807 }
1808 } else if tag == "tr" {
1809 pieces.push(Piece::RequiredBreak(1));
1810 }
1811 }
1812 if breaks != 0 {
1813 pieces.push(Piece::RequiredBreak(breaks));
1814 }
1815 }
1816
1817 let mut current = Some(self);
1818 while let Some(node) = current {
1819 if !self.flags.is_in_document() || node.is_display_none() {
1820 return self.text_content();
1821 }
1822 current = node.parent.map(|id| node.with(id));
1823 }
1824 if self.primary_styles().is_none() {
1825 return self.text_content();
1826 }
1827 let mut pieces = Vec::new();
1828 collect(self, &mut pieces);
1829 let mut result = String::new();
1830 let mut pending = 0;
1831 for piece in pieces {
1832 match piece {
1833 Piece::RequiredBreak(count) => pending = pending.max(count),
1834 Piece::Text(value) => {
1835 if !result.is_empty() && pending != 0 {
1836 result.truncate(result.trim_end_matches(' ').len());
1837 for _ in 0..pending {
1838 result.push('\n');
1839 }
1840 }
1841 pending = 0;
1842 if result.is_empty() || result.ends_with('\n') || result.ends_with(' ') {
1843 result.push_str(value.trim_start_matches(' '));
1844 } else {
1845 result.push_str(&value);
1846 }
1847 }
1848 }
1849 }
1850 result.truncate(result.trim_end_matches(' ').len());
1851 result
1852 }
1853
1854 pub fn cssom_offset_parent(&self) -> Option<&Node> {
1857 if !self.flags.is_in_document()
1858 || self.data.is_element_with_tag_name(&local_name!("body"))
1859 || self.data.is_element_with_tag_name(&local_name!("html"))
1860 {
1861 return None;
1862 }
1863 let style = self.primary_styles()?;
1864 if style.clone_position() == Position::Fixed || style.clone_display().is_contents() {
1865 return None;
1866 }
1867 let mut current = Some(self);
1868 while let Some(node) = current {
1869 if node.is_display_none() {
1870 return None;
1871 }
1872 current = node.parent.map(|id| node.with(id));
1873 }
1874 let static_position = style.clone_position() == Position::Static;
1875 let zoom = style.effective_zoom.zoom(1.0);
1876 let mut parent = self.parent.map(|id| self.with(id));
1877 while let Some(node) = parent {
1878 if let Some(style) = node.primary_styles()
1879 && !style.clone_display().is_contents()
1880 && (node.is_offset_parent()
1881 || style.effective_zoom.zoom(1.0) != zoom
1882 || (static_position
1883 && node.data.is_element_with_tag_name(&local_name!("table"))))
1884 {
1885 return Some(node);
1886 }
1887 parent = node.parent.map(|id| node.with(id));
1888 }
1889 None
1890 }
1891
1892 pub fn write_text_content<W: Write>(&self, out: &mut W) {
1905 match &self.data {
1906 NodeData::Text(data) => {
1907 let _ = out.write_str(&data.content);
1908 }
1909 NodeData::Element(..)
1910 | NodeData::AnonymousBlock(..)
1911 | NodeData::Document(..)
1912 | NodeData::DocumentFragment
1913 | NodeData::ShadowRoot(..) => {
1914 for child_id in self.children.iter() {
1915 self.with(*child_id).write_text_content(out);
1916 }
1917 }
1918 _ => {}
1919 }
1920 }
1921
1922 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1923 if let NodeData::Element(ref mut elem_data) = self.data {
1924 if let Some(guard) = elem_data.guard.clone() {
1925 elem_data.flush_style_attribute(&guard, url_extra_data);
1926 }
1927 }
1928 }
1929
1930 pub fn order(&self) -> i32 {
1931 self.primary_styles()
1932 .map(|s| match s.pseudo() {
1933 Some(PseudoElement::Before) => i32::MIN,
1934 Some(PseudoElement::After) => i32::MAX,
1935 _ => s.clone_order(),
1936 })
1937 .unwrap_or(0)
1938 }
1939
1940 pub fn z_index(&self) -> i32 {
1941 self.primary_styles()
1942 .map(|s| s.clone_z_index().integer_or(0))
1943 .unwrap_or(0)
1944 }
1945
1946 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1948 let Some(style) = self.primary_styles() else {
1949 return false;
1950 };
1951
1952 let position = style.clone_position();
1953 let has_z_index = !style.clone_z_index().is_auto();
1954
1955 if style.clone_opacity() != 1.0 {
1956 return true;
1957 }
1958
1959 let position_based = match position {
1960 Position::Fixed | Position::Sticky => true,
1961 Position::Relative | Position::Absolute => has_z_index,
1962 Position::Static => has_z_index && is_flex_or_grid_item,
1963 };
1964 if position_based {
1965 return true;
1966 }
1967
1968 let box_styles = style.get_box();
1976 if !box_styles.transform.0.is_empty()
1977 || !matches!(box_styles.translate, Translate::None)
1978 || !matches!(box_styles.rotate, Rotate::None)
1979 || !matches!(box_styles.scale, Scale::None)
1980 {
1981 return true;
1982 }
1983
1984 if box_styles.isolation == Isolation::Isolate {
1989 return true;
1990 }
1991
1992 false
1999 }
2000
2001 pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
2010 self.hit_inner(x, y, scale, &mut None)
2011 }
2012
2013 pub(crate) fn hit_inner(
2018 &self,
2019 x: f32,
2020 y: f32,
2021 scale: f64,
2022 scrollbar: &mut Option<crate::node::ScrollbarRef>,
2023 ) -> Option<HitResult> {
2024 self.hit_inner_collect(x, y, scale, scrollbar, &mut None)
2025 }
2026
2027 pub(crate) fn hit_inner_collect(
2029 &self,
2030 x: f32,
2031 y: f32,
2032 scale: f64,
2033 scrollbar: &mut Option<crate::node::ScrollbarRef>,
2034 hits: &mut Option<Vec<HitResult>>,
2035 ) -> Option<HitResult> {
2036 use style::computed_values::pointer_events::T as PointerEvents;
2037 use style::computed_values::visibility::T as Visibility;
2038
2039 if matches!(self.style().display, taffy::Display::None) {
2048 return None;
2049 }
2050
2051 if let Some(style) = self.primary_styles() {
2053 if matches!(
2054 style.clone_visibility(),
2055 Visibility::Hidden | Visibility::Collapse
2056 ) {
2057 return None;
2058 }
2059 }
2060
2061 let pointer_events_none = self
2064 .primary_styles()
2065 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
2066
2067 let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
2068 let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
2069
2070 if let Some(t) = *self.transform() {
2071 let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
2072 x = (p.x / scale) as f32;
2073 y = (p.y / scale) as f32;
2074 }
2075
2076 let size = self.final_layout().size;
2077 let matches_self = !(x < 0.0
2078 || x > size.width + self.scroll_offset().x as f32
2079 || y < 0.0
2080 || y > size.height + self.scroll_offset().y as f32);
2081
2082 let content_size = self.final_layout().content_size;
2083 let matches_content = !(x < 0.0
2084 || x > content_size.width + self.scroll_offset().x as f32
2085 || y < 0.0
2086 || y > content_size.height + self.scroll_offset().y as f32);
2087
2088 let matches_hoisted_content = match &self.stacking_context {
2089 Some(sc) => {
2090 let content_area = sc.content_area;
2091 x >= content_area.left + self.scroll_offset().x as f32
2092 && x <= content_area.right + self.scroll_offset().x as f32
2093 && y >= content_area.top + self.scroll_offset().y as f32
2094 && y <= content_area.bottom + self.scroll_offset().y as f32
2095 }
2096 None => false,
2097 };
2098
2099 let overflow = *self.scrollable_overflow();
2102
2103 let matches_overflow = x >= (overflow.x0 / scale) as f32
2104 && x <= (overflow.x1 / scale) as f32
2105 && y >= (overflow.y0 / scale) as f32
2106 && y <= (overflow.y1 / scale) as f32;
2107
2108 if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
2109 return None;
2110 }
2111
2112 if matches_self
2115 && let Some(sb) = self.scrollbar_at_local(
2116 (x - self.scroll_offset().x as f32) as f64,
2117 (y - self.scroll_offset().y as f32) as f64,
2118 )
2119 {
2120 *scrollbar = Some(sb);
2121 }
2122
2123 if matches_hoisted_content {
2128 if let Some(hoisted) = &self.stacking_context {
2129 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
2130 if hoisted_child.clips.iter().any(|clip| {
2131 x < clip.left || x > clip.right || y < clip.top || y > clip.bottom
2132 }) {
2133 continue;
2134 }
2135 let x = x - hoisted_child.position.x;
2136 let y = y - hoisted_child.position.y;
2137 if let Some(hit) = self
2138 .with(hoisted_child.node_id)
2139 .hit_inner_collect(x, y, scale, scrollbar, hits)
2140 {
2141 return Some(hit);
2142 }
2143 }
2144 }
2145 }
2146
2147 for child_id in self.paint_children.borrow().iter().flatten().rev() {
2149 if let Some(hit) = self
2150 .with(*child_id)
2151 .hit_inner_collect(x, y, scale, scrollbar, hits)
2152 {
2153 return Some(hit);
2154 }
2155 }
2156
2157 if matches_hoisted_content {
2159 if let Some(hoisted) = &self.stacking_context {
2160 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
2161 if hoisted_child.clips.iter().any(|clip| {
2162 x < clip.left || x > clip.right || y < clip.top || y > clip.bottom
2163 }) {
2164 continue;
2165 }
2166 let x = x - hoisted_child.position.x;
2167 let y = y - hoisted_child.position.y;
2168 if let Some(hit) = self
2169 .with(hoisted_child.node_id)
2170 .hit_inner_collect(x, y, scale, scrollbar, hits)
2171 {
2172 return Some(hit);
2173 }
2174 }
2175 }
2176 }
2177
2178 #[cfg(feature = "svg")]
2180 if matches_self && let Some(hit) = self.hit_inline_svg(x, y, hits) {
2181 return Some(hit);
2182 }
2183
2184 if self.flags.is_inline_root() {
2186 x -= self.final_layout().padding.left + self.final_layout().border.left;
2187 y -= self.final_layout().padding.top + self.final_layout().border.top;
2188 let element_data = &self.element_data().unwrap();
2189 if let Some(ild) = element_data.inline_layout_data.as_ref() {
2190 let layout = &ild.layout;
2191 let scale = layout.scale();
2192
2193 if let Some((cluster, _side)) =
2194 Cluster::from_point_exact(layout, x * scale, y * scale)
2195 {
2196 let style_index = cluster.glyphs().next()?.style_index();
2197 let node_id = layout.styles()[style_index].brush.id;
2198 let text_pointer_events_none = self
2199 .with(node_id)
2200 .primary_styles()
2201 .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
2202 if !text_pointer_events_none {
2203 let hit = HitResult {
2204 node_id,
2205 x,
2206 y,
2207 is_text: true,
2208 };
2209 if let Some(hits) = hits.as_mut() {
2210 hits.push(hit);
2211 } else {
2212 return Some(hit);
2213 }
2214 }
2215 }
2216 }
2217 }
2218
2219 if matches_self && !pointer_events_none {
2221 let hit = HitResult {
2222 node_id: self.id,
2223 x,
2224 y,
2225 is_text: false,
2226 };
2227 if let Some(hits) = hits.as_mut() {
2228 hits.push(hit);
2229 } else {
2230 return Some(hit);
2231 }
2232 }
2233
2234 None
2235 }
2236
2237 pub fn inline_root_ancestor(&self) -> Option<&Node> {
2240 let mut node = self;
2241 loop {
2242 if node.flags.is_inline_root() {
2243 return Some(node);
2244 }
2245 let id = node.layout_parent.get()?;
2246 node = self.with(id);
2247 }
2248 }
2249
2250 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
2254 if !self.flags.is_inline_root() {
2255 return None;
2256 }
2257
2258 let element_data = self.element_data()?;
2259 let inline_layout = element_data.inline_layout_data.as_ref()?;
2260 let layout = &inline_layout.layout;
2261 let scale = layout.scale();
2262
2263 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
2265
2266 let is_leading = side == ClusterSide::Left;
2271 let offset = if cluster.is_rtl() {
2272 if is_leading {
2273 cluster.text_range().end
2274 } else {
2275 cluster.text_range().start
2276 }
2277 } else {
2278 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
2280 cluster.text_range().start
2281 } else {
2282 cluster.text_range().end
2283 }
2284 };
2285
2286 Some(offset)
2287 }
2288
2289 pub fn text_range_at_point(
2299 &self,
2300 x: f32,
2301 y: f32,
2302 granularity: TextGranularity,
2303 ) -> Option<Range<usize>> {
2304 if !self.flags.is_inline_root() {
2305 return None;
2306 }
2307
2308 let element_data = self.element_data()?;
2309 let inline_layout = element_data.inline_layout_data.as_ref()?;
2310 let layout = &inline_layout.layout;
2311 let scale = layout.scale();
2312 let (x, y) = (x * scale, y * scale);
2313
2314 Cluster::from_point(layout, x, y)?;
2319
2320 let selection = match granularity {
2321 TextGranularity::Word => Selection::word_from_point(layout, x, y),
2322 TextGranularity::Line => Selection::hard_line_from_point(layout, x, y),
2326 };
2327
2328 let range = selection.text_range();
2329 if range.is_empty() { None } else { Some(range) }
2330 }
2331
2332 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
2334 let x = x + self.final_layout().location.x;
2337 let y = y + self.final_layout().location.y;
2338
2339 self.layout_parent
2341 .get()
2342 .and_then(|id| self.tree().get(id))
2343 .map(|parent| {
2344 parent.absolute_position(
2345 x - parent.scroll_offset().x as f32,
2346 y - parent.scroll_offset().y as f32,
2347 )
2348 })
2349 .unwrap_or(crate::util::Point { x, y })
2350 }
2351
2352 fn is_offset_parent(&self) -> bool {
2355 let Some(styles) = self.primary_styles() else {
2356 return false;
2357 };
2358 if styles.get_box().position != Position::Static {
2359 return true;
2360 }
2361 self.data.is_element_with_tag_name(&local_name!("body"))
2362 || self.data.is_element_with_tag_name(&local_name!("td"))
2363 || self.data.is_element_with_tag_name(&local_name!("th"))
2364 }
2365
2366 pub fn offset_parent(&self) -> Option<&Node> {
2374 let mut node = self;
2375 loop {
2376 node = self.with(node.layout_parent.get()?);
2377 if node.is_offset_parent() {
2378 return Some(node);
2379 }
2380 }
2381 }
2382
2383 pub fn offset_top_left(&self) -> crate::util::Point<f32> {
2386 let mut x = 0.0;
2387 let mut y = 0.0;
2388 let mut current = self;
2389 loop {
2390 let layout = current.final_layout();
2391 x += layout.location.x;
2392 y += layout.location.y;
2393
2394 let Some(parent_id) = current.layout_parent.get() else {
2395 break;
2396 };
2397 let parent = self.with(parent_id);
2398 if parent.is_offset_parent() {
2399 let border = parent.final_layout().border;
2400 x -= border.left;
2401 y -= border.top;
2402 break;
2403 }
2404 current = parent;
2405 }
2406 crate::util::Point { x, y }
2407 }
2408
2409 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
2411 DomEventData::Click(self.synthetic_click_event_data(mods))
2412 }
2413
2414 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
2415 let absolute_position = self.absolute_position(0.0, 0.0);
2416 let x = absolute_position.x + (self.final_layout().size.width / 2.0);
2417 let y = absolute_position.y + (self.final_layout().size.height / 2.0);
2418
2419 BlitzPointerEvent {
2420 id: BlitzPointerId::Mouse,
2421 is_primary: true,
2422 coords: PointerCoords {
2423 page_x: x,
2424 page_y: y,
2425
2426 screen_x: x,
2428 screen_y: y,
2429 client_x: x,
2430 client_y: y,
2431 },
2432 mods,
2433 button: Default::default(),
2434 buttons: Default::default(),
2435 details: Default::default(),
2436 element: Default::default(),
2437 active_pointers: Default::default(),
2438 }
2439 }
2440}
2441
2442impl PartialEq for Node {
2444 fn eq(&self, other: &Self) -> bool {
2445 self.id == other.id
2446 }
2447}
2448
2449impl Eq for Node {}
2450
2451impl std::fmt::Debug for Node {
2452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2453 f.debug_struct("NodeData")
2455 .field("parent", &self.parent)
2456 .field("id", &self.id)
2457 .field("is_inline_root", &self.flags.is_inline_root())
2458 .field("children", &self.children)
2459 .field("layout_children", &self.layout_children.borrow())
2460 .field("node", &self.data)
2462 .field("stylo_element_data", &self.stylo_element_data_opt())
2463 .finish()
2466 }
2467}
2468
2469fn is_raw_text_element(name: &LocalName) -> bool {
2472 matches!(
2473 name.as_ref(),
2474 "style" | "script" | "xmp" | "iframe" | "noembed" | "noframes" | "plaintext" | "noscript"
2475 )
2476}
2477
2478fn is_void_element(name: &LocalName) -> bool {
2480 matches!(
2481 name.as_ref(),
2482 "area"
2483 | "base"
2484 | "basefont"
2485 | "bgsound"
2486 | "br"
2487 | "col"
2488 | "embed"
2489 | "frame"
2490 | "hr"
2491 | "img"
2492 | "input"
2493 | "keygen"
2494 | "link"
2495 | "meta"
2496 | "param"
2497 | "source"
2498 | "track"
2499 | "wbr"
2500 )
2501}
2502
2503fn escape_html(text: &str, attribute: bool, writer: &mut String) {
2506 for ch in text.chars() {
2507 match ch {
2508 '&' => writer.push_str("&"),
2509 '\u{a0}' => writer.push_str(" "),
2510 '"' if attribute => writer.push_str("""),
2511 '<' if !attribute => writer.push_str("<"),
2512 '>' if !attribute => writer.push_str(">"),
2513 _ => writer.push(ch),
2514 }
2515 }
2516}
2517
2518#[cfg(test)]
2519mod test {
2520 use style_dom::ElementState;
2521
2522 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
2523
2524 #[test]
2525 fn absolute_position_tolerates_a_detached_layout_parent() {
2526 let mut document = BaseDocument::new(DocumentConfig::default());
2527 let parent = document.create_node(NodeData::Element(Box::new(ElementData::new(
2528 qual_name!("section"),
2529 vec![],
2530 ))));
2531 let child = document.create_node(NodeData::Element(Box::new(ElementData::new(
2532 qual_name!("button"),
2533 vec![],
2534 ))));
2535 document
2536 .get_node(child)
2537 .unwrap()
2538 .layout_parent
2539 .set(Some(parent));
2540
2541 document.remove_node_from_tree(parent);
2546
2547 assert_eq!(
2548 document
2549 .get_node(child)
2550 .unwrap()
2551 .absolute_position(4.0, 7.0),
2552 crate::util::Point { x: 4.0, y: 7.0 },
2553 );
2554 }
2555
2556 #[test]
2557 fn create_node_with_disabled_attr() {
2558 let mut document = BaseDocument::new(DocumentConfig::default());
2559 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2560 qual_name!("button"),
2561 vec![Attribute {
2562 name: qual_name!("disabled"),
2563 value: "".into(),
2564 }],
2565 ))));
2566 let node = document.get_node(node).unwrap();
2567
2568 assert!(
2569 node.element_state().contains(ElementState::DISABLED),
2570 "form node is disabled"
2571 );
2572 assert!(
2573 !node.element_state().contains(ElementState::ENABLED),
2574 "form node is not enabled"
2575 );
2576 }
2577
2578 #[test]
2579 fn ignore_disabled_attr_content() {
2580 let mut document = BaseDocument::new(DocumentConfig::default());
2581 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2582 qual_name!("button"),
2583 vec![Attribute {
2584 name: qual_name!("disabled"),
2585 value: "false".into(),
2586 }],
2587 ))));
2588 let node = document.get_node(node).unwrap();
2589
2590 assert!(
2591 node.element_state().contains(ElementState::DISABLED),
2592 "form node is disabled"
2593 );
2594 assert!(
2595 !node.element_state().contains(ElementState::ENABLED),
2596 "form node is not enabled"
2597 );
2598 }
2599
2600 #[test]
2601 fn create_node_with_ignored_disable() {
2602 let mut document = BaseDocument::new(DocumentConfig::default());
2603 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2604 qual_name!("a"),
2605 vec![Attribute {
2606 name: qual_name!("disabled"),
2607 value: "".into(),
2608 }],
2609 ))));
2610 let node = document.get_node(node).unwrap();
2611
2612 assert!(
2613 !node.element_state().contains(ElementState::DISABLED),
2614 "Non form node cannot be disabled"
2615 );
2616 assert!(
2617 !node.element_state().contains(ElementState::ENABLED),
2618 "Non form node cannot be enabled"
2619 );
2620 }
2621
2622 #[test]
2623 fn create_empty_enabled_node() {
2624 let mut document = BaseDocument::new(DocumentConfig::default());
2625 let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2626 qual_name!("button"),
2627 vec![],
2628 ))));
2629 let node = document.get_node(node).unwrap();
2630
2631 assert!(
2632 node.element_state().contains(ElementState::ENABLED),
2633 "Button should be enabled by default"
2634 );
2635 }
2636}