1use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
2use bitflags::bitflags;
3use bliss_traits::events::{
4 BlissPointerEvent, BlissPointerId, DomEventData, HitResult, PointerCoords,
5};
6use bliss_traits::shell::ShellProvider;
7use html_escape::encode_quoted_attribute_to_string;
8use keyboard_types::Modifiers;
9use markup5ever::{LocalName, local_name};
10use parley::{BreakReason, Cluster, ClusterSide};
11use selectors::matching::ElementSelectorFlags;
12use slab::Slab;
13use std::cell::{Cell, RefCell};
14use std::fmt::Write;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17use style::Atom;
18use style::invalidation::element::restyle_hints::RestyleHint;
19use style::properties::ComputedValues;
20use style::properties::generated::longhands::position::computed_value::T as Position;
21use style::selector_parser::{PseudoElement, RestyleDamage};
22use style::stylesheets::UrlExtraData;
23use style::values::computed::Display as StyloDisplay;
24use style::values::specified::box_::{DisplayInside, DisplayOutside};
25use style::{data::ElementData as StyloElementData, shared_lock::SharedRwLock};
26use style_dom::ElementState;
27use style_traits::values::ToCss;
28use taffy::{
29 Cache,
30 prelude::{Layout, Style},
31};
32
33use crate::Document;
34use crate::document::BaseDocument;
35use crate::layout::damage::HoistedPaintChildren;
36
37use super::{Attribute, ElementData};
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum DisplayOuter {
41 Block,
42 Inline,
43 None,
44}
45
46bitflags! {
47 #[derive(Clone, Copy, PartialEq)]
48 pub struct NodeFlags: u32 {
49 const IS_INLINE_ROOT = 0b00000001;
51 const IS_TABLE_ROOT = 0b00000010;
53 const IS_IN_DOCUMENT = 0b00000100;
55 }
56}
57
58impl NodeFlags {
59 #[inline(always)]
60 pub fn is_inline_root(&self) -> bool {
61 self.contains(Self::IS_INLINE_ROOT)
62 }
63
64 #[inline(always)]
65 pub fn is_table_root(&self) -> bool {
66 self.contains(Self::IS_TABLE_ROOT)
67 }
68
69 #[inline(always)]
70 pub fn is_in_document(&self) -> bool {
71 self.contains(Self::IS_IN_DOCUMENT)
72 }
73
74 #[inline(always)]
75 pub fn reset_construction_flags(&mut self) {
76 self.remove(Self::IS_INLINE_ROOT);
77 self.remove(Self::IS_TABLE_ROOT);
78 }
79}
80
81pub struct Node {
82 tree: *const Slab<Node>,
93
94 doc: *const BaseDocument,
103
104 pub id: usize,
106 pub parent: Option<usize>,
108 pub children: Vec<usize>,
110 pub layout_parent: Cell<Option<usize>>,
112 pub layout_children: RefCell<Option<Vec<usize>>>,
114 pub paint_children: RefCell<Option<Vec<usize>>>,
116 pub stacking_context: Option<Box<HoistedPaintChildren>>,
117
118 pub flags: NodeFlags,
120
121 pub data: NodeData,
123
124 pub stylo_element_data: AtomicRefCell<Option<StyloElementData>>,
127 pub selector_flags: AtomicRefCell<ElementSelectorFlags>,
128 pub guard: SharedRwLock,
129 pub element_state: ElementState,
130
131 pub before: Option<usize>,
133 pub after: Option<usize>,
134
135 pub style: Style<Atom>,
137 pub has_snapshot: bool,
138 pub snapshot_handled: AtomicBool,
139 pub dirty_descendants: AtomicBool,
142 pub display_constructed_as: StyloDisplay,
143 pub cache: Cache,
144 pub unrounded_layout: Layout,
145 pub final_layout: Layout,
146 pub scroll_offset: crate::Point<f64>,
147}
148
149unsafe impl Send for Node {}
171
172unsafe impl Sync for Node {}
201
202impl Node {
203 pub(crate) fn new(
204 tree: *const Slab<Node>,
205 doc: *const BaseDocument,
206 id: usize,
207 guard: SharedRwLock,
208 data: NodeData,
209 ) -> Self {
210 let state = match &data {
212 NodeData::Element(data) => {
213 let mut state = ElementState::empty();
214 if data.can_be_disabled() {
215 state.insert(match data.has_attr(local_name!("disabled")) {
216 true => ElementState::DISABLED,
217 false => ElementState::ENABLED,
218 })
219 }
220
221 state
222 }
223 _ => ElementState::empty(),
224 };
225
226 Self {
227 tree,
228 doc,
229
230 id,
231 parent: None,
232 children: vec![],
233 layout_parent: Cell::new(None),
234 layout_children: RefCell::new(None),
235 paint_children: RefCell::new(None),
236 stacking_context: None,
237
238 flags: NodeFlags::empty(),
239 data,
240
241 stylo_element_data: Default::default(),
242 selector_flags: AtomicRefCell::new(ElementSelectorFlags::empty()),
243 guard,
244 element_state: state,
245
246 before: None,
247 after: None,
248
249 style: Default::default(),
250 has_snapshot: false,
251 snapshot_handled: AtomicBool::new(false),
252 dirty_descendants: AtomicBool::new(true),
253 display_constructed_as: StyloDisplay::Block,
254 cache: Cache::new(),
255 unrounded_layout: Layout::new(),
256 final_layout: Layout::new(),
257 scroll_offset: crate::Point::ZERO,
258 }
259 }
260
261 pub fn pe_by_index(&self, index: usize) -> Option<usize> {
262 match index {
263 0 => self.after,
264 1 => self.before,
265 _ => None,
266 }
267 }
268
269 pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
270 match index {
271 0 => self.after = value,
272 1 => self.before = value,
273 _ => {},
274 }
275 }
276
277 pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
278 Some(self.primary_styles().as_ref()?.clone_display())
279 }
280
281 pub fn is_or_contains_block(&self) -> bool {
282 let style = self.primary_styles();
283 let style = style.as_ref();
284
285 let position = style
287 .map(|s| s.clone_position())
288 .unwrap_or(Position::Relative);
289 let is_in_flow = matches!(
290 position,
291 Position::Static | Position::Relative | Position::Sticky
292 );
293 if !is_in_flow {
294 return false;
295 }
296 let display = style
297 .map(|s| s.clone_display())
298 .unwrap_or(StyloDisplay::inline());
299 match display.outside() {
300 DisplayOutside::None => false,
301 DisplayOutside::Block => true,
302 _ => {
303 if display.inside() == DisplayInside::Flow {
304 self.children
305 .iter()
306 .copied()
307 .any(|child_id| self.tree()[child_id].is_or_contains_block())
308 } else {
309 false
310 }
311 }
312 }
313 }
314
315 pub fn is_whitespace_node(&self) -> bool {
316 match &self.data {
317 NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
318 _ => false,
319 }
320 }
321
322 pub fn is_focussable(&self) -> bool {
323 self.data
324 .downcast_element()
325 .map(|el| el.is_focussable)
326 .unwrap_or(false)
327 }
328
329 pub fn set_restyle_hint(&self, hint: RestyleHint) {
330 if let Some(element_data) = self.stylo_element_data.borrow_mut().as_mut() {
331 element_data.hint.insert(hint);
332 }
333 self.mark_ancestors_dirty();
336 }
337
338 pub fn has_dirty_descendants(&self) -> bool {
340 self.dirty_descendants.load(Ordering::Relaxed)
341 }
342
343 pub fn set_dirty_descendants(&self) {
345 self.dirty_descendants.store(true, Ordering::Relaxed);
346 }
347
348 pub fn unset_dirty_descendants(&self) {
350 self.dirty_descendants.store(false, Ordering::Relaxed);
351 }
352
353 pub fn mark_ancestors_dirty(&self) {
357 let mut current_id = self.parent;
358 while let Some(parent_id) = current_id {
359 let parent = &self.tree()[parent_id];
360 if parent.dirty_descendants.swap(true, Ordering::Relaxed) {
363 break;
364 }
365 current_id = parent.parent;
366 }
367 }
368
369 pub fn damage_mut(&self) -> Option<AtomicRefMut<'_, RestyleDamage>> {
370 let element_data = self.stylo_element_data.borrow_mut();
371 #[allow(clippy::manual_map, reason = "false positive")]
372 match *element_data {
373 Some(_) => Some(AtomicRefMut::map(
374 element_data,
375 |data: &mut Option<StyloElementData>| &mut data.as_mut().unwrap().damage,
376 )),
377 None => None,
378 }
379 }
380
381 pub fn damage(&mut self) -> Option<RestyleDamage> {
382 self.stylo_element_data
383 .get_mut()
384 .as_ref()
385 .map(|data| data.damage)
386 }
387
388 pub fn set_damage(&self, damage: RestyleDamage) {
389 if let Some(data) = self.stylo_element_data.borrow_mut().as_mut() {
390 data.damage = damage;
391 }
392 }
393
394 pub fn insert_damage(&mut self, damage: RestyleDamage) {
395 if let Some(data) = self.stylo_element_data.get_mut().as_mut() {
396 data.damage |= damage;
397 }
398 }
399
400 pub fn remove_damage(&self, damage: RestyleDamage) {
401 if let Some(data) = self.stylo_element_data.borrow_mut().as_mut() {
402 data.damage.remove(damage);
403 }
404 }
405
406 pub fn clear_damage_mut(&mut self) {
407 if let Some(data) = self.stylo_element_data.get_mut() {
408 data.damage = RestyleDamage::empty();
409 }
410 }
411
412 pub fn hover(&mut self) {
413 self.element_state.insert(ElementState::HOVER);
414 self.set_restyle_hint(RestyleHint::restyle_subtree());
415 }
416
417 pub fn unhover(&mut self) {
418 self.element_state.remove(ElementState::HOVER);
419 self.set_restyle_hint(RestyleHint::restyle_subtree());
420 }
421
422 pub fn is_hovered(&self) -> bool {
423 self.element_state.contains(ElementState::HOVER)
424 }
425
426 pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
427 self.element_state
428 .insert(ElementState::FOCUS | ElementState::FOCUSRING);
429 self.set_restyle_hint(RestyleHint::restyle_subtree());
430
431 if self
433 .element_data()
434 .and_then(|elem| elem.text_input_data())
435 .is_some()
436 {
437 shell_provider.set_ime_enabled(true);
438 let mut pos = self.absolute_position(0.0, 0.0);
439 pos.x += self.final_layout.content_box_x();
440 pos.y += self.final_layout.content_box_y();
441 let width = self.final_layout.content_box_width();
442 let height = self.final_layout.content_box_height();
443 shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
444 }
445 }
446
447 pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
448 self.element_state
449 .remove(ElementState::FOCUS | ElementState::FOCUSRING);
450 self.set_restyle_hint(RestyleHint::restyle_subtree());
451
452 if self
454 .element_data()
455 .and_then(|elem| elem.text_input_data())
456 .is_some()
457 {
458 shell_provider.set_ime_enabled(false);
459 }
460 }
461
462 pub fn is_focussed(&self) -> bool {
463 self.element_state.contains(ElementState::FOCUS)
464 }
465
466 pub fn active(&mut self) {
467 self.element_state.insert(ElementState::ACTIVE);
468 self.set_restyle_hint(RestyleHint::restyle_subtree());
469 }
470
471 pub fn unactive(&mut self) {
472 self.element_state.remove(ElementState::ACTIVE);
473 self.set_restyle_hint(RestyleHint::restyle_subtree());
474 }
475
476 pub fn is_active(&self) -> bool {
477 self.element_state.contains(ElementState::ACTIVE)
478 }
479
480 pub fn disable(&mut self) {
483 if self
484 .data
485 .downcast_element()
486 .is_some_and(|data| data.can_be_disabled())
487 {
488 self.element_state.insert(ElementState::DISABLED);
489 self.element_state.remove(ElementState::ENABLED);
490 }
491 self.set_restyle_hint(RestyleHint::restyle_subtree());
492 }
493
494 pub fn enable(&mut self) {
497 if self
498 .data
499 .downcast_element()
500 .is_some_and(|data| data.can_be_disabled())
501 {
502 self.element_state.insert(ElementState::ENABLED);
503 self.element_state.remove(ElementState::DISABLED);
504 }
505 self.set_restyle_hint(RestyleHint::restyle_subtree());
506 }
507
508 pub fn subdoc(&self) -> Option<&dyn Document> {
509 self.element_data().and_then(|el| el.sub_doc_data())
510 }
511
512 pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
513 self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
514 }
515
516 pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
517 if let Some(input_data) = self
520 .data
521 .downcast_element()
522 .and_then(|el| el.text_input_data())
523 {
524 if !input_data.is_multiline {
525 let content_box_height = self.final_layout.content_box_height();
526 let input_height = input_data
527 .editor
528 .try_layout()
529 .map(|layout| layout.height() / scale as f32)
530 .unwrap_or(0.0);
531 let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
532
533 return y_offset as f64;
534 }
535 }
536
537 0.0
538 }
539}
540
541#[derive(Debug, Clone, Copy, PartialEq)]
542pub enum NodeKind {
543 Document,
544 Element,
545 AnonymousBlock,
546 Text,
547 Comment,
548}
549
550#[derive(Debug, Clone)]
552pub enum NodeData {
553 Document,
555
556 Element(ElementData),
558
559 AnonymousBlock(ElementData),
561
562 Text(TextNodeData),
564
565 Comment,
567
568 ShadowRoot {
570 host: usize,
572 },
573 }
580
581impl NodeData {
582 pub fn downcast_element(&self) -> Option<&ElementData> {
583 match self {
584 Self::Element(data) => Some(data),
585 Self::AnonymousBlock(data) => Some(data),
586 _ => None,
587 }
588 }
589
590 pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
591 match self {
592 Self::Element(data) => Some(data),
593 Self::AnonymousBlock(data) => Some(data),
594 _ => None,
595 }
596 }
597
598 pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
599 let Some(elem) = self.downcast_element() else {
600 return false;
601 };
602 *name == elem.name.local
603 }
604
605 pub fn attrs(&self) -> Option<&[Attribute]> {
606 Some(&self.downcast_element()?.attrs)
607 }
608
609 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
610 self.downcast_element()?.attr(name)
611 }
612
613 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
614 self.downcast_element()
615 .is_some_and(|elem| elem.has_attr(name))
616 }
617
618 pub fn kind(&self) -> NodeKind {
619 match self {
620 NodeData::Document => NodeKind::Document,
621 NodeData::Element(_) => NodeKind::Element,
622 NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
623 NodeData::Text(_) => NodeKind::Text,
624 NodeData::Comment => NodeKind::Comment,
625 NodeData::ShadowRoot { .. } => NodeKind::Element,
626 }
627 }
628}
629
630#[derive(Debug, Clone)]
631pub struct TextNodeData {
632 pub content: String,
634}
635
636impl TextNodeData {
637 pub fn new(content: String) -> Self {
638 Self { content }
639 }
640}
641
642impl Node {
667 pub fn doc(&self) -> &BaseDocument {
673 unsafe { &*self.doc }
674 }
675
676 pub fn tree(&self) -> &Slab<Node> {
677 unsafe { &*self.tree }
678 }
679
680 #[track_caller]
681 pub fn with(&self, id: usize) -> &Node {
682 self.tree().get(id).unwrap()
683 }
684
685 pub fn print_tree(&self, level: usize) {
686 println!(
687 "{} {} {:?} {} {:?}",
688 " ".repeat(level),
689 self.id,
690 self.parent,
691 self.node_debug_str().replace('\n', ""),
692 self.children
693 );
694 for child_id in self.children.iter() {
696 let child = self.with(*child_id);
697 child.print_tree(level + 1)
698 }
699 }
700
701 pub fn index_of_child(&self, child_id: usize) -> Option<usize> {
703 self.children.iter().position(|id| *id == child_id)
704 }
705
706 pub fn child_index(&self) -> Option<usize> {
708 self.tree()[self.parent?]
709 .children
710 .iter()
711 .position(|id| *id == self.id)
712 }
713
714 pub fn forward(&self, n: usize) -> Option<&Node> {
716 let child_idx = self.child_index().unwrap_or(0);
717 self.tree()[self.parent?]
718 .children
719 .get(child_idx + n)
720 .map(|id| self.with(*id))
721 }
722
723 pub fn backward(&self, n: usize) -> Option<&Node> {
724 let child_idx = self.child_index().unwrap_or(0);
725 if child_idx < n {
726 return None;
727 }
728
729 self.tree()[self.parent?]
730 .children
731 .get(child_idx - n)
732 .map(|id| self.with(*id))
733 }
734
735 pub fn is_element(&self) -> bool {
736 matches!(self.data, NodeData::Element { .. })
737 }
738
739 pub fn is_shadow_root(&self) -> bool {
740 matches!(self.data, NodeData::ShadowRoot { .. })
741 }
742
743 pub fn shadow_host(&self) -> Option<usize> {
744 match self.data {
745 NodeData::ShadowRoot { host } => Some(host),
746 _ => None,
747 }
748 }
749
750 pub fn is_anonymous(&self) -> bool {
751 matches!(self.data, NodeData::AnonymousBlock { .. })
752 }
753
754 pub fn is_text_node(&self) -> bool {
755 matches!(self.data, NodeData::Text { .. })
756 }
757
758 pub fn element_data(&self) -> Option<&ElementData> {
759 match self.data {
760 NodeData::Element(ref data) => Some(data),
761 NodeData::AnonymousBlock(ref data) => Some(data),
762 _ => None,
763 }
764 }
765
766 pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
767 match self.data {
768 NodeData::Element(ref mut data) => Some(data),
769 NodeData::AnonymousBlock(ref mut data) => Some(data),
770 _ => None,
771 }
772 }
773
774 pub fn text_data(&self) -> Option<&TextNodeData> {
775 match self.data {
776 NodeData::Text(ref data) => Some(data),
777 _ => None,
778 }
779 }
780
781 pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
782 match self.data {
783 NodeData::Text(ref mut data) => Some(data),
784 _ => None,
785 }
786 }
787
788 pub fn node_debug_str(&self) -> String {
789 let mut s = String::new();
790
791 match &self.data {
792 NodeData::Document => write!(s, "DOCUMENT"),
793 NodeData::Text(data) => {
795 let bytes = data.content.as_bytes();
796 write!(
797 s,
798 "TEXT {}",
799 &std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
800 .unwrap_or("INVALID UTF8")
801 )
802 }
803 NodeData::Comment => write!(
804 s,
805 "COMMENT",
806 ),
808 NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
809 NodeData::ShadowRoot { host } => write!(s, "#shadow-root (host={host})"),
810 NodeData::Element(data) => {
811 let name = &data.name;
812 let class = self.attr(local_name!("class")).unwrap_or("");
813 let id = self.attr(local_name!("id")).unwrap_or("");
814 let display = self.display_constructed_as.to_css_string();
815 write!(s, "<{}", name.local).unwrap();
816 if !id.is_empty() {
817 write!(s, " #{id}").unwrap();
818 }
819 if !class.is_empty() {
820 if class.contains(' ') {
821 write!(s, " class=\"{class}\"").unwrap()
822 } else {
823 write!(s, " .{class}").unwrap()
824 }
825 }
826 write!(s, "> ({display})")
827 } }
829 .unwrap();
830 s
831 }
832
833 pub fn outer_html(&self) -> String {
834 let mut output = String::new();
835 self.write_outer_html(&mut output);
836 output
837 }
838
839 pub fn write_outer_html(&self, writer: &mut String) {
840 let has_children = !self.children.is_empty();
841 let current_color = self
842 .primary_styles()
843 .map(|style| style.clone_color())
844 .map(|color| color.to_css_string());
845
846 match &self.data {
847 NodeData::Document => {}
848 NodeData::Comment => {}
849 NodeData::AnonymousBlock(_) => {}
850 NodeData::ShadowRoot { .. } => {}
851 NodeData::Text(data) => {
853 writer.push_str(data.content.as_str());
854 }
855 NodeData::Element(data) => {
856 writer.push('<');
857 writer.push_str(&data.name.local);
858
859 for attr in data.attrs() {
860 writer.push(' ');
861 writer.push_str(&attr.name.local);
862 writer.push_str("=\"");
863 #[allow(clippy::unnecessary_unwrap)] if current_color.is_some() && attr.value.contains("currentColor") {
865 let value = attr
866 .value
867 .replace("currentColor", current_color.as_ref().unwrap());
868 encode_quoted_attribute_to_string(&value, writer);
869 } else {
870 encode_quoted_attribute_to_string(&attr.value, writer);
871 }
872 writer.push('"');
873 }
874 if !has_children {
875 writer.push_str(" /");
876 }
877 writer.push('>');
878
879 if has_children {
880 for &child_id in &self.children {
881 self.tree()[child_id].write_outer_html(writer);
882 }
883
884 writer.push_str("</");
885 writer.push_str(&data.name.local);
886 writer.push('>');
887 }
888 }
889 }
890 }
891
892 pub fn attrs(&self) -> Option<&[Attribute]> {
893 Some(&self.element_data()?.attrs)
894 }
895
896 pub fn attr(&self, name: LocalName) -> Option<&str> {
897 let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
898 Some(&attr.value)
899 }
900
901 pub fn primary_styles(&self) -> Option<AtomicRef<'_, ComputedValues>> {
902 let stylo_element_data = self.stylo_element_data.borrow();
903 if stylo_element_data
904 .as_ref()
905 .and_then(|d| d.styles.get_primary())
906 .is_some()
907 {
908 Some(AtomicRef::map(
909 stylo_element_data,
910 |data: &Option<StyloElementData>| -> &ComputedValues {
911 data.as_ref().unwrap().styles.get_primary().unwrap()
912 },
913 ))
914 } else {
915 None
916 }
917 }
918
919 pub fn text_content(&self) -> String {
920 let mut out = String::new();
921 self.write_text_content(&mut out);
922 out
923 }
924
925 fn write_text_content(&self, out: &mut String) {
926 match &self.data {
927 NodeData::Text(data) => {
928 out.push_str(&data.content);
929 }
930 NodeData::Element(..) | NodeData::AnonymousBlock(..) | NodeData::ShadowRoot { .. } => {
931 for child_id in self.children.iter() {
932 self.with(*child_id).write_text_content(out);
933 }
934 }
935 _ => {}
936 }
937 }
938
939 pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
940 if let NodeData::Element(ref mut elem_data) = self.data {
941 elem_data.flush_style_attribute(&self.guard, url_extra_data);
942 }
943 }
944
945 pub fn order(&self) -> i32 {
946 self.primary_styles()
947 .map(|s| match s.pseudo() {
948 Some(PseudoElement::Before) => i32::MIN,
949 Some(PseudoElement::After) => i32::MAX,
950 _ => s.clone_order(),
951 })
952 .unwrap_or(0)
953 }
954
955 pub fn z_index(&self) -> i32 {
956 self.primary_styles()
957 .map(|s| s.clone_z_index().integer_or(0))
958 .unwrap_or(0)
959 }
960
961 pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
963 let Some(style) = self.primary_styles() else {
964 return false;
965 };
966
967 let position = style.clone_position();
968 let has_z_index = !style.clone_z_index().is_auto();
969
970 if style.clone_opacity() != 1.0 {
971 return true;
972 }
973
974 let position_based = match position {
975 Position::Fixed | Position::Sticky => true,
976 Position::Relative | Position::Absolute => has_z_index,
977 Position::Static => has_z_index && is_flex_or_grid_item,
978 };
979 if position_based {
980 return true;
981 }
982
983 {
988 use style::properties::longhands::mix_blend_mode::computed_value::T as MixBlendMode;
989 if !matches!(style.clone_mix_blend_mode(), MixBlendMode::Normal) {
990 return true;
991 }
992 }
993
994 {
996 use style::values::computed::transform::Transform as StyloTransform;
997 if style.clone_transform() != StyloTransform::none() {
998 return true;
999 }
1000 }
1001
1002 if !style.clone_filter().0.is_empty() {
1004 return true;
1005 }
1006
1007 {
1009 use style::properties::longhands::isolation::computed_value::T as Isolation;
1010 if matches!(style.clone_isolation(), Isolation::Isolate) {
1011 return true;
1012 }
1013 }
1014
1015 {
1017 use style::properties::longhands::contain::computed_value::T as Contain;
1018 let contain = style.clone_contain();
1019 if contain.intersects(Contain::PAINT) {
1020 return true;
1021 }
1022 }
1023
1024 {
1026 use style::values::computed::basic_shape::ClipPath as StyloClipPath;
1027 if !matches!(style.clone_clip_path(), StyloClipPath::None) {
1028 return true;
1029 }
1030 }
1031
1032 if !style.clone_mask_image().0.is_empty() {
1034 return true;
1035 }
1036
1037 false
1038 }
1039
1040 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1049 use style::computed_values::visibility::T as Visibility;
1050
1051 if let Some(style) = self.primary_styles() {
1053 if matches!(
1054 style.clone_visibility(),
1055 Visibility::Hidden | Visibility::Collapse
1056 ) {
1057 return None;
1058 }
1059 }
1060
1061 let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
1062 let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
1063
1064 let size = self.final_layout.size;
1065 let matches_self = !(x < 0.0
1066 || x > size.width + self.scroll_offset.x as f32
1067 || y < 0.0
1068 || y > size.height + self.scroll_offset.y as f32);
1069
1070 let content_size = self.final_layout.content_size;
1071 let matches_content = !(x < 0.0
1072 || x > content_size.width + self.scroll_offset.x as f32
1073 || y < 0.0
1074 || y > content_size.height + self.scroll_offset.y as f32);
1075
1076 let matches_hoisted_content = match &self.stacking_context {
1077 Some(sc) => {
1078 let content_area = sc.content_area;
1079 x >= content_area.left + self.scroll_offset.x as f32
1080 && x <= content_area.right + self.scroll_offset.x as f32
1081 && y >= content_area.top + self.scroll_offset.y as f32
1082 && y <= content_area.bottom + self.scroll_offset.y as f32
1083 }
1084 None => false,
1085 };
1086
1087 if !matches_self && !matches_content && !matches_hoisted_content {
1088 return None;
1089 }
1090
1091 if self.flags.is_inline_root() {
1092 let content_box_offset = taffy::Point {
1093 x: self.final_layout.padding.left + self.final_layout.border.left,
1094 y: self.final_layout.padding.top + self.final_layout.border.top,
1095 };
1096 x -= content_box_offset.x;
1097 y -= content_box_offset.y;
1098 }
1099
1100 if matches_hoisted_content {
1102 if let Some(hoisted) = &self.stacking_context {
1103 for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1104 let x = x - hoisted_child.position.x;
1105 let y = y - hoisted_child.position.y;
1106 if let Some(hit) = self.with(hoisted_child.node_id).hit(x, y) {
1107 return Some(hit);
1108 }
1109 }
1110 }
1111 }
1112
1113 for child_id in self.paint_children.borrow().iter().flatten().rev() {
1115 if let Some(hit) = self.with(*child_id).hit(x, y) {
1116 return Some(hit);
1117 }
1118 }
1119
1120 if matches_hoisted_content {
1122 if let Some(hoisted) = &self.stacking_context {
1123 for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1124 let x = x - hoisted_child.position.x;
1125 let y = y - hoisted_child.position.y;
1126 if let Some(hit) = self.with(hoisted_child.node_id).hit(x, y) {
1127 return Some(hit);
1128 }
1129 }
1130 }
1131 }
1132
1133 if self.flags.is_inline_root() {
1135 let Some(element_data) = self.element_data() else {
1136 return None;
1137 };
1138 if let Some(ild) = element_data.inline_layout_data.as_ref() {
1139 let layout = &ild.layout;
1140 let scale = layout.scale();
1141
1142 if let Some((cluster, _side)) =
1143 Cluster::from_point_exact(layout, x * scale, y * scale)
1144 {
1145 let style_index = cluster.glyphs().next()?.style_index();
1146 let node_id = layout.styles()[style_index].brush.id;
1147 return Some(HitResult {
1148 node_id,
1149 x,
1150 y,
1151 is_text: true,
1152 });
1153 }
1154 }
1155 }
1156
1157 if matches_self {
1159 return Some(HitResult {
1160 node_id: self.id,
1161 x,
1162 y,
1163 is_text: false,
1164 });
1165 }
1166
1167 None
1168 }
1169
1170 pub fn inline_root_ancestor(&self) -> Option<&Node> {
1173 let mut node = self;
1174 loop {
1175 if node.flags.is_inline_root() {
1176 return Some(node);
1177 }
1178 match node.layout_parent.get() {
1179 Some(id) => node = self.with(id),
1180 None => return None,
1181 }
1182 }
1183 }
1184
1185 pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1189 if !self.flags.is_inline_root() {
1190 return None;
1191 }
1192
1193 let element_data = self.element_data()?;
1194 let inline_layout = element_data.inline_layout_data.as_ref()?;
1195 let layout = &inline_layout.layout;
1196 let scale = layout.scale();
1197
1198 let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1200
1201 let is_leading = side == ClusterSide::Left;
1206 let offset = if cluster.is_rtl() {
1207 if is_leading {
1208 cluster.text_range().end
1209 } else {
1210 cluster.text_range().start
1211 }
1212 } else {
1213 if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1215 cluster.text_range().start
1216 } else {
1217 cluster.text_range().end
1218 }
1219 };
1220
1221 Some(offset)
1222 }
1223
1224 pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1226 let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
1227 let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
1228
1229 self.layout_parent
1231 .get()
1232 .map(|i| self.with(i).absolute_position(x, y))
1233 .unwrap_or(crate::util::Point { x, y })
1234 }
1235
1236 pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1238 DomEventData::Click(self.synthetic_click_event_data(mods))
1239 }
1240
1241 pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlissPointerEvent {
1242 let absolute_position = self.absolute_position(0.0, 0.0);
1243 let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1244 let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1245
1246 BlissPointerEvent {
1247 id: BlissPointerId::Mouse,
1248 is_primary: true,
1249 coords: PointerCoords {
1250 page_x: x,
1251 page_y: y,
1252
1253 screen_x: x,
1255 screen_y: y,
1256 client_x: x,
1257 client_y: y,
1258 },
1259 mods,
1260 button: Default::default(),
1261 buttons: Default::default(),
1262 details: Default::default(),
1263 }
1264 }
1265}
1266
1267impl PartialEq for Node {
1269 fn eq(&self, other: &Self) -> bool {
1270 self.id == other.id
1271 }
1272}
1273
1274impl Eq for Node {}
1275
1276impl std::fmt::Debug for Node {
1277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1278 f.debug_struct("NodeData")
1280 .field("parent", &self.parent)
1281 .field("id", &self.id)
1282 .field("is_inline_root", &self.flags.is_inline_root())
1283 .field("children", &self.children)
1284 .field("layout_children", &self.layout_children.borrow())
1285 .field("node", &self.data)
1287 .field("stylo_element_data", &self.stylo_element_data)
1288 .finish()
1291 }
1292}
1293
1294#[cfg(test)]
1295mod test {
1296 use style_dom::ElementState;
1297
1298 use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1299
1300 #[test]
1301 fn create_node_with_disabled_attr() {
1302 let mut document = BaseDocument::new(DocumentConfig::default());
1303 let node = document.create_node(NodeData::Element(ElementData::new(
1304 qual_name!("button"),
1305 vec![Attribute {
1306 name: qual_name!("disabled"),
1307 value: "".into(),
1308 }],
1309 )));
1310 let node = document.get_node(node).unwrap();
1311
1312 assert!(
1313 node.element_state.contains(ElementState::DISABLED),
1314 "form node is disabled"
1315 );
1316 assert!(
1317 !node.element_state.contains(ElementState::ENABLED),
1318 "form node is not enabled"
1319 );
1320 }
1321
1322 #[test]
1323 fn ignore_disabled_attr_content() {
1324 let mut document = BaseDocument::new(DocumentConfig::default());
1325 let node = document.create_node(NodeData::Element(ElementData::new(
1326 qual_name!("button"),
1327 vec![Attribute {
1328 name: qual_name!("disabled"),
1329 value: "false".into(),
1330 }],
1331 )));
1332 let node = document.get_node(node).unwrap();
1333
1334 assert!(
1335 node.element_state.contains(ElementState::DISABLED),
1336 "form node is disabled"
1337 );
1338 assert!(
1339 !node.element_state.contains(ElementState::ENABLED),
1340 "form node is not enabled"
1341 );
1342 }
1343
1344 #[test]
1345 fn create_node_with_ignored_disable() {
1346 let mut document = BaseDocument::new(DocumentConfig::default());
1347 let node = document.create_node(NodeData::Element(ElementData::new(
1348 qual_name!("a"),
1349 vec![Attribute {
1350 name: qual_name!("disabled"),
1351 value: "".into(),
1352 }],
1353 )));
1354 let node = document.get_node(node).unwrap();
1355
1356 assert!(
1357 !node.element_state.contains(ElementState::DISABLED),
1358 "Non form node cannot be disabled"
1359 );
1360 assert!(
1361 !node.element_state.contains(ElementState::ENABLED),
1362 "Non form node cannot be enabled"
1363 );
1364 }
1365
1366 #[test]
1367 fn create_empty_enabled_node() {
1368 let mut document = BaseDocument::new(DocumentConfig::default());
1369 let node = document.create_node(NodeData::Element(ElementData::new(
1370 qual_name!("button"),
1371 vec![],
1372 )));
1373 let node = document.get_node(node).unwrap();
1374
1375 assert!(
1376 node.element_state.contains(ElementState::ENABLED),
1377 "Button should be enabled by default"
1378 );
1379 }
1380}