1use std::rc::Rc;
7
8use app_units::Au;
9use compositing_traits::display_list::ScrollTree;
10use euclid::default::{Point2D, Rect};
11use euclid::{SideOffsets2D, Size2D};
12use itertools::Itertools;
13use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
14use layout_api::{
15 AxesOverflow, BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse,
16 PhysicalSides, ScrollContainerQueryFlags, ScrollContainerResponse,
17};
18use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
19use servo_arc::Arc as ServoArc;
20use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
21use servo_url::ServoUrl;
22use style::computed_values::display::T as Display;
23use style::computed_values::position::T as Position;
24use style::computed_values::visibility::T as Visibility;
25use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
26use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
27use style::dom::{NodeInfo, OpaqueNode, TElement, TNode};
28use style::properties::style_structs::Font;
29use style::properties::{
30 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
31 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
32};
33use style::selector_parser::PseudoElement;
34use style::shared_lock::SharedRwLock;
35use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
36use style::stylist::RuleInclusion;
37use style::traversal::resolve_style;
38use style::values::computed::{Float, Size};
39use style::values::generics::font::LineHeight;
40use style::values::generics::position::AspectRatio;
41use style::values::specified::GenericGridTemplateComponent;
42use style::values::specified::box_::DisplayInside;
43use style::values::specified::text::TextTransformCase;
44use style_traits::{ParsingMode, ToCss};
45
46use crate::ArcRefCell;
47use crate::display_list::StackingContextTree;
48use crate::dom::NodeExt;
49use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
50use crate::fragment_tree::{
51 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
52};
53use crate::style_ext::ComputedValuesExt;
54use crate::taffy::SpecificTaffyGridInfo;
55
56fn root_transform_for_layout_node(
59 scroll_tree: &ScrollTree,
60 node: ServoThreadSafeLayoutNode<'_>,
61) -> Option<FastLayoutTransform> {
62 let fragments = node.fragments_for_pseudo(None);
63 let box_fragment = fragments
64 .first()
65 .and_then(Fragment::retrieve_box_fragment)?
66 .borrow();
67 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
68 Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
69}
70
71pub(crate) fn process_padding_request(
72 node: ServoThreadSafeLayoutNode<'_>,
73) -> Option<PhysicalSides> {
74 let fragments = node.fragments_for_pseudo(None);
75 let fragment = fragments.first()?;
76 Some(match fragment {
77 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
78 let padding = box_fragment.borrow().padding;
79 PhysicalSides {
80 top: padding.top,
81 left: padding.left,
82 bottom: padding.bottom,
83 right: padding.right,
84 }
85 },
86 _ => Default::default(),
87 })
88}
89
90pub(crate) fn process_box_area_request(
91 stacking_context_tree: &StackingContextTree,
92 node: ServoThreadSafeLayoutNode<'_>,
93 area: BoxAreaType,
94 exclude_transform_and_inline: bool,
95) -> Option<Rect<Au>> {
96 let rects: Vec<_> = node
97 .fragments_for_pseudo(None)
98 .iter()
99 .filter(|fragment| {
100 !exclude_transform_and_inline ||
101 fragment
102 .retrieve_box_fragment()
103 .is_none_or(|fragment| !fragment.borrow().is_inline_box())
104 })
105 .filter_map(|node| node.cumulative_box_area_rect(area))
106 .collect();
107 if rects.is_empty() {
108 return None;
109 }
110 let rect_union = rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
111 rect.to_untyped().union(&unioned_rect)
112 });
113
114 if exclude_transform_and_inline {
115 return Some(rect_union);
116 }
117
118 let Some(transform) =
119 root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
120 else {
121 return Some(Rect::new(rect_union.origin, Size2D::zero()));
122 };
123
124 transform_au_rectangle(rect_union, transform)
125}
126
127pub(crate) fn process_box_areas_request(
128 stacking_context_tree: &StackingContextTree,
129 node: ServoThreadSafeLayoutNode<'_>,
130 area: BoxAreaType,
131) -> Vec<Rect<Au>> {
132 let fragments = node.fragments_for_pseudo(None);
133 let box_areas = fragments
134 .iter()
135 .filter_map(|node| node.cumulative_box_area_rect(area))
136 .map(|rect| rect.to_untyped());
137
138 let Some(transform) =
139 root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
140 else {
141 return box_areas
142 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
143 .collect();
144 };
145
146 box_areas
147 .filter_map(|rect| transform_au_rectangle(rect, transform))
148 .collect()
149}
150
151pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<i32> {
152 node.fragments_for_pseudo(None)
153 .first()
154 .map(Fragment::client_rect)
155 .unwrap_or_default()
156}
157
158pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
165 let Some(layout_data) = node.to_threadsafe().inner_layout_data() else {
166 return 1.0;
167 };
168 let layout_box = layout_data.self_box.borrow();
169 let Some(layout_box) = layout_box.as_ref() else {
170 return 1.0;
171 };
172 layout_box
173 .with_first_base(|base| base.style.effective_zoom.value())
174 .unwrap_or(1.0)
175}
176
177pub fn process_node_scroll_area_request(
179 requested_node: Option<ServoThreadSafeLayoutNode<'_>>,
180 fragment_tree: Option<Rc<FragmentTree>>,
181) -> Rect<i32> {
182 let Some(tree) = fragment_tree else {
183 return Rect::zero();
184 };
185
186 let rect = match requested_node {
187 Some(node) => node
188 .fragments_for_pseudo(None)
189 .first()
190 .map(Fragment::scrolling_area)
191 .unwrap_or_default(),
192 None => tree.scrollable_overflow(),
193 };
194
195 Rect::new(
196 Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
197 Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
198 )
199 .round()
200 .to_i32()
201 .to_untyped()
202}
203
204pub fn process_resolved_style_request(
207 context: &SharedStyleContext,
208 node: ServoLayoutNode<'_>,
209 pseudo: &Option<PseudoElement>,
210 property: &PropertyId,
211) -> String {
212 if !node.as_element().unwrap().has_data() {
213 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
214 }
215
216 let layout_element = node.to_threadsafe().as_element().unwrap();
219 let layout_element = match pseudo {
220 Some(pseudo_element_type) => {
221 match layout_element.with_pseudo(*pseudo_element_type) {
222 Some(layout_element) => layout_element,
223 None => {
224 return String::new();
228 },
229 }
230 },
231 None => layout_element,
232 };
233
234 let style = &*layout_element.style(context);
235 let longhand_id = match *property {
236 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
237 Ok(longhand_id) => longhand_id,
238 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
239 },
240 PropertyId::Custom(ref name) => {
241 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
242 },
243 }
244 .to_physical(style.writing_mode);
245
246 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
247 LonghandId::MinWidth
248 if style.clone_min_width() == Size::Auto &&
249 !should_honor_min_size_auto(fragment, style) =>
250 {
251 String::from("0px")
252 },
253 LonghandId::MinHeight
254 if style.clone_min_height() == Size::Auto &&
255 !should_honor_min_size_auto(fragment, style) =>
256 {
257 String::from("0px")
258 },
259 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
260 };
261
262 if longhand_id == LonghandId::LineHeight {
271 let font = style.get_font();
272 let font_size = font.font_size.computed_size();
273 return match font.line_height {
274 LineHeight::Normal => computed_style(None),
277 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
278 LineHeight::Length(value) => value.0.to_css_string(),
279 };
280 }
281
282 let display = style.get_box().display;
286 if display.is_none() || display.is_contents() {
287 return computed_style(None);
288 }
289
290 let resolve_for_fragment = |fragment: &Fragment| {
291 let (content_rect, margins, padding, specific_layout_info) = match fragment {
292 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
293 let box_fragment = box_fragment.borrow();
294 if style.get_box().position != Position::Static {
295 let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
296 match longhand_id {
297 LonghandId::Top => return resolved_insets().top.to_css_string(),
298 LonghandId::Right => {
299 return resolved_insets().right.to_css_string();
300 },
301 LonghandId::Bottom => {
302 return resolved_insets().bottom.to_css_string();
303 },
304 LonghandId::Left => {
305 return resolved_insets().left.to_css_string();
306 },
307 _ => {},
308 }
309 }
310 let content_rect = box_fragment.content_rect;
311 let margins = box_fragment.margin;
312 let padding = box_fragment.padding;
313 let specific_layout_info = box_fragment.specific_layout_info().cloned();
314 (content_rect, margins, padding, specific_layout_info)
315 },
316 Fragment::Positioning(positioning_fragment) => {
317 let content_rect = positioning_fragment.borrow().rect;
318 (
319 content_rect,
320 SideOffsets2D::zero(),
321 SideOffsets2D::zero(),
322 None,
323 )
324 },
325 _ => return computed_style(Some(fragment)),
326 };
327
328 if display.inside() == DisplayInside::Grid {
334 if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
335 if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
336 return value;
337 }
338 }
339 }
340
341 match longhand_id {
349 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
350 content_rect.size.width
351 },
352 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
353 content_rect.size.height
354 },
355 LonghandId::MarginBottom => margins.bottom,
356 LonghandId::MarginTop => margins.top,
357 LonghandId::MarginLeft => margins.left,
358 LonghandId::MarginRight => margins.right,
359 LonghandId::PaddingBottom => padding.bottom,
360 LonghandId::PaddingTop => padding.top,
361 LonghandId::PaddingLeft => padding.left,
362 LonghandId::PaddingRight => padding.right,
363 _ => return computed_style(Some(fragment)),
364 }
365 .to_css_string()
366 };
367
368 node.to_threadsafe()
369 .fragments_for_pseudo(*pseudo)
370 .first()
371 .map(resolve_for_fragment)
372 .unwrap_or_else(|| computed_style(None))
373}
374
375fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
376 match fragment {
379 Fragment::Box(box_fragment) => !box_fragment.borrow().is_inline_box(),
380 Fragment::Float(_) |
381 Fragment::Positioning(_) |
382 Fragment::AbsoluteOrFixedPositioned(_) |
383 Fragment::Image(_) |
384 Fragment::IFrame(_) => true,
385 Fragment::Text(_) => false,
386 }
387}
388
389fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
390 let Some(Fragment::Box(box_fragment)) = fragment else {
399 return false;
400 };
401 let flags = box_fragment.borrow().base.flags;
402 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
403 style.clone_aspect_ratio() != AspectRatio::auto()
404}
405
406fn resolve_grid_template(
407 grid_info: &SpecificTaffyGridInfo,
408 style: &ComputedValues,
409 longhand_id: LonghandId,
410) -> Option<String> {
411 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
413 match track_sizes.is_empty() {
414 true => None,
418 false => Some(
425 track_sizes
426 .iter()
427 .map(|size| size.to_css_string())
428 .join(" "),
429 ),
430 }
431 }
432
433 let (track_info, computed_value) = match longhand_id {
434 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
435 LonghandId::GridTemplateColumns => (
436 &grid_info.columns,
437 &style.get_position().grid_template_columns,
438 ),
439 _ => return None,
440 };
441
442 match computed_value {
443 GenericGridTemplateComponent::None |
447 GenericGridTemplateComponent::TrackList(_) |
448 GenericGridTemplateComponent::Masonry => {
449 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
450 },
451
452 GenericGridTemplateComponent::Subgrid(_) => None,
460 }
461}
462
463pub fn process_resolved_style_request_for_unstyled_node(
464 context: &SharedStyleContext,
465 node: ServoLayoutNode<'_>,
466 pseudo: &Option<PseudoElement>,
467 property: &PropertyId,
468) -> String {
469 if pseudo.is_some() {
471 return String::new();
472 }
473
474 let mut tlc = ThreadLocalStyleContext::new();
475 let mut context = StyleContext {
476 shared: context,
477 thread_local: &mut tlc,
478 };
479
480 let element = node.as_element().unwrap();
481 let styles = resolve_style(
482 &mut context,
483 element,
484 RuleInclusion::All,
485 pseudo.as_ref(),
486 None,
487 );
488 let style = styles.primary();
489 let longhand_id = match *property {
490 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
491 Ok(longhand_id) => longhand_id,
492 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
493 },
494 PropertyId::Custom(ref name) => {
495 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
496 },
497 };
498
499 match longhand_id {
500 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
503 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
504
505 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
508 }
509}
510
511fn shorthand_to_css_string(
512 id: style::properties::ShorthandId,
513 style: &style::properties::ComputedValues,
514) -> String {
515 use style::values::resolved::Context;
516 let mut block = PropertyDeclarationBlock::new();
517 let mut dest = String::new();
518 for longhand in id.longhands() {
519 block.push(
520 style.computed_or_resolved_declaration(
521 longhand,
522 Some(&Context {
523 style,
524 for_property: longhand.into(),
525 }),
526 ),
527 Importance::Normal,
528 );
529 }
530 match block.shorthand_to_css(id, &mut dest) {
531 Ok(_) => dest.to_owned(),
532 Err(_) => String::new(),
533 }
534}
535
536struct OffsetParentFragments {
537 parent: ArcRefCell<BoxFragment>,
538 grandparent: Option<Fragment>,
539}
540
541fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
543 let fragment = node
549 .to_threadsafe()
550 .fragments_for_pseudo(None)
551 .first()
552 .cloned()?;
553 let flags = fragment.base()?.flags;
554 if flags.intersects(
555 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
556 ) {
557 return None;
558 }
559 if matches!(
560 fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
561 ) {
562 return None;
563 }
564
565 let mut maybe_parent_node = node.parent_node();
572 while let Some(parent_node) = maybe_parent_node {
573 maybe_parent_node = parent_node.parent_node();
574
575 if let Some(parent_fragment) = parent_node
576 .to_threadsafe()
577 .fragments_for_pseudo(None)
578 .first()
579 {
580 let parent_fragment = match parent_fragment {
581 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
582 _ => continue,
583 };
584
585 let grandparent_fragment = maybe_parent_node.and_then(|node| {
586 node.to_threadsafe()
587 .fragments_for_pseudo(None)
588 .first()
589 .cloned()
590 });
591
592 if parent_fragment.borrow().style.get_box().position != Position::Static {
593 return Some(OffsetParentFragments {
594 parent: parent_fragment.clone(),
595 grandparent: grandparent_fragment,
596 });
597 }
598
599 let flags = parent_fragment.borrow().base.flags;
600 if flags.intersects(
601 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
602 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
603 ) {
604 return Some(OffsetParentFragments {
605 parent: parent_fragment.clone(),
606 grandparent: grandparent_fragment,
607 });
608 }
609 }
610 }
611
612 None
613}
614
615#[inline]
616pub fn process_offset_parent_query(
617 scroll_tree: &ScrollTree,
618 node: ServoLayoutNode<'_>,
619) -> Option<OffsetParentResponse> {
620 let fragment = node
637 .to_threadsafe()
638 .fragments_for_pseudo(None)
639 .first()
640 .cloned()?;
641 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
642 let cumulative_sticky_offsets = fragment
643 .retrieve_box_fragment()
644 .and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
645 .map(|node_id| {
646 scroll_tree
647 .cumulative_sticky_offsets(node_id)
648 .map(Au::from_f32_px)
649 .cast_unit()
650 });
651 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
652
653 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
658 return Some(OffsetParentResponse {
659 node_address: None,
660 rect: border_box.to_untyped(),
661 });
662 };
663
664 let parent_fragment = offset_parent_fragment.parent.borrow();
665 let parent_is_static_body_element = parent_fragment
666 .base
667 .flags
668 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
669 parent_fragment.style.get_box().position == Position::Static;
670
671 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
680 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
681 Some(box_fragment)
682 },
683 _ => None,
684 };
685
686 let parent_offset_rect = if parent_is_static_body_element {
694 if let Some(grandparent_fragment) = grandparent_box_fragment() {
695 let grandparent_fragment = grandparent_fragment.borrow();
696 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
697 } else {
698 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
699 }
700 } else {
701 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
702 }
703 .translate(
704 cumulative_sticky_offsets
705 .and_then(|_| parent_fragment.spatial_tree_node())
706 .map(|node_id| {
707 scroll_tree
708 .cumulative_sticky_offsets(node_id)
709 .map(Au::from_f32_px)
710 .cast_unit()
711 })
712 .unwrap_or_default(),
713 );
714
715 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
716
717 Some(OffsetParentResponse {
718 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
719 rect: border_box.to_untyped(),
720 })
721}
722
723#[inline]
727pub(crate) fn process_scroll_container_query(
728 node: Option<ServoLayoutNode<'_>>,
729 query_flags: ScrollContainerQueryFlags,
730 viewport_overflow: AxesOverflow,
731) -> Option<ScrollContainerResponse> {
732 let Some(node) = node else {
733 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
734 };
735
736 let layout_data = node.to_threadsafe().inner_layout_data()?;
737
738 let layout_box = layout_data.self_box.borrow();
741 let layout_box = layout_box.as_ref()?;
742
743 let (style, flags) =
744 layout_box.with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
745
746 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
752 flags.intersects(
753 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
754 )
755 {
756 return None;
757 }
758
759 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
760 style.establishes_scroll_container(flags)
761 {
762 return Some(ScrollContainerResponse::Element(
763 node.opaque().into(),
764 style.effective_overflow(flags),
765 ));
766 }
767
768 let mut current_position_value = style.clone_position();
788 let mut current_ancestor = node.as_element()?;
789 while let Some(ancestor) = current_ancestor.traversal_parent() {
790 current_ancestor = ancestor;
791
792 let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
793 continue;
794 };
795 let ancestor_layout_box = layout_data.self_box.borrow();
796 let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
797 continue;
798 };
799
800 let Some((ancestor_style, ancestor_flags)) = ancestor_layout_box
801 .with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))
802 else {
803 continue;
804 };
805
806 let is_containing_block = match current_position_value {
807 Position::Static | Position::Relative | Position::Sticky => {
808 !ancestor_style.is_inline_box(ancestor_flags)
809 },
810 Position::Absolute => {
811 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
812 },
813 Position::Fixed => {
814 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
815 },
816 };
817 if !is_containing_block {
818 continue;
819 }
820
821 if ancestor_style.establishes_scroll_container(ancestor_flags) {
822 return Some(ScrollContainerResponse::Element(
823 ancestor.as_node().opaque().into(),
824 ancestor_style.effective_overflow(ancestor_flags),
825 ));
826 }
827
828 current_position_value = ancestor_style.clone_position();
829 }
830
831 match current_position_value {
832 Position::Fixed => None,
833 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
834 }
835}
836
837pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
839 let mut results = Vec::new();
845 let mut max_req_line_break_count = 0;
846
847 let mut state = Default::default();
849 for child in node.dom_children() {
850 let mut current = rendered_text_collection_steps(child, &mut state);
852 results.append(&mut current);
854 }
855
856 let mut output = Vec::new();
857 for item in results {
858 match item {
859 InnerOrOuterTextItem::Text(s) => {
860 if !s.is_empty() {
862 if max_req_line_break_count > 0 {
863 output.push("\u{000A}".repeat(max_req_line_break_count));
865 max_req_line_break_count = 0;
866 }
867 output.push(s);
868 }
869 },
870 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
871 if output.is_empty() {
873 continue;
875 }
876 if count > max_req_line_break_count {
880 max_req_line_break_count = count;
881 }
882 },
883 }
884 }
885 output.into_iter().collect()
886}
887
888enum InnerOrOuterTextItem {
889 Text(String),
890 RequiredLineBreakCount(usize),
891}
892
893#[derive(Clone)]
894struct RenderedTextCollectionState {
895 first_table_row: bool,
897 first_table_cell: bool,
899 within_table: bool,
902 may_start_with_whitespace: bool,
904 did_truncate_trailing_white_space: bool,
907 within_table_content: bool,
910}
911
912impl Default for RenderedTextCollectionState {
913 fn default() -> Self {
914 RenderedTextCollectionState {
915 first_table_row: true,
916 first_table_cell: true,
917 may_start_with_whitespace: true,
918 did_truncate_trailing_white_space: false,
919 within_table: false,
920 within_table_content: false,
921 }
922 }
923}
924
925fn rendered_text_collection_steps(
927 node: ServoLayoutNode<'_>,
928 state: &mut RenderedTextCollectionState,
929) -> Vec<InnerOrOuterTextItem> {
930 let mut items = vec![];
934 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
935 return items;
936 }
937
938 match node.type_id() {
939 LayoutNodeType::Text => {
940 if let Some(element) = node.parent_node() {
941 match element.type_id() {
942 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
944 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
945 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
946 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
947 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
948 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
949 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
950 return items;
951 },
952 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
956 if let Some(element) = element.parent_node() {
957 if !matches!(
958 element.type_id(),
959 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
960 ) {
961 return items;
962 }
963 } else {
964 return items;
965 }
966 },
967 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
968 _ => {},
969 }
970
971 if state.within_table && !state.within_table_content {
975 return items;
976 }
977
978 let Some(style_data) = element.style_data() else {
979 return items;
980 };
981
982 let element_data = style_data.element_data.borrow();
983 let Some(style) = element_data.styles.get_primary() else {
984 return items;
985 };
986
987 if style.get_inherited_box().visibility != Visibility::Visible {
993 return items;
994 }
995
996 let display = style.get_box().display;
1000 if display == Display::None {
1001 match element.type_id() {
1002 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1005 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) => {},
1006 _ => {
1007 return items;
1008 },
1009 }
1010 }
1011
1012 let text_content = node.to_threadsafe().node_text_content();
1013
1014 let white_space_collapse = style.clone_white_space_collapse();
1015 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1016 let is_inline = matches!(
1017 display,
1018 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1019 );
1020 let trim_beginning_white_space =
1024 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1025 let with_white_space_rules_applied = WhitespaceCollapse::new(
1026 text_content.chars(),
1027 white_space_collapse,
1028 trim_beginning_white_space,
1029 );
1030
1031 let text_transform = style.clone_text_transform().case();
1039 let mut transformed_text: String =
1040 TextTransformation::new(with_white_space_rules_applied, text_transform)
1041 .collect();
1042
1043 if TextTransformCase::Capitalize == text_transform {
1047 transformed_text = capitalize_string(&transformed_text, true);
1048 }
1049
1050 let is_preformatted_element =
1051 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1052
1053 let is_final_character_whitespace = transformed_text
1054 .chars()
1055 .next_back()
1056 .filter(char::is_ascii_whitespace)
1057 .is_some();
1058
1059 let is_first_character_whitespace = transformed_text
1060 .chars()
1061 .next()
1062 .filter(char::is_ascii_whitespace)
1063 .is_some();
1064
1065 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1069 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1070 };
1071
1072 if !transformed_text.is_empty() {
1073 if is_final_character_whitespace && !is_preformatted_element {
1076 state.may_start_with_whitespace = false;
1077 state.did_truncate_trailing_white_space = true;
1078 transformed_text.pop();
1079 } else {
1080 state.may_start_with_whitespace = is_final_character_whitespace;
1081 state.did_truncate_trailing_white_space = false;
1082 }
1083 items.push(InnerOrOuterTextItem::Text(transformed_text));
1084 }
1085 } else {
1086 items.push(InnerOrOuterTextItem::Text(
1089 node.to_threadsafe().node_text_content().into(),
1090 ));
1091 }
1092 },
1093 LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
1094 state.did_truncate_trailing_white_space = false;
1097 state.may_start_with_whitespace = true;
1098 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1099 },
1100 _ => {
1101 let Some(style_data) = node.style_data() else {
1104 return items;
1105 };
1106
1107 let element_data = style_data.element_data.borrow();
1108 let Some(style) = element_data.styles.get_primary() else {
1109 return items;
1110 };
1111 let inherited_box = style.get_inherited_box();
1112
1113 if inherited_box.visibility != Visibility::Visible {
1114 for child in node.dom_children() {
1118 items.append(&mut rendered_text_collection_steps(child, state));
1119 }
1120 return items;
1121 }
1122
1123 let style_box = style.get_box();
1124 let display = style_box.display;
1125 let mut surrounding_line_breaks = 0;
1126
1127 if style_box.position == Position::Absolute || style_box.float != Float::None {
1129 surrounding_line_breaks = 1;
1130 }
1131
1132 match display {
1135 Display::Table => {
1136 surrounding_line_breaks = 1;
1137 state.within_table = true;
1138 },
1139 Display::TableCell => {
1144 if !state.first_table_cell {
1145 items.push(InnerOrOuterTextItem::Text(String::from(
1146 "\u{0009}", )));
1148 state.did_truncate_trailing_white_space = false;
1150 }
1151 state.first_table_cell = false;
1152 state.within_table_content = true;
1153 },
1154 Display::TableRow => {
1159 if !state.first_table_row {
1160 items.push(InnerOrOuterTextItem::Text(String::from(
1161 "\u{000A}", )));
1163 state.did_truncate_trailing_white_space = false;
1165 }
1166 state.first_table_row = false;
1167 state.first_table_cell = true;
1168 },
1169 Display::Block => {
1172 surrounding_line_breaks = 1;
1173 },
1174 Display::TableCaption => {
1175 surrounding_line_breaks = 1;
1176 state.within_table_content = true;
1177 },
1178 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1179 if state.did_truncate_trailing_white_space {
1183 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1184 state.did_truncate_trailing_white_space = false;
1185 state.may_start_with_whitespace = true;
1186 }
1187 },
1188 _ => {},
1189 }
1190
1191 match node.type_id() {
1192 LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1195 surrounding_line_breaks = 2;
1196 },
1197 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1200 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
1201 surrounding_line_breaks = 1;
1202 },
1203 _ => {},
1204 }
1205
1206 if surrounding_line_breaks > 0 {
1207 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1208 surrounding_line_breaks,
1209 ));
1210 state.did_truncate_trailing_white_space = false;
1211 state.may_start_with_whitespace = true;
1212 }
1213
1214 match node.type_id() {
1215 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1220 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1221 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1222 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1223 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1224 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1225 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
1226 if display != Display::Block && state.did_truncate_trailing_white_space {
1227 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1228 state.did_truncate_trailing_white_space = false;
1229 };
1230 state.may_start_with_whitespace = false;
1231 },
1232 _ => {
1233 for child in node.dom_children() {
1236 items.append(&mut rendered_text_collection_steps(child, state));
1237 }
1238 },
1239 }
1240
1241 match display {
1244 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1245 state.did_truncate_trailing_white_space = false;
1246 state.may_start_with_whitespace = false;
1247 },
1248 Display::Table => {
1249 state.within_table = false;
1250 },
1251 Display::TableCell | Display::TableCaption => {
1252 state.within_table_content = false;
1253 },
1254 _ => {},
1255 }
1256
1257 if surrounding_line_breaks > 0 {
1258 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1259 surrounding_line_breaks,
1260 ));
1261 state.did_truncate_trailing_white_space = false;
1262 state.may_start_with_whitespace = true;
1263 }
1264 },
1265 };
1266 items
1267}
1268
1269pub fn process_text_index_request(_node: OpaqueNode, _point: Point2D<Au>) -> Option<usize> {
1270 None
1271}
1272
1273pub fn process_resolved_font_style_query<'dom, E>(
1274 context: &SharedStyleContext,
1275 node: E,
1276 value: &str,
1277 url_data: ServoUrl,
1278 shared_lock: &SharedRwLock,
1279) -> Option<ServoArc<Font>>
1280where
1281 E: LayoutNode<'dom>,
1282{
1283 fn create_font_declaration(
1284 value: &str,
1285 url_data: &ServoUrl,
1286 quirks_mode: QuirksMode,
1287 ) -> Option<PropertyDeclarationBlock> {
1288 let mut declarations = SourcePropertyDeclaration::default();
1289 let result = parse_one_declaration_into(
1290 &mut declarations,
1291 PropertyId::NonCustom(ShorthandId::Font.into()),
1292 value,
1293 Origin::Author,
1294 &UrlExtraData(url_data.get_arc()),
1295 None,
1296 ParsingMode::DEFAULT,
1297 quirks_mode,
1298 CssRuleType::Style,
1299 );
1300 let declarations = match result {
1301 Ok(()) => {
1302 let mut block = PropertyDeclarationBlock::new();
1303 block.extend(declarations.drain(), Importance::Normal);
1304 block
1305 },
1306 Err(_) => return None,
1307 };
1308 Some(declarations)
1310 }
1311 fn resolve_for_declarations<'dom, E>(
1312 context: &SharedStyleContext,
1313 parent_style: Option<&ComputedValues>,
1314 declarations: PropertyDeclarationBlock,
1315 shared_lock: &SharedRwLock,
1316 ) -> ServoArc<ComputedValues>
1317 where
1318 E: LayoutNode<'dom>,
1319 {
1320 let parent_style = match parent_style {
1321 Some(parent) => parent,
1322 None => context.stylist.device().default_computed_values(),
1323 };
1324 context
1325 .stylist
1326 .compute_for_declarations::<E::ConcreteElement>(
1327 &context.guards,
1328 parent_style,
1329 ServoArc::new(shared_lock.wrap(declarations)),
1330 )
1331 }
1332
1333 let quirks_mode = context.quirks_mode();
1336 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1337
1338 let element = node.as_element().unwrap();
1342 let parent_style = if node.is_connected() {
1343 if element.has_data() {
1344 node.to_threadsafe().as_element().unwrap().style(context)
1345 } else {
1346 let mut tlc = ThreadLocalStyleContext::new();
1347 let mut context = StyleContext {
1348 shared: context,
1349 thread_local: &mut tlc,
1350 };
1351 let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
1352 styles.primary().clone()
1353 }
1354 } else {
1355 let default_declarations =
1356 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1357 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1358 };
1359
1360 let computed_values =
1362 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1363
1364 Some(computed_values.clone_font())
1365}
1366
1367pub(crate) fn transform_au_rectangle(
1368 rect_to_transform: Rect<Au>,
1369 transform: FastLayoutTransform,
1370) -> Option<Rect<Au>> {
1371 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1372 let outer_transformed_rect = match transform {
1373 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1374 FastLayoutTransform::Transform { transform, .. } => {
1375 transform.outer_transformed_rect(rect_to_transform)
1376 },
1377 };
1378 outer_transformed_rect
1379 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()))
1380}