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