1use crate::layout::cache::Cache;
2use blitz_traits::node_id::NodeId;
3use cssparser::ParserInput;
4use kurbo::{Affine, Rect as KurboRect};
5use linebender_resource_handle::Blob;
6use markup5ever::{LocalName, QualName, local_name};
7use selectors::matching::{ElementSelectorFlags, QuirksMode};
8use std::cell::Cell;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::sync::atomic::AtomicBool;
12use style::Atom;
13use style::parser::ParserContext;
14use style::properties::ComputedValues;
15use style::properties::{Importance, PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
16use style::stylesheets::{DocumentStyleSheet, Origin, UrlExtraData};
17use style::values::computed::Display as StyloDisplay;
18use style::{
19 properties::{PropertyDeclarationBlock, parse_style_attribute},
20 servo_arc::Arc as ServoArc,
21 shared_lock::{Locked, SharedRwLock},
22 stylesheets::CssRuleType,
23};
24use style_dom::ElementState;
25use style_traits::ParsingMode;
26use taffy::prelude::{Layout, Style};
27use url::Url;
28
29use super::stylo_data::StyloData;
30use super::{Attribute, Attributes};
31use crate::Document;
32use crate::layout::table::TableContext;
33use crate::node::{SelectData, TextBrush, TextInputData, TextLayout};
34
35#[cfg(feature = "shadow-dom")]
36use super::custom_element::CustomElementData;
37#[cfg(feature = "custom-widget")]
38use super::custom_widget::CustomWidgetData;
39
40macro_rules! local_names {
41 ($($name:tt),+) => {
42 [$(local_name!($name),)+]
43 };
44}
45
46pub struct ElementData {
47 pub name: QualName,
49
50 pub id: Option<Atom>,
52
53 pub attrs: Attributes,
55
56 pub inline_event_attributes: Option<Box<Vec<Attribute>>>,
59
60 pub is_focussable: bool,
62
63 pub style_attribute: Option<ServoArc<Locked<PropertyDeclarationBlock>>>,
65
66 pub special_data: SpecialElementData,
72
73 #[cfg(feature = "svg")]
75 pub(crate) svg_geometry: Option<Arc<crate::layout::svg_geometry::SvgGeometry>>,
76
77 pub background_images: Vec<Option<ImageResourceData>>,
78
79 pub mask_images: Vec<Option<ImageResourceData>>,
80
81 pub inline_layout_data: Option<Box<TextLayout>>,
83
84 pub list_item_data: Option<Box<ListItemLayout>>,
87
88 pub template_contents: Option<NodeId>,
90
91 pub shadow_root: Option<NodeId>,
94
95 pub assigned_slot: Option<NodeId>,
99 pub stylo_element_data: StyloData,
108 pub selector_flags: Cell<ElementSelectorFlags>,
109 pub guard: Option<SharedRwLock>,
112 pub element_state: ElementState,
113 pub has_snapshot: bool,
114 pub snapshot_handled: AtomicBool,
115 pub dirty_descendants: AtomicBool,
118
119 pub before: Option<NodeId>,
121 pub after: Option<NodeId>,
122
123 pub detailed_grid_info: Option<Box<taffy::DetailedGridInfo>>,
126
127 pub style: Style<Atom>,
129 pub subtree_hoists: bool,
138
139 pub style_source: Option<ServoArc<ComputedValues>>,
148 pub unflushed_style_seen: Option<ServoArc<ComputedValues>>,
152 pub display_constructed_as: StyloDisplay,
153 cache: Option<Box<Cache>>,
173 pub unrounded_layout: Layout,
174 pub final_layout: Layout,
175 pub scroll_offset: crate::Point<f64>,
176 pub scrollable_overflow: KurboRect,
177 pub transform: Option<Affine>,
178}
179
180pub struct DocumentData {
186 pub content_type: &'static str,
187 pub ids: std::collections::HashMap<String, Vec<NodeId>>,
190 pub quirks_mode: u8,
192 pub stylo_element_data: StyloData,
193 pub selector_flags: Cell<ElementSelectorFlags>,
197 pub guard: Option<SharedRwLock>,
200 pub dirty_descendants: AtomicBool,
201 pub element_state: ElementState,
202 pub has_snapshot: bool,
203 pub snapshot_handled: AtomicBool,
204 pub style: Style<Atom>,
205 pub subtree_hoists: bool,
207
208 pub style_source: Option<ServoArc<ComputedValues>>,
211 pub display_constructed_as: StyloDisplay,
212 cache: Option<Box<Cache>>,
232 pub unrounded_layout: Layout,
233 pub final_layout: Layout,
234 pub scroll_offset: crate::Point<f64>,
235 pub scrollable_overflow: KurboRect,
236 pub transform: Option<Affine>,
237}
238
239impl std::fmt::Debug for DocumentData {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 f.debug_struct("DocumentData")
244 .field("content_type", &self.content_type)
245 .field("ids", &self.ids)
246 .field("quirks_mode", &self.quirks_mode)
247 .field("stylo_element_data", &self.stylo_element_data)
248 .field("guard", &self.guard)
249 .field("dirty_descendants", &self.dirty_descendants)
250 .field("element_state", &self.element_state)
251 .field("has_snapshot", &self.has_snapshot)
252 .field("snapshot_handled", &self.snapshot_handled)
253 .field("style", &self.style)
254 .field("display_constructed_as", &self.display_constructed_as)
255 .field("cache", &self.cache)
256 .field("unrounded_layout", &self.unrounded_layout)
257 .field("final_layout", &self.final_layout)
258 .field("scroll_offset", &self.scroll_offset)
259 .field("scrollable_overflow", &self.scrollable_overflow)
260 .field("transform", &self.transform)
261 .finish_non_exhaustive()
262 }
263}
264
265impl DocumentData {
266 #[inline]
271 pub fn cache(&self) -> &Cache {
272 self.cache.as_deref().unwrap_or(&EMPTY_CACHE)
273 }
274
275 #[inline]
276 pub fn cache_mut(&mut self) -> &mut Cache {
277 self.cache.get_or_insert_with(|| Box::new(Cache::new()))
278 }
279
280 #[inline]
281 pub fn cache_release(&mut self) {
282 self.cache = None;
283 }
284
285 pub fn new() -> Self {
286 Self {
287 content_type: "text/html",
288 ids: Default::default(),
289 quirks_mode: 0,
290 stylo_element_data: Default::default(),
291 selector_flags: Cell::new(ElementSelectorFlags::empty()),
292 guard: None,
293 dirty_descendants: AtomicBool::new(true),
294 element_state: ElementState::empty(),
295 has_snapshot: false,
296 snapshot_handled: AtomicBool::new(false),
297 style: Default::default(),
298 style_source: None,
299 subtree_hoists: false,
300 display_constructed_as: StyloDisplay::Block,
301 cache: None,
302 unrounded_layout: Layout::new(),
303 final_layout: Layout::new(),
304 scroll_offset: crate::Point::ZERO,
305 scrollable_overflow: KurboRect::ZERO,
306 transform: None,
307 }
308 }
309}
310
311impl Default for DocumentData {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317impl Clone for DocumentData {
318 fn clone(&self) -> Self {
319 Self {
322 content_type: self.content_type,
323 quirks_mode: self.quirks_mode,
324 guard: self.guard.clone(),
325 ..Self::new()
326 }
327 }
328}
329
330impl std::fmt::Debug for ElementData {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 f.debug_struct("ElementData")
333 .field("name", &self.name)
334 .field("id", &self.id)
335 .field("attrs", &self.attrs)
336 .field("is_focussable", &self.is_focussable)
337 .field("style_attribute", &self.style_attribute)
338 .field("special_data", &self.special_data)
339 .field("background_images", &self.background_images)
340 .field("mask_images", &self.mask_images)
341 .field("inline_layout_data", &self.inline_layout_data)
342 .field("list_item_data", &self.list_item_data)
343 .field("template_contents", &self.template_contents)
344 .field("element_state", &self.element_state)
345 .field("display_constructed_as", &self.display_constructed_as)
346 .finish_non_exhaustive()
347 }
348}
349
350impl Clone for ElementData {
351 fn clone(&self) -> Self {
357 Self {
358 name: self.name.clone(),
359 id: self.id.clone(),
360 attrs: self.attrs.clone(),
361 inline_event_attributes: self.inline_event_attributes.clone(),
362 is_focussable: self.is_focussable,
363 style_attribute: self.style_attribute.clone(),
364 special_data: self.special_data.clone(),
365 #[cfg(feature = "svg")]
366 svg_geometry: None,
367 background_images: self.background_images.clone(),
368 mask_images: self.mask_images.clone(),
369 inline_layout_data: self.inline_layout_data.clone(),
370 list_item_data: self.list_item_data.clone(),
371 template_contents: None,
372
373 shadow_root: None,
379 assigned_slot: None,
380 stylo_element_data: Default::default(),
381 selector_flags: Cell::new(ElementSelectorFlags::empty()),
382 guard: self.guard.clone(),
383 element_state: self.element_state,
384 has_snapshot: false,
385 snapshot_handled: AtomicBool::new(false),
386 dirty_descendants: AtomicBool::new(true),
387 before: None,
388 after: None,
389 detailed_grid_info: None,
390 style: Default::default(),
391 style_source: None,
392 unflushed_style_seen: None,
393 subtree_hoists: false,
394 display_constructed_as: StyloDisplay::Block,
395 cache: None,
396 unrounded_layout: Layout::new(),
397 final_layout: Layout::new(),
398 scroll_offset: crate::Point::ZERO,
399 scrollable_overflow: KurboRect::ZERO,
400 transform: None,
401 }
402 }
403}
404
405#[derive(Copy, Clone, Default)]
406#[non_exhaustive]
407pub enum SpecialElementType {
408 Stylesheet,
409 Image,
410 Canvas,
411 TableRoot,
412 TextInput,
413 CheckboxInput,
414 Select,
415 #[cfg(feature = "file-input")]
416 FileInput,
417 #[default]
418 None,
419}
420
421#[derive(Default)]
423pub enum SpecialElementData {
424 SubDocument(Box<dyn Document>),
426 #[cfg(feature = "custom-widget")]
428 CustomWidget(CustomWidgetData),
429 #[cfg(feature = "shadow-dom")]
431 CustomElement(CustomElementData),
432 Stylesheet(DocumentStyleSheet),
434 Image(Box<ImageData>),
436 Canvas(CanvasData),
438 TableRoot(Arc<TableContext>),
440 TextInput(TextInputData),
442 CheckboxInput(bool),
444 Select(SelectData),
446 #[cfg(feature = "file-input")]
448 FileInput(FileData),
449 #[default]
451 None,
452}
453
454impl Clone for SpecialElementData {
455 fn clone(&self) -> Self {
456 match self {
457 Self::SubDocument(_) => Self::None, #[cfg(feature = "custom-widget")]
459 Self::CustomWidget(_) => Self::None, #[cfg(feature = "shadow-dom")]
461 Self::CustomElement(_) => Self::None, Self::Stylesheet(data) => Self::Stylesheet(data.clone()),
463 Self::Image(data) => Self::Image(data.clone()),
464 Self::Canvas(data) => Self::Canvas(data.clone()),
465 Self::TableRoot(data) => Self::TableRoot(data.clone()),
466 Self::TextInput(data) => Self::TextInput(data.clone()),
467 Self::CheckboxInput(data) => Self::CheckboxInput(*data),
468 Self::Select(data) => Self::Select(data.clone()),
469 #[cfg(feature = "file-input")]
470 Self::FileInput(data) => Self::FileInput(data.clone()),
471 Self::None => Self::None,
472 }
473 }
474}
475
476impl SpecialElementData {
477 pub fn take(&mut self) -> Self {
478 std::mem::take(self)
479 }
480}
481
482static EMPTY_CACHE: Cache = Cache::new();
491
492impl ElementData {
493 #[inline]
496 pub fn cache(&self) -> &Cache {
497 self.cache.as_deref().unwrap_or(&EMPTY_CACHE)
498 }
499
500 #[inline]
505 pub fn cache_mut(&mut self) -> &mut Cache {
506 self.cache.get_or_insert_with(|| Box::new(Cache::new()))
507 }
508
509 #[inline]
516 pub fn cache_release(&mut self) {
517 self.cache = None;
518 }
519
520 pub fn new(name: QualName, attrs: Vec<Attribute>) -> Self {
521 let handlers: Vec<_> = attrs
522 .iter()
523 .filter(|attr| {
524 attr.name.ns == markup5ever::ns!() && attr.name.local.as_ref().starts_with("on")
525 })
526 .cloned()
527 .collect();
528 let inline_event_attributes = (!handlers.is_empty()).then(|| Box::new(handlers));
529 let id_attr_atom = attrs
530 .iter()
531 .find(|attr| &attr.name.local == "id")
532 .map(|attr| attr.value.as_ref())
533 .map(|value: &str| Atom::from(value));
534
535 let mut data = ElementData {
536 name,
537 id: id_attr_atom,
538 attrs: Attributes::new(attrs),
539 inline_event_attributes,
540 is_focussable: false,
541 style_attribute: Default::default(),
542 inline_layout_data: None,
543 list_item_data: None,
544 special_data: SpecialElementData::None,
545 #[cfg(feature = "svg")]
546 svg_geometry: None,
547 template_contents: None,
548 shadow_root: None,
549 assigned_slot: None,
550 background_images: Vec::new(),
551 mask_images: Vec::new(),
552
553 stylo_element_data: Default::default(),
554 selector_flags: Cell::new(ElementSelectorFlags::empty()),
555 guard: None,
556 element_state: ElementState::empty(),
557 has_snapshot: false,
558 snapshot_handled: AtomicBool::new(false),
559 dirty_descendants: AtomicBool::new(true),
560 before: None,
561 after: None,
562 detailed_grid_info: None,
563 style: Default::default(),
564 style_source: None,
565 unflushed_style_seen: None,
566 subtree_hoists: false,
567 display_constructed_as: StyloDisplay::Block,
568 cache: None,
569 unrounded_layout: Layout::new(),
570 final_layout: Layout::new(),
571 scroll_offset: crate::Point::ZERO,
572 scrollable_overflow: KurboRect::ZERO,
573 transform: None,
574 };
575 data.flush_is_focussable();
576
577 if data.can_be_disabled() {
579 data.element_state
580 .insert(match data.has_attr(local_name!("disabled")) {
581 true => ElementState::DISABLED,
582 false => ElementState::ENABLED,
583 });
584 }
585
586 data
587 }
588
589 pub fn attrs(&self) -> &[Attribute] {
590 &self.attrs
591 }
592
593 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
594 let attr = self
595 .attrs
596 .iter()
597 .find(|attr| attr.name.ns == markup5ever::ns!() && name == attr.name.local)?;
598 Some(&attr.value)
599 }
600
601 pub fn link_href(&self) -> Option<&str> {
603 self.attr(local_name!("href")).or_else(|| {
604 if self.name.ns != markup5ever::ns!(svg) {
605 return None;
606 }
607 self.attrs()
608 .iter()
609 .find(|attr| {
610 attr.name.ns == markup5ever::ns!(xlink)
611 && attr.name.local == local_name!("href")
612 })
613 .map(|attr| attr.value.as_ref())
614 })
615 }
616
617 pub fn attr_parsed<T: FromStr>(&self, name: impl PartialEq<LocalName>) -> Option<T> {
618 let attr = self
619 .attrs
620 .iter()
621 .find(|attr| attr.name.ns == markup5ever::ns!() && name == attr.name.local)?;
622 attr.value.parse::<T>().ok()
623 }
624
625 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
627 self.attrs
628 .iter()
629 .any(|attr| attr.name.ns == markup5ever::ns!() && name == attr.name.local)
630 }
631
632 pub fn can_be_disabled(&self) -> bool {
633 local_names!("button", "input", "select", "textarea").contains(&self.name.local)
634 }
635
636 pub fn image_data(&self) -> Option<&ImageData> {
637 match &self.special_data {
638 SpecialElementData::Image(data) => Some(&**data),
639 _ => None,
640 }
641 }
642
643 pub fn image_data_mut(&mut self) -> Option<&mut ImageData> {
644 match self.special_data {
645 SpecialElementData::Image(ref mut data) => Some(&mut **data),
646 _ => None,
647 }
648 }
649
650 pub fn raster_image_data(&self) -> Option<&RasterImageData> {
651 match self.image_data()? {
652 ImageData::Raster(data) => Some(data),
653 _ => None,
654 }
655 }
656
657 pub fn raster_image_data_mut(&mut self) -> Option<&mut RasterImageData> {
658 match self.image_data_mut()? {
659 ImageData::Raster(data) => Some(data),
660 _ => None,
661 }
662 }
663
664 pub fn canvas_data(&self) -> Option<&CanvasData> {
665 match &self.special_data {
666 SpecialElementData::Canvas(data) => Some(data),
667 _ => None,
668 }
669 }
670
671 pub fn sub_doc_data(&self) -> Option<&dyn Document> {
672 match &self.special_data {
673 SpecialElementData::SubDocument(data) => Some(data.as_ref()),
674 _ => None,
675 }
676 }
677
678 pub fn sub_doc_data_mut(&mut self) -> Option<&mut dyn Document> {
679 match &mut self.special_data {
680 SpecialElementData::SubDocument(data) => Some(data.as_mut()),
681 _ => None,
682 }
683 }
684
685 #[cfg(feature = "svg")]
686 pub fn svg_data(&self) -> Option<&usvg::Tree> {
687 match self.image_data()? {
688 ImageData::Svg(data) => Some(&data.tree),
689 _ => None,
690 }
691 }
692
693 pub fn text_input_data(&self) -> Option<&TextInputData> {
694 match &self.special_data {
695 SpecialElementData::TextInput(data) => Some(data),
696 _ => None,
697 }
698 }
699
700 pub fn text_input_data_mut(&mut self) -> Option<&mut TextInputData> {
701 match &mut self.special_data {
702 SpecialElementData::TextInput(data) => Some(data),
703 _ => None,
704 }
705 }
706
707 #[cfg(feature = "custom-widget")]
708 pub fn custom_widget_data(&self) -> Option<&CustomWidgetData> {
709 match &self.special_data {
710 SpecialElementData::CustomWidget(data) => Some(data),
711 _ => None,
712 }
713 }
714
715 #[cfg(feature = "custom-widget")]
716 pub fn custom_widget_data_mut(&mut self) -> Option<&mut CustomWidgetData> {
717 match &mut self.special_data {
718 SpecialElementData::CustomWidget(data) => Some(data),
719 _ => None,
720 }
721 }
722
723 #[cfg(feature = "shadow-dom")]
724 pub fn custom_element_data(&self) -> Option<&CustomElementData> {
725 match &self.special_data {
726 SpecialElementData::CustomElement(data) => Some(data),
727 _ => None,
728 }
729 }
730
731 #[cfg(feature = "shadow-dom")]
732 pub fn custom_element_data_mut(&mut self) -> Option<&mut CustomElementData> {
733 match &mut self.special_data {
734 SpecialElementData::CustomElement(data) => Some(data),
735 _ => None,
736 }
737 }
738
739 pub fn checkbox_input_checked(&self) -> Option<bool> {
740 match self.special_data {
741 SpecialElementData::CheckboxInput(checked) => Some(checked),
742 _ => None,
743 }
744 }
745
746 pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
747 match self.special_data {
748 SpecialElementData::CheckboxInput(ref mut checked) => Some(checked),
749 _ => None,
750 }
751 }
752
753 pub fn select_data(&self) -> Option<&SelectData> {
761 match &self.special_data {
762 SpecialElementData::Select(data) => Some(data),
763 _ => None,
764 }
765 }
766
767 pub fn select_data_mut(&mut self) -> Option<&mut SelectData> {
768 match &mut self.special_data {
769 SpecialElementData::Select(data) => Some(data),
770 _ => None,
771 }
772 }
773
774 #[cfg(feature = "file-input")]
775 pub fn file_data(&self) -> Option<&FileData> {
776 match &self.special_data {
777 SpecialElementData::FileInput(data) => Some(data),
778 _ => None,
779 }
780 }
781
782 #[cfg(feature = "file-input")]
783 pub fn file_data_mut(&mut self) -> Option<&mut FileData> {
784 match &mut self.special_data {
785 SpecialElementData::FileInput(data) => Some(data),
786 _ => None,
787 }
788 }
789
790 pub fn flush_is_focussable(&mut self) {
791 let disabled: bool = self.attr_parsed(local_name!("disabled")).unwrap_or(false);
792 let tabindex: Option<i32> = self.attr_parsed(local_name!("tabindex"));
793 let contains_sub_document: bool = self.sub_doc_data().is_some();
794
795 self.is_focussable = contains_sub_document
796 || (!disabled
797 && match tabindex {
798 Some(index) => index >= 0,
799 None => {
800 if [local_name!("a"), local_name!("area")].contains(&self.name.local) {
807 self.attr(local_name!("href")).is_some()
808 } else {
809 const DEFAULT_FOCUSSABLE_ELEMENTS: [LocalName; 7] = [
810 local_name!("button"),
811 local_name!("input"),
812 local_name!("select"),
813 local_name!("textarea"),
814 local_name!("frame"),
815 local_name!("iframe"),
816 local_name!("summary"),
817 ];
818 DEFAULT_FOCUSSABLE_ELEMENTS.contains(&self.name.local)
819 }
820 }
821 })
822 }
823
824 pub fn flush_style_attribute(&mut self, guard: &SharedRwLock, url_extra_data: &UrlExtraData) {
825 self.style_attribute = self.attr(local_name!("style")).map(|style_str| {
826 ServoArc::new(guard.wrap(parse_style_attribute(
827 style_str,
828 url_extra_data,
829 None,
830 QuirksMode::NoQuirks,
831 CssRuleType::Style,
832 )))
833 });
834 }
835
836 pub fn set_style_property(
837 &mut self,
838 name: &str,
839 value: &str,
840 guard: &SharedRwLock,
841 url_extra_data: UrlExtraData,
842 ) -> bool {
843 let context = ParserContext::new(
844 Origin::Author,
845 &url_extra_data,
846 Some(CssRuleType::Style),
847 ParsingMode::DEFAULT,
848 QuirksMode::NoQuirks,
849 Default::default(),
850 None,
851 None,
852 Default::default(),
853 );
854
855 let Ok(property_id) = PropertyId::parse(name, &context) else {
856 #[cfg(feature = "tracing")]
857 tracing::warn!(property = name, "Unsupported property");
858 return false;
859 };
860 let mut source_property_declaration = SourcePropertyDeclaration::default();
861 let mut input = ParserInput::new(value);
862 let mut parser = style::values::Parser::new(&mut input);
863 let Ok(_) = PropertyDeclaration::parse_into(
864 &mut source_property_declaration,
865 property_id,
866 &context,
867 &mut parser,
868 ) else {
869 #[cfg(feature = "tracing")]
870 tracing::warn!(property = name, value, "Invalid property value");
871 return false;
872 };
873
874 if self.style_attribute.is_none() {
875 self.style_attribute = Some(ServoArc::new(guard.wrap(PropertyDeclarationBlock::new())));
876 }
877 self.style_attribute
878 .as_mut()
879 .unwrap()
880 .write_with(&mut guard.write())
881 .extend(source_property_declaration.drain(), Importance::Normal);
882
883 true
884 }
885
886 pub fn remove_style_property(
887 &mut self,
888 name: &str,
889 guard: &SharedRwLock,
890 url_extra_data: UrlExtraData,
891 ) -> bool {
892 let context = ParserContext::new(
893 Origin::Author,
894 &url_extra_data,
895 Some(CssRuleType::Style),
896 ParsingMode::DEFAULT,
897 QuirksMode::NoQuirks,
898 Default::default(),
899 None,
900 None,
901 Default::default(),
902 );
903 let Ok(property_id) = PropertyId::parse(name, &context) else {
904 #[cfg(feature = "tracing")]
905 tracing::warn!(property = name, "Unsupported property");
906 return false;
907 };
908
909 if let Some(style) = &mut self.style_attribute {
910 let mut guard = guard.write();
911 let style = style.write_with(&mut guard);
912 if let Some(index) = style.first_declaration_to_remove(&property_id) {
913 style.remove_property(&property_id, index);
914 return true;
915 }
916 }
917
918 false
919 }
920
921 pub fn set_sub_document(&mut self, sub_document: Box<dyn Document>) {
922 self.special_data = SpecialElementData::SubDocument(sub_document);
923 }
924
925 pub fn remove_sub_document(&mut self) {
926 self.special_data = SpecialElementData::None;
927 }
928
929 #[cfg(feature = "custom-widget")]
930 pub fn set_custom_widget(&mut self, widget: Box<dyn crate::Widget>) {
931 use crate::node::custom_widget::CustomWidgetData;
932 self.special_data = SpecialElementData::CustomWidget(CustomWidgetData::new(widget));
933 }
934
935 #[cfg(feature = "custom-widget")]
936 pub fn remove_custom_widget(&mut self) -> Vec<anyrender::ResourceId> {
937 let resource_ids = self
938 .custom_widget_data_mut()
939 .map(|widget_data| widget_data.take_resource_ids())
940 .unwrap_or_default();
941 self.special_data = SpecialElementData::None;
942 resource_ids
943 }
944
945 pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
946 std::mem::take(&mut self.inline_layout_data)
947 }
948
949 pub fn is_submit_button(&self) -> bool {
950 if self.name.local != local_name!("button") {
951 return false;
952 }
953 let type_attr = self.attr(local_name!("type"));
954 let is_submit = type_attr == Some("submit");
955 let is_auto_submit = type_attr.is_none()
956 && self.attr(LocalName::from("command")).is_none()
957 && self.attr(LocalName::from("commandfor")).is_none();
958 is_submit || is_auto_submit
959 }
960}
961
962#[derive(Debug, Clone, PartialEq)]
963pub struct RasterImageData {
964 pub width: u32,
966 pub height: u32,
968 pub data: Blob<u8>,
970}
971impl RasterImageData {
972 pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
973 Self {
974 width,
975 height,
976 data: Blob::new(data),
977 }
978 }
979}
980
981#[cfg(feature = "svg")]
991#[derive(Debug, Clone)]
992pub struct SvgImageData {
993 pub tree: Arc<usvg::Tree>,
995}
996
997#[cfg(feature = "svg")]
998impl SvgImageData {
999 pub fn intrinsic_width(&self) -> Option<f32> {
1002 use usvg::svgtypes::LengthUnit;
1003 let declared = self
1004 .tree
1005 .intrinsic_dimensions()
1006 .width
1007 .is_some_and(|len| len.unit != LengthUnit::Percent);
1008 declared.then(|| self.tree.size().width())
1009 }
1010
1011 pub fn intrinsic_height(&self) -> Option<f32> {
1014 use usvg::svgtypes::LengthUnit;
1015 let declared = self
1016 .tree
1017 .intrinsic_dimensions()
1018 .height
1019 .is_some_and(|len| len.unit != LengthUnit::Percent);
1020 declared.then(|| self.tree.size().height())
1021 }
1022
1023 pub fn viewbox_aspect_ratio(&self) -> Option<f32> {
1025 self.tree
1026 .intrinsic_dimensions()
1027 .view_box
1028 .map(|vb| vb.width() / vb.height())
1029 }
1030
1031 pub fn resolved_width(&self, container_width: Option<f32>) -> Option<f32> {
1040 use usvg::svgtypes::LengthUnit;
1041 match self.tree.intrinsic_dimensions().width {
1042 Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().width()),
1043 Some(len) => container_width.map(|cw| cw * (len.number as f32) / 100.0),
1044 None => None,
1045 }
1046 }
1047
1048 pub fn resolved_height(&self, container_height: Option<f32>) -> Option<f32> {
1051 use usvg::svgtypes::LengthUnit;
1052 match self.tree.intrinsic_dimensions().height {
1053 Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().height()),
1054 Some(len) => container_height.map(|ch| ch * (len.number as f32) / 100.0),
1055 None => None,
1056 }
1057 }
1058
1059 pub fn aspect_ratio(&self) -> f32 {
1064 match (self.intrinsic_width(), self.intrinsic_height()) {
1065 (Some(w), Some(h)) => w / h,
1066 _ => self.viewbox_aspect_ratio().unwrap_or_else(|| {
1067 let size = self.tree.size();
1068 size.width() / size.height()
1069 }),
1070 }
1071 }
1072
1073 pub fn intrinsic_size(&self) -> (f32, f32) {
1078 let aspect_ratio = self.aspect_ratio();
1079 match (self.intrinsic_width(), self.intrinsic_height()) {
1080 (Some(w), Some(h)) => (w, h),
1081 (Some(w), None) => (w, w / aspect_ratio),
1082 (None, Some(h)) => (h * aspect_ratio, h),
1083 (None, None) => {
1084 if self.viewbox_aspect_ratio().is_some() {
1088 let scale = (300.0 / aspect_ratio).min(150.0);
1089 (scale * aspect_ratio, scale)
1090 } else {
1091 let size = self.tree.size();
1092 (size.width(), size.height())
1093 }
1094 }
1095 }
1096 }
1097}
1098
1099#[derive(Debug, Clone)]
1100pub enum ImageData {
1101 Raster(RasterImageData),
1102 #[cfg(feature = "svg")]
1103 Svg(SvgImageData),
1104 None,
1105}
1106
1107#[derive(Debug, Clone, PartialEq)]
1108pub enum Status {
1109 Ok,
1110 Error,
1111 Loading,
1112}
1113
1114#[derive(Debug, Clone)]
1115pub struct ImageResourceData {
1116 pub url: ServoArc<Url>,
1118 pub status: Status,
1120 pub image: ImageData,
1122}
1123
1124impl ImageResourceData {
1125 pub fn new(url: ServoArc<Url>) -> Self {
1126 Self {
1127 url,
1128 status: Status::Loading,
1129 image: ImageData::None,
1130 }
1131 }
1132}
1133
1134#[derive(Debug, Clone)]
1135pub struct CanvasData {
1136 pub custom_paint_source_id: u64,
1137}
1138
1139impl std::fmt::Debug for SpecialElementData {
1140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1141 match self {
1142 SpecialElementData::SubDocument(_) => f.write_str("NodeSpecificData::SubDocument"),
1143 #[cfg(feature = "custom-widget")]
1144 SpecialElementData::CustomWidget(_) => f.write_str("NodeSpecificData::CustomWidget"),
1145 #[cfg(feature = "shadow-dom")]
1146 SpecialElementData::CustomElement(_) => f.write_str("NodeSpecificData::CustomElement"),
1147 SpecialElementData::Stylesheet(_) => f.write_str("NodeSpecificData::Stylesheet"),
1148 SpecialElementData::Image(data) => match **data {
1149 ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
1150 #[cfg(feature = "svg")]
1151 ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
1152 ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
1153 },
1154 SpecialElementData::Canvas(_) => f.write_str("NodeSpecificData::Canvas"),
1155 SpecialElementData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
1156 SpecialElementData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
1157 SpecialElementData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
1158 SpecialElementData::Select(data) => {
1159 write!(f, "NodeSpecificData::Select({data:?})")
1160 }
1161 #[cfg(feature = "file-input")]
1162 SpecialElementData::FileInput(_) => f.write_str("NodeSpecificData::FileInput"),
1163 SpecialElementData::None => f.write_str("NodeSpecificData::None"),
1164 }
1165 }
1166}
1167
1168#[derive(Clone)]
1169pub struct ListItemLayout {
1170 pub marker: Marker,
1171 pub position: ListItemLayoutPosition,
1172}
1173
1174#[derive(Debug, PartialEq, Clone)]
1177pub enum Marker {
1178 Char(char),
1179 String(String),
1180}
1181
1182#[derive(Clone)]
1184pub enum ListItemLayoutPosition {
1185 Inside,
1186 Outside(Box<parley::Layout<TextBrush>>),
1187}
1188
1189impl std::fmt::Debug for ListItemLayout {
1190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1191 write!(f, "ListItemLayout - marker {:?}", self.marker)
1192 }
1193}
1194
1195#[cfg(feature = "file-input")]
1196mod file_data {
1197 use std::ops::{Deref, DerefMut};
1198 use std::path::PathBuf;
1199
1200 #[derive(Clone, Debug)]
1201 pub struct FileData(pub Vec<PathBuf>);
1202 impl Deref for FileData {
1203 type Target = Vec<PathBuf>;
1204
1205 fn deref(&self) -> &Self::Target {
1206 &self.0
1207 }
1208 }
1209 impl DerefMut for FileData {
1210 fn deref_mut(&mut self) -> &mut Self::Target {
1211 &mut self.0
1212 }
1213 }
1214 impl From<Vec<PathBuf>> for FileData {
1215 fn from(files: Vec<PathBuf>) -> Self {
1216 Self(files)
1217 }
1218 }
1219}
1220#[cfg(feature = "file-input")]
1221pub use file_data::FileData;
1222
1223#[cfg(test)]
1224mod tests {
1225 use super::{ElementData, TextInputData};
1226 use parley::{FontContext, LayoutContext};
1227
1228 #[test]
1241 fn element_data_stays_out_of_the_large_size_classes() {
1242 let size = std::mem::size_of::<ElementData>();
1243 assert!(
1244 size <= 1536,
1245 "ElementData grew to {size} bytes. It is allocated per element, so \
1246 this is a multiplier on the whole DOM's footprint: at 2848 bytes \
1247 it was 814 MB of a live instance. Box the new field instead, as \
1248 `cache` is."
1249 );
1250 }
1251
1252 #[test]
1257 fn an_unlaid_out_element_carries_no_cache() {
1258 let data = ElementData::new(
1259 markup5ever::QualName::new(None, markup5ever::ns!(html), "div".into()),
1260 Vec::new(),
1261 );
1262 assert!(
1263 std::mem::size_of_val(&data) < std::mem::size_of::<taffy::Cache>() * 2,
1264 "the cache must not be inline: a fresh element should be smaller \
1265 than two caches"
1266 );
1267 }
1268
1269 fn make_input(is_multiline: bool, text: &str) -> TextInputData {
1271 let mut font_ctx = FontContext::new();
1272 let mut layout_ctx = LayoutContext::new();
1273 let mut data = TextInputData::new(is_multiline);
1274 data.editor.set_scale(1.0);
1275 data.editor.set_text(text);
1276 data.editor
1277 .driver(&mut font_ctx, &mut layout_ctx)
1278 .refresh_layout();
1279 data
1280 }
1281
1282 #[test]
1283 fn short_text_does_not_scroll() {
1284 let mut data = make_input(false, "hi");
1285 data.clamp_scroll_offset(1000.0, 100.0);
1287 assert_eq!(data.scroll_offset, 0.0);
1288 }
1289
1290 #[test]
1291 fn single_line_scrolls_to_follow_caret() {
1292 let text = "the quick brown fox jumps over the lazy dog repeatedly and at length";
1293 let mut data = make_input(false, text);
1294 let content_box_width = 40.0;
1295 let content_box_height = 20.0;
1296
1297 data.editor
1299 .driver(&mut FontContext::new(), &mut LayoutContext::new())
1300 .move_to_text_end();
1301 data.clamp_scroll_offset(content_box_width, content_box_height);
1302
1303 let layout_width = data.editor.try_layout().unwrap().full_width();
1304 if layout_width > content_box_width {
1305 assert!(
1306 data.scroll_offset > 0.0,
1307 "expected horizontal scroll for overflowing single-line input"
1308 );
1309 let caret = data.editor.cursor_geometry(1.5).unwrap();
1311 assert!(caret.x1 as f32 <= data.scroll_offset + content_box_width + 0.5);
1312 assert!(caret.x0 as f32 >= data.scroll_offset - 0.5);
1313 }
1314
1315 data.editor
1317 .driver(&mut FontContext::new(), &mut LayoutContext::new())
1318 .move_to_text_start();
1319 data.clamp_scroll_offset(content_box_width, content_box_height);
1320 assert_eq!(data.scroll_offset, 0.0);
1321 }
1322
1323 #[test]
1324 fn multiline_scrolls_vertically_not_horizontally() {
1325 let text = (0..40)
1326 .map(|i| format!("line {i}"))
1327 .collect::<Vec<_>>()
1328 .join("\n");
1329 let mut data = make_input(true, &text);
1330 data.editor.set_width(Some(200.0));
1332 data.editor
1333 .driver(&mut FontContext::new(), &mut LayoutContext::new())
1334 .refresh_layout();
1335
1336 let content_box_width = 200.0;
1337 let content_box_height = 30.0;
1338
1339 data.editor
1340 .driver(&mut FontContext::new(), &mut LayoutContext::new())
1341 .move_to_text_end();
1342 data.clamp_scroll_offset(content_box_width, content_box_height);
1343
1344 let layout_height = data.editor.try_layout().unwrap().height();
1345 if layout_height > content_box_height {
1346 assert!(
1347 data.scroll_offset > 0.0,
1348 "expected vertical scroll for overflowing multi-line input"
1349 );
1350 }
1351 }
1352
1353 #[cfg(target_os = "macos")]
1358 #[test]
1359 fn scroll_by_clamps_and_bubbles() {
1360 let text = (0..40)
1361 .map(|i| format!("line {i}"))
1362 .collect::<Vec<_>>()
1363 .join("\n");
1364 let mut data = make_input(true, &text);
1365 data.editor.set_width(Some(200.0));
1366 data.editor
1367 .driver(&mut FontContext::new(), &mut LayoutContext::new())
1368 .refresh_layout();
1369
1370 let content_box_width = 200.0;
1371 let content_box_height = 30.0;
1372 let max = data.max_scroll_offset(content_box_width, content_box_height);
1373 assert!(max > 0.0, "test text should overflow the content box");
1374
1375 assert_eq!(data.scroll_offset, 0.0);
1378 let bubbled = data.scroll_by(15.0, content_box_width, content_box_height);
1379 assert_eq!(data.scroll_offset, 0.0);
1380 assert_eq!(bubbled, 15.0);
1381
1382 let bubbled = data.scroll_by(-10.0, content_box_width, content_box_height);
1384 assert_eq!(data.scroll_offset, 10.0);
1385 assert_eq!(bubbled, 0.0);
1386
1387 let bubbled = data.scroll_by(-(max + 100.0), content_box_width, content_box_height);
1391 assert_eq!(data.scroll_offset, max);
1392 assert!((bubbled - (-110.0)).abs() < 1e-3);
1393 }
1394
1395 #[test]
1396 fn single_line_does_not_scroll_when_text_fits() {
1397 let mut data = make_input(false, "hi");
1398 let bubbled = data.scroll_by(-50.0, 1000.0, 100.0);
1400 assert_eq!(data.scroll_offset, 0.0);
1401 assert_eq!(bubbled, -50.0);
1402 }
1403}