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