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 layout_api::{
16 AxesOverflow, BoxAreaType, CSSPixelRectVec, DangerousStyleElementOf, LayoutElement,
17 LayoutElementType, LayoutNode, LayoutNodeType, OffsetParentResponse, PhysicalSides,
18 ScrollContainerQueryFlags, ScrollContainerResponse,
19};
20use paint_api::display_list::ScrollTree;
21use script::layout_dom::ServoLayoutNode;
22use servo_arc::Arc as ServoArc;
23use servo_base::text::Utf32CodeUnits;
24use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
25use servo_url::ServoUrl;
26use style::computed_values::display::T as Display;
27use style::computed_values::position::T as Position;
28use style::computed_values::visibility::T as Visibility;
29use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
30use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
31use style::dom::{NodeInfo, OpaqueNode};
32use style::properties::style_structs::Font;
33use style::properties::{
34 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
35 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
36};
37use style::selector_parser::PseudoElement;
38use style::shared_lock::SharedRwLock;
39use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
40use style::stylist::RuleInclusion;
41use style::traversal::resolve_style;
42use style::values::computed::transform::Matrix3D;
43use style::values::computed::{Float, Size};
44use style::values::generics::font::LineHeight;
45use style::values::generics::position::AspectRatio;
46use style::values::specified::GenericGridTemplateComponent;
47use style::values::specified::box_::DisplayInside;
48use style_traits::{CSSPixel, ParsingMode, ToCss};
49use webrender_api::units::LayoutPixel;
50
51use crate::cell::RefOrAtomicRef;
52use crate::display_list::{ClosestFragmentSearch, StackingContextTree, au_rect_to_length_rect};
53use crate::dom::NodeExt;
54use crate::flow::inline::text_transform::TextTransformationIterator;
55use crate::fragment_tree::{
56 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
57};
58use crate::layout_impl::LayoutThread;
59use crate::style_ext::ComputedValuesExt;
60use crate::taffy::SpecificTaffyGridInfo;
61
62fn root_transform_for_fragments(
64 scroll_tree: &ScrollTree,
65 fragments: &[Fragment],
66) -> Option<FastLayoutTransform> {
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 node.with_fragments(|fragments| {
103 let mut rects = fragments
104 .iter()
105 .filter(|fragment| {
106 !exclude_transform_and_inline ||
107 fragment
108 .retrieve_box_fragment()
109 .is_none_or(|fragment| !fragment.with_style().is_inline_box())
110 })
111 .filter_map(|node| node.cumulative_box_area_rect(area, layout_thread.into()))
112 .peekable();
113
114 rects.peek()?;
115 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
116
117 if exclude_transform_and_inline {
118 return Some(rect_union);
119 }
120
121 let Some(transform) =
122 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, fragments)
123 else {
124 return Some(Rect::new(rect_union.origin, Size2D::zero()));
125 };
126
127 transform_au_rectangle(rect_union, transform)
128 })?
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.fragments_for_pseudo(None);
138 let transform =
139 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, &fragments);
140
141 let rects = fragments
142 .into_iter()
143 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area, layout_thread.into()));
144
145 let Some(transform) = transform else {
146 return rects
147 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
148 .collect();
149 };
150
151 rects
152 .filter_map(move |rect| transform_au_rectangle(rect, transform))
153 .collect()
154}
155
156pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
157 node.fragments_for_pseudo(None)
158 .first()
159 .map(Fragment::client_rect)
160 .unwrap_or_default()
161}
162
163pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
170 let Some(layout_data) = node.inner_layout_data() else {
171 return 1.0;
172 };
173 let layout_box = layout_data.self_box.borrow();
174 let Some(layout_box) = layout_box.as_ref() else {
175 return 1.0;
176 };
177 layout_box
178 .with_base(|base| base.style.effective_zoom.value())
179 .unwrap_or(1.0)
180}
181
182pub fn process_node_scroll_area_request(
184 layout_thread: &LayoutThread,
185 requested_node: Option<ServoLayoutNode<'_>>,
186 fragment_tree: Option<Rc<FragmentTree>>,
187) -> Rect<i32, CSSPixel> {
188 let Some(tree) = fragment_tree else {
189 return Rect::zero();
190 };
191
192 let rect = match requested_node {
193 Some(node) => node
194 .fragments_for_pseudo(None)
195 .first()
196 .map(|fragment| fragment.scrolling_area(layout_thread))
197 .unwrap_or_default(),
198 None => tree
199 .scrollable_overflow()
200 .union(&tree.initial_containing_block),
201 };
202
203 Rect::new(
204 rect.origin.map(Au::to_f32_px),
205 rect.size.to_vector().map(Au::to_f32_px).to_size(),
206 )
207 .round()
208 .to_i32()
209}
210
211pub fn process_resolved_style_request(
214 layout_thread: &LayoutThread,
215 context: &SharedStyleContext,
216 node: ServoLayoutNode<'_>,
217 pseudo: &Option<PseudoElement>,
218 property: &PropertyId,
219) -> String {
220 if node
221 .as_element()
222 .is_none_or(|element| element.style_data().is_none())
223 {
224 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
225 }
226
227 let layout_element = node.as_element().unwrap();
230 let layout_element = match pseudo {
231 Some(pseudo_element_type) => {
232 match layout_element.with_pseudo(*pseudo_element_type) {
233 Some(layout_element) => layout_element,
234 None => {
235 return String::new();
239 },
240 }
241 },
242 None => layout_element,
243 };
244
245 let style = &*layout_element.style(context);
246 let longhand_id = match *property {
247 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
248 Ok(longhand_id) => longhand_id,
249 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
250 },
251 PropertyId::Custom(ref name) => {
252 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
253 },
254 }
255 .to_physical(style.writing_mode);
256
257 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
259 let transform_list = &style.get_box().transform;
260
261 if transform_list.0.is_empty() {
265 return Ok("none".into());
266 }
267
268 let length_rect = box_fragment
273 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
274 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
275
276 let matrix = Matrix3D::from(transform);
281 if !is_3d {
282 Ok(matrix.into_2d()?.to_css_string())
283 } else {
284 Ok(matrix.to_css_string())
285 }
286 };
287
288 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
289 LonghandId::MinWidth
290 if style.clone_min_width() == Size::Auto &&
291 !should_honor_min_size_auto(fragment, style) =>
292 {
293 String::from("0px")
294 },
295 LonghandId::MinHeight
296 if style.clone_min_height() == Size::Auto &&
297 !should_honor_min_size_auto(fragment, style) =>
298 {
299 String::from("0px")
300 },
301 LonghandId::Transform => match serialize_transform_value(None) {
302 Ok(value) => value,
303 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
304 },
305 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
306 };
307
308 if longhand_id == LonghandId::LineHeight {
317 let font = style.get_font();
318 let font_size = font.font_size.computed_size();
319 return match font.line_height {
320 LineHeight::Normal => computed_style(None),
323 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
324 LineHeight::Length(value) => value.0.to_css_string(),
325 };
326 }
327
328 let display = style.get_box().display;
332 if display.is_none() || display.is_contents() {
333 return computed_style(None);
334 }
335
336 let resolve_for_fragment = |fragment: &Fragment| {
337 if let Some(box_fragment) = fragment.retrieve_box_fragment() &&
338 style.get_box().position != Position::Static
339 {
340 let resolved_insets =
341 || box_fragment.calculate_resolved_insets_if_positioned(layout_thread.into());
342 match longhand_id {
343 LonghandId::Top => return resolved_insets().top.to_css_string(),
344 LonghandId::Right => {
345 return resolved_insets().right.to_css_string();
346 },
347 LonghandId::Bottom => {
348 return resolved_insets().bottom.to_css_string();
349 },
350 LonghandId::Left => {
351 return resolved_insets().left.to_css_string();
352 },
353 LonghandId::Transform => {
354 if let Ok(string) = serialize_transform_value(Some(&box_fragment)) {
357 return string;
358 }
359 },
360 _ => {},
361 }
362 }
363
364 if !matches!(
365 fragment,
366 Fragment::LayoutRoot(..) | Fragment::Box(..) | Fragment::Positioning(..)
367 ) {
368 return computed_style(Some(fragment));
369 }
370
371 let specific_layout_info = fragment
377 .retrieve_box_fragment()
378 .and_then(|box_fragment| box_fragment.specific_layout_info().as_deref().cloned());
379 if display.inside() == DisplayInside::Grid &&
380 let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info &&
381 let Some(value) = resolve_grid_template(&info, style, longhand_id)
382 {
383 return value;
384 }
385
386 let content_rect =
394 LazyCell::new(|| fragment.base().map(|base| base.rect()).unwrap_or_default());
395 let margins = LazyCell::new(|| {
396 fragment
397 .retrieve_box_fragment()
398 .map(|fragment| fragment.margin)
399 .unwrap_or_default()
400 });
401 let padding = LazyCell::new(|| {
402 fragment
403 .retrieve_box_fragment()
404 .map(|fragment| fragment.padding)
405 .unwrap_or_default()
406 });
407 match longhand_id {
408 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
409 content_rect.size.width
410 },
411 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
412 content_rect.size.height
413 },
414 LonghandId::MarginBottom => margins.bottom,
415 LonghandId::MarginTop => margins.top,
416 LonghandId::MarginLeft => margins.left,
417 LonghandId::MarginRight => margins.right,
418 LonghandId::PaddingBottom => padding.bottom,
419 LonghandId::PaddingTop => padding.top,
420 LonghandId::PaddingLeft => padding.left,
421 LonghandId::PaddingRight => padding.right,
422 _ => return computed_style(Some(fragment)),
423 }
424 .to_css_string()
425 };
426
427 node.fragments_for_pseudo(*pseudo)
428 .first()
429 .map(resolve_for_fragment)
430 .unwrap_or_else(|| computed_style(None))
431}
432
433fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
434 match fragment {
437 Fragment::LayoutRoot(layout_root) => {
438 resolved_size_should_be_used_value(&layout_root.inner())
439 },
440 Fragment::Box(box_fragment) => !box_fragment.with_style().is_inline_box(),
441 Fragment::Float(_) |
442 Fragment::Positioning(_) |
443 Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
444 Fragment::Image(_) |
445 Fragment::IFrame(_) => true,
446 Fragment::Text(_) => false,
447 }
448}
449
450fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
451 let Some(box_fragment) = fragment.and_then(|fragment| fragment.retrieve_box_fragment()) else {
460 return false;
461 };
462 let flags = box_fragment.base.flags;
463 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
464 style.clone_aspect_ratio() != AspectRatio::auto()
465}
466
467fn resolve_grid_template(
468 grid_info: &SpecificTaffyGridInfo,
469 style: &ComputedValues,
470 longhand_id: LonghandId,
471) -> Option<String> {
472 let (track_info, computed_value) = match longhand_id {
473 LonghandId::GridTemplateRows => (
474 &grid_info.info.rows,
475 &style.get_position().grid_template_rows,
476 ),
477 LonghandId::GridTemplateColumns => (
478 &grid_info.info.columns,
479 &style.get_position().grid_template_columns,
480 ),
481 _ => return None,
482 };
483
484 match computed_value {
485 GenericGridTemplateComponent::None |
489 GenericGridTemplateComponent::TrackList(_) |
490 GenericGridTemplateComponent::Masonry => {
491 (!track_info.positions.is_empty()).then(|| track_info.to_track_list_string())
492 },
493
494 GenericGridTemplateComponent::Subgrid(_) => None,
502 }
503}
504
505#[expect(unsafe_code)]
506pub fn process_resolved_style_request_for_unstyled_node(
507 context: &SharedStyleContext,
508 node: ServoLayoutNode<'_>,
509 pseudo: &Option<PseudoElement>,
510 property: &PropertyId,
511) -> String {
512 if pseudo.is_some() {
514 return String::new();
515 }
516
517 let mut tlc = ThreadLocalStyleContext::new();
518 let mut context = StyleContext {
519 shared: context,
520 thread_local: &mut tlc,
521 };
522
523 let element = node.as_element().unwrap();
524 let styles = resolve_style(
525 &mut context,
526 unsafe { element.dangerous_style_element() },
527 RuleInclusion::All,
528 pseudo.as_ref(),
529 None,
530 );
531 let style = styles.primary();
532 let longhand_id = match *property {
533 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
534 Ok(longhand_id) => longhand_id,
535 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
536 },
537 PropertyId::Custom(ref name) => {
538 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
539 },
540 };
541
542 match longhand_id {
543 LonghandId::MinWidth if style.clone_min_width() == Size::Auto => String::from("0px"),
546 LonghandId::MinHeight if style.clone_min_height() == Size::Auto => String::from("0px"),
547
548 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
551 }
552}
553
554fn shorthand_to_css_string(
555 id: style::properties::ShorthandId,
556 style: &style::properties::ComputedValues,
557) -> String {
558 use style::values::resolved::Context;
559 let mut block = PropertyDeclarationBlock::new();
560 let mut dest = String::new();
561 for longhand in id.longhands() {
562 block.push(
563 style.computed_or_resolved_declaration(
564 longhand,
565 Some(&mut Context {
566 style,
567 for_property: PropertyId::NonCustom(longhand.into()),
568 current_longhand: None,
569 }),
570 ),
571 Importance::Normal,
572 );
573 }
574 match block.shorthand_to_css(id, &mut dest) {
575 Ok(_) => dest,
576 Err(_) => String::new(),
577 }
578}
579
580struct OffsetParentFragments {
581 parent: Arc<BoxFragment>,
582 grandparent: Option<Fragment>,
583}
584
585impl OffsetParentFragments {
586 fn grandparent_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
587 self.grandparent
588 .as_ref()
589 .and_then(|grandparent| grandparent.retrieve_box_fragment())
590 }
591}
592#[expect(unsafe_code)]
594fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
595 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
601 let flags = fragment.base()?.flags;
602 if flags.intersects(
603 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
604 ) {
605 return None;
606 }
607
608 if fragment
609 .retrieve_box_fragment()
610 .is_some_and(|fragment| fragment.style().get_box().position == Position::Fixed)
611 {
612 return None;
613 }
614
615 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
622 while let Some(parent_node) = maybe_parent_node {
623 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
624
625 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
626 let Some(parent_fragment) = parent_fragment.retrieve_box_fragment() else {
627 continue;
628 };
629
630 let grandparent_fragment =
631 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
632
633 if parent_fragment.style().get_box().position != Position::Static {
634 return Some(OffsetParentFragments {
635 parent: parent_fragment.clone(),
636 grandparent: grandparent_fragment,
637 });
638 }
639
640 let flags = parent_fragment.base.flags;
641 if flags.intersects(
642 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
643 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
644 ) {
645 return Some(OffsetParentFragments {
646 parent: parent_fragment.clone(),
647 grandparent: grandparent_fragment,
648 });
649 }
650 }
651 }
652
653 None
654}
655
656#[inline]
657pub fn process_offset_parent_query(
658 layout_thread: &LayoutThread,
659 scroll_tree: &ScrollTree,
660 node: ServoLayoutNode<'_>,
661) -> Option<OffsetParentResponse> {
662 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
679 let mut border_box =
680 fragment.cumulative_box_area_rect(BoxAreaType::Border, layout_thread.into())?;
681 let cumulative_sticky_offsets = fragment
682 .retrieve_box_fragment()
683 .and_then(|box_fragment| box_fragment.spatial_tree_node())
684 .map(|node_id| {
685 scroll_tree
686 .cumulative_sticky_offsets(node_id)
687 .map(Au::from_f32_px)
688 .cast_unit()
689 });
690 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
691
692 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
697 return Some(OffsetParentResponse {
698 node_address: None,
699 rect: border_box,
700 });
701 };
702
703 let parent_fragment = &offset_parent_fragment.parent;
704 let parent_is_static_body_element = parent_fragment
705 .base
706 .flags
707 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
708 parent_fragment.style().get_box().position == Position::Static;
709
710 let parent_offset_rect = if parent_is_static_body_element {
727 if let Some(grandparent_fragment) = offset_parent_fragment.grandparent_box_fragment() {
728 grandparent_fragment.offset_by_containing_block(
729 &grandparent_fragment.border_rect(),
730 layout_thread.into(),
731 )
732 } else {
733 parent_fragment
734 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
735 }
736 } else {
737 parent_fragment
738 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
739 }
740 .translate(
741 cumulative_sticky_offsets
742 .and_then(|_| parent_fragment.spatial_tree_node())
743 .map(|node_id| {
744 scroll_tree
745 .cumulative_sticky_offsets(node_id)
746 .map(Au::from_f32_px)
747 .cast_unit()
748 })
749 .unwrap_or_default(),
750 );
751
752 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
753
754 Some(OffsetParentResponse {
755 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
756 rect: border_box,
757 })
758}
759
760fn style_and_flags_for_node(
761 node: &ServoLayoutNode,
762) -> Option<(ServoArc<ComputedValues>, FragmentFlags)> {
763 let layout_data = node.inner_layout_data()?;
764 let layout_box = layout_data.self_box.borrow();
765 let layout_box = layout_box.as_ref()?;
766
767 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
768}
769
770fn is_containing_block_for_position(
771 position: Position,
772 ancestor_style: &ServoArc<ComputedValues>,
773 ancestor_flags: FragmentFlags,
774) -> bool {
775 match position {
776 Position::Static | Position::Relative | Position::Sticky => {
777 !ancestor_style.is_inline_box(ancestor_flags)
778 },
779 Position::Absolute => {
780 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
781 },
782 Position::Fixed => {
783 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
784 },
785 }
786}
787
788fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
789 let (style, _flags) = style_and_flags_for_node(&node)?;
790
791 let mut current_position_value = style.clone_position();
792 let mut current_ancestor = node;
793
794 #[expect(unsafe_code)]
795 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
796 current_ancestor = ancestor;
797
798 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
799 continue;
800 };
801
802 if is_containing_block_for_position(current_position_value, &ancestor_style, ancestor_flags)
803 {
804 return Some(ancestor);
805 }
806
807 current_position_value = ancestor_style.clone_position();
808 }
809 None
810}
811
812#[inline]
816pub(crate) fn process_scroll_container_query(
817 node: Option<ServoLayoutNode<'_>>,
818 query_flags: ScrollContainerQueryFlags,
819 viewport_overflow: AxesOverflow,
820) -> Option<ScrollContainerResponse> {
821 let Some(node) = node else {
822 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
823 };
824
825 let (style, flags) = style_and_flags_for_node(&node)?;
828
829 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
835 flags.intersects(
836 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
837 )
838 {
839 return None;
840 }
841
842 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
843 style.establishes_scroll_container(flags)
844 {
845 return Some(ScrollContainerResponse::Element(
846 node.opaque().into(),
847 style.effective_overflow(flags),
848 ));
849 }
850
851 let mut current_position_value = style.clone_position();
871 let mut current_ancestor = node;
872
873 #[expect(unsafe_code)]
874 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
875 current_ancestor = ancestor;
876
877 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
878 continue;
879 };
880
881 if !is_containing_block_for_position(
882 current_position_value,
883 &ancestor_style,
884 ancestor_flags,
885 ) {
886 continue;
887 }
888
889 if ancestor_style.establishes_scroll_container(ancestor_flags) {
890 return Some(ScrollContainerResponse::Element(
891 ancestor.opaque().into(),
892 ancestor_style.effective_overflow(ancestor_flags),
893 ));
894 }
895
896 current_position_value = ancestor_style.clone_position();
897 }
898
899 match current_position_value {
900 Position::Fixed => None,
901 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
902 }
903}
904
905pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
907 let mut results = Vec::new();
913 let mut max_req_line_break_count = 0;
914
915 let mut state = Default::default();
917 for child in node.dom_children() {
918 let mut current = rendered_text_collection_steps(child, &mut state);
920 results.append(&mut current);
922 }
923
924 let mut output = String::new();
925 for item in results {
926 match item {
927 InnerOrOuterTextItem::Text(s) => {
928 if !s.is_empty() {
930 if max_req_line_break_count > 0 {
931 output.push_str(&"\u{000A}".repeat(max_req_line_break_count));
933 max_req_line_break_count = 0;
934 }
935 output.push_str(&s);
936 }
937 },
938 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
939 if output.is_empty() {
941 continue;
943 }
944 if count > max_req_line_break_count {
948 max_req_line_break_count = count;
949 }
950 },
951 }
952 }
953 output
954}
955
956enum InnerOrOuterTextItem {
957 Text(Cow<'static, str>),
958 RequiredLineBreakCount(usize),
959}
960
961#[derive(Clone)]
962struct RenderedTextCollectionState {
963 first_table_row: bool,
965 first_table_cell: bool,
967 within_table: bool,
970 may_start_with_whitespace: bool,
972 did_truncate_trailing_white_space: bool,
975 within_table_content: bool,
978}
979
980impl Default for RenderedTextCollectionState {
981 fn default() -> Self {
982 RenderedTextCollectionState {
983 first_table_row: true,
984 first_table_cell: true,
985 may_start_with_whitespace: true,
986 did_truncate_trailing_white_space: false,
987 within_table: false,
988 within_table_content: false,
989 }
990 }
991}
992
993#[expect(unsafe_code)]
995fn rendered_text_collection_steps(
996 node: ServoLayoutNode<'_>,
997 state: &mut RenderedTextCollectionState,
998) -> Vec<InnerOrOuterTextItem> {
999 let mut items = vec![];
1003 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
1004 return items;
1005 }
1006
1007 match node.type_id() {
1008 Some(LayoutNodeType::Text) => {
1009 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
1010 match parent_node.type_id() {
1011 Some(
1013 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1014 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1015 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1016 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1017 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1018 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1019 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1020 ) => {
1021 return items;
1022 },
1023 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1027 if let Some(grandparent_node) =
1028 unsafe { parent_node.dangerous_dom_parent() }
1029 {
1030 if !matches!(
1031 grandparent_node.type_id(),
1032 Some(LayoutNodeType::Element(
1033 LayoutElementType::HTMLSelectElement
1034 ))
1035 ) {
1036 return items;
1037 }
1038 } else {
1039 return items;
1040 }
1041 },
1042 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1043 return items;
1044 },
1045 _ => {},
1046 }
1047
1048 if state.within_table && !state.within_table_content {
1052 return items;
1053 }
1054
1055 let Some(parent_element) = parent_node.as_element() else {
1056 return items;
1057 };
1058 let Some(style_data) = parent_element.style_data() else {
1059 return items;
1060 };
1061
1062 let element_data = style_data.element_data.borrow();
1063 let Some(style) = element_data.styles.get_primary() else {
1064 return items;
1065 };
1066
1067 if style.get_inherited_box().visibility != Visibility::Visible {
1073 return items;
1074 }
1075
1076 let display = style.get_box().display;
1080 if display == Display::None {
1081 match parent_element.type_id() {
1082 Some(
1085 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1086 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1087 ) => {},
1088 _ => {
1089 return items;
1090 },
1091 }
1092 }
1093
1094 let text_content = node.text_content();
1095
1096 let white_space_collapse = style.clone_white_space_collapse();
1097 let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1098 let is_inline = matches!(
1099 display,
1100 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1101 );
1102
1103 let trim_leading_white_space =
1107 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1108 let on_word_boundary = true;
1111
1112 let mut transformed_text = String::with_capacity(text_content.len());
1120 for iteration in TextTransformationIterator::new(
1121 &text_content,
1122 style,
1123 trim_leading_white_space,
1124 on_word_boundary,
1125 ) {
1126 transformed_text.extend(iteration.characters());
1127 }
1128
1129 let is_preformatted_element =
1130 white_space_collapse == WhiteSpaceCollapseValue::Preserve;
1131
1132 let is_final_character_whitespace = transformed_text
1133 .chars()
1134 .next_back()
1135 .filter(char::is_ascii_whitespace)
1136 .is_some();
1137
1138 let is_first_character_whitespace = transformed_text
1139 .chars()
1140 .next()
1141 .filter(char::is_ascii_whitespace)
1142 .is_some();
1143
1144 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1148 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1149 };
1150
1151 if !transformed_text.is_empty() {
1152 if is_final_character_whitespace && !is_preformatted_element {
1155 state.may_start_with_whitespace = false;
1156 state.did_truncate_trailing_white_space = true;
1157 transformed_text.pop();
1158 } else {
1159 state.may_start_with_whitespace = is_final_character_whitespace;
1160 state.did_truncate_trailing_white_space = false;
1161 }
1162 items.push(InnerOrOuterTextItem::Text(Cow::Owned(transformed_text)));
1163 }
1164 } else {
1165 items.push(InnerOrOuterTextItem::Text(Cow::Owned(
1168 node.text_content().deref().into(),
1169 )));
1170 }
1171 },
1172 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1173 state.did_truncate_trailing_white_space = false;
1176 state.may_start_with_whitespace = true;
1177 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed("\u{000A}")));
1178 },
1179 _ => {
1180 let Some(element) = node.as_element() else {
1183 return items;
1184 };
1185 let Some(style_data) = element.style_data() else {
1186 return items;
1187 };
1188
1189 let element_data = style_data.element_data.borrow();
1190 let Some(style) = element_data.styles.get_primary() else {
1191 return items;
1192 };
1193 let inherited_box = style.get_inherited_box();
1194
1195 if inherited_box.visibility != Visibility::Visible {
1196 for child in node.dom_children() {
1200 items.append(&mut rendered_text_collection_steps(child, state));
1201 }
1202 return items;
1203 }
1204
1205 let style_box = style.get_box();
1206 let display = style_box.display;
1207 let mut surrounding_line_breaks = 0;
1208
1209 if style_box.position == Position::Absolute || style_box.float != Float::None {
1211 surrounding_line_breaks = 1;
1212 }
1213
1214 match display {
1217 Display::Table => {
1218 surrounding_line_breaks = 1;
1219 state.within_table = true;
1220 },
1221 Display::TableCell => {
1226 if !state.first_table_cell {
1227 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1228 "\u{0009}", )));
1230 state.did_truncate_trailing_white_space = false;
1232 }
1233 state.first_table_cell = false;
1234 state.within_table_content = true;
1235 },
1236 Display::TableRow => {
1241 if !state.first_table_row {
1242 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1243 "\u{000A}", )));
1245 state.did_truncate_trailing_white_space = false;
1247 }
1248 state.first_table_row = false;
1249 state.first_table_cell = true;
1250 },
1251 Display::Block => {
1254 surrounding_line_breaks = 1;
1255 },
1256 Display::TableCaption => {
1257 surrounding_line_breaks = 1;
1258 state.within_table_content = true;
1259 },
1260 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock
1264 if state.did_truncate_trailing_white_space =>
1265 {
1266 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1267 state.did_truncate_trailing_white_space = false;
1268 state.may_start_with_whitespace = true;
1269 },
1270 _ => {},
1271 }
1272
1273 match node.type_id() {
1274 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1277 surrounding_line_breaks = 2;
1278 },
1279 Some(
1282 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1283 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1284 ) => {
1285 surrounding_line_breaks = 1;
1286 },
1287 _ => {},
1288 }
1289
1290 if surrounding_line_breaks > 0 {
1291 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1292 surrounding_line_breaks,
1293 ));
1294 state.did_truncate_trailing_white_space = false;
1295 state.may_start_with_whitespace = true;
1296 }
1297
1298 match node.type_id() {
1299 Some(
1304 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1305 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1306 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1307 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1308 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1309 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1310 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1311 ) => {
1312 if display != Display::Block && state.did_truncate_trailing_white_space {
1313 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1314 state.did_truncate_trailing_white_space = false;
1315 };
1316 state.may_start_with_whitespace = false;
1317 },
1318 _ => {
1319 for child in node.dom_children() {
1322 items.append(&mut rendered_text_collection_steps(child, state));
1323 }
1324 },
1325 }
1326
1327 match display {
1330 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1331 state.did_truncate_trailing_white_space = false;
1332 state.may_start_with_whitespace = false;
1333 },
1334 Display::Table => {
1335 state.within_table = false;
1336 },
1337 Display::TableCell | Display::TableCaption => {
1338 state.within_table_content = false;
1339 },
1340 _ => {},
1341 }
1342
1343 if surrounding_line_breaks > 0 {
1344 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1345 surrounding_line_breaks,
1346 ));
1347 state.did_truncate_trailing_white_space = false;
1348 state.may_start_with_whitespace = true;
1349 }
1350 },
1351 };
1352 items
1353}
1354
1355pub fn find_character_offset_in_fragment_descendants(
1356 node: &ServoLayoutNode,
1357 stacking_context_tree: &StackingContextTree,
1358 point_in_viewport: Point2D<Au, CSSPixel>,
1359) -> Option<(OpaqueNode, Utf32CodeUnits)> {
1360 let mut search = ClosestFragmentSearch::default();
1361 for fragment in &node.fragments_for_pseudo(None) {
1362 if let Some(point_in_fragment) =
1363 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1364 {
1365 search.collect_relevant_children(fragment, point_in_fragment);
1366 }
1367 }
1368 search.into_dom_position()
1369}
1370
1371pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1372 let containing_block = containing_block_for_node(node);
1373 containing_block.map(|node| node.opaque().into())
1374}
1375
1376pub fn process_containing_block_descendant_query(
1377 possible_ancestor: ServoLayoutNode,
1378 mut possible_descendant: ServoLayoutNode,
1379) -> bool {
1380 while let Some(establishing_node) = containing_block_for_node(possible_descendant) {
1381 if establishing_node == possible_ancestor {
1382 return true;
1383 }
1384 possible_descendant = establishing_node;
1385 }
1386 false
1387}
1388
1389pub fn process_resolved_font_style_query<'dom, E>(
1390 context: &SharedStyleContext,
1391 node: E,
1392 value: &str,
1393 url_data: ServoUrl,
1394 shared_lock: &SharedRwLock,
1395) -> Option<ServoArc<Font>>
1396where
1397 E: LayoutNode<'dom>,
1398{
1399 fn create_font_declaration(
1400 value: &str,
1401 url_data: &ServoUrl,
1402 quirks_mode: QuirksMode,
1403 ) -> Option<PropertyDeclarationBlock> {
1404 let mut declarations = SourcePropertyDeclaration::default();
1405 let result = parse_one_declaration_into(
1406 &mut declarations,
1407 PropertyId::NonCustom(ShorthandId::Font.into()),
1408 value,
1409 Origin::Author,
1410 &UrlExtraData(url_data.get_arc()),
1411 None,
1412 ParsingMode::DEFAULT,
1413 quirks_mode,
1414 CssRuleType::Style,
1415 );
1416 let declarations = match result {
1417 Ok(()) => {
1418 let mut block = PropertyDeclarationBlock::new();
1419 block.extend(declarations.drain(), Importance::Normal);
1420 block
1421 },
1422 Err(_) => return None,
1423 };
1424 Some(declarations)
1426 }
1427 fn resolve_for_declarations<'dom, E>(
1428 context: &SharedStyleContext,
1429 parent_style: Option<&ComputedValues>,
1430 declarations: PropertyDeclarationBlock,
1431 shared_lock: &SharedRwLock,
1432 ) -> ServoArc<ComputedValues>
1433 where
1434 E: LayoutNode<'dom>,
1435 {
1436 let parent_style = match parent_style {
1437 Some(parent) => parent,
1438 None => context.stylist.device().default_computed_values(),
1439 };
1440 context
1441 .stylist
1442 .compute_for_declarations::<DangerousStyleElementOf<'dom, E::ConcreteTypeBundle>>(
1443 &context.guards,
1444 parent_style,
1445 ServoArc::new(shared_lock.wrap(declarations)),
1446 )
1447 }
1448
1449 let quirks_mode = context.quirks_mode();
1452 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1453
1454 let element = node.as_element().unwrap();
1458 let parent_style = if node.is_connected() {
1459 if element.style_data().is_some() {
1460 element.style(context)
1461 } else {
1462 let mut tlc = ThreadLocalStyleContext::new();
1463 let mut context = StyleContext {
1464 shared: context,
1465 thread_local: &mut tlc,
1466 };
1467 #[expect(unsafe_code)]
1468 let styles = resolve_style(
1469 &mut context,
1470 unsafe { element.dangerous_style_element() },
1471 RuleInclusion::All,
1472 None,
1473 None,
1474 );
1475 styles.primary().clone()
1476 }
1477 } else {
1478 let default_declarations =
1479 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1480 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1481 };
1482
1483 let computed_values =
1485 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1486
1487 Some(computed_values.clone_font())
1488}
1489
1490pub(crate) fn transform_au_rectangle(
1491 rect_to_transform: Rect<Au, CSSPixel>,
1492 transform: FastLayoutTransform,
1493) -> Option<Rect<Au, CSSPixel>> {
1494 transform_f32_rectangle(
1495 au_rect_to_f32_rect(rect_to_transform).cast_unit(),
1496 transform,
1497 )
1498 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1499}
1500
1501pub(crate) fn transform_f32_rectangle(
1502 rect_to_transform: Rect<f32, LayoutPixel>,
1503 transform: FastLayoutTransform,
1504) -> Option<Rect<f32, LayoutPixel>> {
1505 match transform {
1506 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1507 FastLayoutTransform::Transform { transform, .. } => {
1508 transform.outer_transformed_rect(&rect_to_transform)
1509 },
1510 }
1511}
1512
1513pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1514 let fragments = node.fragments_for_pseudo(None);
1515 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1516
1517 Some(
1518 box_fragment
1519 .style()
1520 .effective_overflow(box_fragment.base.flags),
1521 )
1522}