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, LayoutElementType, LayoutNodeType, OffsetParentResponse,
14 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 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
111 .iter()
112 .fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
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.paint_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, CSSPixel>> {
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
137 let Some(transform) =
138 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
139 else {
140 return box_areas
141 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
142 .collect();
143 };
144
145 box_areas
146 .filter_map(|rect| transform_au_rectangle(rect, transform))
147 .collect()
148}
149
150pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<i32, CSSPixel> {
151 node.fragments_for_pseudo(None)
152 .first()
153 .map(Fragment::client_rect)
154 .unwrap_or_default()
155}
156
157pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
164 let Some(layout_data) = node.to_threadsafe().inner_layout_data() else {
165 return 1.0;
166 };
167 let layout_box = layout_data.self_box.borrow();
168 let Some(layout_box) = layout_box.as_ref() else {
169 return 1.0;
170 };
171 layout_box
172 .with_base(|base| base.style.effective_zoom.value())
173 .unwrap_or(1.0)
174}
175
176pub fn process_node_scroll_area_request(
178 requested_node: Option<ServoThreadSafeLayoutNode<'_>>,
179 fragment_tree: Option<Rc<FragmentTree>>,
180) -> Rect<i32, CSSPixel> {
181 let Some(tree) = fragment_tree else {
182 return Rect::zero();
183 };
184
185 let rect = match requested_node {
186 Some(node) => node
187 .fragments_for_pseudo(None)
188 .first()
189 .map(Fragment::scrolling_area)
190 .unwrap_or_default(),
191 None => tree.scrollable_overflow(),
192 };
193
194 Rect::new(
195 rect.origin.map(Au::to_f32_px),
196 rect.size.to_vector().map(Au::to_f32_px).to_size(),
197 )
198 .round()
199 .to_i32()
200}
201
202pub fn process_resolved_style_request(
205 context: &SharedStyleContext,
206 node: ServoLayoutNode<'_>,
207 pseudo: &Option<PseudoElement>,
208 property: &PropertyId,
209) -> String {
210 if !node.as_element().unwrap().has_data() {
211 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
212 }
213
214 let layout_element = node.to_threadsafe().as_element().unwrap();
217 let layout_element = match pseudo {
218 Some(pseudo_element_type) => {
219 match layout_element.with_pseudo(*pseudo_element_type) {
220 Some(layout_element) => layout_element,
221 None => {
222 return String::new();
226 },
227 }
228 },
229 None => layout_element,
230 };
231
232 let style = &*layout_element.style(context);
233 let longhand_id = match *property {
234 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
235 Ok(longhand_id) => longhand_id,
236 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
237 },
238 PropertyId::Custom(ref name) => {
239 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
240 },
241 }
242 .to_physical(style.writing_mode);
243
244 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
246 let transform_list = &style.get_box().transform;
247
248 if transform_list.0.is_empty() {
252 return Ok("none".into());
253 }
254
255 let length_rect = box_fragment
260 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
261 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
262
263 let matrix = Matrix3D::from(transform);
268 if !is_3d {
269 Ok(matrix.into_2d()?.to_css_string())
270 } else {
271 Ok(matrix.to_css_string())
272 }
273 };
274
275 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
276 LonghandId::MinWidth
277 if style.clone_min_width() == Size::Auto &&
278 !should_honor_min_size_auto(fragment, style) =>
279 {
280 String::from("0px")
281 },
282 LonghandId::MinHeight
283 if style.clone_min_height() == Size::Auto &&
284 !should_honor_min_size_auto(fragment, style) =>
285 {
286 String::from("0px")
287 },
288 LonghandId::Transform => match serialize_transform_value(None) {
289 Ok(value) => value,
290 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
291 },
292 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
293 };
294
295 if longhand_id == LonghandId::LineHeight {
304 let font = style.get_font();
305 let font_size = font.font_size.computed_size();
306 return match font.line_height {
307 LineHeight::Normal => computed_style(None),
310 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
311 LineHeight::Length(value) => value.0.to_css_string(),
312 };
313 }
314
315 let display = style.get_box().display;
319 if display.is_none() || display.is_contents() {
320 return computed_style(None);
321 }
322
323 let resolve_for_fragment = |fragment: &Fragment| {
324 let (content_rect, margins, padding, specific_layout_info) = match fragment {
325 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
326 let box_fragment = box_fragment.borrow();
327 if style.get_box().position != Position::Static {
328 let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
329 match longhand_id {
330 LonghandId::Top => return resolved_insets().top.to_css_string(),
331 LonghandId::Right => {
332 return resolved_insets().right.to_css_string();
333 },
334 LonghandId::Bottom => {
335 return resolved_insets().bottom.to_css_string();
336 },
337 LonghandId::Left => {
338 return resolved_insets().left.to_css_string();
339 },
340 LonghandId::Transform => {
341 if let Ok(string) = serialize_transform_value(Some(&*box_fragment)) {
344 return string;
345 }
346 },
347 _ => {},
348 }
349 }
350 let content_rect = box_fragment.base.rect;
351 let margins = box_fragment.margin;
352 let padding = box_fragment.padding;
353 let specific_layout_info = box_fragment.specific_layout_info().cloned();
354 (content_rect, margins, padding, specific_layout_info)
355 },
356 Fragment::Positioning(positioning_fragment) => (
357 positioning_fragment.borrow().base.rect,
358 SideOffsets2D::zero(),
359 SideOffsets2D::zero(),
360 None,
361 ),
362 _ => return computed_style(Some(fragment)),
363 };
364
365 if display.inside() == DisplayInside::Grid {
371 if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
372 if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
373 return value;
374 }
375 }
376 }
377
378 match longhand_id {
386 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
387 content_rect.size.width
388 },
389 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
390 content_rect.size.height
391 },
392 LonghandId::MarginBottom => margins.bottom,
393 LonghandId::MarginTop => margins.top,
394 LonghandId::MarginLeft => margins.left,
395 LonghandId::MarginRight => margins.right,
396 LonghandId::PaddingBottom => padding.bottom,
397 LonghandId::PaddingTop => padding.top,
398 LonghandId::PaddingLeft => padding.left,
399 LonghandId::PaddingRight => padding.right,
400 _ => return computed_style(Some(fragment)),
401 }
402 .to_css_string()
403 };
404
405 node.to_threadsafe()
406 .fragments_for_pseudo(*pseudo)
407 .first()
408 .map(resolve_for_fragment)
409 .unwrap_or_else(|| computed_style(None))
410}
411
412fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
413 match fragment {
416 Fragment::Box(box_fragment) => !box_fragment.borrow().is_inline_box(),
417 Fragment::Float(_) |
418 Fragment::Positioning(_) |
419 Fragment::AbsoluteOrFixedPositioned(_) |
420 Fragment::Image(_) |
421 Fragment::IFrame(_) => true,
422 Fragment::Text(_) => false,
423 }
424}
425
426fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
427 let Some(Fragment::Box(box_fragment)) = fragment else {
436 return false;
437 };
438 let flags = box_fragment.borrow().base.flags;
439 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
440 style.clone_aspect_ratio() != AspectRatio::auto()
441}
442
443fn resolve_grid_template(
444 grid_info: &SpecificTaffyGridInfo,
445 style: &ComputedValues,
446 longhand_id: LonghandId,
447) -> Option<String> {
448 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
450 match track_sizes.is_empty() {
451 true => None,
455 false => Some(
462 track_sizes
463 .iter()
464 .map(|size| size.to_css_string())
465 .join(" "),
466 ),
467 }
468 }
469
470 let (track_info, computed_value) = match longhand_id {
471 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
472 LonghandId::GridTemplateColumns => (
473 &grid_info.columns,
474 &style.get_position().grid_template_columns,
475 ),
476 _ => return None,
477 };
478
479 match computed_value {
480 GenericGridTemplateComponent::None |
484 GenericGridTemplateComponent::TrackList(_) |
485 GenericGridTemplateComponent::Masonry => {
486 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
487 },
488
489 GenericGridTemplateComponent::Subgrid(_) => None,
497 }
498}
499
500pub fn process_resolved_style_request_for_unstyled_node(
501 context: &SharedStyleContext,
502 node: ServoLayoutNode<'_>,
503 pseudo: &Option<PseudoElement>,
504 property: &PropertyId,
505) -> String {
506 if pseudo.is_some() {
508 return String::new();
509 }
510
511 let mut tlc = ThreadLocalStyleContext::new();
512 let mut context = StyleContext {
513 shared: context,
514 thread_local: &mut tlc,
515 };
516
517 let element = node.as_element().unwrap();
518 let styles = resolve_style(
519 &mut context,
520 element,
521 RuleInclusion::All,
522 pseudo.as_ref(),
523 None,
524 );
525 let style = styles.primary();
526 let longhand_id = match *property {
527 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
528 Ok(longhand_id) => longhand_id,
529 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
530 },
531 PropertyId::Custom(ref name) => {
532 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
533 },
534 };
535
536 match longhand_id {
537 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
540 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
541
542 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
545 }
546}
547
548fn shorthand_to_css_string(
549 id: style::properties::ShorthandId,
550 style: &style::properties::ComputedValues,
551) -> String {
552 use style::values::resolved::Context;
553 let mut block = PropertyDeclarationBlock::new();
554 let mut dest = String::new();
555 for longhand in id.longhands() {
556 block.push(
557 style.computed_or_resolved_declaration(
558 longhand,
559 Some(&mut Context {
560 style,
561 for_property: longhand.into(),
562 current_longhand: None,
563 }),
564 ),
565 Importance::Normal,
566 );
567 }
568 match block.shorthand_to_css(id, &mut dest) {
569 Ok(_) => dest.to_owned(),
570 Err(_) => String::new(),
571 }
572}
573
574struct OffsetParentFragments {
575 parent: ArcRefCell<BoxFragment>,
576 grandparent: Option<Fragment>,
577}
578
579fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
581 let fragment = node
587 .to_threadsafe()
588 .fragments_for_pseudo(None)
589 .first()
590 .cloned()?;
591 let flags = fragment.base()?.flags;
592 if flags.intersects(
593 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
594 ) {
595 return None;
596 }
597 if matches!(
598 fragment, Fragment::Box(fragment) if fragment.borrow().style().get_box().position == Position::Fixed
599 ) {
600 return None;
601 }
602
603 let mut maybe_parent_node = node.parent_node();
610 while let Some(parent_node) = maybe_parent_node {
611 maybe_parent_node = parent_node.parent_node();
612
613 if let Some(parent_fragment) = parent_node
614 .to_threadsafe()
615 .fragments_for_pseudo(None)
616 .first()
617 {
618 let parent_fragment = match parent_fragment {
619 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
620 _ => continue,
621 };
622
623 let grandparent_fragment = maybe_parent_node.and_then(|node| {
624 node.to_threadsafe()
625 .fragments_for_pseudo(None)
626 .first()
627 .cloned()
628 });
629
630 if parent_fragment.borrow().style().get_box().position != Position::Static {
631 return Some(OffsetParentFragments {
632 parent: parent_fragment.clone(),
633 grandparent: grandparent_fragment,
634 });
635 }
636
637 let flags = parent_fragment.borrow().base.flags;
638 if flags.intersects(
639 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
640 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
641 ) {
642 return Some(OffsetParentFragments {
643 parent: parent_fragment.clone(),
644 grandparent: grandparent_fragment,
645 });
646 }
647 }
648 }
649
650 None
651}
652
653#[inline]
654pub fn process_offset_parent_query(
655 scroll_tree: &ScrollTree,
656 node: ServoLayoutNode<'_>,
657) -> Option<OffsetParentResponse> {
658 let fragment = node
675 .to_threadsafe()
676 .fragments_for_pseudo(None)
677 .first()
678 .cloned()?;
679 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
680 let cumulative_sticky_offsets = fragment
681 .retrieve_box_fragment()
682 .and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
683 .map(|node_id| {
684 scroll_tree
685 .cumulative_sticky_offsets(node_id)
686 .map(Au::from_f32_px)
687 .cast_unit()
688 });
689 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
690
691 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
696 return Some(OffsetParentResponse {
697 node_address: None,
698 rect: border_box,
699 });
700 };
701
702 let parent_fragment = offset_parent_fragment.parent.borrow();
703 let parent_is_static_body_element = parent_fragment
704 .base
705 .flags
706 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
707 parent_fragment.style().get_box().position == Position::Static;
708
709 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
718 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
719 Some(box_fragment)
720 },
721 _ => None,
722 };
723
724 let parent_offset_rect = if parent_is_static_body_element {
732 if let Some(grandparent_fragment) = grandparent_box_fragment() {
733 let grandparent_fragment = grandparent_fragment.borrow();
734 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
735 } else {
736 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
737 }
738 } else {
739 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
740 }
741 .translate(
742 cumulative_sticky_offsets
743 .and_then(|_| parent_fragment.spatial_tree_node())
744 .map(|node_id| {
745 scroll_tree
746 .cumulative_sticky_offsets(node_id)
747 .map(Au::from_f32_px)
748 .cast_unit()
749 })
750 .unwrap_or_default(),
751 );
752
753 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
754
755 Some(OffsetParentResponse {
756 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
757 rect: border_box,
758 })
759}
760
761#[inline]
765pub(crate) fn process_scroll_container_query(
766 node: Option<ServoLayoutNode<'_>>,
767 query_flags: ScrollContainerQueryFlags,
768 viewport_overflow: AxesOverflow,
769) -> Option<ScrollContainerResponse> {
770 let Some(node) = node else {
771 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
772 };
773
774 let layout_data = node.to_threadsafe().inner_layout_data()?;
775
776 let layout_box = layout_data.self_box.borrow();
779 let layout_box = layout_box.as_ref()?;
780
781 let (style, flags) =
782 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
783
784 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
790 flags.intersects(
791 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
792 )
793 {
794 return None;
795 }
796
797 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
798 style.establishes_scroll_container(flags)
799 {
800 return Some(ScrollContainerResponse::Element(
801 node.opaque().into(),
802 style.effective_overflow(flags),
803 ));
804 }
805
806 let mut current_position_value = style.clone_position();
826 let mut current_ancestor = node.as_element()?;
827 while let Some(ancestor) = current_ancestor.traversal_parent() {
828 current_ancestor = ancestor;
829
830 let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
831 continue;
832 };
833 let ancestor_layout_box = layout_data.self_box.borrow();
834 let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
835 continue;
836 };
837
838 let Some((ancestor_style, ancestor_flags)) = ancestor_layout_box
839 .with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
840 else {
841 continue;
842 };
843
844 let is_containing_block = match current_position_value {
845 Position::Static | Position::Relative | Position::Sticky => {
846 !ancestor_style.is_inline_box(ancestor_flags)
847 },
848 Position::Absolute => {
849 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
850 },
851 Position::Fixed => {
852 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
853 },
854 };
855 if !is_containing_block {
856 continue;
857 }
858
859 if ancestor_style.establishes_scroll_container(ancestor_flags) {
860 return Some(ScrollContainerResponse::Element(
861 ancestor.as_node().opaque().into(),
862 ancestor_style.effective_overflow(ancestor_flags),
863 ));
864 }
865
866 current_position_value = ancestor_style.clone_position();
867 }
868
869 match current_position_value {
870 Position::Fixed => None,
871 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
872 }
873}
874
875pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
877 let mut results = Vec::new();
883 let mut max_req_line_break_count = 0;
884
885 let mut state = Default::default();
887 for child in node.dom_children() {
888 let mut current = rendered_text_collection_steps(child, &mut state);
890 results.append(&mut current);
892 }
893
894 let mut output = Vec::new();
895 for item in results {
896 match item {
897 InnerOrOuterTextItem::Text(s) => {
898 if !s.is_empty() {
900 if max_req_line_break_count > 0 {
901 output.push("\u{000A}".repeat(max_req_line_break_count));
903 max_req_line_break_count = 0;
904 }
905 output.push(s);
906 }
907 },
908 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
909 if output.is_empty() {
911 continue;
913 }
914 if count > max_req_line_break_count {
918 max_req_line_break_count = count;
919 }
920 },
921 }
922 }
923 output.into_iter().collect()
924}
925
926enum InnerOrOuterTextItem {
927 Text(String),
928 RequiredLineBreakCount(usize),
929}
930
931#[derive(Clone)]
932struct RenderedTextCollectionState {
933 first_table_row: bool,
935 first_table_cell: bool,
937 within_table: bool,
940 may_start_with_whitespace: bool,
942 did_truncate_trailing_white_space: bool,
945 within_table_content: bool,
948}
949
950impl Default for RenderedTextCollectionState {
951 fn default() -> Self {
952 RenderedTextCollectionState {
953 first_table_row: true,
954 first_table_cell: true,
955 may_start_with_whitespace: true,
956 did_truncate_trailing_white_space: false,
957 within_table: false,
958 within_table_content: false,
959 }
960 }
961}
962
963fn rendered_text_collection_steps(
965 node: ServoLayoutNode<'_>,
966 state: &mut RenderedTextCollectionState,
967) -> Vec<InnerOrOuterTextItem> {
968 let mut items = vec![];
972 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
973 return items;
974 }
975
976 match node.type_id() {
977 LayoutNodeType::Text => {
978 if let Some(element) = node.parent_node() {
979 match element.type_id() {
980 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
982 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
983 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
984 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
985 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
986 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
987 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
988 return items;
989 },
990 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
994 if let Some(element) = element.parent_node() {
995 if !matches!(
996 element.type_id(),
997 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
998 ) {
999 return items;
1000 }
1001 } else {
1002 return items;
1003 }
1004 },
1005 LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
1006 _ => {},
1007 }
1008
1009 if state.within_table && !state.within_table_content {
1013 return items;
1014 }
1015
1016 let Some(style_data) = element.style_data() else {
1017 return items;
1018 };
1019
1020 let element_data = style_data.element_data.borrow();
1021 let Some(style) = element_data.styles.get_primary() else {
1022 return items;
1023 };
1024
1025 if style.get_inherited_box().visibility != Visibility::Visible {
1031 return items;
1032 }
1033
1034 let display = style.get_box().display;
1038 if display == Display::None {
1039 match element.type_id() {
1040 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1043 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) => {},
1044 _ => {
1045 return items;
1046 },
1047 }
1048 }
1049
1050 let text_content = node.to_threadsafe().text_content();
1051
1052 let white_space_collapse = style.clone_white_space_collapse();
1053 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1054 let is_inline = matches!(
1055 display,
1056 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1057 );
1058 let trim_beginning_white_space =
1062 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1063 let with_white_space_rules_applied = WhitespaceCollapse::new(
1064 text_content.chars(),
1065 white_space_collapse,
1066 trim_beginning_white_space,
1067 );
1068
1069 let text_transform = style.clone_text_transform().case();
1077 let mut transformed_text: String =
1078 TextTransformation::new(with_white_space_rules_applied, text_transform)
1079 .collect();
1080
1081 if TextTransformCase::Capitalize == text_transform {
1085 transformed_text = capitalize_string(&transformed_text, true);
1086 }
1087
1088 let is_preformatted_element =
1089 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1090
1091 let is_final_character_whitespace = transformed_text
1092 .chars()
1093 .next_back()
1094 .filter(char::is_ascii_whitespace)
1095 .is_some();
1096
1097 let is_first_character_whitespace = transformed_text
1098 .chars()
1099 .next()
1100 .filter(char::is_ascii_whitespace)
1101 .is_some();
1102
1103 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1107 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1108 };
1109
1110 if !transformed_text.is_empty() {
1111 if is_final_character_whitespace && !is_preformatted_element {
1114 state.may_start_with_whitespace = false;
1115 state.did_truncate_trailing_white_space = true;
1116 transformed_text.pop();
1117 } else {
1118 state.may_start_with_whitespace = is_final_character_whitespace;
1119 state.did_truncate_trailing_white_space = false;
1120 }
1121 items.push(InnerOrOuterTextItem::Text(transformed_text));
1122 }
1123 } else {
1124 items.push(InnerOrOuterTextItem::Text(
1127 node.to_threadsafe().text_content().into(),
1128 ));
1129 }
1130 },
1131 LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
1132 state.did_truncate_trailing_white_space = false;
1135 state.may_start_with_whitespace = true;
1136 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1137 },
1138 _ => {
1139 let Some(style_data) = node.style_data() else {
1142 return items;
1143 };
1144
1145 let element_data = style_data.element_data.borrow();
1146 let Some(style) = element_data.styles.get_primary() else {
1147 return items;
1148 };
1149 let inherited_box = style.get_inherited_box();
1150
1151 if inherited_box.visibility != Visibility::Visible {
1152 for child in node.dom_children() {
1156 items.append(&mut rendered_text_collection_steps(child, state));
1157 }
1158 return items;
1159 }
1160
1161 let style_box = style.get_box();
1162 let display = style_box.display;
1163 let mut surrounding_line_breaks = 0;
1164
1165 if style_box.position == Position::Absolute || style_box.float != Float::None {
1167 surrounding_line_breaks = 1;
1168 }
1169
1170 match display {
1173 Display::Table => {
1174 surrounding_line_breaks = 1;
1175 state.within_table = true;
1176 },
1177 Display::TableCell => {
1182 if !state.first_table_cell {
1183 items.push(InnerOrOuterTextItem::Text(String::from(
1184 "\u{0009}", )));
1186 state.did_truncate_trailing_white_space = false;
1188 }
1189 state.first_table_cell = false;
1190 state.within_table_content = true;
1191 },
1192 Display::TableRow => {
1197 if !state.first_table_row {
1198 items.push(InnerOrOuterTextItem::Text(String::from(
1199 "\u{000A}", )));
1201 state.did_truncate_trailing_white_space = false;
1203 }
1204 state.first_table_row = false;
1205 state.first_table_cell = true;
1206 },
1207 Display::Block => {
1210 surrounding_line_breaks = 1;
1211 },
1212 Display::TableCaption => {
1213 surrounding_line_breaks = 1;
1214 state.within_table_content = true;
1215 },
1216 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1217 if state.did_truncate_trailing_white_space {
1221 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1222 state.did_truncate_trailing_white_space = false;
1223 state.may_start_with_whitespace = true;
1224 }
1225 },
1226 _ => {},
1227 }
1228
1229 match node.type_id() {
1230 LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
1233 surrounding_line_breaks = 2;
1234 },
1235 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1238 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
1239 surrounding_line_breaks = 1;
1240 },
1241 _ => {},
1242 }
1243
1244 if surrounding_line_breaks > 0 {
1245 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1246 surrounding_line_breaks,
1247 ));
1248 state.did_truncate_trailing_white_space = false;
1249 state.may_start_with_whitespace = true;
1250 }
1251
1252 match node.type_id() {
1253 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1258 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1259 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1260 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1261 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1262 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1263 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
1264 if display != Display::Block && state.did_truncate_trailing_white_space {
1265 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1266 state.did_truncate_trailing_white_space = false;
1267 };
1268 state.may_start_with_whitespace = false;
1269 },
1270 _ => {
1271 for child in node.dom_children() {
1274 items.append(&mut rendered_text_collection_steps(child, state));
1275 }
1276 },
1277 }
1278
1279 match display {
1282 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1283 state.did_truncate_trailing_white_space = false;
1284 state.may_start_with_whitespace = false;
1285 },
1286 Display::Table => {
1287 state.within_table = false;
1288 },
1289 Display::TableCell | Display::TableCaption => {
1290 state.within_table_content = false;
1291 },
1292 _ => {},
1293 }
1294
1295 if surrounding_line_breaks > 0 {
1296 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1297 surrounding_line_breaks,
1298 ));
1299 state.did_truncate_trailing_white_space = false;
1300 state.may_start_with_whitespace = true;
1301 }
1302 },
1303 };
1304 items
1305}
1306
1307pub fn find_character_offset_in_fragment_descendants(
1308 node: &ServoThreadSafeLayoutNode,
1309 stacking_context_tree: &StackingContextTree,
1310 point_in_viewport: Point2D<Au, CSSPixel>,
1311) -> Option<usize> {
1312 type ClosestFragment = Option<(Au, Point2D<Au, CSSPixel>, ArcRefCell<TextFragment>)>;
1313 fn maybe_update_closest(
1314 fragment: &Fragment,
1315 point_in_fragment: Point2D<Au, CSSPixel>,
1316 closest_relative_fragment: &mut ClosestFragment,
1317 ) {
1318 let Fragment::Text(text_fragment) = fragment else {
1319 return;
1320 };
1321 let Some(new_distance) = text_fragment
1322 .borrow()
1323 .distance_to_point_for_glyph_offset(point_in_fragment)
1324 else {
1325 return;
1326 };
1327 if matches!(closest_relative_fragment, Some((old_distance, _, _)) if *old_distance < new_distance)
1328 {
1329 return;
1330 };
1331 *closest_relative_fragment = Some((new_distance, point_in_fragment, text_fragment.clone()))
1332 }
1333
1334 fn collect_relevant_children(
1335 fragment: &Fragment,
1336 point_in_viewport: Point2D<Au, CSSPixel>,
1337 closest_relative_fragment: &mut ClosestFragment,
1338 ) {
1339 maybe_update_closest(fragment, point_in_viewport, closest_relative_fragment);
1340
1341 if let Some(children) = fragment.children() {
1342 for child in children.iter() {
1343 let offset = child
1344 .base()
1345 .map(|base| base.rect.origin)
1346 .unwrap_or_default();
1347 let point = point_in_viewport - offset.to_vector();
1348 collect_relevant_children(child, point, closest_relative_fragment);
1349 }
1350 }
1351 }
1352
1353 let mut closest_relative_fragment = None;
1354 for fragment in &node.fragments_for_pseudo(None) {
1355 if let Some(point_in_fragment) =
1356 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1357 {
1358 collect_relevant_children(fragment, point_in_fragment, &mut closest_relative_fragment);
1359 }
1360 }
1361
1362 closest_relative_fragment.map(|(_, point_in_parent, text_fragment)| {
1363 text_fragment.borrow().character_offset(point_in_parent)
1364 })
1365}
1366
1367pub fn process_resolved_font_style_query<'dom, E>(
1368 context: &SharedStyleContext,
1369 node: E,
1370 value: &str,
1371 url_data: ServoUrl,
1372 shared_lock: &SharedRwLock,
1373) -> Option<ServoArc<Font>>
1374where
1375 E: LayoutNode<'dom>,
1376{
1377 fn create_font_declaration(
1378 value: &str,
1379 url_data: &ServoUrl,
1380 quirks_mode: QuirksMode,
1381 ) -> Option<PropertyDeclarationBlock> {
1382 let mut declarations = SourcePropertyDeclaration::default();
1383 let result = parse_one_declaration_into(
1384 &mut declarations,
1385 PropertyId::NonCustom(ShorthandId::Font.into()),
1386 value,
1387 Origin::Author,
1388 &UrlExtraData(url_data.get_arc()),
1389 None,
1390 ParsingMode::DEFAULT,
1391 quirks_mode,
1392 CssRuleType::Style,
1393 );
1394 let declarations = match result {
1395 Ok(()) => {
1396 let mut block = PropertyDeclarationBlock::new();
1397 block.extend(declarations.drain(), Importance::Normal);
1398 block
1399 },
1400 Err(_) => return None,
1401 };
1402 Some(declarations)
1404 }
1405 fn resolve_for_declarations<'dom, E>(
1406 context: &SharedStyleContext,
1407 parent_style: Option<&ComputedValues>,
1408 declarations: PropertyDeclarationBlock,
1409 shared_lock: &SharedRwLock,
1410 ) -> ServoArc<ComputedValues>
1411 where
1412 E: LayoutNode<'dom>,
1413 {
1414 let parent_style = match parent_style {
1415 Some(parent) => parent,
1416 None => context.stylist.device().default_computed_values(),
1417 };
1418 context
1419 .stylist
1420 .compute_for_declarations::<E::ConcreteElement>(
1421 &context.guards,
1422 parent_style,
1423 ServoArc::new(shared_lock.wrap(declarations)),
1424 )
1425 }
1426
1427 let quirks_mode = context.quirks_mode();
1430 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1431
1432 let element = node.as_element().unwrap();
1436 let parent_style = if node.is_connected() {
1437 if element.has_data() {
1438 node.to_threadsafe().as_element().unwrap().style(context)
1439 } else {
1440 let mut tlc = ThreadLocalStyleContext::new();
1441 let mut context = StyleContext {
1442 shared: context,
1443 thread_local: &mut tlc,
1444 };
1445 let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
1446 styles.primary().clone()
1447 }
1448 } else {
1449 let default_declarations =
1450 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1451 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1452 };
1453
1454 let computed_values =
1456 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1457
1458 Some(computed_values.clone_font())
1459}
1460
1461pub(crate) fn transform_au_rectangle(
1462 rect_to_transform: Rect<Au, CSSPixel>,
1463 transform: FastLayoutTransform,
1464) -> Option<Rect<Au, CSSPixel>> {
1465 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1466 let outer_transformed_rect = match transform {
1467 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1468 FastLayoutTransform::Transform { transform, .. } => {
1469 transform.outer_transformed_rect(rect_to_transform)
1470 },
1471 };
1472 outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1473}