1use std::rc::Rc;
7use std::sync::Arc;
8
9use app_units::Au;
10use embedder_traits::UntrustedNodeAddress;
11use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
12use itertools::Itertools;
13use layout_api::{
14 AxesOverflow, BoxAreaType, CSSPixelRectVec, DangerousStyleElementOf, LayoutElement,
15 LayoutElementType, LayoutNode, LayoutNodeType, OffsetParentResponse, PhysicalSides,
16 ScrollContainerQueryFlags, ScrollContainerResponse,
17};
18use paint_api::display_list::ScrollTree;
19use script::layout_dom::ServoLayoutNode;
20use servo_arc::Arc as ServoArc;
21use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
22use servo_url::ServoUrl;
23use style::computed_values::display::T as Display;
24use style::computed_values::position::T as Position;
25use style::computed_values::visibility::T as Visibility;
26use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
27use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
28use style::dom::NodeInfo;
29use style::properties::style_structs::Font;
30use style::properties::{
31 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
32 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
33};
34use style::selector_parser::PseudoElement;
35use style::shared_lock::SharedRwLock;
36use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
37use style::stylist::RuleInclusion;
38use style::traversal::resolve_style;
39use style::values::computed::transform::Matrix3D;
40use style::values::computed::{Float, Size};
41use style::values::generics::font::LineHeight;
42use style::values::generics::position::AspectRatio;
43use style::values::specified::GenericGridTemplateComponent;
44use style::values::specified::box_::DisplayInside;
45use style::values::specified::text::TextTransformCase;
46use style_traits::{CSSPixel, ParsingMode, ToCss};
47
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::layout_impl::LayoutThread;
55use crate::style_ext::ComputedValuesExt;
56use crate::taffy::SpecificTaffyGridInfo;
57
58fn root_transform_for_layout_node(
61 scroll_tree: &ScrollTree,
62 node: ServoLayoutNode<'_>,
63) -> Option<FastLayoutTransform> {
64 let fragments = node.fragments_for_pseudo(None);
65 let box_fragment = fragments
66 .first()
67 .and_then(Fragment::retrieve_box_fragment)?;
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.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 layout_thread: &LayoutThread,
91 stacking_context_tree: &StackingContextTree,
92 node: ServoLayoutNode<'_>,
93 area: BoxAreaType,
94 exclude_transform_and_inline: bool,
95) -> Option<Rect<Au, CSSPixel>> {
96 let fragments = node.fragments_for_pseudo(None);
97 let mut rects = fragments
98 .iter()
99 .filter(|fragment| {
100 !exclude_transform_and_inline ||
101 fragment
102 .retrieve_box_fragment()
103 .is_none_or(|fragment| !fragment.is_inline_box())
104 })
105 .filter_map(|node| node.cumulative_box_area_rect(area, layout_thread.into()))
106 .peekable();
107
108 rects.peek()?;
109 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
110
111 if exclude_transform_and_inline {
112 return Some(rect_union);
113 }
114
115 let Some(transform) =
116 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
117 else {
118 return Some(Rect::new(rect_union.origin, Size2D::zero()));
119 };
120
121 transform_au_rectangle(rect_union, transform)
122}
123
124pub(crate) fn process_box_areas_request(
125 layout_thread: &LayoutThread,
126 stacking_context_tree: &StackingContextTree,
127 node: ServoLayoutNode<'_>,
128 area: BoxAreaType,
129) -> CSSPixelRectVec {
130 let fragments = node
131 .fragments_for_pseudo(None)
132 .into_iter()
133 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area, layout_thread.into()));
134
135 let Some(transform) =
136 root_transform_for_layout_node(&stacking_context_tree.paint_info.scroll_tree, node)
137 else {
138 return fragments
139 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
140 .collect();
141 };
142
143 fragments
144 .filter_map(move |rect| transform_au_rectangle(rect, transform))
145 .collect()
146}
147
148pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
149 node.fragments_for_pseudo(None)
150 .first()
151 .map(Fragment::client_rect)
152 .unwrap_or_default()
153}
154
155pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
162 let Some(layout_data) = node.inner_layout_data() else {
163 return 1.0;
164 };
165 let layout_box = layout_data.self_box.borrow();
166 let Some(layout_box) = layout_box.as_ref() else {
167 return 1.0;
168 };
169 layout_box
170 .with_base(|base| base.style.effective_zoom.value())
171 .unwrap_or(1.0)
172}
173
174pub fn process_node_scroll_area_request(
176 layout_thread: &LayoutThread,
177 requested_node: Option<ServoLayoutNode<'_>>,
178 fragment_tree: Option<Rc<FragmentTree>>,
179) -> Rect<i32, CSSPixel> {
180 let Some(tree) = fragment_tree else {
181 return Rect::zero();
182 };
183
184 let rect = match requested_node {
185 Some(node) => node
186 .fragments_for_pseudo(None)
187 .first()
188 .map(|fragment| fragment.scrolling_area(layout_thread))
189 .unwrap_or_default(),
190 None => tree.scrollable_overflow(),
191 };
192
193 Rect::new(
194 rect.origin.map(Au::to_f32_px),
195 rect.size.to_vector().map(Au::to_f32_px).to_size(),
196 )
197 .round()
198 .to_i32()
199}
200
201pub fn process_resolved_style_request(
204 layout_thread: &LayoutThread,
205 context: &SharedStyleContext,
206 node: ServoLayoutNode<'_>,
207 pseudo: &Option<PseudoElement>,
208 property: &PropertyId,
209) -> String {
210 if node
211 .as_element()
212 .is_none_or(|element| element.style_data().is_none())
213 {
214 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
215 }
216
217 let layout_element = node.as_element().unwrap();
220 let layout_element = match pseudo {
221 Some(pseudo_element_type) => {
222 match layout_element.with_pseudo(*pseudo_element_type) {
223 Some(layout_element) => layout_element,
224 None => {
225 return String::new();
229 },
230 }
231 },
232 None => layout_element,
233 };
234
235 let style = &*layout_element.style(context);
236 let longhand_id = match *property {
237 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
238 Ok(longhand_id) => longhand_id,
239 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
240 },
241 PropertyId::Custom(ref name) => {
242 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
243 },
244 }
245 .to_physical(style.writing_mode);
246
247 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
249 let transform_list = &style.get_box().transform;
250
251 if transform_list.0.is_empty() {
255 return Ok("none".into());
256 }
257
258 let length_rect = box_fragment
263 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
264 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
265
266 let matrix = Matrix3D::from(transform);
271 if !is_3d {
272 Ok(matrix.into_2d()?.to_css_string())
273 } else {
274 Ok(matrix.to_css_string())
275 }
276 };
277
278 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
279 LonghandId::MinWidth
280 if style.clone_min_width() == Size::Auto &&
281 !should_honor_min_size_auto(fragment, style) =>
282 {
283 String::from("0px")
284 },
285 LonghandId::MinHeight
286 if style.clone_min_height() == Size::Auto &&
287 !should_honor_min_size_auto(fragment, style) =>
288 {
289 String::from("0px")
290 },
291 LonghandId::Transform => match serialize_transform_value(None) {
292 Ok(value) => value,
293 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
294 },
295 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
296 };
297
298 if longhand_id == LonghandId::LineHeight {
307 let font = style.get_font();
308 let font_size = font.font_size.computed_size();
309 return match font.line_height {
310 LineHeight::Normal => computed_style(None),
313 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
314 LineHeight::Length(value) => value.0.to_css_string(),
315 };
316 }
317
318 let display = style.get_box().display;
322 if display.is_none() || display.is_contents() {
323 return computed_style(None);
324 }
325
326 let resolve_for_fragment = |fragment: &Fragment| {
327 let (content_rect, margins, padding, specific_layout_info) = match fragment {
328 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
329 if style.get_box().position != Position::Static {
330 let resolved_insets = || {
331 box_fragment.calculate_resolved_insets_if_positioned(layout_thread.into())
332 };
333 match longhand_id {
334 LonghandId::Top => return resolved_insets().top.to_css_string(),
335 LonghandId::Right => {
336 return resolved_insets().right.to_css_string();
337 },
338 LonghandId::Bottom => {
339 return resolved_insets().bottom.to_css_string();
340 },
341 LonghandId::Left => {
342 return resolved_insets().left.to_css_string();
343 },
344 LonghandId::Transform => {
345 if let Ok(string) = serialize_transform_value(Some(box_fragment)) {
348 return string;
349 }
350 },
351 _ => {},
352 }
353 }
354 let content_rect = box_fragment.base.rect();
355 let margins = box_fragment.margin;
356 let padding = box_fragment.padding;
357 let specific_layout_info = box_fragment.specific_layout_info().as_deref().cloned();
358 (content_rect, margins, padding, specific_layout_info)
359 },
360 Fragment::Positioning(positioning_fragment) => (
361 positioning_fragment.base.rect(),
362 SideOffsets2D::zero(),
363 SideOffsets2D::zero(),
364 None,
365 ),
366 _ => return computed_style(Some(fragment)),
367 };
368
369 if display.inside() == DisplayInside::Grid &&
375 let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info &&
376 let Some(value) = resolve_grid_template(&info, style, longhand_id)
377 {
378 return value;
379 }
380
381 match longhand_id {
389 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
390 content_rect.size.width
391 },
392 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
393 content_rect.size.height
394 },
395 LonghandId::MarginBottom => margins.bottom,
396 LonghandId::MarginTop => margins.top,
397 LonghandId::MarginLeft => margins.left,
398 LonghandId::MarginRight => margins.right,
399 LonghandId::PaddingBottom => padding.bottom,
400 LonghandId::PaddingTop => padding.top,
401 LonghandId::PaddingLeft => padding.left,
402 LonghandId::PaddingRight => padding.right,
403 _ => return computed_style(Some(fragment)),
404 }
405 .to_css_string()
406 };
407
408 node.fragments_for_pseudo(*pseudo)
409 .first()
410 .map(resolve_for_fragment)
411 .unwrap_or_else(|| computed_style(None))
412}
413
414fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
415 match fragment {
418 Fragment::Box(box_fragment) => !box_fragment.is_inline_box(),
419 Fragment::Float(_) |
420 Fragment::Positioning(_) |
421 Fragment::AbsoluteOrFixedPositioned(_) |
422 Fragment::Image(_) |
423 Fragment::IFrame(_) => true,
424 Fragment::Text(_) => false,
425 }
426}
427
428fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
429 let Some(Fragment::Box(box_fragment)) = fragment else {
438 return false;
439 };
440 let flags = box_fragment.base.flags;
441 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
442 style.clone_aspect_ratio() != AspectRatio::auto()
443}
444
445fn resolve_grid_template(
446 grid_info: &SpecificTaffyGridInfo,
447 style: &ComputedValues,
448 longhand_id: LonghandId,
449) -> Option<String> {
450 fn serialize_standalone_non_subgrid_track_list(track_sizes: &[Au]) -> Option<String> {
452 match track_sizes.is_empty() {
453 true => None,
457 false => Some(
464 track_sizes
465 .iter()
466 .map(|size| size.to_css_string())
467 .join(" "),
468 ),
469 }
470 }
471
472 let (track_info, computed_value) = match longhand_id {
473 LonghandId::GridTemplateRows => (&grid_info.rows, &style.get_position().grid_template_rows),
474 LonghandId::GridTemplateColumns => (
475 &grid_info.columns,
476 &style.get_position().grid_template_columns,
477 ),
478 _ => return None,
479 };
480
481 match computed_value {
482 GenericGridTemplateComponent::None |
486 GenericGridTemplateComponent::TrackList(_) |
487 GenericGridTemplateComponent::Masonry => {
488 serialize_standalone_non_subgrid_track_list(&track_info.sizes)
489 },
490
491 GenericGridTemplateComponent::Subgrid(_) => None,
499 }
500}
501
502#[expect(unsafe_code)]
503pub fn process_resolved_style_request_for_unstyled_node(
504 context: &SharedStyleContext,
505 node: ServoLayoutNode<'_>,
506 pseudo: &Option<PseudoElement>,
507 property: &PropertyId,
508) -> String {
509 if pseudo.is_some() {
511 return String::new();
512 }
513
514 let mut tlc = ThreadLocalStyleContext::new();
515 let mut context = StyleContext {
516 shared: context,
517 thread_local: &mut tlc,
518 };
519
520 let element = node.as_element().unwrap();
521 let styles = resolve_style(
522 &mut context,
523 unsafe { element.dangerous_style_element() },
524 RuleInclusion::All,
525 pseudo.as_ref(),
526 None,
527 );
528 let style = styles.primary();
529 let longhand_id = match *property {
530 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
531 Ok(longhand_id) => longhand_id,
532 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
533 },
534 PropertyId::Custom(ref name) => {
535 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
536 },
537 };
538
539 match longhand_id {
540 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
543 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
544
545 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
548 }
549}
550
551fn shorthand_to_css_string(
552 id: style::properties::ShorthandId,
553 style: &style::properties::ComputedValues,
554) -> String {
555 use style::values::resolved::Context;
556 let mut block = PropertyDeclarationBlock::new();
557 let mut dest = String::new();
558 for longhand in id.longhands() {
559 block.push(
560 style.computed_or_resolved_declaration(
561 longhand,
562 Some(&mut Context {
563 style,
564 for_property: PropertyId::NonCustom(longhand.into()),
565 current_longhand: None,
566 }),
567 ),
568 Importance::Normal,
569 );
570 }
571 match block.shorthand_to_css(id, &mut dest) {
572 Ok(_) => dest.to_owned(),
573 Err(_) => String::new(),
574 }
575}
576
577struct OffsetParentFragments {
578 parent: Arc<BoxFragment>,
579 grandparent: Option<Fragment>,
580}
581
582#[expect(unsafe_code)]
584fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
585 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
591 let flags = fragment.base()?.flags;
592 if flags.intersects(
593 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
594 ) {
595 return None;
596 }
597 if matches!(
598 fragment, Fragment::Box(fragment) if fragment.style().get_box().position == Position::Fixed
599 ) {
600 return None;
601 }
602
603 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
610 while let Some(parent_node) = maybe_parent_node {
611 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
612
613 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
614 let parent_fragment = match parent_fragment {
615 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
616 _ => continue,
617 };
618
619 let grandparent_fragment =
620 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
621
622 if parent_fragment.style().get_box().position != Position::Static {
623 return Some(OffsetParentFragments {
624 parent: parent_fragment.clone(),
625 grandparent: grandparent_fragment,
626 });
627 }
628
629 let flags = parent_fragment.base.flags;
630 if flags.intersects(
631 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
632 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
633 ) {
634 return Some(OffsetParentFragments {
635 parent: parent_fragment.clone(),
636 grandparent: grandparent_fragment,
637 });
638 }
639 }
640 }
641
642 None
643}
644
645#[inline]
646pub fn process_offset_parent_query(
647 layout_thread: &LayoutThread,
648 scroll_tree: &ScrollTree,
649 node: ServoLayoutNode<'_>,
650) -> Option<OffsetParentResponse> {
651 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
668 let mut border_box =
669 fragment.cumulative_box_area_rect(BoxAreaType::Border, layout_thread.into())?;
670 let cumulative_sticky_offsets = fragment
671 .retrieve_box_fragment()
672 .and_then(|box_fragment| box_fragment.spatial_tree_node())
673 .map(|node_id| {
674 scroll_tree
675 .cumulative_sticky_offsets(node_id)
676 .map(Au::from_f32_px)
677 .cast_unit()
678 });
679 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
680
681 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
686 return Some(OffsetParentResponse {
687 node_address: None,
688 rect: border_box,
689 });
690 };
691
692 let parent_fragment = offset_parent_fragment.parent;
693 let parent_is_static_body_element = parent_fragment
694 .base
695 .flags
696 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
697 parent_fragment.style().get_box().position == Position::Static;
698
699 let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
708 Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
709 Some(box_fragment)
710 },
711 _ => None,
712 };
713
714 let parent_offset_rect = if parent_is_static_body_element {
722 if let Some(grandparent_fragment) = grandparent_box_fragment() {
723 grandparent_fragment.offset_by_containing_block(
724 &grandparent_fragment.border_rect(),
725 layout_thread.into(),
726 )
727 } else {
728 parent_fragment
729 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
730 }
731 } else {
732 parent_fragment
733 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
734 }
735 .translate(
736 cumulative_sticky_offsets
737 .and_then(|_| parent_fragment.spatial_tree_node())
738 .map(|node_id| {
739 scroll_tree
740 .cumulative_sticky_offsets(node_id)
741 .map(Au::from_f32_px)
742 .cast_unit()
743 })
744 .unwrap_or_default(),
745 );
746
747 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
748
749 Some(OffsetParentResponse {
750 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
751 rect: border_box,
752 })
753}
754
755fn style_and_flags_for_node(
756 node: &ServoLayoutNode,
757) -> Option<(ServoArc<ComputedValues>, FragmentFlags)> {
758 let layout_data = node.inner_layout_data()?;
759 let layout_box = layout_data.self_box.borrow();
760 let layout_box = layout_box.as_ref()?;
761
762 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
763}
764
765fn is_containing_block_for_position(
766 position: Position,
767 ancestor_style: &ServoArc<ComputedValues>,
768 ancestor_flags: FragmentFlags,
769) -> bool {
770 match position {
771 Position::Static | Position::Relative | Position::Sticky => {
772 !ancestor_style.is_inline_box(ancestor_flags)
773 },
774 Position::Absolute => {
775 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
776 },
777 Position::Fixed => {
778 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
779 },
780 }
781}
782
783fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
784 let (style, _flags) = style_and_flags_for_node(&node)?;
785
786 let mut current_position_value = style.clone_position();
787 let mut current_ancestor = node;
788
789 #[expect(unsafe_code)]
790 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
791 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
792 continue;
793 };
794
795 if is_containing_block_for_position(current_position_value, &ancestor_style, ancestor_flags)
796 {
797 return Some(ancestor);
798 }
799
800 current_position_value = ancestor_style.clone_position();
801 current_ancestor = ancestor;
802 }
803 None
804}
805
806#[inline]
810pub(crate) fn process_scroll_container_query(
811 node: Option<ServoLayoutNode<'_>>,
812 query_flags: ScrollContainerQueryFlags,
813 viewport_overflow: AxesOverflow,
814) -> Option<ScrollContainerResponse> {
815 let Some(node) = node else {
816 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
817 };
818
819 let (style, flags) = style_and_flags_for_node(&node)?;
822
823 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
829 flags.intersects(
830 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
831 )
832 {
833 return None;
834 }
835
836 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
837 style.establishes_scroll_container(flags)
838 {
839 return Some(ScrollContainerResponse::Element(
840 node.opaque().into(),
841 style.effective_overflow(flags),
842 ));
843 }
844
845 let mut current_position_value = style.clone_position();
865 let mut current_ancestor = node;
866
867 #[expect(unsafe_code)]
868 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
869 current_ancestor = ancestor;
870
871 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
872 continue;
873 };
874
875 if !is_containing_block_for_position(
876 current_position_value,
877 &ancestor_style,
878 ancestor_flags,
879 ) {
880 continue;
881 }
882
883 if ancestor_style.establishes_scroll_container(ancestor_flags) {
884 return Some(ScrollContainerResponse::Element(
885 ancestor.opaque().into(),
886 ancestor_style.effective_overflow(ancestor_flags),
887 ));
888 }
889
890 current_position_value = ancestor_style.clone_position();
891 }
892
893 match current_position_value {
894 Position::Fixed => None,
895 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
896 }
897}
898
899pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
901 let mut results = Vec::new();
907 let mut max_req_line_break_count = 0;
908
909 let mut state = Default::default();
911 for child in node.dom_children() {
912 let mut current = rendered_text_collection_steps(child, &mut state);
914 results.append(&mut current);
916 }
917
918 let mut output = String::new();
919 for item in results {
920 match item {
921 InnerOrOuterTextItem::Text(s) => {
922 if !s.is_empty() {
924 if max_req_line_break_count > 0 {
925 output.push_str(&"\u{000A}".repeat(max_req_line_break_count));
927 max_req_line_break_count = 0;
928 }
929 output.push_str(&s);
930 }
931 },
932 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
933 if output.is_empty() {
935 continue;
937 }
938 if count > max_req_line_break_count {
942 max_req_line_break_count = count;
943 }
944 },
945 }
946 }
947 output
948}
949
950enum InnerOrOuterTextItem {
951 Text(String),
952 RequiredLineBreakCount(usize),
953}
954
955#[derive(Clone)]
956struct RenderedTextCollectionState {
957 first_table_row: bool,
959 first_table_cell: bool,
961 within_table: bool,
964 may_start_with_whitespace: bool,
966 did_truncate_trailing_white_space: bool,
969 within_table_content: bool,
972}
973
974impl Default for RenderedTextCollectionState {
975 fn default() -> Self {
976 RenderedTextCollectionState {
977 first_table_row: true,
978 first_table_cell: true,
979 may_start_with_whitespace: true,
980 did_truncate_trailing_white_space: false,
981 within_table: false,
982 within_table_content: false,
983 }
984 }
985}
986
987#[expect(unsafe_code)]
989fn rendered_text_collection_steps(
990 node: ServoLayoutNode<'_>,
991 state: &mut RenderedTextCollectionState,
992) -> Vec<InnerOrOuterTextItem> {
993 let mut items = vec![];
997 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
998 return items;
999 }
1000
1001 match node.type_id() {
1002 Some(LayoutNodeType::Text) => {
1003 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
1004 match parent_node.type_id() {
1005 Some(
1007 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1008 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1009 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1010 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1011 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1012 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1013 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1014 ) => {
1015 return items;
1016 },
1017 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1021 if let Some(grandparent_node) =
1022 unsafe { parent_node.dangerous_dom_parent() }
1023 {
1024 if !matches!(
1025 grandparent_node.type_id(),
1026 Some(LayoutNodeType::Element(
1027 LayoutElementType::HTMLSelectElement
1028 ))
1029 ) {
1030 return items;
1031 }
1032 } else {
1033 return items;
1034 }
1035 },
1036 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1037 return items;
1038 },
1039 _ => {},
1040 }
1041
1042 if state.within_table && !state.within_table_content {
1046 return items;
1047 }
1048
1049 let Some(parent_element) = parent_node.as_element() else {
1050 return items;
1051 };
1052 let Some(style_data) = parent_element.style_data() else {
1053 return items;
1054 };
1055
1056 let element_data = style_data.element_data.borrow();
1057 let Some(style) = element_data.styles.get_primary() else {
1058 return items;
1059 };
1060
1061 if style.get_inherited_box().visibility != Visibility::Visible {
1067 return items;
1068 }
1069
1070 let display = style.get_box().display;
1074 if display == Display::None {
1075 match parent_element.type_id() {
1076 Some(
1079 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1080 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1081 ) => {},
1082 _ => {
1083 return items;
1084 },
1085 }
1086 }
1087
1088 let text_content = node.text_content();
1089
1090 let white_space_collapse = style.clone_white_space_collapse();
1091 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1092 let is_inline = matches!(
1093 display,
1094 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1095 );
1096 let trim_beginning_white_space =
1100 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1101 let with_white_space_rules_applied = WhitespaceCollapse::new(
1102 text_content.chars(),
1103 white_space_collapse,
1104 trim_beginning_white_space,
1105 );
1106
1107 let text_transform = style.clone_text_transform().case();
1115 let mut transformed_text: String =
1116 TextTransformation::new(with_white_space_rules_applied, text_transform)
1117 .collect();
1118
1119 if TextTransformCase::Capitalize == text_transform {
1123 transformed_text = capitalize_string(&transformed_text, true);
1124 }
1125
1126 let is_preformatted_element =
1127 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1128
1129 let is_final_character_whitespace = transformed_text
1130 .chars()
1131 .next_back()
1132 .filter(char::is_ascii_whitespace)
1133 .is_some();
1134
1135 let is_first_character_whitespace = transformed_text
1136 .chars()
1137 .next()
1138 .filter(char::is_ascii_whitespace)
1139 .is_some();
1140
1141 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1145 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1146 };
1147
1148 if !transformed_text.is_empty() {
1149 if is_final_character_whitespace && !is_preformatted_element {
1152 state.may_start_with_whitespace = false;
1153 state.did_truncate_trailing_white_space = true;
1154 transformed_text.pop();
1155 } else {
1156 state.may_start_with_whitespace = is_final_character_whitespace;
1157 state.did_truncate_trailing_white_space = false;
1158 }
1159 items.push(InnerOrOuterTextItem::Text(transformed_text));
1160 }
1161 } else {
1162 items.push(InnerOrOuterTextItem::Text(node.text_content().into()));
1165 }
1166 },
1167 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1168 state.did_truncate_trailing_white_space = false;
1171 state.may_start_with_whitespace = true;
1172 items.push(InnerOrOuterTextItem::Text(String::from("\u{000A}")));
1173 },
1174 _ => {
1175 let Some(element) = node.as_element() else {
1178 return items;
1179 };
1180 let Some(style_data) = element.style_data() else {
1181 return items;
1182 };
1183
1184 let element_data = style_data.element_data.borrow();
1185 let Some(style) = element_data.styles.get_primary() else {
1186 return items;
1187 };
1188 let inherited_box = style.get_inherited_box();
1189
1190 if inherited_box.visibility != Visibility::Visible {
1191 for child in node.dom_children() {
1195 items.append(&mut rendered_text_collection_steps(child, state));
1196 }
1197 return items;
1198 }
1199
1200 let style_box = style.get_box();
1201 let display = style_box.display;
1202 let mut surrounding_line_breaks = 0;
1203
1204 if style_box.position == Position::Absolute || style_box.float != Float::None {
1206 surrounding_line_breaks = 1;
1207 }
1208
1209 match display {
1212 Display::Table => {
1213 surrounding_line_breaks = 1;
1214 state.within_table = true;
1215 },
1216 Display::TableCell => {
1221 if !state.first_table_cell {
1222 items.push(InnerOrOuterTextItem::Text(String::from(
1223 "\u{0009}", )));
1225 state.did_truncate_trailing_white_space = false;
1227 }
1228 state.first_table_cell = false;
1229 state.within_table_content = true;
1230 },
1231 Display::TableRow => {
1236 if !state.first_table_row {
1237 items.push(InnerOrOuterTextItem::Text(String::from(
1238 "\u{000A}", )));
1240 state.did_truncate_trailing_white_space = false;
1242 }
1243 state.first_table_row = false;
1244 state.first_table_cell = true;
1245 },
1246 Display::Block => {
1249 surrounding_line_breaks = 1;
1250 },
1251 Display::TableCaption => {
1252 surrounding_line_breaks = 1;
1253 state.within_table_content = true;
1254 },
1255 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock
1259 if state.did_truncate_trailing_white_space =>
1260 {
1261 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1262 state.did_truncate_trailing_white_space = false;
1263 state.may_start_with_whitespace = true;
1264 },
1265 _ => {},
1266 }
1267
1268 match node.type_id() {
1269 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1272 surrounding_line_breaks = 2;
1273 },
1274 Some(
1277 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1278 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1279 ) => {
1280 surrounding_line_breaks = 1;
1281 },
1282 _ => {},
1283 }
1284
1285 if surrounding_line_breaks > 0 {
1286 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1287 surrounding_line_breaks,
1288 ));
1289 state.did_truncate_trailing_white_space = false;
1290 state.may_start_with_whitespace = true;
1291 }
1292
1293 match node.type_id() {
1294 Some(
1299 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1300 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1301 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1302 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1303 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1304 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1305 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1306 ) => {
1307 if display != Display::Block && state.did_truncate_trailing_white_space {
1308 items.push(InnerOrOuterTextItem::Text(String::from(" ")));
1309 state.did_truncate_trailing_white_space = false;
1310 };
1311 state.may_start_with_whitespace = false;
1312 },
1313 _ => {
1314 for child in node.dom_children() {
1317 items.append(&mut rendered_text_collection_steps(child, state));
1318 }
1319 },
1320 }
1321
1322 match display {
1325 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1326 state.did_truncate_trailing_white_space = false;
1327 state.may_start_with_whitespace = false;
1328 },
1329 Display::Table => {
1330 state.within_table = false;
1331 },
1332 Display::TableCell | Display::TableCaption => {
1333 state.within_table_content = false;
1334 },
1335 _ => {},
1336 }
1337
1338 if surrounding_line_breaks > 0 {
1339 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1340 surrounding_line_breaks,
1341 ));
1342 state.did_truncate_trailing_white_space = false;
1343 state.may_start_with_whitespace = true;
1344 }
1345 },
1346 };
1347 items
1348}
1349
1350struct ClosestFragment {
1351 fragment: Arc<TextFragment>,
1352 point_in_fragment: Point2D<Au, CSSPixel>,
1353 distance: Au,
1354 point_in_vertical_bounds: bool,
1355}
1356
1357impl ClosestFragment {
1358 fn should_replace(&self, new_distance: Au, point_in_vertical_bounds: bool) -> bool {
1359 if point_in_vertical_bounds && !self.point_in_vertical_bounds {
1360 return true;
1361 }
1362 if self.point_in_vertical_bounds && !point_in_vertical_bounds {
1363 return false;
1364 }
1365 new_distance <= self.distance
1366 }
1367}
1368
1369pub fn find_character_offset_in_fragment_descendants(
1370 node: &ServoLayoutNode,
1371 stacking_context_tree: &StackingContextTree,
1372 point_in_viewport: Point2D<Au, CSSPixel>,
1373) -> Option<usize> {
1374 fn maybe_update_closest(
1375 fragment: &Fragment,
1376 point_in_fragment: Point2D<Au, CSSPixel>,
1377 closest_relative_fragment: &mut Option<ClosestFragment>,
1378 ) {
1379 let Fragment::Text(text_fragment) = fragment else {
1380 return;
1381 };
1382
1383 let (distance, point_in_vertical_bounds) = {
1384 (
1385 text_fragment.distance_to_point_for_glyph_offset(point_in_fragment),
1386 text_fragment.point_is_within_vertical_boundaries(point_in_fragment),
1387 )
1388 };
1389
1390 if closest_relative_fragment
1391 .as_ref()
1392 .is_none_or(|closest_fragment| {
1393 closest_fragment.should_replace(distance, point_in_vertical_bounds)
1394 })
1395 {
1396 *closest_relative_fragment = Some(ClosestFragment {
1397 fragment: text_fragment.clone(),
1398 point_in_fragment,
1399 distance,
1400 point_in_vertical_bounds,
1401 });
1402 }
1403 }
1404
1405 fn collect_relevant_children(
1406 fragment: &Fragment,
1407 point_in_viewport: Point2D<Au, CSSPixel>,
1408 closest_relative_fragment: &mut Option<ClosestFragment>,
1409 ) {
1410 maybe_update_closest(fragment, point_in_viewport, closest_relative_fragment);
1411
1412 if let Some(children) = fragment.children() {
1413 for child in children.iter() {
1414 let offset = child
1415 .base()
1416 .map(|base| base.rect().origin)
1417 .unwrap_or_default();
1418 let point = point_in_viewport - offset.to_vector();
1419 collect_relevant_children(child, point, closest_relative_fragment);
1420 }
1421 }
1422 }
1423
1424 let mut closest_relative_fragment = None;
1425 for fragment in &node.fragments_for_pseudo(None) {
1426 if let Some(point_in_fragment) =
1427 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1428 {
1429 collect_relevant_children(fragment, point_in_fragment, &mut closest_relative_fragment);
1430 }
1431 }
1432
1433 closest_relative_fragment.and_then(|closest_fragment| {
1434 closest_fragment
1435 .fragment
1436 .character_offset(closest_fragment.point_in_fragment)
1437 })
1438}
1439
1440pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1441 let containing_block = containing_block_for_node(node);
1442 containing_block.map(|node| node.opaque().into())
1443}
1444
1445pub fn process_resolved_font_style_query<'dom, E>(
1446 context: &SharedStyleContext,
1447 node: E,
1448 value: &str,
1449 url_data: ServoUrl,
1450 shared_lock: &SharedRwLock,
1451) -> Option<ServoArc<Font>>
1452where
1453 E: LayoutNode<'dom>,
1454{
1455 fn create_font_declaration(
1456 value: &str,
1457 url_data: &ServoUrl,
1458 quirks_mode: QuirksMode,
1459 ) -> Option<PropertyDeclarationBlock> {
1460 let mut declarations = SourcePropertyDeclaration::default();
1461 let result = parse_one_declaration_into(
1462 &mut declarations,
1463 PropertyId::NonCustom(ShorthandId::Font.into()),
1464 value,
1465 Origin::Author,
1466 &UrlExtraData(url_data.get_arc()),
1467 None,
1468 ParsingMode::DEFAULT,
1469 quirks_mode,
1470 CssRuleType::Style,
1471 );
1472 let declarations = match result {
1473 Ok(()) => {
1474 let mut block = PropertyDeclarationBlock::new();
1475 block.extend(declarations.drain(), Importance::Normal);
1476 block
1477 },
1478 Err(_) => return None,
1479 };
1480 Some(declarations)
1482 }
1483 fn resolve_for_declarations<'dom, E>(
1484 context: &SharedStyleContext,
1485 parent_style: Option<&ComputedValues>,
1486 declarations: PropertyDeclarationBlock,
1487 shared_lock: &SharedRwLock,
1488 ) -> ServoArc<ComputedValues>
1489 where
1490 E: LayoutNode<'dom>,
1491 {
1492 let parent_style = match parent_style {
1493 Some(parent) => parent,
1494 None => context.stylist.device().default_computed_values(),
1495 };
1496 context
1497 .stylist
1498 .compute_for_declarations::<DangerousStyleElementOf<'dom, E::ConcreteTypeBundle>>(
1499 &context.guards,
1500 parent_style,
1501 ServoArc::new(shared_lock.wrap(declarations)),
1502 )
1503 }
1504
1505 let quirks_mode = context.quirks_mode();
1508 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1509
1510 let element = node.as_element().unwrap();
1514 let parent_style = if node.is_connected() {
1515 if element.style_data().is_some() {
1516 element.style(context)
1517 } else {
1518 let mut tlc = ThreadLocalStyleContext::new();
1519 let mut context = StyleContext {
1520 shared: context,
1521 thread_local: &mut tlc,
1522 };
1523 #[expect(unsafe_code)]
1524 let styles = resolve_style(
1525 &mut context,
1526 unsafe { element.dangerous_style_element() },
1527 RuleInclusion::All,
1528 None,
1529 None,
1530 );
1531 styles.primary().clone()
1532 }
1533 } else {
1534 let default_declarations =
1535 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1536 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1537 };
1538
1539 let computed_values =
1541 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1542
1543 Some(computed_values.clone_font())
1544}
1545
1546pub(crate) fn transform_au_rectangle(
1547 rect_to_transform: Rect<Au, CSSPixel>,
1548 transform: FastLayoutTransform,
1549) -> Option<Rect<Au, CSSPixel>> {
1550 let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
1551 let outer_transformed_rect = match transform {
1552 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1553 FastLayoutTransform::Transform { transform, .. } => {
1554 transform.outer_transformed_rect(rect_to_transform)
1555 },
1556 };
1557 outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1558}
1559
1560pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1561 let fragments = node.fragments_for_pseudo(None);
1562 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1563
1564 Some(
1565 box_fragment
1566 .style()
1567 .effective_overflow(box_fragment.base.flags),
1568 )
1569}