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