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