1use std::rc::Rc;
7
8use app_units::Au;
9use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
10use itertools::Itertools;
11use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
12use layout_api::{
13 AxesOverflow, BoxAreaType, CSSPixelRectIterator, LayoutElementType, LayoutNodeType,
14 OffsetParentResponse, PhysicalSides, ScrollContainerQueryFlags, ScrollContainerResponse,
15};
16use paint_api::display_list::ScrollTree;
17use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
18use servo_arc::Arc as ServoArc;
19use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
20use servo_url::ServoUrl;
21use style::computed_values::display::T as Display;
22use style::computed_values::position::T as Position;
23use style::computed_values::visibility::T as Visibility;
24use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
25use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
26use style::dom::{NodeInfo, TElement, TNode};
27use style::properties::style_structs::Font;
28use style::properties::{
29 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
30 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
31};
32use style::selector_parser::PseudoElement;
33use style::shared_lock::SharedRwLock;
34use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
35use style::stylist::RuleInclusion;
36use style::traversal::resolve_style;
37use style::values::computed::transform::Matrix3D;
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::{CSSPixel, ParsingMode, ToCss};
45
46use crate::ArcRefCell;
47use crate::display_list::{StackingContextTree, au_rect_to_length_rect};
48use crate::dom::NodeExt;
49use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
50use crate::fragment_tree::{
51 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, TextFragment,
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, CSSPixel>> {
96 let fragments = node.fragments_for_pseudo(None);
97 let mut rects = fragments
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 .peekable();
107
108 rects.peek()?;
109 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
110
111 if exclude_transform_and_inline {
112 return Some(rect_union);
113 }
114
115 let Some(transform) =
116 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
117 else {
118 return Some(Rect::new(rect_union.origin, Size2D::zero()));
119 };
120
121 transform_au_rectangle(rect_union, transform)
122}
123
124pub(crate) fn process_box_areas_request(
125 stacking_context_tree: &StackingContextTree,
126 node: ServoThreadSafeLayoutNode<'_>,
127 area: BoxAreaType,
128) -> CSSPixelRectIterator {
129 let fragments = node
130 .fragments_for_pseudo(None)
131 .into_iter()
132 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area));
133
134 let Some(transform) =
135 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
136 else {
137 return Box::new(fragments.map(|rect| Rect::new(rect.origin, Size2D::zero())));
138 };
139
140 Box::new(fragments.filter_map(move |rect| transform_au_rectangle(rect, transform)))
141}
142
143pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<i32, CSSPixel> {
144 node.fragments_for_pseudo(None)
145 .first()
146 .map(Fragment::client_rect)
147 .unwrap_or_default()
148}
149
150pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
157 let Some(layout_data) = node.to_threadsafe().inner_layout_data() else {
158 return 1.0;
159 };
160 let layout_box = layout_data.self_box.borrow();
161 let Some(layout_box) = layout_box.as_ref() else {
162 return 1.0;
163 };
164 layout_box
165 .with_base(|base| base.style.effective_zoom.value())
166 .unwrap_or(1.0)
167}
168
169pub fn process_node_scroll_area_request(
171 requested_node: Option<ServoThreadSafeLayoutNode<'_>>,
172 fragment_tree: Option<Rc<FragmentTree>>,
173) -> Rect<i32, CSSPixel> {
174 let Some(tree) = fragment_tree else {
175 return Rect::zero();
176 };
177
178 let rect = match requested_node {
179 Some(node) => node
180 .fragments_for_pseudo(None)
181 .first()
182 .map(Fragment::scrolling_area)
183 .unwrap_or_default(),
184 None => tree.scrollable_overflow(),
185 };
186
187 Rect::new(
188 rect.origin.map(Au::to_f32_px),
189 rect.size.to_vector().map(Au::to_f32_px).to_size(),
190 )
191 .round()
192 .to_i32()
193}
194
195pub fn process_resolved_style_request(
198 context: &SharedStyleContext,
199 node: ServoLayoutNode<'_>,
200 pseudo: &Option<PseudoElement>,
201 property: &PropertyId,
202) -> String {
203 if !node.as_element().unwrap().has_data() {
204 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
205 }
206
207 let layout_element = node.to_threadsafe().as_element().unwrap();
210 let layout_element = match pseudo {
211 Some(pseudo_element_type) => {
212 match layout_element.with_pseudo(*pseudo_element_type) {
213 Some(layout_element) => layout_element,
214 None => {
215 return String::new();
219 },
220 }
221 },
222 None => layout_element,
223 };
224
225 let style = &*layout_element.style(context);
226 let longhand_id = match *property {
227 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
228 Ok(longhand_id) => longhand_id,
229 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
230 },
231 PropertyId::Custom(ref name) => {
232 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
233 },
234 }
235 .to_physical(style.writing_mode);
236
237 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
239 let transform_list = &style.get_box().transform;
240
241 if transform_list.0.is_empty() {
245 return Ok("none".into());
246 }
247
248 let length_rect = box_fragment
253 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
254 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
255
256 let matrix = Matrix3D::from(transform);
261 if !is_3d {
262 Ok(matrix.into_2d()?.to_css_string())
263 } else {
264 Ok(matrix.to_css_string())
265 }
266 };
267
268 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
269 LonghandId::MinWidth
270 if style.clone_min_width() == Size::Auto &&
271 !should_honor_min_size_auto(fragment, style) =>
272 {
273 String::from("0px")
274 },
275 LonghandId::MinHeight
276 if style.clone_min_height() == Size::Auto &&
277 !should_honor_min_size_auto(fragment, style) =>
278 {
279 String::from("0px")
280 },
281 LonghandId::Transform => match serialize_transform_value(None) {
282 Ok(value) => value,
283 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
284 },
285 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
286 };
287
288 if longhand_id == LonghandId::LineHeight {
297 let font = style.get_font();
298 let font_size = font.font_size.computed_size();
299 return match font.line_height {
300 LineHeight::Normal => computed_style(None),
303 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
304 LineHeight::Length(value) => value.0.to_css_string(),
305 };
306 }
307
308 let display = style.get_box().display;
312 if display.is_none() || display.is_contents() {
313 return computed_style(None);
314 }
315
316 let resolve_for_fragment = |fragment: &Fragment| {
317 let (content_rect, margins, padding, specific_layout_info) = match fragment {
318 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
319 let box_fragment = box_fragment.borrow();
320 if style.get_box().position != Position::Static {
321 let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
322 match longhand_id {
323 LonghandId::Top => return resolved_insets().top.to_css_string(),
324 LonghandId::Right => {
325 return resolved_insets().right.to_css_string();
326 },
327 LonghandId::Bottom => {
328 return resolved_insets().bottom.to_css_string();
329 },
330 LonghandId::Left => {
331 return resolved_insets().left.to_css_string();
332 },
333 LonghandId::Transform => {
334 if let Ok(string) = serialize_transform_value(Some(&*box_fragment)) {
337 return string;
338 }
339 },
340 _ => {},
341 }
342 }
343 let content_rect = box_fragment.base.rect;
344 let margins = box_fragment.margin;
345 let padding = box_fragment.padding;
346 let specific_layout_info = box_fragment.specific_layout_info().cloned();
347 (content_rect, margins, padding, specific_layout_info)
348 },
349 Fragment::Positioning(positioning_fragment) => (
350 positioning_fragment.borrow().base.rect,
351 SideOffsets2D::zero(),
352 SideOffsets2D::zero(),
353 None,
354 ),
355 _ => return computed_style(Some(fragment)),
356 };
357
358 if display.inside() == DisplayInside::Grid {
364 if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
365 if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
366 return value;
367 }
368 }
369 }
370
371 match longhand_id {
379 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
380 content_rect.size.width
381 },
382 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
383 content_rect.size.height
384 },
385 LonghandId::MarginBottom => margins.bottom,
386 LonghandId::MarginTop => margins.top,
387 LonghandId::MarginLeft => margins.left,
388 LonghandId::MarginRight => margins.right,
389 LonghandId::PaddingBottom => padding.bottom,
390 LonghandId::PaddingTop => padding.top,
391 LonghandId::PaddingLeft => padding.left,
392 LonghandId::PaddingRight => padding.right,
393 _ => return computed_style(Some(fragment)),
394 }
395 .to_css_string()
396 };
397
398 node.to_threadsafe()
399 .fragments_for_pseudo(*pseudo)
400 .first()
401 .map(resolve_for_fragment)
402 .unwrap_or_else(|| computed_style(None))
403}
404
405fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
406 match fragment {
409 Fragment::Box(box_fragment) => !box_fragment.borrow().is_inline_box(),
410 Fragment::Float(_) |
411 Fragment::Positioning(_) |
412 Fragment::AbsoluteOrFixedPositioned(_) |
413 Fragment::Image(_) |
414 Fragment::IFrame(_) => true,
415 Fragment::Text(_) => false,
416 }
417}
418
419fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
420 let Some(Fragment::Box(box_fragment)) = fragment else {
429 return false;
430 };
431 let flags = box_fragment.borrow().base.flags;
432 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
433 style.clone_aspect_ratio() != AspectRatio::auto()
434}
435
436fn resolve_grid_template(
437 grid_info: &SpecificTaffyGridInfo,
438 style: &ComputedValues,
439 longhand_id: LonghandId,
440) -> Option<String> {
441 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
443 match track_sizes.is_empty() {
444 true => None,
448 false => Some(
455 track_sizes
456 .iter()
457 .map(|size| size.to_css_string())
458 .join(" "),
459 ),
460 }
461 }
462
463 let (track_info, computed_value) = match longhand_id {
464 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
465 LonghandId::GridTemplateColumns => (
466 &grid_info.columns,
467 &style.get_position().grid_template_columns,
468 ),
469 _ => return None,
470 };
471
472 match computed_value {
473 GenericGridTemplateComponent::None |
477 GenericGridTemplateComponent::TrackList(_) |
478 GenericGridTemplateComponent::Masonry => {
479 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
480 },
481
482 GenericGridTemplateComponent::Subgrid(_) => None,
490 }
491}
492
493pub fn process_resolved_style_request_for_unstyled_node(
494 context: &SharedStyleContext,
495 node: ServoLayoutNode<'_>,
496 pseudo: &Option<PseudoElement>,
497 property: &PropertyId,
498) -> String {
499 if pseudo.is_some() {
501 return String::new();
502 }
503
504 let mut tlc = ThreadLocalStyleContext::new();
505 let mut context = StyleContext {
506 shared: context,
507 thread_local: &mut tlc,
508 };
509
510 let element = node.as_element().unwrap();
511 let styles = resolve_style(
512 &mut context,
513 element,
514 RuleInclusion::All,
515 pseudo.as_ref(),
516 None,
517 );
518 let style = styles.primary();
519 let longhand_id = match *property {
520 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
521 Ok(longhand_id) => longhand_id,
522 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
523 },
524 PropertyId::Custom(ref name) => {
525 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
526 },
527 };
528
529 match longhand_id {
530 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
533 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
534
535 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
538 }
539}
540
541fn shorthand_to_css_string(
542 id: style::properties::ShorthandId,
543 style: &style::properties::ComputedValues,
544) -> String {
545 use style::values::resolved::Context;
546 let mut block = PropertyDeclarationBlock::new();
547 let mut dest = String::new();
548 for longhand in id.longhands() {
549 block.push(
550 style.computed_or_resolved_declaration(
551 longhand,
552 Some(&mut Context {
553 style,
554 for_property: longhand.into(),
555 current_longhand: None,
556 }),
557 ),
558 Importance::Normal,
559 );
560 }
561 match block.shorthand_to_css(id, &mut dest) {
562 Ok(_) => dest.to_owned(),
563 Err(_) => String::new(),
564 }
565}
566
567struct OffsetParentFragments {
568 parent: ArcRefCell<BoxFragment>,
569 grandparent: Option<Fragment>,
570}
571
572fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
574 let fragment = node
580 .to_threadsafe()
581 .fragments_for_pseudo(None)
582 .first()
583 .cloned()?;
584 let flags = fragment.base()?.flags;
585 if flags.intersects(
586 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
587 ) {
588 return None;
589 }
590 if matches!(
591 fragment, Fragment::Box(fragment) if fragment.borrow().style().get_box().position == Position::Fixed
592 ) {
593 return None;
594 }
595
596 let mut maybe_parent_node = node.parent_node();
603 while let Some(parent_node) = maybe_parent_node {
604 maybe_parent_node = parent_node.parent_node();
605
606 if let Some(parent_fragment) = parent_node
607 .to_threadsafe()
608 .fragments_for_pseudo(None)
609 .first()
610 {
611 let parent_fragment = match parent_fragment {
612 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
613 _ => continue,
614 };
615
616 let grandparent_fragment = maybe_parent_node.and_then(|node| {
617 node.to_threadsafe()
618 .fragments_for_pseudo(None)
619 .first()
620 .cloned()
621 });
622
623 if parent_fragment.borrow().style().get_box().position != Position::Static {
624 return Some(OffsetParentFragments {
625 parent: parent_fragment.clone(),
626 grandparent: grandparent_fragment,
627 });
628 }
629
630 let flags = parent_fragment.borrow().base.flags;
631 if flags.intersects(
632 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
633 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
634 ) {
635 return Some(OffsetParentFragments {
636 parent: parent_fragment.clone(),
637 grandparent: grandparent_fragment,
638 });
639 }
640 }
641 }
642
643 None
644}
645
646#[inline]
647pub fn process_offset_parent_query(
648 scroll_tree: &ScrollTree,
649 node: ServoLayoutNode<'_>,
650) -> Option<OffsetParentResponse> {
651 let fragment = node
668 .to_threadsafe()
669 .fragments_for_pseudo(None)
670 .first()
671 .cloned()?;
672 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
673 let cumulative_sticky_offsets = fragment
674 .retrieve_box_fragment()
675 .and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
676 .map(|node_id| {
677 scroll_tree
678 .cumulative_sticky_offsets(node_id)
679 .map(Au::from_f32_px)
680 .cast_unit()
681 });
682 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
683
684 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
689 return Some(OffsetParentResponse {
690 node_address: None,
691 rect: border_box,
692 });
693 };
694
695 let parent_fragment = offset_parent_fragment.parent.borrow();
696 let parent_is_static_body_element = parent_fragment
697 .base
698 .flags
699 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
700 parent_fragment.style().get_box().position == Position::Static;
701
702 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
711 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
712 Some(box_fragment)
713 },
714 _ => None,
715 };
716
717 let parent_offset_rect = if parent_is_static_body_element {
725 if let Some(grandparent_fragment) = grandparent_box_fragment() {
726 let grandparent_fragment = grandparent_fragment.borrow();
727 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
728 } else {
729 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
730 }
731 } else {
732 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
733 }
734 .translate(
735 cumulative_sticky_offsets
736 .and_then(|_| parent_fragment.spatial_tree_node())
737 .map(|node_id| {
738 scroll_tree
739 .cumulative_sticky_offsets(node_id)
740 .map(Au::from_f32_px)
741 .cast_unit()
742 })
743 .unwrap_or_default(),
744 );
745
746 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
747
748 Some(OffsetParentResponse {
749 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
750 rect: border_box,
751 })
752}
753
754#[inline]
758pub(crate) fn process_scroll_container_query(
759 node: Option<ServoLayoutNode<'_>>,
760 query_flags: ScrollContainerQueryFlags,
761 viewport_overflow: AxesOverflow,
762) -> Option<ScrollContainerResponse> {
763 let Some(node) = node else {
764 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
765 };
766
767 let layout_data = node.to_threadsafe().inner_layout_data()?;
768
769 let layout_box = layout_data.self_box.borrow();
772 let layout_box = layout_box.as_ref()?;
773
774 let (style, flags) =
775 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
776
777 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
783 flags.intersects(
784 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
785 )
786 {
787 return None;
788 }
789
790 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
791 style.establishes_scroll_container(flags)
792 {
793 return Some(ScrollContainerResponse::Element(
794 node.opaque().into(),
795 style.effective_overflow(flags),
796 ));
797 }
798
799 let mut current_position_value = style.clone_position();
819 let mut current_ancestor = node.as_element()?;
820 while let Some(ancestor) = current_ancestor.traversal_parent() {
821 current_ancestor = ancestor;
822
823 let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
824 continue;
825 };
826 let ancestor_layout_box = layout_data.self_box.borrow();
827 let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
828 continue;
829 };
830
831 let Some((ancestor_style, ancestor_flags)) = ancestor_layout_box
832 .with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
833 else {
834 continue;
835 };
836
837 let is_containing_block = match current_position_value {
838 Position::Static | Position::Relative | Position::Sticky => {
839 !ancestor_style.is_inline_box(ancestor_flags)
840 },
841 Position::Absolute => {
842 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
843 },
844 Position::Fixed => {
845 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
846 },
847 };
848 if !is_containing_block {
849 continue;
850 }
851
852 if ancestor_style.establishes_scroll_container(ancestor_flags) {
853 return Some(ScrollContainerResponse::Element(
854 ancestor.as_node().opaque().into(),
855 ancestor_style.effective_overflow(ancestor_flags),
856 ));
857 }
858
859 current_position_value = ancestor_style.clone_position();
860 }
861
862 match current_position_value {
863 Position::Fixed => None,
864 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
865 }
866}
867
868pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
870 let mut results = Vec::new();
876 let mut max_req_line_break_count = 0;
877
878 let mut state = Default::default();
880 for child in node.dom_children() {
881 let mut current = rendered_text_collection_steps(child, &mut state);
883 results.append(&mut current);
885 }
886
887 let mut output = String::new();
888 for item in results {
889 match item {
890 InnerOrOuterTextItem::Text(s) => {
891 if !s.is_empty() {
893 if max_req_line_break_count > 0 {
894 output.push_str(&"\u{000A}".repeat(max_req_line_break_count));
896 max_req_line_break_count = 0;
897 }
898 output.push_str(&s);
899 }
900 },
901 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
902 if output.is_empty() {
904 continue;
906 }
907 if count > max_req_line_break_count {
911 max_req_line_break_count = count;
912 }
913 },
914 }
915 }
916 output
917}
918
919enum InnerOrOuterTextItem {
920 Text(String),
921 RequiredLineBreakCount(usize),
922}
923
924#[derive(Clone)]
925struct RenderedTextCollectionState {
926 first_table_row: bool,
928 first_table_cell: bool,
930 within_table: bool,
933 may_start_with_whitespace: bool,
935 did_truncate_trailing_white_space: bool,
938 within_table_content: bool,
941}
942
943impl Default for RenderedTextCollectionState {
944 fn default() -> Self {
945 RenderedTextCollectionState {
946 first_table_row: true,
947 first_table_cell: true,
948 may_start_with_whitespace: true,
949 did_truncate_trailing_white_space: false,
950 within_table: false,
951 within_table_content: false,
952 }
953 }
954}
955
956fn rendered_text_collection_steps(
958 node: ServoLayoutNode<'_>,
959 state: &mut RenderedTextCollectionState,
960) -> Vec<InnerOrOuterTextItem> {
961 let mut items = vec![];
965 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
966 return items;
967 }
968
969 match node.type_id() {
970 LayoutNodeType::Text => {
971 if let Some(element) = node.parent_node() {
972 match element.type_id() {
973 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
975 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
976 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
977 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
978 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
979 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
980 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
981 return items;
982 },
983 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
987 if let Some(element) = element.parent_node() {
988 if !matches!(
989 element.type_id(),
990 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
991 ) {
992 return items;
993 }
994 } else {
995 return items;
996 }
997 },
998 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
999 _ => {},
1000 }
1001
1002 if state.within_table && !state.within_table_content {
1006 return items;
1007 }
1008
1009 let Some(style_data) = element.style_data() else {
1010 return items;
1011 };
1012
1013 let element_data = style_data.element_data.borrow();
1014 let Some(style) = element_data.styles.get_primary() else {
1015 return items;
1016 };
1017
1018 if style.get_inherited_box().visibility != Visibility::Visible {
1024 return items;
1025 }
1026
1027 let display = style.get_box().display;
1031 if display == Display::None {
1032 match element.type_id() {
1033 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1036 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) => {},
1037 _ => {
1038 return items;
1039 },
1040 }
1041 }
1042
1043 let text_content = node.to_threadsafe().text_content();
1044
1045 let white_space_collapse = style.clone_white_space_collapse();
1046 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1047 let is_inline = matches!(
1048 display,
1049 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1050 );
1051 let trim_beginning_white_space =
1055 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1056 let with_white_space_rules_applied = WhitespaceCollapse::new(
1057 text_content.chars(),
1058 white_space_collapse,
1059 trim_beginning_white_space,
1060 );
1061
1062 let text_transform = style.clone_text_transform().case();
1070 let mut transformed_text: String =
1071 TextTransformation::new(with_white_space_rules_applied, text_transform)
1072 .collect();
1073
1074 if TextTransformCase::Capitalize == text_transform {
1078 transformed_text = capitalize_string(&transformed_text, true);
1079 }
1080
1081 let is_preformatted_element =
1082 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1083
1084 let is_final_character_whitespace = transformed_text
1085 .chars()
1086 .next_back()
1087 .filter(char::is_ascii_whitespace)
1088 .is_some();
1089
1090 let is_first_character_whitespace = transformed_text
1091 .chars()
1092 .next()
1093 .filter(char::is_ascii_whitespace)
1094 .is_some();
1095
1096 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1100 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1101 };
1102
1103 if !transformed_text.is_empty() {
1104 if is_final_character_whitespace && !is_preformatted_element {
1107 state.may_start_with_whitespace = false;
1108 state.did_truncate_trailing_white_space = true;
1109 transformed_text.pop();
1110 } else {
1111 state.may_start_with_whitespace = is_final_character_whitespace;
1112 state.did_truncate_trailing_white_space = false;
1113 }
1114 items.push(InnerOrOuterTextItem::Text(transformed_text));
1115 }
1116 } else {
1117 items.push(InnerOrOuterTextItem::Text(
1120 node.to_threadsafe().text_content().into(),
1121 ));
1122 }
1123 },
1124 LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
1125 state.did_truncate_trailing_white_space = false;
1128 state.may_start_with_whitespace = true;
1129 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1130 },
1131 _ => {
1132 let Some(style_data) = node.style_data() else {
1135 return items;
1136 };
1137
1138 let element_data = style_data.element_data.borrow();
1139 let Some(style) = element_data.styles.get_primary() else {
1140 return items;
1141 };
1142 let inherited_box = style.get_inherited_box();
1143
1144 if inherited_box.visibility != Visibility::Visible {
1145 for child in node.dom_children() {
1149 items.append(&mut rendered_text_collection_steps(child, state));
1150 }
1151 return items;
1152 }
1153
1154 let style_box = style.get_box();
1155 let display = style_box.display;
1156 let mut surrounding_line_breaks = 0;
1157
1158 if style_box.position == Position::Absolute || style_box.float != Float::None {
1160 surrounding_line_breaks = 1;
1161 }
1162
1163 match display {
1166 Display::Table => {
1167 surrounding_line_breaks = 1;
1168 state.within_table = true;
1169 },
1170 Display::TableCell => {
1175 if !state.first_table_cell {
1176 items.push(InnerOrOuterTextItem::Text(String::from(
1177 "\u{0009}", )));
1179 state.did_truncate_trailing_white_space = false;
1181 }
1182 state.first_table_cell = false;
1183 state.within_table_content = true;
1184 },
1185 Display::TableRow => {
1190 if !state.first_table_row {
1191 items.push(InnerOrOuterTextItem::Text(String::from(
1192 "\u{000A}", )));
1194 state.did_truncate_trailing_white_space = false;
1196 }
1197 state.first_table_row = false;
1198 state.first_table_cell = true;
1199 },
1200 Display::Block => {
1203 surrounding_line_breaks = 1;
1204 },
1205 Display::TableCaption => {
1206 surrounding_line_breaks = 1;
1207 state.within_table_content = true;
1208 },
1209 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1210 if state.did_truncate_trailing_white_space {
1214 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1215 state.did_truncate_trailing_white_space = false;
1216 state.may_start_with_whitespace = true;
1217 }
1218 },
1219 _ => {},
1220 }
1221
1222 match node.type_id() {
1223 LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1226 surrounding_line_breaks = 2;
1227 },
1228 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1231 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
1232 surrounding_line_breaks = 1;
1233 },
1234 _ => {},
1235 }
1236
1237 if surrounding_line_breaks > 0 {
1238 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1239 surrounding_line_breaks,
1240 ));
1241 state.did_truncate_trailing_white_space = false;
1242 state.may_start_with_whitespace = true;
1243 }
1244
1245 match node.type_id() {
1246 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1251 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1252 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1253 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1254 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1255 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1256 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
1257 if display != Display::Block && state.did_truncate_trailing_white_space {
1258 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1259 state.did_truncate_trailing_white_space = false;
1260 };
1261 state.may_start_with_whitespace = false;
1262 },
1263 _ => {
1264 for child in node.dom_children() {
1267 items.append(&mut rendered_text_collection_steps(child, state));
1268 }
1269 },
1270 }
1271
1272 match display {
1275 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1276 state.did_truncate_trailing_white_space = false;
1277 state.may_start_with_whitespace = false;
1278 },
1279 Display::Table => {
1280 state.within_table = false;
1281 },
1282 Display::TableCell | Display::TableCaption => {
1283 state.within_table_content = false;
1284 },
1285 _ => {},
1286 }
1287
1288 if surrounding_line_breaks > 0 {
1289 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1290 surrounding_line_breaks,
1291 ));
1292 state.did_truncate_trailing_white_space = false;
1293 state.may_start_with_whitespace = true;
1294 }
1295 },
1296 };
1297 items
1298}
1299
1300pub fn find_character_offset_in_fragment_descendants(
1301 node: &ServoThreadSafeLayoutNode,
1302 stacking_context_tree: &StackingContextTree,
1303 point_in_viewport: Point2D<Au, CSSPixel>,
1304) -> Option<usize> {
1305 type ClosestFragment = Option<(Au, Point2D<Au, CSSPixel>, ArcRefCell<TextFragment>)>;
1306 fn maybe_update_closest(
1307 fragment: &Fragment,
1308 point_in_fragment: Point2D<Au, CSSPixel>,
1309 closest_relative_fragment: &mut ClosestFragment,
1310 ) {
1311 let Fragment::Text(text_fragment) = fragment else {
1312 return;
1313 };
1314 let Some(new_distance) = text_fragment
1315 .borrow()
1316 .distance_to_point_for_glyph_offset(point_in_fragment)
1317 else {
1318 return;
1319 };
1320 if matches!(closest_relative_fragment, Some((old_distance, _, _)) if *old_distance < new_distance)
1321 {
1322 return;
1323 };
1324 *closest_relative_fragment = Some((new_distance, point_in_fragment, text_fragment.clone()))
1325 }
1326
1327 fn collect_relevant_children(
1328 fragment: &Fragment,
1329 point_in_viewport: Point2D<Au, CSSPixel>,
1330 closest_relative_fragment: &mut ClosestFragment,
1331 ) {
1332 maybe_update_closest(fragment, point_in_viewport, closest_relative_fragment);
1333
1334 if let Some(children) = fragment.children() {
1335 for child in children.iter() {
1336 let offset = child
1337 .base()
1338 .map(|base| base.rect.origin)
1339 .unwrap_or_default();
1340 let point = point_in_viewport - offset.to_vector();
1341 collect_relevant_children(child, point, closest_relative_fragment);
1342 }
1343 }
1344 }
1345
1346 let mut closest_relative_fragment = None;
1347 for fragment in &node.fragments_for_pseudo(None) {
1348 if let Some(point_in_fragment) =
1349 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1350 {
1351 collect_relevant_children(fragment, point_in_fragment, &mut closest_relative_fragment);
1352 }
1353 }
1354
1355 closest_relative_fragment.map(|(_, point_in_parent, text_fragment)| {
1356 text_fragment.borrow().character_offset(point_in_parent)
1357 })
1358}
1359
1360pub fn process_resolved_font_style_query<'dom, E>(
1361 context: &SharedStyleContext,
1362 node: E,
1363 value: &str,
1364 url_data: ServoUrl,
1365 shared_lock: &SharedRwLock,
1366) -> Option<ServoArc<Font>>
1367where
1368 E: LayoutNode<'dom>,
1369{
1370 fn create_font_declaration(
1371 value: &str,
1372 url_data: &ServoUrl,
1373 quirks_mode: QuirksMode,
1374 ) -> Option<PropertyDeclarationBlock> {
1375 let mut declarations = SourcePropertyDeclaration::default();
1376 let result = parse_one_declaration_into(
1377 &mut declarations,
1378 PropertyId::NonCustom(ShorthandId::Font.into()),
1379 value,
1380 Origin::Author,
1381 &UrlExtraData(url_data.get_arc()),
1382 None,
1383 ParsingMode::DEFAULT,
1384 quirks_mode,
1385 CssRuleType::Style,
1386 );
1387 let declarations = match result {
1388 Ok(()) => {
1389 let mut block = PropertyDeclarationBlock::new();
1390 block.extend(declarations.drain(), Importance::Normal);
1391 block
1392 },
1393 Err(_) => return None,
1394 };
1395 Some(declarations)
1397 }
1398 fn resolve_for_declarations<'dom, E>(
1399 context: &SharedStyleContext,
1400 parent_style: Option<&ComputedValues>,
1401 declarations: PropertyDeclarationBlock,
1402 shared_lock: &SharedRwLock,
1403 ) -> ServoArc<ComputedValues>
1404 where
1405 E: LayoutNode<'dom>,
1406 {
1407 let parent_style = match parent_style {
1408 Some(parent) => parent,
1409 None => context.stylist.device().default_computed_values(),
1410 };
1411 context
1412 .stylist
1413 .compute_for_declarations::<E::ConcreteElement>(
1414 &context.guards,
1415 parent_style,
1416 ServoArc::new(shared_lock.wrap(declarations)),
1417 )
1418 }
1419
1420 let quirks_mode = context.quirks_mode();
1423 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1424
1425 let element = node.as_element().unwrap();
1429 let parent_style = if node.is_connected() {
1430 if element.has_data() {
1431 node.to_threadsafe().as_element().unwrap().style(context)
1432 } else {
1433 let mut tlc = ThreadLocalStyleContext::new();
1434 let mut context = StyleContext {
1435 shared: context,
1436 thread_local: &mut tlc,
1437 };
1438 let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
1439 styles.primary().clone()
1440 }
1441 } else {
1442 let default_declarations =
1443 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1444 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1445 };
1446
1447 let computed_values =
1449 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1450
1451 Some(computed_values.clone_font())
1452}
1453
1454pub(crate) fn transform_au_rectangle(
1455 rect_to_transform: Rect<Au, CSSPixel>,
1456 transform: FastLayoutTransform,
1457) -> Option<Rect<Au, CSSPixel>> {
1458 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1459 let outer_transformed_rect = match transform {
1460 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1461 FastLayoutTransform::Transform { transform, .. } => {
1462 transform.outer_transformed_rect(rect_to_transform)
1463 },
1464 };
1465 outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1466}