1use std::cell::LazyCell;
7use std::ops::Deref;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use app_units::Au;
12use embedder_traits::UntrustedNodeAddress;
13use euclid::{Point2D, Rect, Size2D};
14use itertools::Itertools;
15use layout_api::{
16 AxesOverflow, BoxAreaType, CSSPixelRectVec, DangerousStyleElementOf, LayoutElement,
17 LayoutElementType, LayoutNode, LayoutNodeType, OffsetParentResponse, PhysicalSides,
18 ScrollContainerQueryFlags, ScrollContainerResponse,
19};
20use paint_api::display_list::ScrollTree;
21use script::layout_dom::ServoLayoutNode;
22use servo_arc::Arc as ServoArc;
23use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
24use servo_url::ServoUrl;
25use style::computed_values::display::T as Display;
26use style::computed_values::position::T as Position;
27use style::computed_values::visibility::T as Visibility;
28use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
29use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
30use style::dom::NodeInfo;
31use style::properties::style_structs::Font;
32use style::properties::{
33 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
34 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
35};
36use style::selector_parser::PseudoElement;
37use style::shared_lock::SharedRwLock;
38use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
39use style::stylist::RuleInclusion;
40use style::traversal::resolve_style;
41use style::values::computed::transform::Matrix3D;
42use style::values::computed::{Float, Size};
43use style::values::generics::font::LineHeight;
44use style::values::generics::position::AspectRatio;
45use style::values::specified::GenericGridTemplateComponent;
46use style::values::specified::box_::DisplayInside;
47use style::values::specified::text::TextTransformCase;
48use style_traits::{CSSPixel, ParsingMode, ToCss};
49
50use crate::cell::RefOrAtomicRef;
51use crate::display_list::{StackingContextTree, au_rect_to_length_rect};
52use crate::dom::NodeExt;
53use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
54use crate::fragment_tree::{
55 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, TextFragment,
56};
57use crate::layout_impl::LayoutThread;
58use crate::style_ext::ComputedValuesExt;
59use crate::taffy::SpecificTaffyGridInfo;
60
61fn root_transform_for_layout_node(
64 scroll_tree: &ScrollTree,
65 node: ServoLayoutNode<'_>,
66) -> Option<FastLayoutTransform> {
67 let fragments = node.fragments_for_pseudo(None);
68 let box_fragment = fragments
69 .first()
70 .and_then(Fragment::retrieve_box_fragment)?;
71 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
72 Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
73}
74
75pub(crate) fn process_padding_request(node: ServoLayoutNode<'_>) -> Option<PhysicalSides> {
76 let fragments = node.fragments_for_pseudo(None);
77 let fragment = fragments.first()?;
78 Some(
79 fragment
80 .retrieve_box_fragment()
81 .map(|box_fragment| {
82 let padding = box_fragment.padding;
83 PhysicalSides {
84 top: padding.top,
85 left: padding.left,
86 bottom: padding.bottom,
87 right: padding.right,
88 }
89 })
90 .unwrap_or_default(),
91 )
92}
93
94pub(crate) fn process_box_area_request(
95 layout_thread: &LayoutThread,
96 stacking_context_tree: &StackingContextTree,
97 node: ServoLayoutNode<'_>,
98 area: BoxAreaType,
99 exclude_transform_and_inline: bool,
100) -> Option<Rect<Au, CSSPixel>> {
101 let fragments = node.fragments_for_pseudo(None);
102 let mut rects = fragments
103 .iter()
104 .filter(|fragment| {
105 !exclude_transform_and_inline ||
106 fragment
107 .retrieve_box_fragment()
108 .is_none_or(|fragment| !fragment.with_style().is_inline_box())
109 })
110 .filter_map(|node| node.cumulative_box_area_rect(area, layout_thread.into()))
111 .peekable();
112
113 rects.peek()?;
114 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
115
116 if exclude_transform_and_inline {
117 return Some(rect_union);
118 }
119
120 let Some(transform) =
121 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
122 else {
123 return Some(Rect::new(rect_union.origin, Size2D::zero()));
124 };
125
126 transform_au_rectangle(rect_union, transform)
127}
128
129pub(crate) fn process_box_areas_request(
130 layout_thread: &LayoutThread,
131 stacking_context_tree: &StackingContextTree,
132 node: ServoLayoutNode<'_>,
133 area: BoxAreaType,
134) -> CSSPixelRectVec {
135 let fragments = node
136 .fragments_for_pseudo(None)
137 .into_iter()
138 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area, layout_thread.into()));
139
140 let Some(transform) =
141 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
142 else {
143 return fragments
144 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
145 .collect();
146 };
147
148 fragments
149 .filter_map(move |rect| transform_au_rectangle(rect, transform))
150 .collect()
151}
152
153pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
154 node.fragments_for_pseudo(None)
155 .first()
156 .map(Fragment::client_rect)
157 .unwrap_or_default()
158}
159
160pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
167 let Some(layout_data) = node.inner_layout_data() else {
168 return 1.0;
169 };
170 let layout_box = layout_data.self_box.borrow();
171 let Some(layout_box) = layout_box.as_ref() else {
172 return 1.0;
173 };
174 layout_box
175 .with_base(|base| base.style.effective_zoom.value())
176 .unwrap_or(1.0)
177}
178
179pub fn process_node_scroll_area_request(
181 layout_thread: &LayoutThread,
182 requested_node: Option<ServoLayoutNode<'_>>,
183 fragment_tree: Option<Rc<FragmentTree>>,
184) -> Rect<i32, CSSPixel> {
185 let Some(tree) = fragment_tree else {
186 return Rect::zero();
187 };
188
189 let rect = match requested_node {
190 Some(node) => node
191 .fragments_for_pseudo(None)
192 .first()
193 .map(|fragment| fragment.scrolling_area(layout_thread))
194 .unwrap_or_default(),
195 None => tree
196 .scrollable_overflow()
197 .union(&tree.initial_containing_block),
198 };
199
200 Rect::new(
201 rect.origin.map(Au::to_f32_px),
202 rect.size.to_vector().map(Au::to_f32_px).to_size(),
203 )
204 .round()
205 .to_i32()
206}
207
208pub fn process_resolved_style_request(
211 layout_thread: &LayoutThread,
212 context: &SharedStyleContext,
213 node: ServoLayoutNode<'_>,
214 pseudo: &Option<PseudoElement>,
215 property: &PropertyId,
216) -> String {
217 if node
218 .as_element()
219 .is_none_or(|element| element.style_data().is_none())
220 {
221 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
222 }
223
224 let layout_element = node.as_element().unwrap();
227 let layout_element = match pseudo {
228 Some(pseudo_element_type) => {
229 match layout_element.with_pseudo(*pseudo_element_type) {
230 Some(layout_element) => layout_element,
231 None => {
232 return String::new();
236 },
237 }
238 },
239 None => layout_element,
240 };
241
242 let style = &*layout_element.style(context);
243 let longhand_id = match *property {
244 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
245 Ok(longhand_id) => longhand_id,
246 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
247 },
248 PropertyId::Custom(ref name) => {
249 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
250 },
251 }
252 .to_physical(style.writing_mode);
253
254 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
256 let transform_list = &style.get_box().transform;
257
258 if transform_list.0.is_empty() {
262 return Ok("none".into());
263 }
264
265 let length_rect = box_fragment
270 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
271 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
272
273 let matrix = Matrix3D::from(transform);
278 if !is_3d {
279 Ok(matrix.into_2d()?.to_css_string())
280 } else {
281 Ok(matrix.to_css_string())
282 }
283 };
284
285 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
286 LonghandId::MinWidth
287 if style.clone_min_width() == Size::Auto &&
288 !should_honor_min_size_auto(fragment, style) =>
289 {
290 String::from("0px")
291 },
292 LonghandId::MinHeight
293 if style.clone_min_height() == Size::Auto &&
294 !should_honor_min_size_auto(fragment, style) =>
295 {
296 String::from("0px")
297 },
298 LonghandId::Transform => match serialize_transform_value(None) {
299 Ok(value) => value,
300 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
301 },
302 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
303 };
304
305 if longhand_id == LonghandId::LineHeight {
314 let font = style.get_font();
315 let font_size = font.font_size.computed_size();
316 return match font.line_height {
317 LineHeight::Normal => computed_style(None),
320 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
321 LineHeight::Length(value) => value.0.to_css_string(),
322 };
323 }
324
325 let display = style.get_box().display;
329 if display.is_none() || display.is_contents() {
330 return computed_style(None);
331 }
332
333 let resolve_for_fragment = |fragment: &Fragment| {
334 if let Some(box_fragment) = fragment.retrieve_box_fragment() &&
335 style.get_box().position != Position::Static
336 {
337 let resolved_insets =
338 || box_fragment.calculate_resolved_insets_if_positioned(layout_thread.into());
339 match longhand_id {
340 LonghandId::Top => return resolved_insets().top.to_css_string(),
341 LonghandId::Right => {
342 return resolved_insets().right.to_css_string();
343 },
344 LonghandId::Bottom => {
345 return resolved_insets().bottom.to_css_string();
346 },
347 LonghandId::Left => {
348 return resolved_insets().left.to_css_string();
349 },
350 LonghandId::Transform => {
351 if let Ok(string) = serialize_transform_value(Some(&box_fragment)) {
354 return string;
355 }
356 },
357 _ => {},
358 }
359 }
360
361 if !matches!(
362 fragment,
363 Fragment::LayoutRoot(..) | Fragment::Box(..) | Fragment::Positioning(..)
364 ) {
365 return computed_style(Some(fragment));
366 }
367
368 let specific_layout_info = fragment
374 .retrieve_box_fragment()
375 .and_then(|box_fragment| box_fragment.specific_layout_info().as_deref().cloned());
376 if display.inside() == DisplayInside::Grid &&
377 let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info &&
378 let Some(value) = resolve_grid_template(&info, style, longhand_id)
379 {
380 return value;
381 }
382
383 let content_rect =
391 LazyCell::new(|| fragment.base().map(|base| base.rect()).unwrap_or_default());
392 let margins = LazyCell::new(|| {
393 fragment
394 .retrieve_box_fragment()
395 .map(|fragment| fragment.margin)
396 .unwrap_or_default()
397 });
398 let padding = LazyCell::new(|| {
399 fragment
400 .retrieve_box_fragment()
401 .map(|fragment| fragment.padding)
402 .unwrap_or_default()
403 });
404 match longhand_id {
405 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
406 content_rect.size.width
407 },
408 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
409 content_rect.size.height
410 },
411 LonghandId::MarginBottom => margins.bottom,
412 LonghandId::MarginTop => margins.top,
413 LonghandId::MarginLeft => margins.left,
414 LonghandId::MarginRight => margins.right,
415 LonghandId::PaddingBottom => padding.bottom,
416 LonghandId::PaddingTop => padding.top,
417 LonghandId::PaddingLeft => padding.left,
418 LonghandId::PaddingRight => padding.right,
419 _ => return computed_style(Some(fragment)),
420 }
421 .to_css_string()
422 };
423
424 node.fragments_for_pseudo(*pseudo)
425 .first()
426 .map(resolve_for_fragment)
427 .unwrap_or_else(|| computed_style(None))
428}
429
430fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
431 match fragment {
434 Fragment::LayoutRoot(layout_root) => {
435 resolved_size_should_be_used_value(&layout_root.inner())
436 },
437 Fragment::Box(box_fragment) => !box_fragment.with_style().is_inline_box(),
438 Fragment::Float(_) |
439 Fragment::Positioning(_) |
440 Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
441 Fragment::Image(_) |
442 Fragment::IFrame(_) => true,
443 Fragment::Text(_) => false,
444 }
445}
446
447fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
448 let Some(box_fragment) = fragment.and_then(|fragment| fragment.retrieve_box_fragment()) else {
457 return false;
458 };
459 let flags = box_fragment.base.flags;
460 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
461 style.clone_aspect_ratio() != AspectRatio::auto()
462}
463
464fn resolve_grid_template(
465 grid_info: &SpecificTaffyGridInfo,
466 style: &ComputedValues,
467 longhand_id: LonghandId,
468) -> Option<String> {
469 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
471 match track_sizes.is_empty() {
472 true => None,
476 false => Some(
483 track_sizes
484 .iter()
485 .map(|size| size.to_css_string())
486 .join(" "),
487 ),
488 }
489 }
490
491 let (track_info, computed_value) = match longhand_id {
492 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
493 LonghandId::GridTemplateColumns => (
494 &grid_info.columns,
495 &style.get_position().grid_template_columns,
496 ),
497 _ => return None,
498 };
499
500 match computed_value {
501 GenericGridTemplateComponent::None |
505 GenericGridTemplateComponent::TrackList(_) |
506 GenericGridTemplateComponent::Masonry => {
507 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
508 },
509
510 GenericGridTemplateComponent::Subgrid(_) => None,
518 }
519}
520
521#[expect(unsafe_code)]
522pub fn process_resolved_style_request_for_unstyled_node(
523 context: &SharedStyleContext,
524 node: ServoLayoutNode<'_>,
525 pseudo: &Option<PseudoElement>,
526 property: &PropertyId,
527) -> String {
528 if pseudo.is_some() {
530 return String::new();
531 }
532
533 let mut tlc = ThreadLocalStyleContext::new();
534 let mut context = StyleContext {
535 shared: context,
536 thread_local: &mut tlc,
537 };
538
539 let element = node.as_element().unwrap();
540 let styles = resolve_style(
541 &mut context,
542 unsafe { element.dangerous_style_element() },
543 RuleInclusion::All,
544 pseudo.as_ref(),
545 None,
546 );
547 let style = styles.primary();
548 let longhand_id = match *property {
549 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
550 Ok(longhand_id) => longhand_id,
551 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
552 },
553 PropertyId::Custom(ref name) => {
554 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
555 },
556 };
557
558 match longhand_id {
559 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
562 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
563
564 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
567 }
568}
569
570fn shorthand_to_css_string(
571 id: style::properties::ShorthandId,
572 style: &style::properties::ComputedValues,
573) -> String {
574 use style::values::resolved::Context;
575 let mut block = PropertyDeclarationBlock::new();
576 let mut dest = String::new();
577 for longhand in id.longhands() {
578 block.push(
579 style.computed_or_resolved_declaration(
580 longhand,
581 Some(&mut Context {
582 style,
583 for_property: PropertyId::NonCustom(longhand.into()),
584 current_longhand: None,
585 }),
586 ),
587 Importance::Normal,
588 );
589 }
590 match block.shorthand_to_css(id, &mut dest) {
591 Ok(_) => dest.to_owned(),
592 Err(_) => String::new(),
593 }
594}
595
596struct OffsetParentFragments {
597 parent: Arc<BoxFragment>,
598 grandparent: Option<Fragment>,
599}
600
601impl OffsetParentFragments {
602 fn grandparent_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
603 self.grandparent
604 .as_ref()
605 .and_then(|grandparent| grandparent.retrieve_box_fragment())
606 }
607}
608#[expect(unsafe_code)]
610fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
611 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
617 let flags = fragment.base()?.flags;
618 if flags.intersects(
619 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
620 ) {
621 return None;
622 }
623
624 if fragment
625 .retrieve_box_fragment()
626 .is_some_and(|fragment| fragment.style().get_box().position == Position::Fixed)
627 {
628 return None;
629 }
630
631 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
638 while let Some(parent_node) = maybe_parent_node {
639 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
640
641 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
642 let Some(parent_fragment) = parent_fragment.retrieve_box_fragment() else {
643 continue;
644 };
645
646 let grandparent_fragment =
647 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
648
649 if parent_fragment.style().get_box().position != Position::Static {
650 return Some(OffsetParentFragments {
651 parent: parent_fragment.clone(),
652 grandparent: grandparent_fragment,
653 });
654 }
655
656 let flags = parent_fragment.base.flags;
657 if flags.intersects(
658 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
659 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
660 ) {
661 return Some(OffsetParentFragments {
662 parent: parent_fragment.clone(),
663 grandparent: grandparent_fragment,
664 });
665 }
666 }
667 }
668
669 None
670}
671
672#[inline]
673pub fn process_offset_parent_query(
674 layout_thread: &LayoutThread,
675 scroll_tree: &ScrollTree,
676 node: ServoLayoutNode<'_>,
677) -> Option<OffsetParentResponse> {
678 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
695 let mut border_box =
696 fragment.cumulative_box_area_rect(BoxAreaType::Border, layout_thread.into())?;
697 let cumulative_sticky_offsets = fragment
698 .retrieve_box_fragment()
699 .and_then(|box_fragment| box_fragment.spatial_tree_node())
700 .map(|node_id| {
701 scroll_tree
702 .cumulative_sticky_offsets(node_id)
703 .map(Au::from_f32_px)
704 .cast_unit()
705 });
706 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
707
708 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
713 return Some(OffsetParentResponse {
714 node_address: None,
715 rect: border_box,
716 });
717 };
718
719 let parent_fragment = &offset_parent_fragment.parent;
720 let parent_is_static_body_element = parent_fragment
721 .base
722 .flags
723 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
724 parent_fragment.style().get_box().position == Position::Static;
725
726 let parent_offset_rect = if parent_is_static_body_element {
743 if let Some(grandparent_fragment) = offset_parent_fragment.grandparent_box_fragment() {
744 grandparent_fragment.offset_by_containing_block(
745 &grandparent_fragment.border_rect(),
746 layout_thread.into(),
747 )
748 } else {
749 parent_fragment
750 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
751 }
752 } else {
753 parent_fragment
754 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
755 }
756 .translate(
757 cumulative_sticky_offsets
758 .and_then(|_| parent_fragment.spatial_tree_node())
759 .map(|node_id| {
760 scroll_tree
761 .cumulative_sticky_offsets(node_id)
762 .map(Au::from_f32_px)
763 .cast_unit()
764 })
765 .unwrap_or_default(),
766 );
767
768 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
769
770 Some(OffsetParentResponse {
771 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
772 rect: border_box,
773 })
774}
775
776fn style_and_flags_for_node(
777 node: &ServoLayoutNode,
778) -> Option<(ServoArc<ComputedValues>, FragmentFlags)> {
779 let layout_data = node.inner_layout_data()?;
780 let layout_box = layout_data.self_box.borrow();
781 let layout_box = layout_box.as_ref()?;
782
783 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
784}
785
786fn is_containing_block_for_position(
787 position: Position,
788 ancestor_style: &ServoArc<ComputedValues>,
789 ancestor_flags: FragmentFlags,
790) -> bool {
791 match position {
792 Position::Static | Position::Relative | Position::Sticky => {
793 !ancestor_style.is_inline_box(ancestor_flags)
794 },
795 Position::Absolute => {
796 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
797 },
798 Position::Fixed => {
799 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
800 },
801 }
802}
803
804fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
805 let (style, _flags) = style_and_flags_for_node(&node)?;
806
807 let mut current_position_value = style.clone_position();
808 let mut current_ancestor = node;
809
810 #[expect(unsafe_code)]
811 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
812 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
813 continue;
814 };
815
816 if is_containing_block_for_position(current_position_value, &ancestor_style, ancestor_flags)
817 {
818 return Some(ancestor);
819 }
820
821 current_position_value = ancestor_style.clone_position();
822 current_ancestor = ancestor;
823 }
824 None
825}
826
827#[inline]
831pub(crate) fn process_scroll_container_query(
832 node: Option<ServoLayoutNode<'_>>,
833 query_flags: ScrollContainerQueryFlags,
834 viewport_overflow: AxesOverflow,
835) -> Option<ScrollContainerResponse> {
836 let Some(node) = node else {
837 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
838 };
839
840 let (style, flags) = style_and_flags_for_node(&node)?;
843
844 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
850 flags.intersects(
851 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
852 )
853 {
854 return None;
855 }
856
857 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
858 style.establishes_scroll_container(flags)
859 {
860 return Some(ScrollContainerResponse::Element(
861 node.opaque().into(),
862 style.effective_overflow(flags),
863 ));
864 }
865
866 let mut current_position_value = style.clone_position();
886 let mut current_ancestor = node;
887
888 #[expect(unsafe_code)]
889 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
890 current_ancestor = ancestor;
891
892 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
893 continue;
894 };
895
896 if !is_containing_block_for_position(
897 current_position_value,
898 &ancestor_style,
899 ancestor_flags,
900 ) {
901 continue;
902 }
903
904 if ancestor_style.establishes_scroll_container(ancestor_flags) {
905 return Some(ScrollContainerResponse::Element(
906 ancestor.opaque().into(),
907 ancestor_style.effective_overflow(ancestor_flags),
908 ));
909 }
910
911 current_position_value = ancestor_style.clone_position();
912 }
913
914 match current_position_value {
915 Position::Fixed => None,
916 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
917 }
918}
919
920pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
922 let mut results = Vec::new();
928 let mut max_req_line_break_count = 0;
929
930 let mut state = Default::default();
932 for child in node.dom_children() {
933 let mut current = rendered_text_collection_steps(child, &mut state);
935 results.append(&mut current);
937 }
938
939 let mut output = String::new();
940 for item in results {
941 match item {
942 InnerOrOuterTextItem::Text(s) => {
943 if !s.is_empty() {
945 if max_req_line_break_count > 0 {
946 output.push_str(&"\u{000A}".repeat(max_req_line_break_count));
948 max_req_line_break_count = 0;
949 }
950 output.push_str(&s);
951 }
952 },
953 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
954 if output.is_empty() {
956 continue;
958 }
959 if count > max_req_line_break_count {
963 max_req_line_break_count = count;
964 }
965 },
966 }
967 }
968 output
969}
970
971enum InnerOrOuterTextItem {
972 Text(String),
973 RequiredLineBreakCount(usize),
974}
975
976#[derive(Clone)]
977struct RenderedTextCollectionState {
978 first_table_row: bool,
980 first_table_cell: bool,
982 within_table: bool,
985 may_start_with_whitespace: bool,
987 did_truncate_trailing_white_space: bool,
990 within_table_content: bool,
993}
994
995impl Default for RenderedTextCollectionState {
996 fn default() -> Self {
997 RenderedTextCollectionState {
998 first_table_row: true,
999 first_table_cell: true,
1000 may_start_with_whitespace: true,
1001 did_truncate_trailing_white_space: false,
1002 within_table: false,
1003 within_table_content: false,
1004 }
1005 }
1006}
1007
1008#[expect(unsafe_code)]
1010fn rendered_text_collection_steps(
1011 node: ServoLayoutNode<'_>,
1012 state: &mut RenderedTextCollectionState,
1013) -> Vec<InnerOrOuterTextItem> {
1014 let mut items = vec![];
1018 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
1019 return items;
1020 }
1021
1022 match node.type_id() {
1023 Some(LayoutNodeType::Text) => {
1024 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
1025 match parent_node.type_id() {
1026 Some(
1028 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1029 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1030 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1031 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1032 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1033 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1034 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1035 ) => {
1036 return items;
1037 },
1038 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1042 if let Some(grandparent_node) =
1043 unsafe { parent_node.dangerous_dom_parent() }
1044 {
1045 if !matches!(
1046 grandparent_node.type_id(),
1047 Some(LayoutNodeType::Element(
1048 LayoutElementType::HTMLSelectElement
1049 ))
1050 ) {
1051 return items;
1052 }
1053 } else {
1054 return items;
1055 }
1056 },
1057 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1058 return items;
1059 },
1060 _ => {},
1061 }
1062
1063 if state.within_table && !state.within_table_content {
1067 return items;
1068 }
1069
1070 let Some(parent_element) = parent_node.as_element() else {
1071 return items;
1072 };
1073 let Some(style_data) = parent_element.style_data() else {
1074 return items;
1075 };
1076
1077 let element_data = style_data.element_data.borrow();
1078 let Some(style) = element_data.styles.get_primary() else {
1079 return items;
1080 };
1081
1082 if style.get_inherited_box().visibility != Visibility::Visible {
1088 return items;
1089 }
1090
1091 let display = style.get_box().display;
1095 if display == Display::None {
1096 match parent_element.type_id() {
1097 Some(
1100 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1101 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1102 ) => {},
1103 _ => {
1104 return items;
1105 },
1106 }
1107 }
1108
1109 let text_content = node.text_content();
1110
1111 let white_space_collapse = style.clone_white_space_collapse();
1112 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1113 let is_inline = matches!(
1114 display,
1115 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1116 );
1117 let trim_beginning_white_space =
1121 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1122 let with_white_space_rules_applied = WhitespaceCollapse::new(
1123 text_content.chars(),
1124 white_space_collapse,
1125 trim_beginning_white_space,
1126 );
1127
1128 let text_transform = style.clone_text_transform().case();
1136 let mut transformed_text: String =
1137 TextTransformation::new(with_white_space_rules_applied, text_transform)
1138 .collect();
1139
1140 if TextTransformCase::Capitalize == text_transform {
1144 transformed_text = capitalize_string(&transformed_text, true);
1145 }
1146
1147 let is_preformatted_element =
1148 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1149
1150 let is_final_character_whitespace = transformed_text
1151 .chars()
1152 .next_back()
1153 .filter(char::is_ascii_whitespace)
1154 .is_some();
1155
1156 let is_first_character_whitespace = transformed_text
1157 .chars()
1158 .next()
1159 .filter(char::is_ascii_whitespace)
1160 .is_some();
1161
1162 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1166 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1167 };
1168
1169 if !transformed_text.is_empty() {
1170 if is_final_character_whitespace && !is_preformatted_element {
1173 state.may_start_with_whitespace = false;
1174 state.did_truncate_trailing_white_space = true;
1175 transformed_text.pop();
1176 } else {
1177 state.may_start_with_whitespace = is_final_character_whitespace;
1178 state.did_truncate_trailing_white_space = false;
1179 }
1180 items.push(InnerOrOuterTextItem::Text(transformed_text));
1181 }
1182 } else {
1183 items.push(InnerOrOuterTextItem::Text(
1186 node.text_content().deref().into(),
1187 ));
1188 }
1189 },
1190 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1191 state.did_truncate_trailing_white_space = false;
1194 state.may_start_with_whitespace = true;
1195 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1196 },
1197 _ => {
1198 let Some(element) = node.as_element() else {
1201 return items;
1202 };
1203 let Some(style_data) = element.style_data() else {
1204 return items;
1205 };
1206
1207 let element_data = style_data.element_data.borrow();
1208 let Some(style) = element_data.styles.get_primary() else {
1209 return items;
1210 };
1211 let inherited_box = style.get_inherited_box();
1212
1213 if inherited_box.visibility != Visibility::Visible {
1214 for child in node.dom_children() {
1218 items.append(&mut rendered_text_collection_steps(child, state));
1219 }
1220 return items;
1221 }
1222
1223 let style_box = style.get_box();
1224 let display = style_box.display;
1225 let mut surrounding_line_breaks = 0;
1226
1227 if style_box.position == Position::Absolute || style_box.float != Float::None {
1229 surrounding_line_breaks = 1;
1230 }
1231
1232 match display {
1235 Display::Table => {
1236 surrounding_line_breaks = 1;
1237 state.within_table = true;
1238 },
1239 Display::TableCell => {
1244 if !state.first_table_cell {
1245 items.push(InnerOrOuterTextItem::Text(String::from(
1246 "\u{0009}", )));
1248 state.did_truncate_trailing_white_space = false;
1250 }
1251 state.first_table_cell = false;
1252 state.within_table_content = true;
1253 },
1254 Display::TableRow => {
1259 if !state.first_table_row {
1260 items.push(InnerOrOuterTextItem::Text(String::from(
1261 "\u{000A}", )));
1263 state.did_truncate_trailing_white_space = false;
1265 }
1266 state.first_table_row = false;
1267 state.first_table_cell = true;
1268 },
1269 Display::Block => {
1272 surrounding_line_breaks = 1;
1273 },
1274 Display::TableCaption => {
1275 surrounding_line_breaks = 1;
1276 state.within_table_content = true;
1277 },
1278 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock
1282 if state.did_truncate_trailing_white_space =>
1283 {
1284 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1285 state.did_truncate_trailing_white_space = false;
1286 state.may_start_with_whitespace = true;
1287 },
1288 _ => {},
1289 }
1290
1291 match node.type_id() {
1292 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1295 surrounding_line_breaks = 2;
1296 },
1297 Some(
1300 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1301 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1302 ) => {
1303 surrounding_line_breaks = 1;
1304 },
1305 _ => {},
1306 }
1307
1308 if surrounding_line_breaks > 0 {
1309 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1310 surrounding_line_breaks,
1311 ));
1312 state.did_truncate_trailing_white_space = false;
1313 state.may_start_with_whitespace = true;
1314 }
1315
1316 match node.type_id() {
1317 Some(
1322 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1323 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1324 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1325 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1326 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1327 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1328 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1329 ) => {
1330 if display != Display::Block && state.did_truncate_trailing_white_space {
1331 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1332 state.did_truncate_trailing_white_space = false;
1333 };
1334 state.may_start_with_whitespace = false;
1335 },
1336 _ => {
1337 for child in node.dom_children() {
1340 items.append(&mut rendered_text_collection_steps(child, state));
1341 }
1342 },
1343 }
1344
1345 match display {
1348 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1349 state.did_truncate_trailing_white_space = false;
1350 state.may_start_with_whitespace = false;
1351 },
1352 Display::Table => {
1353 state.within_table = false;
1354 },
1355 Display::TableCell | Display::TableCaption => {
1356 state.within_table_content = false;
1357 },
1358 _ => {},
1359 }
1360
1361 if surrounding_line_breaks > 0 {
1362 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1363 surrounding_line_breaks,
1364 ));
1365 state.did_truncate_trailing_white_space = false;
1366 state.may_start_with_whitespace = true;
1367 }
1368 },
1369 };
1370 items
1371}
1372
1373struct ClosestFragment {
1374 fragment: Arc<TextFragment>,
1375 point_in_fragment: Point2D<Au, CSSPixel>,
1376 distance: Au,
1377 point_in_vertical_bounds: bool,
1378}
1379
1380impl ClosestFragment {
1381 fn should_replace(&self, new_distance: Au, point_in_vertical_bounds: bool) -> bool {
1382 if point_in_vertical_bounds && !self.point_in_vertical_bounds {
1383 return true;
1384 }
1385 if self.point_in_vertical_bounds && !point_in_vertical_bounds {
1386 return false;
1387 }
1388 new_distance <= self.distance
1389 }
1390}
1391
1392pub fn find_character_offset_in_fragment_descendants(
1393 node: &ServoLayoutNode,
1394 stacking_context_tree: &StackingContextTree,
1395 point_in_viewport: Point2D<Au, CSSPixel>,
1396) -> Option<usize> {
1397 fn maybe_update_closest(
1398 fragment: &Fragment,
1399 point_in_fragment: Point2D<Au, CSSPixel>,
1400 closest_relative_fragment: &mut Option<ClosestFragment>,
1401 ) {
1402 let Fragment::Text(text_fragment) = fragment else {
1403 return;
1404 };
1405
1406 let (distance, point_in_vertical_bounds) = {
1407 (
1408 text_fragment.distance_to_point_for_glyph_offset(point_in_fragment),
1409 text_fragment.point_is_within_vertical_boundaries(point_in_fragment),
1410 )
1411 };
1412
1413 if closest_relative_fragment
1414 .as_ref()
1415 .is_none_or(|closest_fragment| {
1416 closest_fragment.should_replace(distance, point_in_vertical_bounds)
1417 })
1418 {
1419 *closest_relative_fragment = Some(ClosestFragment {
1420 fragment: text_fragment.clone(),
1421 point_in_fragment,
1422 distance,
1423 point_in_vertical_bounds,
1424 });
1425 }
1426 }
1427
1428 fn collect_relevant_children(
1429 fragment: &Fragment,
1430 point_in_viewport: Point2D<Au, CSSPixel>,
1431 closest_relative_fragment: &mut Option<ClosestFragment>,
1432 ) {
1433 maybe_update_closest(fragment, point_in_viewport, closest_relative_fragment);
1434
1435 if let Some(children) = fragment.children() {
1436 for child in children.iter() {
1437 let offset = child
1438 .base()
1439 .map(|base| base.rect().origin)
1440 .unwrap_or_default();
1441 let point = point_in_viewport - offset.to_vector();
1442 collect_relevant_children(child, point, closest_relative_fragment);
1443 }
1444 }
1445 }
1446
1447 let mut closest_relative_fragment = None;
1448 for fragment in &node.fragments_for_pseudo(None) {
1449 if let Some(point_in_fragment) =
1450 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1451 {
1452 collect_relevant_children(fragment, point_in_fragment, &mut closest_relative_fragment);
1453 }
1454 }
1455
1456 closest_relative_fragment.and_then(|closest_fragment| {
1457 closest_fragment
1458 .fragment
1459 .character_offset(closest_fragment.point_in_fragment)
1460 })
1461}
1462
1463pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1464 let containing_block = containing_block_for_node(node);
1465 containing_block.map(|node| node.opaque().into())
1466}
1467
1468pub fn process_containing_block_descendant_query(
1469 possible_ancestor: ServoLayoutNode,
1470 mut possible_descendant: ServoLayoutNode,
1471) -> bool {
1472 while let Some(establishing_node) = containing_block_for_node(possible_descendant) {
1473 if establishing_node == possible_ancestor {
1474 return true;
1475 }
1476 possible_descendant = establishing_node;
1477 }
1478 false
1479}
1480
1481pub fn process_resolved_font_style_query<'dom, E>(
1482 context: &SharedStyleContext,
1483 node: E,
1484 value: &str,
1485 url_data: ServoUrl,
1486 shared_lock: &SharedRwLock,
1487) -> Option<ServoArc<Font>>
1488where
1489 E: LayoutNode<'dom>,
1490{
1491 fn create_font_declaration(
1492 value: &str,
1493 url_data: &ServoUrl,
1494 quirks_mode: QuirksMode,
1495 ) -> Option<PropertyDeclarationBlock> {
1496 let mut declarations = SourcePropertyDeclaration::default();
1497 let result = parse_one_declaration_into(
1498 &mut declarations,
1499 PropertyId::NonCustom(ShorthandId::Font.into()),
1500 value,
1501 Origin::Author,
1502 &UrlExtraData(url_data.get_arc()),
1503 None,
1504 ParsingMode::DEFAULT,
1505 quirks_mode,
1506 CssRuleType::Style,
1507 );
1508 let declarations = match result {
1509 Ok(()) => {
1510 let mut block = PropertyDeclarationBlock::new();
1511 block.extend(declarations.drain(), Importance::Normal);
1512 block
1513 },
1514 Err(_) => return None,
1515 };
1516 Some(declarations)
1518 }
1519 fn resolve_for_declarations<'dom, E>(
1520 context: &SharedStyleContext,
1521 parent_style: Option<&ComputedValues>,
1522 declarations: PropertyDeclarationBlock,
1523 shared_lock: &SharedRwLock,
1524 ) -> ServoArc<ComputedValues>
1525 where
1526 E: LayoutNode<'dom>,
1527 {
1528 let parent_style = match parent_style {
1529 Some(parent) => parent,
1530 None => context.stylist.device().default_computed_values(),
1531 };
1532 context
1533 .stylist
1534 .compute_for_declarations::<DangerousStyleElementOf<'dom, E::ConcreteTypeBundle>>(
1535 &context.guards,
1536 parent_style,
1537 ServoArc::new(shared_lock.wrap(declarations)),
1538 )
1539 }
1540
1541 let quirks_mode = context.quirks_mode();
1544 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1545
1546 let element = node.as_element().unwrap();
1550 let parent_style = if node.is_connected() {
1551 if element.style_data().is_some() {
1552 element.style(context)
1553 } else {
1554 let mut tlc = ThreadLocalStyleContext::new();
1555 let mut context = StyleContext {
1556 shared: context,
1557 thread_local: &mut tlc,
1558 };
1559 #[expect(unsafe_code)]
1560 let styles = resolve_style(
1561 &mut context,
1562 unsafe { element.dangerous_style_element() },
1563 RuleInclusion::All,
1564 None,
1565 None,
1566 );
1567 styles.primary().clone()
1568 }
1569 } else {
1570 let default_declarations =
1571 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1572 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1573 };
1574
1575 let computed_values =
1577 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1578
1579 Some(computed_values.clone_font())
1580}
1581
1582pub(crate) fn transform_au_rectangle(
1583 rect_to_transform: Rect<Au, CSSPixel>,
1584 transform: FastLayoutTransform,
1585) -> Option<Rect<Au, CSSPixel>> {
1586 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1587 let outer_transformed_rect = match transform {
1588 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1589 FastLayoutTransform::Transform { transform, .. } => {
1590 transform.outer_transformed_rect(rect_to_transform)
1591 },
1592 };
1593 outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1594}
1595
1596pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1597 let fragments = node.fragments_for_pseudo(None);
1598 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1599
1600 Some(
1601 box_fragment
1602 .style()
1603 .effective_overflow(box_fragment.base.flags),
1604 )
1605}