1use std::rc::Rc;
7
8use app_units::Au;
9use embedder_traits::UntrustedNodeAddress;
10use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
11use itertools::Itertools;
12use layout_api::{
13 AxesOverflow, BoxAreaType, CSSPixelRectIterator, LayoutElement, LayoutElementType, LayoutNode,
14 LayoutNodeType, OffsetParentResponse, PhysicalSides, ScrollContainerQueryFlags,
15 ScrollContainerResponse,
16};
17use paint_api::display_list::ScrollTree;
18use script::layout_dom::ServoLayoutNode;
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;
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::transform::Matrix3D;
39use style::values::computed::{Float, Size};
40use style::values::generics::font::LineHeight;
41use style::values::generics::position::AspectRatio;
42use style::values::specified::GenericGridTemplateComponent;
43use style::values::specified::box_::DisplayInside;
44use style::values::specified::text::TextTransformCase;
45use style_traits::{CSSPixel, ParsingMode, ToCss};
46
47use crate::ArcRefCell;
48use crate::display_list::{StackingContextTree, au_rect_to_length_rect};
49use crate::dom::NodeExt;
50use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
51use crate::fragment_tree::{
52 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, TextFragment,
53};
54use crate::style_ext::ComputedValuesExt;
55use crate::taffy::SpecificTaffyGridInfo;
56
57fn root_transform_for_layout_node(
60 scroll_tree: &ScrollTree,
61 node: ServoLayoutNode<'_>,
62) -> Option<FastLayoutTransform> {
63 let fragments = node.fragments_for_pseudo(None);
64 let box_fragment = fragments
65 .first()
66 .and_then(Fragment::retrieve_box_fragment)?
67 .borrow();
68 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
69 Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
70}
71
72pub(crate) fn process_padding_request(node: ServoLayoutNode<'_>) -> Option<PhysicalSides> {
73 let fragments = node.fragments_for_pseudo(None);
74 let fragment = fragments.first()?;
75 Some(match fragment {
76 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
77 let padding = box_fragment.borrow().padding;
78 PhysicalSides {
79 top: padding.top,
80 left: padding.left,
81 bottom: padding.bottom,
82 right: padding.right,
83 }
84 },
85 _ => Default::default(),
86 })
87}
88
89pub(crate) fn process_box_area_request(
90 stacking_context_tree: &StackingContextTree,
91 node: ServoLayoutNode<'_>,
92 area: BoxAreaType,
93 exclude_transform_and_inline: bool,
94) -> Option<Rect<Au, CSSPixel>> {
95 let fragments = node.fragments_for_pseudo(None);
96 let mut rects = fragments
97 .iter()
98 .filter(|fragment| {
99 !exclude_transform_and_inline ||
100 fragment
101 .retrieve_box_fragment()
102 .is_none_or(|fragment| !fragment.borrow().is_inline_box())
103 })
104 .filter_map(|node| node.cumulative_box_area_rect(area))
105 .peekable();
106
107 rects.peek()?;
108 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
109
110 if exclude_transform_and_inline {
111 return Some(rect_union);
112 }
113
114 let Some(transform) =
115 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
116 else {
117 return Some(Rect::new(rect_union.origin, Size2D::zero()));
118 };
119
120 transform_au_rectangle(rect_union, transform)
121}
122
123pub(crate) fn process_box_areas_request(
124 stacking_context_tree: &StackingContextTree,
125 node: ServoLayoutNode<'_>,
126 area: BoxAreaType,
127) -> CSSPixelRectIterator {
128 let fragments = node
129 .fragments_for_pseudo(None)
130 .into_iter()
131 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area));
132
133 let Some(transform) =
134 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
135 else {
136 return Box::new(fragments.map(|rect| Rect::new(rect.origin, Size2D::zero())));
137 };
138
139 Box::new(fragments.filter_map(move |rect| transform_au_rectangle(rect, transform)))
140}
141
142pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
143 node.fragments_for_pseudo(None)
144 .first()
145 .map(Fragment::client_rect)
146 .unwrap_or_default()
147}
148
149pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
156 let Some(layout_data) = node.inner_layout_data() else {
157 return 1.0;
158 };
159 let layout_box = layout_data.self_box.borrow();
160 let Some(layout_box) = layout_box.as_ref() else {
161 return 1.0;
162 };
163 layout_box
164 .with_base(|base| base.style.effective_zoom.value())
165 .unwrap_or(1.0)
166}
167
168pub fn process_node_scroll_area_request(
170 requested_node: Option<ServoLayoutNode<'_>>,
171 fragment_tree: Option<Rc<FragmentTree>>,
172) -> Rect<i32, CSSPixel> {
173 let Some(tree) = fragment_tree else {
174 return Rect::zero();
175 };
176
177 let rect = match requested_node {
178 Some(node) => node
179 .fragments_for_pseudo(None)
180 .first()
181 .map(Fragment::scrolling_area)
182 .unwrap_or_default(),
183 None => tree.scrollable_overflow(),
184 };
185
186 Rect::new(
187 rect.origin.map(Au::to_f32_px),
188 rect.size.to_vector().map(Au::to_f32_px).to_size(),
189 )
190 .round()
191 .to_i32()
192}
193
194pub fn process_resolved_style_request(
197 context: &SharedStyleContext,
198 node: ServoLayoutNode<'_>,
199 pseudo: &Option<PseudoElement>,
200 property: &PropertyId,
201) -> String {
202 if node
203 .as_element()
204 .is_none_or(|element| element.style_data().is_none())
205 {
206 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
207 }
208
209 let layout_element = node.as_element().unwrap();
212 let layout_element = match pseudo {
213 Some(pseudo_element_type) => {
214 match layout_element.with_pseudo(*pseudo_element_type) {
215 Some(layout_element) => layout_element,
216 None => {
217 return String::new();
221 },
222 }
223 },
224 None => layout_element,
225 };
226
227 let style = &*layout_element.style(context);
228 let longhand_id = match *property {
229 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
230 Ok(longhand_id) => longhand_id,
231 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
232 },
233 PropertyId::Custom(ref name) => {
234 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
235 },
236 }
237 .to_physical(style.writing_mode);
238
239 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
241 let transform_list = &style.get_box().transform;
242
243 if transform_list.0.is_empty() {
247 return Ok("none".into());
248 }
249
250 let length_rect = box_fragment
255 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
256 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
257
258 let matrix = Matrix3D::from(transform);
263 if !is_3d {
264 Ok(matrix.into_2d()?.to_css_string())
265 } else {
266 Ok(matrix.to_css_string())
267 }
268 };
269
270 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
271 LonghandId::MinWidth
272 if style.clone_min_width() == Size::Auto &&
273 !should_honor_min_size_auto(fragment, style) =>
274 {
275 String::from("0px")
276 },
277 LonghandId::MinHeight
278 if style.clone_min_height() == Size::Auto &&
279 !should_honor_min_size_auto(fragment, style) =>
280 {
281 String::from("0px")
282 },
283 LonghandId::Transform => match serialize_transform_value(None) {
284 Ok(value) => value,
285 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
286 },
287 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
288 };
289
290 if longhand_id == LonghandId::LineHeight {
299 let font = style.get_font();
300 let font_size = font.font_size.computed_size();
301 return match font.line_height {
302 LineHeight::Normal => computed_style(None),
305 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
306 LineHeight::Length(value) => value.0.to_css_string(),
307 };
308 }
309
310 let display = style.get_box().display;
314 if display.is_none() || display.is_contents() {
315 return computed_style(None);
316 }
317
318 let resolve_for_fragment = |fragment: &Fragment| {
319 let (content_rect, margins, padding, specific_layout_info) = match fragment {
320 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
321 let box_fragment = box_fragment.borrow();
322 if style.get_box().position != Position::Static {
323 let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
324 match longhand_id {
325 LonghandId::Top => return resolved_insets().top.to_css_string(),
326 LonghandId::Right => {
327 return resolved_insets().right.to_css_string();
328 },
329 LonghandId::Bottom => {
330 return resolved_insets().bottom.to_css_string();
331 },
332 LonghandId::Left => {
333 return resolved_insets().left.to_css_string();
334 },
335 LonghandId::Transform => {
336 if let Ok(string) = serialize_transform_value(Some(&*box_fragment)) {
339 return string;
340 }
341 },
342 _ => {},
343 }
344 }
345 let content_rect = box_fragment.base.rect;
346 let margins = box_fragment.margin;
347 let padding = box_fragment.padding;
348 let specific_layout_info = box_fragment.specific_layout_info().as_deref().cloned();
349 (content_rect, margins, padding, specific_layout_info)
350 },
351 Fragment::Positioning(positioning_fragment) => (
352 positioning_fragment.borrow().base.rect,
353 SideOffsets2D::zero(),
354 SideOffsets2D::zero(),
355 None,
356 ),
357 _ => return computed_style(Some(fragment)),
358 };
359
360 if display.inside() == DisplayInside::Grid {
366 if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
367 if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
368 return value;
369 }
370 }
371 }
372
373 match longhand_id {
381 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
382 content_rect.size.width
383 },
384 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
385 content_rect.size.height
386 },
387 LonghandId::MarginBottom => margins.bottom,
388 LonghandId::MarginTop => margins.top,
389 LonghandId::MarginLeft => margins.left,
390 LonghandId::MarginRight => margins.right,
391 LonghandId::PaddingBottom => padding.bottom,
392 LonghandId::PaddingTop => padding.top,
393 LonghandId::PaddingLeft => padding.left,
394 LonghandId::PaddingRight => padding.right,
395 _ => return computed_style(Some(fragment)),
396 }
397 .to_css_string()
398 };
399
400 node.fragments_for_pseudo(*pseudo)
401 .first()
402 .map(resolve_for_fragment)
403 .unwrap_or_else(|| computed_style(None))
404}
405
406fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
407 match fragment {
410 Fragment::Box(box_fragment) => !box_fragment.borrow().is_inline_box(),
411 Fragment::Float(_) |
412 Fragment::Positioning(_) |
413 Fragment::AbsoluteOrFixedPositioned(_) |
414 Fragment::Image(_) |
415 Fragment::IFrame(_) => true,
416 Fragment::Text(_) => false,
417 }
418}
419
420fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
421 let Some(Fragment::Box(box_fragment)) = fragment else {
430 return false;
431 };
432 let flags = box_fragment.borrow().base.flags;
433 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
434 style.clone_aspect_ratio() != AspectRatio::auto()
435}
436
437fn resolve_grid_template(
438 grid_info: &SpecificTaffyGridInfo,
439 style: &ComputedValues,
440 longhand_id: LonghandId,
441) -> Option<String> {
442 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
444 match track_sizes.is_empty() {
445 true => None,
449 false => Some(
456 track_sizes
457 .iter()
458 .map(|size| size.to_css_string())
459 .join(" "),
460 ),
461 }
462 }
463
464 let (track_info, computed_value) = match longhand_id {
465 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
466 LonghandId::GridTemplateColumns => (
467 &grid_info.columns,
468 &style.get_position().grid_template_columns,
469 ),
470 _ => return None,
471 };
472
473 match computed_value {
474 GenericGridTemplateComponent::None |
478 GenericGridTemplateComponent::TrackList(_) |
479 GenericGridTemplateComponent::Masonry => {
480 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
481 },
482
483 GenericGridTemplateComponent::Subgrid(_) => None,
491 }
492}
493
494#[expect(unsafe_code)]
495pub fn process_resolved_style_request_for_unstyled_node(
496 context: &SharedStyleContext,
497 node: ServoLayoutNode<'_>,
498 pseudo: &Option<PseudoElement>,
499 property: &PropertyId,
500) -> String {
501 if pseudo.is_some() {
503 return String::new();
504 }
505
506 let mut tlc = ThreadLocalStyleContext::new();
507 let mut context = StyleContext {
508 shared: context,
509 thread_local: &mut tlc,
510 };
511
512 let element = node.as_element().unwrap();
513 let styles = resolve_style(
514 &mut context,
515 unsafe { element.dangerous_style_element() },
516 RuleInclusion::All,
517 pseudo.as_ref(),
518 None,
519 );
520 let style = styles.primary();
521 let longhand_id = match *property {
522 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
523 Ok(longhand_id) => longhand_id,
524 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
525 },
526 PropertyId::Custom(ref name) => {
527 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
528 },
529 };
530
531 match longhand_id {
532 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
535 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
536
537 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
540 }
541}
542
543fn shorthand_to_css_string(
544 id: style::properties::ShorthandId,
545 style: &style::properties::ComputedValues,
546) -> String {
547 use style::values::resolved::Context;
548 let mut block = PropertyDeclarationBlock::new();
549 let mut dest = String::new();
550 for longhand in id.longhands() {
551 block.push(
552 style.computed_or_resolved_declaration(
553 longhand,
554 Some(&mut Context {
555 style,
556 for_property: longhand.into(),
557 current_longhand: None,
558 }),
559 ),
560 Importance::Normal,
561 );
562 }
563 match block.shorthand_to_css(id, &mut dest) {
564 Ok(_) => dest.to_owned(),
565 Err(_) => String::new(),
566 }
567}
568
569struct OffsetParentFragments {
570 parent: ArcRefCell<BoxFragment>,
571 grandparent: Option<Fragment>,
572}
573
574#[expect(unsafe_code)]
576fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
577 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
583 let flags = fragment.base()?.flags;
584 if flags.intersects(
585 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
586 ) {
587 return None;
588 }
589 if matches!(
590 fragment, Fragment::Box(fragment) if fragment.borrow().style().get_box().position == Position::Fixed
591 ) {
592 return None;
593 }
594
595 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
602 while let Some(parent_node) = maybe_parent_node {
603 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
604
605 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
606 let parent_fragment = match parent_fragment {
607 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
608 _ => continue,
609 };
610
611 let grandparent_fragment =
612 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
613
614 if parent_fragment.borrow().style().get_box().position != Position::Static {
615 return Some(OffsetParentFragments {
616 parent: parent_fragment.clone(),
617 grandparent: grandparent_fragment,
618 });
619 }
620
621 let flags = parent_fragment.borrow().base.flags;
622 if flags.intersects(
623 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
624 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
625 ) {
626 return Some(OffsetParentFragments {
627 parent: parent_fragment.clone(),
628 grandparent: grandparent_fragment,
629 });
630 }
631 }
632 }
633
634 None
635}
636
637#[inline]
638pub fn process_offset_parent_query(
639 scroll_tree: &ScrollTree,
640 node: ServoLayoutNode<'_>,
641) -> Option<OffsetParentResponse> {
642 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
659 let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
660 let cumulative_sticky_offsets = fragment
661 .retrieve_box_fragment()
662 .and_then(|box_fragment| box_fragment.borrow().spatial_tree_node())
663 .map(|node_id| {
664 scroll_tree
665 .cumulative_sticky_offsets(node_id)
666 .map(Au::from_f32_px)
667 .cast_unit()
668 });
669 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
670
671 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
676 return Some(OffsetParentResponse {
677 node_address: None,
678 rect: border_box,
679 });
680 };
681
682 let parent_fragment = offset_parent_fragment.parent.borrow();
683 let parent_is_static_body_element = parent_fragment
684 .base
685 .flags
686 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
687 parent_fragment.style().get_box().position == Position::Static;
688
689 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
698 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
699 Some(box_fragment)
700 },
701 _ => None,
702 };
703
704 let parent_offset_rect = if parent_is_static_body_element {
712 if let Some(grandparent_fragment) = grandparent_box_fragment() {
713 let grandparent_fragment = grandparent_fragment.borrow();
714 grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
715 } else {
716 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
717 }
718 } else {
719 parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
720 }
721 .translate(
722 cumulative_sticky_offsets
723 .and_then(|_| parent_fragment.spatial_tree_node())
724 .map(|node_id| {
725 scroll_tree
726 .cumulative_sticky_offsets(node_id)
727 .map(Au::from_f32_px)
728 .cast_unit()
729 })
730 .unwrap_or_default(),
731 );
732
733 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
734
735 Some(OffsetParentResponse {
736 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
737 rect: border_box,
738 })
739}
740
741fn style_and_flags_for_node(
742 node: &ServoLayoutNode,
743) -> Option<(ServoArc<ComputedValues>, FragmentFlags)> {
744 let layout_data = node.inner_layout_data()?;
745 let layout_box = layout_data.self_box.borrow();
746 let layout_box = layout_box.as_ref()?;
747
748 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
749}
750
751fn is_containing_block_for_position(
752 position: Position,
753 ancestor_style: &ServoArc<ComputedValues>,
754 ancestor_flags: FragmentFlags,
755) -> bool {
756 match position {
757 Position::Static | Position::Relative | Position::Sticky => {
758 !ancestor_style.is_inline_box(ancestor_flags)
759 },
760 Position::Absolute => {
761 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
762 },
763 Position::Fixed => {
764 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
765 },
766 }
767}
768
769fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
770 let (style, _flags) = style_and_flags_for_node(&node)?;
771
772 let mut current_position_value = style.clone_position();
773 let mut current_ancestor = node;
774
775 #[expect(unsafe_code)]
776 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
777 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
778 continue;
779 };
780
781 if is_containing_block_for_position(current_position_value, &ancestor_style, ancestor_flags)
782 {
783 return Some(ancestor);
784 }
785
786 current_position_value = ancestor_style.clone_position();
787 current_ancestor = ancestor;
788 }
789 None
790}
791
792#[inline]
796pub(crate) fn process_scroll_container_query(
797 node: Option<ServoLayoutNode<'_>>,
798 query_flags: ScrollContainerQueryFlags,
799 viewport_overflow: AxesOverflow,
800) -> Option<ScrollContainerResponse> {
801 let Some(node) = node else {
802 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
803 };
804
805 let (style, flags) = style_and_flags_for_node(&node)?;
808
809 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
815 flags.intersects(
816 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
817 )
818 {
819 return None;
820 }
821
822 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
823 style.establishes_scroll_container(flags)
824 {
825 return Some(ScrollContainerResponse::Element(
826 node.opaque().into(),
827 style.effective_overflow(flags),
828 ));
829 }
830
831 let mut current_position_value = style.clone_position();
851 let mut current_ancestor = node;
852
853 #[expect(unsafe_code)]
854 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
855 current_ancestor = ancestor;
856
857 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
858 continue;
859 };
860
861 if !is_containing_block_for_position(
862 current_position_value,
863 &ancestor_style,
864 ancestor_flags,
865 ) {
866 continue;
867 }
868
869 if ancestor_style.establishes_scroll_container(ancestor_flags) {
870 return Some(ScrollContainerResponse::Element(
871 ancestor.opaque().into(),
872 ancestor_style.effective_overflow(ancestor_flags),
873 ));
874 }
875
876 current_position_value = ancestor_style.clone_position();
877 }
878
879 match current_position_value {
880 Position::Fixed => None,
881 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
882 }
883}
884
885pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
887 let mut results = Vec::new();
893 let mut max_req_line_break_count = 0;
894
895 let mut state = Default::default();
897 for child in node.dom_children() {
898 let mut current = rendered_text_collection_steps(child, &mut state);
900 results.append(&mut current);
902 }
903
904 let mut output = String::new();
905 for item in results {
906 match item {
907 InnerOrOuterTextItem::Text(s) => {
908 if !s.is_empty() {
910 if max_req_line_break_count > 0 {
911 output.push_str(&"\u{000A}".repeat(max_req_line_break_count));
913 max_req_line_break_count = 0;
914 }
915 output.push_str(&s);
916 }
917 },
918 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
919 if output.is_empty() {
921 continue;
923 }
924 if count > max_req_line_break_count {
928 max_req_line_break_count = count;
929 }
930 },
931 }
932 }
933 output
934}
935
936enum InnerOrOuterTextItem {
937 Text(String),
938 RequiredLineBreakCount(usize),
939}
940
941#[derive(Clone)]
942struct RenderedTextCollectionState {
943 first_table_row: bool,
945 first_table_cell: bool,
947 within_table: bool,
950 may_start_with_whitespace: bool,
952 did_truncate_trailing_white_space: bool,
955 within_table_content: bool,
958}
959
960impl Default for RenderedTextCollectionState {
961 fn default() -> Self {
962 RenderedTextCollectionState {
963 first_table_row: true,
964 first_table_cell: true,
965 may_start_with_whitespace: true,
966 did_truncate_trailing_white_space: false,
967 within_table: false,
968 within_table_content: false,
969 }
970 }
971}
972
973#[expect(unsafe_code)]
975fn rendered_text_collection_steps(
976 node: ServoLayoutNode<'_>,
977 state: &mut RenderedTextCollectionState,
978) -> Vec<InnerOrOuterTextItem> {
979 let mut items = vec![];
983 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
984 return items;
985 }
986
987 match node.type_id() {
988 Some(LayoutNodeType::Text) => {
989 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
990 match parent_node.type_id() {
991 Some(
993 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
994 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
995 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
996 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
997 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
998 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
999 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1000 ) => {
1001 return items;
1002 },
1003 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1007 if let Some(grandparent_node) =
1008 unsafe { parent_node.dangerous_dom_parent() }
1009 {
1010 if !matches!(
1011 grandparent_node.type_id(),
1012 Some(LayoutNodeType::Element(
1013 LayoutElementType::HTMLSelectElement
1014 ))
1015 ) {
1016 return items;
1017 }
1018 } else {
1019 return items;
1020 }
1021 },
1022 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1023 return items;
1024 },
1025 _ => {},
1026 }
1027
1028 if state.within_table && !state.within_table_content {
1032 return items;
1033 }
1034
1035 let Some(parent_element) = parent_node.as_element() else {
1036 return items;
1037 };
1038 let Some(style_data) = parent_element.style_data() else {
1039 return items;
1040 };
1041
1042 let element_data = style_data.element_data.borrow();
1043 let Some(style) = element_data.styles.get_primary() else {
1044 return items;
1045 };
1046
1047 if style.get_inherited_box().visibility != Visibility::Visible {
1053 return items;
1054 }
1055
1056 let display = style.get_box().display;
1060 if display == Display::None {
1061 match parent_element.type_id() {
1062 Some(
1065 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1066 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1067 ) => {},
1068 _ => {
1069 return items;
1070 },
1071 }
1072 }
1073
1074 let text_content = node.text_content();
1075
1076 let white_space_collapse = style.clone_white_space_collapse();
1077 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1078 let is_inline = matches!(
1079 display,
1080 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1081 );
1082 let trim_beginning_white_space =
1086 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1087 let with_white_space_rules_applied = WhitespaceCollapse::new(
1088 text_content.chars(),
1089 white_space_collapse,
1090 trim_beginning_white_space,
1091 );
1092
1093 let text_transform = style.clone_text_transform().case();
1101 let mut transformed_text: String =
1102 TextTransformation::new(with_white_space_rules_applied, text_transform)
1103 .collect();
1104
1105 if TextTransformCase::Capitalize == text_transform {
1109 transformed_text = capitalize_string(&transformed_text, true);
1110 }
1111
1112 let is_preformatted_element =
1113 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1114
1115 let is_final_character_whitespace = transformed_text
1116 .chars()
1117 .next_back()
1118 .filter(char::is_ascii_whitespace)
1119 .is_some();
1120
1121 let is_first_character_whitespace = transformed_text
1122 .chars()
1123 .next()
1124 .filter(char::is_ascii_whitespace)
1125 .is_some();
1126
1127 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1131 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1132 };
1133
1134 if !transformed_text.is_empty() {
1135 if is_final_character_whitespace && !is_preformatted_element {
1138 state.may_start_with_whitespace = false;
1139 state.did_truncate_trailing_white_space = true;
1140 transformed_text.pop();
1141 } else {
1142 state.may_start_with_whitespace = is_final_character_whitespace;
1143 state.did_truncate_trailing_white_space = false;
1144 }
1145 items.push(InnerOrOuterTextItem::Text(transformed_text));
1146 }
1147 } else {
1148 items.push(InnerOrOuterTextItem::Text(node.text_content().into()));
1151 }
1152 },
1153 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1154 state.did_truncate_trailing_white_space = false;
1157 state.may_start_with_whitespace = true;
1158 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1159 },
1160 _ => {
1161 let Some(element) = node.as_element() else {
1164 return items;
1165 };
1166 let Some(style_data) = element.style_data() else {
1167 return items;
1168 };
1169
1170 let element_data = style_data.element_data.borrow();
1171 let Some(style) = element_data.styles.get_primary() else {
1172 return items;
1173 };
1174 let inherited_box = style.get_inherited_box();
1175
1176 if inherited_box.visibility != Visibility::Visible {
1177 for child in node.dom_children() {
1181 items.append(&mut rendered_text_collection_steps(child, state));
1182 }
1183 return items;
1184 }
1185
1186 let style_box = style.get_box();
1187 let display = style_box.display;
1188 let mut surrounding_line_breaks = 0;
1189
1190 if style_box.position == Position::Absolute || style_box.float != Float::None {
1192 surrounding_line_breaks = 1;
1193 }
1194
1195 match display {
1198 Display::Table => {
1199 surrounding_line_breaks = 1;
1200 state.within_table = true;
1201 },
1202 Display::TableCell => {
1207 if !state.first_table_cell {
1208 items.push(InnerOrOuterTextItem::Text(String::from(
1209 "\u{0009}", )));
1211 state.did_truncate_trailing_white_space = false;
1213 }
1214 state.first_table_cell = false;
1215 state.within_table_content = true;
1216 },
1217 Display::TableRow => {
1222 if !state.first_table_row {
1223 items.push(InnerOrOuterTextItem::Text(String::from(
1224 "\u{000A}", )));
1226 state.did_truncate_trailing_white_space = false;
1228 }
1229 state.first_table_row = false;
1230 state.first_table_cell = true;
1231 },
1232 Display::Block => {
1235 surrounding_line_breaks = 1;
1236 },
1237 Display::TableCaption => {
1238 surrounding_line_breaks = 1;
1239 state.within_table_content = true;
1240 },
1241 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1242 if state.did_truncate_trailing_white_space {
1246 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1247 state.did_truncate_trailing_white_space = false;
1248 state.may_start_with_whitespace = true;
1249 }
1250 },
1251 _ => {},
1252 }
1253
1254 match node.type_id() {
1255 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1258 surrounding_line_breaks = 2;
1259 },
1260 Some(
1263 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1264 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1265 ) => {
1266 surrounding_line_breaks = 1;
1267 },
1268 _ => {},
1269 }
1270
1271 if surrounding_line_breaks > 0 {
1272 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1273 surrounding_line_breaks,
1274 ));
1275 state.did_truncate_trailing_white_space = false;
1276 state.may_start_with_whitespace = true;
1277 }
1278
1279 match node.type_id() {
1280 Some(
1285 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1286 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1287 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1288 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1289 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1290 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1291 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1292 ) => {
1293 if display != Display::Block && state.did_truncate_trailing_white_space {
1294 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1295 state.did_truncate_trailing_white_space = false;
1296 };
1297 state.may_start_with_whitespace = false;
1298 },
1299 _ => {
1300 for child in node.dom_children() {
1303 items.append(&mut rendered_text_collection_steps(child, state));
1304 }
1305 },
1306 }
1307
1308 match display {
1311 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1312 state.did_truncate_trailing_white_space = false;
1313 state.may_start_with_whitespace = false;
1314 },
1315 Display::Table => {
1316 state.within_table = false;
1317 },
1318 Display::TableCell | Display::TableCaption => {
1319 state.within_table_content = false;
1320 },
1321 _ => {},
1322 }
1323
1324 if surrounding_line_breaks > 0 {
1325 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1326 surrounding_line_breaks,
1327 ));
1328 state.did_truncate_trailing_white_space = false;
1329 state.may_start_with_whitespace = true;
1330 }
1331 },
1332 };
1333 items
1334}
1335
1336struct ClosestFragment {
1337 fragment: ArcRefCell<TextFragment>,
1338 point_in_fragment: Point2D<Au, CSSPixel>,
1339 distance: Au,
1340 point_in_vertical_bounds: bool,
1341}
1342
1343impl ClosestFragment {
1344 fn should_replace(&self, new_distance: Au, point_in_vertical_bounds: bool) -> bool {
1345 if point_in_vertical_bounds && !self.point_in_vertical_bounds {
1346 return true;
1347 }
1348 if self.point_in_vertical_bounds && !point_in_vertical_bounds {
1349 return false;
1350 }
1351 new_distance <= self.distance
1352 }
1353}
1354
1355pub fn find_character_offset_in_fragment_descendants(
1356 node: &ServoLayoutNode,
1357 stacking_context_tree: &StackingContextTree,
1358 point_in_viewport: Point2D<Au, CSSPixel>,
1359) -> Option<usize> {
1360 fn maybe_update_closest(
1361 fragment: &Fragment,
1362 point_in_fragment: Point2D<Au, CSSPixel>,
1363 closest_relative_fragment: &mut Option<ClosestFragment>,
1364 ) {
1365 let Fragment::Text(text_fragment) = fragment else {
1366 return;
1367 };
1368
1369 let (distance, point_in_vertical_bounds) = {
1370 let borrowed_text_fragment = text_fragment.borrow();
1371 (
1372 borrowed_text_fragment.distance_to_point_for_glyph_offset(point_in_fragment),
1373 borrowed_text_fragment.point_is_within_vertical_boundaries(point_in_fragment),
1374 )
1375 };
1376
1377 if closest_relative_fragment
1378 .as_ref()
1379 .is_none_or(|closest_fragment| {
1380 closest_fragment.should_replace(distance, point_in_vertical_bounds)
1381 })
1382 {
1383 *closest_relative_fragment = Some(ClosestFragment {
1384 fragment: text_fragment.clone(),
1385 point_in_fragment,
1386 distance,
1387 point_in_vertical_bounds,
1388 });
1389 }
1390 }
1391
1392 fn collect_relevant_children(
1393 fragment: &Fragment,
1394 point_in_viewport: Point2D<Au, CSSPixel>,
1395 closest_relative_fragment: &mut Option<ClosestFragment>,
1396 ) {
1397 maybe_update_closest(fragment, point_in_viewport, closest_relative_fragment);
1398
1399 if let Some(children) = fragment.children() {
1400 for child in children.iter() {
1401 let offset = child
1402 .base()
1403 .map(|base| base.rect.origin)
1404 .unwrap_or_default();
1405 let point = point_in_viewport - offset.to_vector();
1406 collect_relevant_children(child, point, closest_relative_fragment);
1407 }
1408 }
1409 }
1410
1411 let mut closest_relative_fragment = None;
1412 for fragment in &node.fragments_for_pseudo(None) {
1413 if let Some(point_in_fragment) =
1414 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1415 {
1416 collect_relevant_children(fragment, point_in_fragment, &mut closest_relative_fragment);
1417 }
1418 }
1419
1420 closest_relative_fragment.and_then(|closest_fragment| {
1421 closest_fragment
1422 .fragment
1423 .borrow()
1424 .character_offset(closest_fragment.point_in_fragment)
1425 })
1426}
1427
1428pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1429 let containing_block = containing_block_for_node(node);
1430 containing_block.map(|node| node.opaque().into())
1431}
1432
1433pub fn process_resolved_font_style_query<'dom, E>(
1434 context: &SharedStyleContext,
1435 node: E,
1436 value: &str,
1437 url_data: ServoUrl,
1438 shared_lock: &SharedRwLock,
1439) -> Option<ServoArc<Font>>
1440where
1441 E: LayoutNode<'dom>,
1442{
1443 fn create_font_declaration(
1444 value: &str,
1445 url_data: &ServoUrl,
1446 quirks_mode: QuirksMode,
1447 ) -> Option<PropertyDeclarationBlock> {
1448 let mut declarations = SourcePropertyDeclaration::default();
1449 let result = parse_one_declaration_into(
1450 &mut declarations,
1451 PropertyId::NonCustom(ShorthandId::Font.into()),
1452 value,
1453 Origin::Author,
1454 &UrlExtraData(url_data.get_arc()),
1455 None,
1456 ParsingMode::DEFAULT,
1457 quirks_mode,
1458 CssRuleType::Style,
1459 );
1460 let declarations = match result {
1461 Ok(()) => {
1462 let mut block = PropertyDeclarationBlock::new();
1463 block.extend(declarations.drain(), Importance::Normal);
1464 block
1465 },
1466 Err(_) => return None,
1467 };
1468 Some(declarations)
1470 }
1471 fn resolve_for_declarations<'dom, E>(
1472 context: &SharedStyleContext,
1473 parent_style: Option<&ComputedValues>,
1474 declarations: PropertyDeclarationBlock,
1475 shared_lock: &SharedRwLock,
1476 ) -> ServoArc<ComputedValues>
1477 where
1478 E: LayoutNode<'dom>,
1479 {
1480 let parent_style = match parent_style {
1481 Some(parent) => parent,
1482 None => context.stylist.device().default_computed_values(),
1483 };
1484 context
1485 .stylist
1486 .compute_for_declarations::<E::ConcreteDangerousStyleElement>(
1487 &context.guards,
1488 parent_style,
1489 ServoArc::new(shared_lock.wrap(declarations)),
1490 )
1491 }
1492
1493 let quirks_mode = context.quirks_mode();
1496 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1497
1498 let element = node.as_element().unwrap();
1502 let parent_style = if node.is_connected() {
1503 if element.style_data().is_some() {
1504 element.style(context)
1505 } else {
1506 let mut tlc = ThreadLocalStyleContext::new();
1507 let mut context = StyleContext {
1508 shared: context,
1509 thread_local: &mut tlc,
1510 };
1511 #[expect(unsafe_code)]
1512 let styles = resolve_style(
1513 &mut context,
1514 unsafe { element.dangerous_style_element() },
1515 RuleInclusion::All,
1516 None,
1517 None,
1518 );
1519 styles.primary().clone()
1520 }
1521 } else {
1522 let default_declarations =
1523 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1524 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1525 };
1526
1527 let computed_values =
1529 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1530
1531 Some(computed_values.clone_font())
1532}
1533
1534pub(crate) fn transform_au_rectangle(
1535 rect_to_transform: Rect<Au, CSSPixel>,
1536 transform: FastLayoutTransform,
1537) -> Option<Rect<Au, CSSPixel>> {
1538 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1539 let outer_transformed_rect = match transform {
1540 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1541 FastLayoutTransform::Transform { transform, .. } => {
1542 transform.outer_transformed_rect(rect_to_transform)
1543 },
1544 };
1545 outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1546}
1547
1548pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1549 let fragments = node.fragments_for_pseudo(None);
1550 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1551 let box_fragment = box_fragment.borrow();
1552
1553 Some(
1554 box_fragment
1555 .style()
1556 .effective_overflow(box_fragment.base.flags),
1557 )
1558}