1use atomic_refcell::{AtomicRef, AtomicRefCell};
2use color::{AlphaColor, Srgb};
3use keyboard_types::Modifiers;
4use markup5ever::{LocalName, QualName, local_name};
5use parley::{Cluster, FontContext, LayoutContext};
6use peniko::kurbo;
7use selectors::matching::{ElementSelectorFlags, QuirksMode};
8use slab::Slab;
9use std::cell::{Cell, RefCell};
10use std::fmt::Write;
11use std::str::FromStr;
12use std::sync::Arc;
13use std::sync::atomic::AtomicBool;
14use style::Atom;
15use style::invalidation::element::restyle_hints::RestyleHint;
16use style::properties::ComputedValues;
17use style::properties::generated::longhands::position::computed_value::T as Position;
18use style::selector_parser::PseudoElement;
19use style::stylesheets::UrlExtraData;
20use style::values::computed::Display;
21use style::values::specified::box_::{DisplayInside, DisplayOutside};
22use style::{
23 data::ElementData,
24 properties::{PropertyDeclarationBlock, parse_style_attribute},
25 servo_arc::Arc as ServoArc,
26 shared_lock::{Locked, SharedRwLock},
27 stylesheets::CssRuleType,
28};
29use style_dom::ElementState;
30use style_traits::values::ToCss;
31use taffy::{
32 Cache,
33 prelude::{Layout, Style},
34};
35use url::Url;
36
37use crate::layout::table::TableContext;
38use blitz_traits::{BlitzMouseButtonEvent, DomEventData, HitResult};
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum DisplayOuter {
42 Block,
43 Inline,
44 None,
45}
46
47pub struct Node {
49 tree: *mut Slab<Node>,
51
52 pub id: usize,
54 pub parent: Option<usize>,
56 pub children: Vec<usize>,
58 pub layout_parent: Cell<Option<usize>>,
60 pub layout_children: RefCell<Option<Vec<usize>>>,
62 pub paint_children: RefCell<Option<Vec<usize>>>,
64
65 pub data: NodeData,
67
68 pub stylo_element_data: AtomicRefCell<Option<ElementData>>,
71 pub selector_flags: AtomicRefCell<ElementSelectorFlags>,
72 pub guard: SharedRwLock,
73 pub element_state: ElementState,
74
75 pub before: Option<usize>,
77 pub after: Option<usize>,
78
79 pub style: Style,
81 pub has_snapshot: bool,
82 pub snapshot_handled: AtomicBool,
83 pub display_outer: DisplayOuter,
84 pub cache: Cache,
85 pub unrounded_layout: Layout,
86 pub final_layout: Layout,
87 pub scroll_offset: kurbo::Point,
88
89 pub is_inline_root: bool,
91 pub is_table_root: bool,
92}
93
94impl Node {
95 pub(crate) fn new(
96 tree: *mut Slab<Node>,
97 id: usize,
98 guard: SharedRwLock,
99 data: NodeData,
100 ) -> Self {
101 Self {
102 tree,
103
104 id,
105 parent: None,
106 children: vec![],
107 layout_parent: Cell::new(None),
108 layout_children: RefCell::new(None),
109 paint_children: RefCell::new(None),
110
111 data,
112 stylo_element_data: Default::default(),
113 selector_flags: AtomicRefCell::new(ElementSelectorFlags::empty()),
114 guard,
115 element_state: ElementState::empty(),
116
117 before: None,
118 after: None,
119
120 style: Default::default(),
121 has_snapshot: false,
122 snapshot_handled: AtomicBool::new(false),
123 display_outer: DisplayOuter::Block,
124 cache: Cache::new(),
125 unrounded_layout: Layout::new(),
126 final_layout: Layout::new(),
127 scroll_offset: kurbo::Point::ZERO,
128 is_inline_root: false,
129 is_table_root: false,
130 }
131 }
132
133 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
134 match index {
135 0 => self.after,
136 1 => self.before,
137 _ => panic!("Invalid pseudo element index"),
138 }
139 }
140
141 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
142 match index {
143 0 => self.after = value,
144 1 => self.before = value,
145 _ => panic!("Invalid pseudo element index"),
146 }
147 }
148
149 pub(crate) fn display_style(&self) -> Option<Display> {
150 Some(self.primary_styles().as_ref()?.clone_display())
151 }
152
153 pub fn is_or_contains_block(&self) -> bool {
154 let style = self.primary_styles();
155 let style = style.as_ref();
156
157 let position = style
159 .map(|s| s.clone_position())
160 .unwrap_or(Position::Relative);
161 let is_in_flow = matches!(
162 position,
163 Position::Static | Position::Relative | Position::Sticky
164 );
165 if !is_in_flow {
166 return false;
167 }
168 let display = style
169 .map(|s| s.clone_display())
170 .unwrap_or(Display::inline());
171 match display.outside() {
172 DisplayOutside::None => false,
173 DisplayOutside::Block => true,
174 _ => {
175 if display.inside() == DisplayInside::Flow {
176 self.children
177 .iter()
178 .copied()
179 .any(|child_id| self.tree()[child_id].is_or_contains_block())
180 } else {
181 false
182 }
183 }
184 }
185 }
186
187 pub fn is_focussable(&self) -> bool {
188 self.data
189 .downcast_element()
190 .map(|el| el.is_focussable)
191 .unwrap_or(false)
192 }
193
194 pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
195 if let Some(element_data) = self.stylo_element_data.borrow_mut().as_mut() {
196 element_data.hint.insert(hint);
197 }
198 }
199
200 pub fn hover(&mut self) {
201 self.element_state.insert(ElementState::HOVER);
202 self.set_restyle_hint(RestyleHint::restyle_subtree());
203 }
204
205 pub fn unhover(&mut self) {
206 self.element_state.remove(ElementState::HOVER);
207 self.set_restyle_hint(RestyleHint::restyle_subtree());
208 }
209
210 pub fn is_hovered(&self) -> bool {
211 self.element_state.contains(ElementState::HOVER)
212 }
213
214 pub fn focus(&mut self) {
215 self.element_state
216 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
217 self.set_restyle_hint(RestyleHint::restyle_subtree());
218 }
219
220 pub fn blur(&mut self) {
221 self.element_state
222 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
223 self.set_restyle_hint(RestyleHint::restyle_subtree());
224 }
225
226 pub fn is_focussed(&self) -> bool {
227 self.element_state.contains(ElementState::FOCUS)
228 }
229
230 pub fn active(&mut self) {
231 self.element_state.insert(ElementState::ACTIVE);
232 self.set_restyle_hint(RestyleHint::restyle_subtree());
233 }
234
235 pub fn unactive(&mut self) {
236 self.element_state.remove(ElementState::ACTIVE);
237 self.set_restyle_hint(RestyleHint::restyle_subtree());
238 }
239
240 pub fn is_active(&self) -> bool {
241 self.element_state.contains(ElementState::ACTIVE)
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq)]
246pub enum NodeKind {
247 Document,
248 Element,
249 AnonymousBlock,
250 Text,
251 Comment,
252}
253
254#[derive(Debug, Clone)]
256pub enum NodeData {
257 Document,
259
260 Element(ElementNodeData),
262
263 AnonymousBlock(ElementNodeData),
265
266 Text(TextNodeData),
268
269 Comment,
271 }
280
281impl NodeData {
282 pub fn downcast_element(&self) -> Option<&ElementNodeData> {
283 match self {
284 Self::Element(data) => Some(data),
285 Self::AnonymousBlock(data) => Some(data),
286 _ => None,
287 }
288 }
289
290 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementNodeData> {
291 match self {
292 Self::Element(data) => Some(data),
293 Self::AnonymousBlock(data) => Some(data),
294 _ => None,
295 }
296 }
297
298 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
299 let Some(elem) = self.downcast_element() else {
300 return false;
301 };
302 *name == elem.name.local
303 }
304
305 pub fn attrs(&self) -> Option<&[Attribute]> {
306 Some(&self.downcast_element()?.attrs)
307 }
308
309 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
310 self.downcast_element()?.attr(name)
311 }
312
313 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
314 self.downcast_element()
315 .is_some_and(|elem| elem.has_attr(name))
316 }
317
318 pub fn kind(&self) -> NodeKind {
319 match self {
320 NodeData::Document => NodeKind::Document,
321 NodeData::Element(_) => NodeKind::Element,
322 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
323 NodeData::Text(_) => NodeKind::Text,
324 NodeData::Comment => NodeKind::Comment,
325 }
326 }
327}
328
329#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
336pub struct Attribute {
337 pub name: QualName,
339 pub value: String,
341}
342
343#[derive(Debug, Clone)]
344pub struct ElementNodeData {
345 pub name: QualName,
347
348 pub id: Option<Atom>,
350
351 pub attrs: Vec<Attribute>,
353
354 pub is_focussable: bool,
356
357 pub style_attribute: Option<ServoArc<Locked<PropertyDeclarationBlock>>>,
359
360 pub node_specific_data: NodeSpecificData,
366
367 pub background_images: Vec<Option<BackgroundImageData>>,
368
369 pub inline_layout_data: Option<Box<TextLayout>>,
371
372 pub list_item_data: Option<Box<ListItemLayout>>,
375
376 pub template_contents: Option<usize>,
378 }
381
382impl ElementNodeData {
383 pub fn new(name: QualName, attrs: Vec<Attribute>) -> Self {
384 let id_attr_atom = attrs
385 .iter()
386 .find(|attr| &attr.name.local == "id")
387 .map(|attr| attr.value.as_ref())
388 .map(|value: &str| Atom::from(value));
389
390 let mut data = ElementNodeData {
391 name,
392 id: id_attr_atom,
393 attrs,
394 is_focussable: false,
395 style_attribute: Default::default(),
396 inline_layout_data: None,
397 list_item_data: None,
398 node_specific_data: NodeSpecificData::None,
399 template_contents: None,
400 background_images: Vec::new(),
401 };
402 data.flush_is_focussable();
403 data
404 }
405
406 pub fn attrs(&self) -> &[Attribute] {
407 &self.attrs
408 }
409
410 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
411 let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
412 Some(&attr.value)
413 }
414
415 pub fn attr_parsed<T: FromStr>(&self, name: impl PartialEq<LocalName>) -> Option<T> {
416 let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
417 attr.value.parse::<T>().ok()
418 }
419
420 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
422 self.attrs.iter().any(|attr| name == attr.name.local)
423 }
424
425 pub fn image_data(&self) -> Option<&ImageData> {
426 match &self.node_specific_data {
427 NodeSpecificData::Image(data) => Some(&**data),
428 _ => None,
429 }
430 }
431
432 pub fn image_data_mut(&mut self) -> Option<&mut ImageData> {
433 match self.node_specific_data {
434 NodeSpecificData::Image(ref mut data) => Some(&mut **data),
435 _ => None,
436 }
437 }
438
439 pub fn raster_image_data(&self) -> Option<&RasterImageData> {
440 match self.image_data()? {
441 ImageData::Raster(data) => Some(data),
442 _ => None,
443 }
444 }
445
446 pub fn raster_image_data_mut(&mut self) -> Option<&mut RasterImageData> {
447 match self.image_data_mut()? {
448 ImageData::Raster(data) => Some(data),
449 _ => None,
450 }
451 }
452
453 #[cfg(feature = "svg")]
454 pub fn svg_data(&self) -> Option<&usvg::Tree> {
455 match self.image_data()? {
456 ImageData::Svg(data) => Some(data),
457 _ => None,
458 }
459 }
460
461 #[cfg(feature = "svg")]
462 pub fn svg_data_mut(&mut self) -> Option<&mut usvg::Tree> {
463 match self.image_data_mut()? {
464 ImageData::Svg(data) => Some(data),
465 _ => None,
466 }
467 }
468
469 pub fn text_input_data(&self) -> Option<&TextInputData> {
470 match &self.node_specific_data {
471 NodeSpecificData::TextInput(data) => Some(data),
472 _ => None,
473 }
474 }
475
476 pub fn text_input_data_mut(&mut self) -> Option<&mut TextInputData> {
477 match &mut self.node_specific_data {
478 NodeSpecificData::TextInput(data) => Some(data),
479 _ => None,
480 }
481 }
482
483 pub fn checkbox_input_checked(&self) -> Option<bool> {
484 match self.node_specific_data {
485 NodeSpecificData::CheckboxInput(checked) => Some(checked),
486 _ => None,
487 }
488 }
489
490 pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
491 match self.node_specific_data {
492 NodeSpecificData::CheckboxInput(ref mut checked) => Some(checked),
493 _ => None,
494 }
495 }
496
497 pub fn flush_is_focussable(&mut self) {
498 let disabled: bool = self.attr_parsed(local_name!("disabled")).unwrap_or(false);
499 let tabindex: Option<i32> = self.attr_parsed(local_name!("tabindex"));
500
501 self.is_focussable = !disabled
502 && match tabindex {
503 Some(index) => index >= 0,
504 None => {
505 if [local_name!("a"), local_name!("area")].contains(&self.name.local) {
512 self.attr(local_name!("href")).is_some()
513 } else {
514 const DEFAULT_FOCUSSABLE_ELEMENTS: [LocalName; 6] = [
515 local_name!("button"),
516 local_name!("input"),
517 local_name!("select"),
518 local_name!("textarea"),
519 local_name!("frame"),
520 local_name!("iframe"),
521 ];
522 DEFAULT_FOCUSSABLE_ELEMENTS.contains(&self.name.local)
523 }
524 }
525 }
526 }
527
528 pub fn flush_style_attribute(&mut self, guard: &SharedRwLock, base_url: Option<Url>) {
529 self.style_attribute = self.attr(local_name!("style")).map(|style_str| {
530 let url = UrlExtraData::from(base_url.clone().unwrap_or_else(|| {
531 "data:text/css;charset=utf-8;base64,"
532 .parse::<Url>()
533 .unwrap()
534 }));
535
536 ServoArc::new(guard.wrap(parse_style_attribute(
537 style_str,
538 &url,
539 None,
540 QuirksMode::NoQuirks,
541 CssRuleType::Style,
542 )))
543 });
544 }
545
546 pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
547 std::mem::take(&mut self.inline_layout_data)
548 }
549}
550
551#[derive(Debug, Clone, PartialEq, Default)]
552pub struct RasterImageData {
553 pub width: u32,
555 pub height: u32,
557 pub data: Arc<Vec<u8>>,
559}
560impl RasterImageData {
561 pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
562 Self {
563 width,
564 height,
565 data,
566 }
567 }
568}
569
570#[derive(Debug, Clone)]
571pub enum ImageData {
572 Raster(RasterImageData),
573 #[cfg(feature = "svg")]
574 Svg(Box<usvg::Tree>),
575 None,
576}
577#[cfg(feature = "svg")]
578impl From<usvg::Tree> for ImageData {
579 fn from(value: usvg::Tree) -> Self {
580 Self::Svg(Box::new(value))
581 }
582}
583
584#[derive(Debug, Clone, PartialEq)]
585pub enum Status {
586 Ok,
587 Error,
588 Loading,
589}
590
591#[derive(Debug, Clone)]
592pub struct BackgroundImageData {
593 pub url: ServoArc<Url>,
595 pub status: Status,
597 pub image: ImageData,
599}
600
601impl BackgroundImageData {
602 pub fn new(url: ServoArc<Url>) -> Self {
603 Self {
604 url,
605 status: Status::Loading,
606 image: ImageData::None,
607 }
608 }
609}
610
611pub struct TextInputData {
612 pub editor: Box<parley::PlainEditor<TextBrush>>,
614 pub is_multiline: bool,
616}
617
618impl Clone for TextInputData {
620 fn clone(&self) -> Self {
621 TextInputData::new(self.is_multiline)
622 }
623}
624
625impl TextInputData {
626 pub fn new(is_multiline: bool) -> Self {
627 let editor = Box::new(parley::PlainEditor::new(16.0));
628 Self {
629 editor,
630 is_multiline,
631 }
632 }
633
634 pub fn set_text(
635 &mut self,
636 font_ctx: &mut FontContext,
637 layout_ctx: &mut LayoutContext<TextBrush>,
638 text: &str,
639 ) {
640 if self.editor.text() != text {
641 self.editor.set_text(text);
642 self.editor.driver(font_ctx, layout_ctx).refresh_layout();
643 }
644 }
645}
646
647#[derive(Clone)]
649pub enum NodeSpecificData {
650 Image(Box<ImageData>),
652 TableRoot(Arc<TableContext>),
654 TextInput(TextInputData),
656 CheckboxInput(bool),
658 None,
660}
661
662impl std::fmt::Debug for NodeSpecificData {
663 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
664 match self {
665 NodeSpecificData::Image(data) => match **data {
666 ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
667 #[cfg(feature = "svg")]
668 ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
669 ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
670 },
671 NodeSpecificData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
672 NodeSpecificData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
673 NodeSpecificData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
674 NodeSpecificData::None => f.write_str("NodeSpecificData::None"),
675 }
676 }
677}
678
679impl Default for NodeSpecificData {
680 fn default() -> Self {
681 Self::None
682 }
683}
684
685#[derive(Clone)]
686pub struct ListItemLayout {
687 pub marker: Marker,
688 pub position: ListItemLayoutPosition,
689}
690
691#[derive(Debug, PartialEq, Clone)]
694pub enum Marker {
695 Char(char),
696 String(String),
697}
698
699#[derive(Clone)]
701pub enum ListItemLayoutPosition {
702 Inside,
703 Outside(Box<parley::Layout<TextBrush>>),
704}
705
706impl std::fmt::Debug for ListItemLayout {
707 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708 write!(f, "ListItemLayout - marker {:?}", self.marker)
709 }
710}
711
712#[derive(Debug, Clone, Default, PartialEq)]
713pub struct TextBrush {
715 pub id: usize,
717 pub brush: peniko::Brush,
719}
720
721impl TextBrush {
722 pub(crate) fn from_peniko_brush(brush: peniko::Brush) -> Self {
723 Self { id: 0, brush }
724 }
725 pub(crate) fn from_color(color: AlphaColor<Srgb>) -> Self {
726 Self::from_peniko_brush(peniko::Brush::Solid(color))
727 }
728 pub(crate) fn from_id_and_color(id: usize, color: AlphaColor<Srgb>) -> Self {
729 Self {
730 id,
731 brush: peniko::Brush::Solid(color),
732 }
733 }
734}
735
736#[derive(Clone)]
737pub struct TextLayout {
738 pub text: String,
739 pub layout: parley::layout::Layout<TextBrush>,
740}
741
742impl std::fmt::Debug for TextLayout {
743 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744 write!(f, "TextLayout")
745 }
746}
747
748#[derive(Debug, Clone)]
749pub struct TextNodeData {
750 pub content: String,
752}
753
754impl TextNodeData {
755 pub fn new(content: String) -> Self {
756 Self { content }
757 }
758}
759
760impl Node {
785 pub fn tree(&self) -> &Slab<Node> {
786 unsafe { &*self.tree }
787 }
788
789 #[track_caller]
790 pub fn with(&self, id: usize) -> &Node {
791 self.tree().get(id).unwrap()
792 }
793
794 pub fn print_tree(&self, level: usize) {
795 println!(
796 "{} {} {:?} {} {:?}",
797 " ".repeat(level),
798 self.id,
799 self.parent,
800 self.node_debug_str().replace('\n', ""),
801 self.children
802 );
803 for child_id in self.children.iter() {
805 let child = self.with(*child_id);
806 child.print_tree(level + 1)
807 }
808 }
809
810 pub fn child_index(&self) -> Option<usize> {
812 self.tree()[self.parent?]
813 .children
814 .iter()
815 .position(|id| *id == self.id)
816 }
817
818 pub fn forward(&self, n: usize) -> Option<&Node> {
820 let child_idx = self.child_index().unwrap_or(0);
821 self.tree()[self.parent?]
822 .children
823 .get(child_idx + n)
824 .map(|id| self.with(*id))
825 }
826
827 pub fn backward(&self, n: usize) -> Option<&Node> {
828 let child_idx = self.child_index().unwrap_or(0);
829 if child_idx < n {
830 return None;
831 }
832
833 self.tree()[self.parent?]
834 .children
835 .get(child_idx - n)
836 .map(|id| self.with(*id))
837 }
838
839 pub fn is_element(&self) -> bool {
840 matches!(self.data, NodeData::Element { .. })
841 }
842
843 pub fn is_anonymous(&self) -> bool {
844 matches!(self.data, NodeData::AnonymousBlock { .. })
845 }
846
847 pub fn is_text_node(&self) -> bool {
848 matches!(self.data, NodeData::Text { .. })
849 }
850
851 pub fn element_data(&self) -> Option<&ElementNodeData> {
852 match self.data {
853 NodeData::Element(ref data) => Some(data),
854 NodeData::AnonymousBlock(ref data) => Some(data),
855 _ => None,
856 }
857 }
858
859 pub fn element_data_mut(&mut self) -> Option<&mut ElementNodeData> {
860 match self.data {
861 NodeData::Element(ref mut data) => Some(data),
862 NodeData::AnonymousBlock(ref mut data) => Some(data),
863 _ => None,
864 }
865 }
866
867 pub fn text_data(&self) -> Option<&TextNodeData> {
868 match self.data {
869 NodeData::Text(ref data) => Some(data),
870 _ => None,
871 }
872 }
873
874 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
875 match self.data {
876 NodeData::Text(ref mut data) => Some(data),
877 _ => None,
878 }
879 }
880
881 pub fn node_debug_str(&self) -> String {
882 let mut s = String::new();
883
884 match &self.data {
885 NodeData::Document => write!(s, "DOCUMENT"),
886 NodeData::Text(data) => {
888 let bytes = data.content.as_bytes();
889 write!(
890 s,
891 "TEXT {}",
892 &std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
893 .unwrap_or("INVALID UTF8")
894 )
895 }
896 NodeData::Comment => write!(
897 s,
898 "COMMENT",
899 ),
901 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
902 NodeData::Element(data) => {
903 let name = &data.name;
904 let class = self.attr(local_name!("class")).unwrap_or("");
905 if !class.is_empty() {
906 write!(
907 s,
908 "<{} class=\"{}\"> ({:?})",
909 name.local, class, self.display_outer
910 )
911 } else {
912 write!(s, "<{}> ({:?})", name.local, self.display_outer)
913 }
914 } }
916 .unwrap();
917 s
918 }
919
920 pub fn outer_html(&self) -> String {
921 let mut output = String::new();
922 self.write_outer_html(&mut output);
923 output
924 }
925
926 pub fn write_outer_html(&self, writer: &mut String) {
927 let has_children = !self.children.is_empty();
928 let current_color = self
929 .primary_styles()
930 .map(|style| style.clone_color())
931 .map(|color| color.to_css_string());
932
933 match &self.data {
934 NodeData::Document => {}
935 NodeData::Comment => {}
936 NodeData::AnonymousBlock(_) => {}
937 NodeData::Text(data) => {
939 writer.push_str(data.content.as_str());
940 }
941 NodeData::Element(data) => {
942 writer.push('<');
943 writer.push_str(&data.name.local);
944
945 for attr in data.attrs() {
946 writer.push(' ');
947 writer.push_str(&attr.name.local);
948 writer.push_str("=\"");
949 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
951 writer.push_str(
952 &attr
953 .value
954 .replace("currentColor", current_color.as_ref().unwrap()),
955 );
956 } else {
957 writer.push_str(&attr.value);
958 }
959 writer.push('"');
960 }
961 if !has_children {
962 writer.push_str(" /");
963 }
964 writer.push('>');
965
966 if has_children {
967 for &child_id in &self.children {
968 self.tree()[child_id].write_outer_html(writer);
969 }
970
971 writer.push_str("</");
972 writer.push_str(&data.name.local);
973 writer.push('>');
974 }
975 }
976 }
977 }
978
979 pub fn attrs(&self) -> Option<&[Attribute]> {
980 Some(&self.element_data()?.attrs)
981 }
982
983 pub fn attr(&self, name: LocalName) -> Option<&str> {
984 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
985 Some(&attr.value)
986 }
987
988 pub fn primary_styles(&self) -> Option<AtomicRef<'_, ComputedValues>> {
989 let stylo_element_data = self.stylo_element_data.borrow();
990 if stylo_element_data
991 .as_ref()
992 .and_then(|d| d.styles.get_primary())
993 .is_some()
994 {
995 Some(AtomicRef::map(
996 stylo_element_data,
997 |data: &Option<ElementData>| -> &ComputedValues {
998 data.as_ref().unwrap().styles.get_primary().unwrap()
999 },
1000 ))
1001 } else {
1002 None
1003 }
1004 }
1005
1006 pub fn text_content(&self) -> String {
1007 let mut out = String::new();
1008 self.write_text_content(&mut out);
1009 out
1010 }
1011
1012 fn write_text_content(&self, out: &mut String) {
1013 match &self.data {
1014 NodeData::Text(data) => {
1015 out.push_str(&data.content);
1016 }
1017 NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1018 for child_id in self.children.iter() {
1019 self.with(*child_id).write_text_content(out);
1020 }
1021 }
1022 _ => {}
1023 }
1024 }
1025
1026 pub fn flush_style_attribute(&mut self, base_url: Option<Url>) {
1027 if let NodeData::Element(ref mut elem_data) = self.data {
1028 elem_data.flush_style_attribute(&self.guard, base_url);
1029 }
1030 }
1031
1032 pub fn order(&self) -> i32 {
1033 self.primary_styles()
1034 .map(|s| match s.pseudo() {
1035 Some(PseudoElement::Before) => i32::MIN,
1036 Some(PseudoElement::After) => i32::MAX,
1037 _ => s.clone_order(),
1038 })
1039 .unwrap_or(0)
1040 }
1041
1042 pub fn z_index(&self) -> i32 {
1043 self.primary_styles()
1044 .map(|s| s.clone_z_index().integer_or(0))
1045 .unwrap_or(0)
1046 }
1047
1048 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1057 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
1058 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
1059
1060 let size = self.final_layout.size;
1061 let matches_self = !(x < 0.0
1062 || x > size.width + self.scroll_offset.x as f32
1063 || y < 0.0
1064 || y > size.height + self.scroll_offset.y as f32);
1065
1066 let content_size = self.final_layout.content_size;
1067 let matches_content = !(x < 0.0
1068 || x > content_size.width + self.scroll_offset.x as f32
1069 || y < 0.0
1070 || y > content_size.height + self.scroll_offset.y as f32);
1071
1072 if !matches_self && !matches_content {
1073 return None;
1074 }
1075
1076 if self.is_inline_root {
1077 let content_box_offset = taffy::Point {
1078 x: self.final_layout.padding.left + self.final_layout.border.left,
1079 y: self.final_layout.padding.top + self.final_layout.border.top,
1080 };
1081 x -= content_box_offset.x;
1082 y -= content_box_offset.y;
1083 }
1084
1085 self.paint_children
1087 .borrow()
1088 .iter()
1089 .flatten()
1090 .rev()
1091 .find_map(|&i| self.with(i).hit(x, y))
1092 .or_else(|| {
1093 if self.is_inline_root {
1094 let element_data = &self.element_data().unwrap();
1095 let layout = &element_data.inline_layout_data.as_ref().unwrap().layout;
1096 let scale = layout.scale();
1097
1098 Cluster::from_point(layout, x * scale, y * scale).and_then(|(cluster, _)| {
1099 let style_index = cluster.glyphs().next()?.style_index();
1100 let node_id = layout.styles()[style_index].brush.id;
1101 Some(HitResult { node_id, x, y })
1102 })
1103 } else {
1104 None
1105 }
1106 })
1107 .or(Some(HitResult {
1108 node_id: self.id,
1109 x,
1110 y,
1111 })
1112 .filter(|_| matches_self))
1113 }
1114
1115 pub fn absolute_position(&self, x: f32, y: f32) -> taffy::Point<f32> {
1117 let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
1118 let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
1119
1120 self.layout_parent
1122 .get()
1123 .map(|i| self.with(i).absolute_position(x, y))
1124 .unwrap_or(taffy::Point { x, y })
1125 }
1126
1127 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1129 DomEventData::Click(self.synthetic_click_event_data(mods))
1130 }
1131
1132 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzMouseButtonEvent {
1133 let absolute_position = self.absolute_position(0.0, 0.0);
1134 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1135 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1136
1137 BlitzMouseButtonEvent {
1138 x,
1139 y,
1140 mods,
1141 button: Default::default(),
1142 buttons: Default::default(),
1143 }
1144 }
1145}
1146
1147impl PartialEq for Node {
1149 fn eq(&self, other: &Self) -> bool {
1150 self.id == other.id
1151 }
1152}
1153
1154impl Eq for Node {}
1155
1156impl std::fmt::Debug for Node {
1157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1158 f.debug_struct("NodeData")
1160 .field("parent", &self.parent)
1161 .field("id", &self.id)
1162 .field("is_inline_root", &self.is_inline_root)
1163 .field("children", &self.children)
1164 .field("layout_children", &self.layout_children.borrow())
1165 .field("node", &self.data)
1167 .field("stylo_element_data", &self.stylo_element_data)
1168 .finish()
1171 }
1172}