1use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
2use bitflags::bitflags;
3use blitz_traits::events::{BlitzMouseButtonEvent, DomEventData, HitResult};
4use keyboard_types::Modifiers;
5use markup5ever::{LocalName, local_name};
6use parley::Cluster;
7use peniko::kurbo;
8use selectors::matching::ElementSelectorFlags;
9use slab::Slab;
10use std::cell::{Cell, RefCell};
11use std::fmt::Write;
12use std::sync::atomic::AtomicBool;
13use style::Atom;
14use style::invalidation::element::restyle_hints::RestyleHint;
15use style::properties::ComputedValues;
16use style::properties::generated::longhands::position::computed_value::T as Position;
17use style::selector_parser::{PseudoElement, RestyleDamage};
18use style::stylesheets::UrlExtraData;
19use style::values::computed::Display as StyloDisplay;
20use style::values::specified::box_::{DisplayInside, DisplayOutside};
21use style::{data::ElementData as StyloElementData, shared_lock::SharedRwLock};
22use style_dom::ElementState;
23use style_traits::values::ToCss;
24use taffy::{
25 Cache,
26 prelude::{Layout, Style},
27};
28
29use super::{Attribute, ElementData};
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum DisplayOuter {
33 Block,
34 Inline,
35 None,
36}
37
38bitflags! {
39 #[derive(Clone, Copy, PartialEq)]
40 pub struct NodeFlags: u32 {
41 const IS_INLINE_ROOT = 0b00000001;
43 const IS_TABLE_ROOT = 0b00000010;
45 const IS_IN_DOCUMENT = 0b00000100;
47 }
48}
49
50impl NodeFlags {
51 #[inline(always)]
52 pub fn is_inline_root(&self) -> bool {
53 self.contains(Self::IS_INLINE_ROOT)
54 }
55
56 #[inline(always)]
57 pub fn is_table_root(&self) -> bool {
58 self.contains(Self::IS_TABLE_ROOT)
59 }
60
61 #[inline(always)]
62 pub fn is_in_document(&self) -> bool {
63 self.contains(Self::IS_IN_DOCUMENT)
64 }
65
66 #[inline(always)]
67 pub fn reset_construction_flags(&mut self) {
68 self.remove(Self::IS_INLINE_ROOT);
69 self.remove(Self::IS_TABLE_ROOT);
70 }
71}
72
73pub struct Node {
74 tree: *mut Slab<Node>,
76
77 pub id: usize,
79 pub parent: Option<usize>,
81 pub children: Vec<usize>,
83 pub layout_parent: Cell<Option<usize>>,
85 pub layout_children: RefCell<Option<Vec<usize>>>,
87 pub paint_children: RefCell<Option<Vec<usize>>>,
89
90 pub flags: NodeFlags,
92
93 pub data: NodeData,
95
96 pub stylo_element_data: AtomicRefCell<Option<StyloElementData>>,
99 pub selector_flags: AtomicRefCell<ElementSelectorFlags>,
100 pub guard: SharedRwLock,
101 pub element_state: ElementState,
102
103 pub before: Option<usize>,
105 pub after: Option<usize>,
106
107 pub style: Style<Atom>,
109 pub has_snapshot: bool,
110 pub snapshot_handled: AtomicBool,
111 pub display_constructed_as: StyloDisplay,
112 pub cache: Cache,
113 pub unrounded_layout: Layout,
114 pub final_layout: Layout,
115 pub scroll_offset: kurbo::Point,
116}
117
118unsafe impl Send for Node {}
119unsafe impl Sync for Node {}
120
121impl Node {
122 pub(crate) fn new(
123 tree: *mut Slab<Node>,
124 id: usize,
125 guard: SharedRwLock,
126 data: NodeData,
127 ) -> Self {
128 Self {
129 tree,
130
131 id,
132 parent: None,
133 children: vec![],
134 layout_parent: Cell::new(None),
135 layout_children: RefCell::new(None),
136 paint_children: RefCell::new(None),
137
138 flags: NodeFlags::empty(),
139 data,
140
141 stylo_element_data: Default::default(),
142 selector_flags: AtomicRefCell::new(ElementSelectorFlags::empty()),
143 guard,
144 element_state: ElementState::empty(),
145
146 before: None,
147 after: None,
148
149 style: Default::default(),
150 has_snapshot: false,
151 snapshot_handled: AtomicBool::new(false),
152 display_constructed_as: StyloDisplay::Block,
153 cache: Cache::new(),
154 unrounded_layout: Layout::new(),
155 final_layout: Layout::new(),
156 scroll_offset: kurbo::Point::ZERO,
157 }
158 }
159
160 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
161 match index {
162 0 => self.after,
163 1 => self.before,
164 _ => panic!("Invalid pseudo element index"),
165 }
166 }
167
168 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
169 match index {
170 0 => self.after = value,
171 1 => self.before = value,
172 _ => panic!("Invalid pseudo element index"),
173 }
174 }
175
176 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
177 Some(self.primary_styles().as_ref()?.clone_display())
178 }
179
180 pub fn is_or_contains_block(&self) -> bool {
181 let style = self.primary_styles();
182 let style = style.as_ref();
183
184 let position = style
186 .map(|s| s.clone_position())
187 .unwrap_or(Position::Relative);
188 let is_in_flow = matches!(
189 position,
190 Position::Static | Position::Relative | Position::Sticky
191 );
192 if !is_in_flow {
193 return false;
194 }
195 let display = style
196 .map(|s| s.clone_display())
197 .unwrap_or(StyloDisplay::inline());
198 match display.outside() {
199 DisplayOutside::None => false,
200 DisplayOutside::Block => true,
201 _ => {
202 if display.inside() == DisplayInside::Flow {
203 self.children
204 .iter()
205 .copied()
206 .any(|child_id| self.tree()[child_id].is_or_contains_block())
207 } else {
208 false
209 }
210 }
211 }
212 }
213
214 pub fn is_focussable(&self) -> bool {
215 self.data
216 .downcast_element()
217 .map(|el| el.is_focussable)
218 .unwrap_or(false)
219 }
220
221 pub fn set_restyle_hint(&self, hint: RestyleHint) {
222 if let Some(element_data) = self.stylo_element_data.borrow_mut().as_mut() {
223 element_data.hint.insert(hint);
224 }
225 }
226
227 pub fn damage_mut(&self) -> Option<AtomicRefMut<'_, RestyleDamage>> {
228 let element_data = self.stylo_element_data.borrow_mut();
229 #[allow(clippy::manual_map, reason = "false positive")]
230 match *element_data {
231 Some(_) => Some(AtomicRefMut::map(
232 element_data,
233 |data: &mut Option<StyloElementData>| &mut data.as_mut().unwrap().damage,
234 )),
235 None => None,
236 }
237 }
238
239 pub fn damage(&mut self) -> Option<RestyleDamage> {
240 self.stylo_element_data
241 .get_mut()
242 .as_ref()
243 .map(|data| data.damage)
244 }
245
246 pub fn set_damage(&self, damage: RestyleDamage) {
247 if let Some(data) = self.stylo_element_data.borrow_mut().as_mut() {
248 data.damage = damage;
249 }
250 }
251
252 pub fn insert_damage(&mut self, damage: RestyleDamage) {
253 if let Some(data) = self.stylo_element_data.get_mut().as_mut() {
254 data.damage |= damage;
255 }
256 }
257
258 pub fn remove_damage(&self, damage: RestyleDamage) {
259 if let Some(data) = self.stylo_element_data.borrow_mut().as_mut() {
260 data.damage.remove(damage);
261 }
262 }
263
264 pub fn clear_damage_mut(&mut self) {
265 if let Some(data) = self.stylo_element_data.get_mut() {
266 data.damage = RestyleDamage::empty();
267 }
268 }
269
270 pub fn hover(&mut self) {
271 self.element_state.insert(ElementState::HOVER);
272 self.set_restyle_hint(RestyleHint::restyle_subtree());
273 }
274
275 pub fn unhover(&mut self) {
276 self.element_state.remove(ElementState::HOVER);
277 self.set_restyle_hint(RestyleHint::restyle_subtree());
278 }
279
280 pub fn is_hovered(&self) -> bool {
281 self.element_state.contains(ElementState::HOVER)
282 }
283
284 pub fn focus(&mut self) {
285 self.element_state
286 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
287 self.set_restyle_hint(RestyleHint::restyle_subtree());
288 }
289
290 pub fn blur(&mut self) {
291 self.element_state
292 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
293 self.set_restyle_hint(RestyleHint::restyle_subtree());
294 }
295
296 pub fn is_focussed(&self) -> bool {
297 self.element_state.contains(ElementState::FOCUS)
298 }
299
300 pub fn active(&mut self) {
301 self.element_state.insert(ElementState::ACTIVE);
302 self.set_restyle_hint(RestyleHint::restyle_subtree());
303 }
304
305 pub fn unactive(&mut self) {
306 self.element_state.remove(ElementState::ACTIVE);
307 self.set_restyle_hint(RestyleHint::restyle_subtree());
308 }
309
310 pub fn is_active(&self) -> bool {
311 self.element_state.contains(ElementState::ACTIVE)
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq)]
316pub enum NodeKind {
317 Document,
318 Element,
319 AnonymousBlock,
320 Text,
321 Comment,
322}
323
324#[derive(Debug, Clone)]
326pub enum NodeData {
327 Document,
329
330 Element(ElementData),
332
333 AnonymousBlock(ElementData),
335
336 Text(TextNodeData),
338
339 Comment,
341 }
350
351impl NodeData {
352 pub fn downcast_element(&self) -> Option<&ElementData> {
353 match self {
354 Self::Element(data) => Some(data),
355 Self::AnonymousBlock(data) => Some(data),
356 _ => None,
357 }
358 }
359
360 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
361 match self {
362 Self::Element(data) => Some(data),
363 Self::AnonymousBlock(data) => Some(data),
364 _ => None,
365 }
366 }
367
368 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
369 let Some(elem) = self.downcast_element() else {
370 return false;
371 };
372 *name == elem.name.local
373 }
374
375 pub fn attrs(&self) -> Option<&[Attribute]> {
376 Some(&self.downcast_element()?.attrs)
377 }
378
379 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
380 self.downcast_element()?.attr(name)
381 }
382
383 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
384 self.downcast_element()
385 .is_some_and(|elem| elem.has_attr(name))
386 }
387
388 pub fn kind(&self) -> NodeKind {
389 match self {
390 NodeData::Document => NodeKind::Document,
391 NodeData::Element(_) => NodeKind::Element,
392 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
393 NodeData::Text(_) => NodeKind::Text,
394 NodeData::Comment => NodeKind::Comment,
395 }
396 }
397}
398
399#[derive(Debug, Clone)]
400pub struct TextNodeData {
401 pub content: String,
403}
404
405impl TextNodeData {
406 pub fn new(content: String) -> Self {
407 Self { content }
408 }
409}
410
411impl Node {
436 pub fn tree(&self) -> &Slab<Node> {
437 unsafe { &*self.tree }
438 }
439
440 #[track_caller]
441 pub fn with(&self, id: usize) -> &Node {
442 self.tree().get(id).unwrap()
443 }
444
445 pub fn print_tree(&self, level: usize) {
446 println!(
447 "{} {} {:?} {} {:?}",
448 " ".repeat(level),
449 self.id,
450 self.parent,
451 self.node_debug_str().replace('\n', ""),
452 self.children
453 );
454 for child_id in self.children.iter() {
456 let child = self.with(*child_id);
457 child.print_tree(level + 1)
458 }
459 }
460
461 pub fn index_of_child(&self, child_id: usize) -> Option<usize> {
463 self.children.iter().position(|id| *id == child_id)
464 }
465
466 pub fn child_index(&self) -> Option<usize> {
468 self.tree()[self.parent?]
469 .children
470 .iter()
471 .position(|id| *id == self.id)
472 }
473
474 pub fn forward(&self, n: usize) -> Option<&Node> {
476 let child_idx = self.child_index().unwrap_or(0);
477 self.tree()[self.parent?]
478 .children
479 .get(child_idx + n)
480 .map(|id| self.with(*id))
481 }
482
483 pub fn backward(&self, n: usize) -> Option<&Node> {
484 let child_idx = self.child_index().unwrap_or(0);
485 if child_idx < n {
486 return None;
487 }
488
489 self.tree()[self.parent?]
490 .children
491 .get(child_idx - n)
492 .map(|id| self.with(*id))
493 }
494
495 pub fn is_element(&self) -> bool {
496 matches!(self.data, NodeData::Element { .. })
497 }
498
499 pub fn is_anonymous(&self) -> bool {
500 matches!(self.data, NodeData::AnonymousBlock { .. })
501 }
502
503 pub fn is_text_node(&self) -> bool {
504 matches!(self.data, NodeData::Text { .. })
505 }
506
507 pub fn element_data(&self) -> Option<&ElementData> {
508 match self.data {
509 NodeData::Element(ref data) => Some(data),
510 NodeData::AnonymousBlock(ref data) => Some(data),
511 _ => None,
512 }
513 }
514
515 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
516 match self.data {
517 NodeData::Element(ref mut data) => Some(data),
518 NodeData::AnonymousBlock(ref mut data) => Some(data),
519 _ => None,
520 }
521 }
522
523 pub fn text_data(&self) -> Option<&TextNodeData> {
524 match self.data {
525 NodeData::Text(ref data) => Some(data),
526 _ => None,
527 }
528 }
529
530 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
531 match self.data {
532 NodeData::Text(ref mut data) => Some(data),
533 _ => None,
534 }
535 }
536
537 pub fn node_debug_str(&self) -> String {
538 let mut s = String::new();
539
540 match &self.data {
541 NodeData::Document => write!(s, "DOCUMENT"),
542 NodeData::Text(data) => {
544 let bytes = data.content.as_bytes();
545 write!(
546 s,
547 "TEXT {}",
548 &std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
549 .unwrap_or("INVALID UTF8")
550 )
551 }
552 NodeData::Comment => write!(
553 s,
554 "COMMENT",
555 ),
557 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
558 NodeData::Element(data) => {
559 let name = &data.name;
560 let class = self.attr(local_name!("class")).unwrap_or("");
561 let id = self.attr(local_name!("id")).unwrap_or("");
562 let display = self.display_constructed_as.to_css_string();
563 write!(s, "<{}", name.local).unwrap();
564 if !id.is_empty() {
565 write!(s, " #{id}").unwrap();
566 }
567 if !class.is_empty() {
568 if class.contains(' ') {
569 write!(s, " class=\"{class}\"").unwrap()
570 } else {
571 write!(s, " .{class}").unwrap()
572 }
573 }
574 write!(s, "> ({display})")
575 } }
577 .unwrap();
578 s
579 }
580
581 pub fn outer_html(&self) -> String {
582 let mut output = String::new();
583 self.write_outer_html(&mut output);
584 output
585 }
586
587 pub fn write_outer_html(&self, writer: &mut String) {
588 let has_children = !self.children.is_empty();
589 let current_color = self
590 .primary_styles()
591 .map(|style| style.clone_color())
592 .map(|color| color.to_css_string());
593
594 match &self.data {
595 NodeData::Document => {}
596 NodeData::Comment => {}
597 NodeData::AnonymousBlock(_) => {}
598 NodeData::Text(data) => {
600 writer.push_str(data.content.as_str());
601 }
602 NodeData::Element(data) => {
603 writer.push('<');
604 writer.push_str(&data.name.local);
605
606 for attr in data.attrs() {
607 writer.push(' ');
608 writer.push_str(&attr.name.local);
609 writer.push_str("=\"");
610 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
612 writer.push_str(
613 &attr
614 .value
615 .replace("currentColor", current_color.as_ref().unwrap()),
616 );
617 } else {
618 writer.push_str(&attr.value);
619 }
620 writer.push('"');
621 }
622 if !has_children {
623 writer.push_str(" /");
624 }
625 writer.push('>');
626
627 if has_children {
628 for &child_id in &self.children {
629 self.tree()[child_id].write_outer_html(writer);
630 }
631
632 writer.push_str("</");
633 writer.push_str(&data.name.local);
634 writer.push('>');
635 }
636 }
637 }
638 }
639
640 pub fn attrs(&self) -> Option<&[Attribute]> {
641 Some(&self.element_data()?.attrs)
642 }
643
644 pub fn attr(&self, name: LocalName) -> Option<&str> {
645 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
646 Some(&attr.value)
647 }
648
649 pub fn primary_styles(&self) -> Option<AtomicRef<'_, ComputedValues>> {
650 let stylo_element_data = self.stylo_element_data.borrow();
651 if stylo_element_data
652 .as_ref()
653 .and_then(|d| d.styles.get_primary())
654 .is_some()
655 {
656 Some(AtomicRef::map(
657 stylo_element_data,
658 |data: &Option<StyloElementData>| -> &ComputedValues {
659 data.as_ref().unwrap().styles.get_primary().unwrap()
660 },
661 ))
662 } else {
663 None
664 }
665 }
666
667 pub fn text_content(&self) -> String {
668 let mut out = String::new();
669 self.write_text_content(&mut out);
670 out
671 }
672
673 fn write_text_content(&self, out: &mut String) {
674 match &self.data {
675 NodeData::Text(data) => {
676 out.push_str(&data.content);
677 }
678 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
679 for child_id in self.children.iter() {
680 self.with(*child_id).write_text_content(out);
681 }
682 }
683 _ => {}
684 }
685 }
686
687 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
688 if let NodeData::Element(ref mut elem_data) = self.data {
689 elem_data.flush_style_attribute(&self.guard, url_extra_data);
690 }
691 }
692
693 pub fn order(&self) -> i32 {
694 self.primary_styles()
695 .map(|s| match s.pseudo() {
696 Some(PseudoElement::Before) => i32::MIN,
697 Some(PseudoElement::After) => i32::MAX,
698 _ => s.clone_order(),
699 })
700 .unwrap_or(0)
701 }
702
703 pub fn z_index(&self) -> i32 {
704 self.primary_styles()
705 .map(|s| s.clone_z_index().integer_or(0))
706 .unwrap_or(0)
707 }
708
709 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
718 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
719 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
720
721 let size = self.final_layout.size;
722 let matches_self = !(x < 0.0
723 || x > size.width + self.scroll_offset.x as f32
724 || y < 0.0
725 || y > size.height + self.scroll_offset.y as f32);
726
727 let content_size = self.final_layout.content_size;
728 let matches_content = !(x < 0.0
729 || x > content_size.width + self.scroll_offset.x as f32
730 || y < 0.0
731 || y > content_size.height + self.scroll_offset.y as f32);
732
733 if !matches_self && !matches_content {
734 return None;
735 }
736
737 if self.flags.is_inline_root() {
738 let content_box_offset = taffy::Point {
739 x: self.final_layout.padding.left + self.final_layout.border.left,
740 y: self.final_layout.padding.top + self.final_layout.border.top,
741 };
742 x -= content_box_offset.x;
743 y -= content_box_offset.y;
744 }
745
746 self.paint_children
748 .borrow()
749 .iter()
750 .flatten()
751 .rev()
752 .find_map(|&i| self.with(i).hit(x, y))
753 .or_else(|| {
754 if self.flags.is_inline_root() {
755 let element_data = &self.element_data().unwrap();
756 let layout = &element_data.inline_layout_data.as_ref().unwrap().layout;
757 let scale = layout.scale();
758
759 Cluster::from_point(layout, x * scale, y * scale).and_then(|(cluster, _)| {
760 let style_index = cluster.glyphs().next()?.style_index();
761 let node_id = layout.styles()[style_index].brush.id;
762 Some(HitResult { node_id, x, y })
763 })
764 } else {
765 None
766 }
767 })
768 .or(Some(HitResult {
769 node_id: self.id,
770 x,
771 y,
772 })
773 .filter(|_| matches_self))
774 }
775
776 pub fn absolute_position(&self, x: f32, y: f32) -> taffy::Point<f32> {
778 let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
779 let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
780
781 self.layout_parent
783 .get()
784 .map(|i| self.with(i).absolute_position(x, y))
785 .unwrap_or(taffy::Point { x, y })
786 }
787
788 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
790 DomEventData::Click(self.synthetic_click_event_data(mods))
791 }
792
793 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzMouseButtonEvent {
794 let absolute_position = self.absolute_position(0.0, 0.0);
795 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
796 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
797
798 BlitzMouseButtonEvent {
799 x,
800 y,
801 mods,
802 button: Default::default(),
803 buttons: Default::default(),
804 }
805 }
806}
807
808impl PartialEq for Node {
810 fn eq(&self, other: &Self) -> bool {
811 self.id == other.id
812 }
813}
814
815impl Eq for Node {}
816
817impl std::fmt::Debug for Node {
818 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819 f.debug_struct("NodeData")
821 .field("parent", &self.parent)
822 .field("id", &self.id)
823 .field("is_inline_root", &self.flags.is_inline_root())
824 .field("children", &self.children)
825 .field("layout_children", &self.layout_children.borrow())
826 .field("node", &self.data)
828 .field("stylo_element_data", &self.stylo_element_data)
829 .finish()
832 }
833}