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 bitflags::bitflags;
14use embedder_traits::UntrustedNodeAddress;
15use euclid::{Point2D, Rect, Size2D};
16use layout_api::{
17 AxesOverflow, BoxAreaType, CSSPixelRectVec, DangerousStyleElementOf, LayoutElement,
18 LayoutElementType, LayoutNode, LayoutNodeType, OffsetParentResponse, PhysicalSides,
19 ScrollContainerQueryFlags, ScrollContainerResponse,
20};
21use paint_api::display_list::ScrollTree;
22use script::layout_dom::ServoLayoutNode;
23use servo_arc::Arc as ServoArc;
24use servo_base::text::Utf32CodeUnits;
25use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
26use servo_url::ServoUrl;
27use style::computed_values::display::T as Display;
28use style::computed_values::position::T as Position;
29use style::computed_values::visibility::T as Visibility;
30use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
31use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
32use style::dom::{NodeInfo, OpaqueNode};
33use style::properties::style_structs::Font;
34use style::properties::{
35 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
36 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
37};
38use style::selector_parser::PseudoElement;
39use style::shared_lock::SharedRwLock;
40use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
41use style::stylist::RuleInclusion;
42use style::traversal::resolve_style;
43use style::values::computed::transform::Matrix3D;
44use style::values::computed::{Float, Size};
45use style::values::generics::font::LineHeight;
46use style::values::generics::position::AspectRatio;
47use style::values::specified::GenericGridTemplateComponent;
48use style::values::specified::box_::DisplayInside;
49use style_traits::{CSSPixel, ParsingMode, ToCss};
50use webrender_api::units::LayoutPixel;
51
52use crate::cell::RefOrAtomicRef;
53use crate::display_list::{ClosestFragmentSearch, StackingContextTree, au_rect_to_length_rect};
54use crate::dom::NodeExt;
55use crate::flow::inline::text_transform::TextTransformationIterator;
56use crate::fragment_tree::{
57 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
58};
59use crate::layout_impl::LayoutThread;
60use crate::style_ext::ComputedValuesExt;
61use crate::taffy::SpecificTaffyGridInfo;
62
63fn root_transform_for_fragments(
65 scroll_tree: &ScrollTree,
66 fragments: &[Fragment],
67) -> Option<FastLayoutTransform> {
68 let box_fragment = fragments
69 .first()
70 .and_then(Fragment::retrieve_box_fragment)?;
71 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
72 Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
73}
74
75pub(crate) fn process_padding_request(node: ServoLayoutNode<'_>) -> Option<PhysicalSides> {
76 let fragments = node.fragments_for_pseudo(None);
77 let fragment = fragments.first()?;
78 Some(
79 fragment
80 .retrieve_box_fragment()
81 .map(|box_fragment| {
82 let padding = box_fragment.padding;
83 PhysicalSides {
84 top: padding.top,
85 left: padding.left,
86 bottom: padding.bottom,
87 right: padding.right,
88 }
89 })
90 .unwrap_or_default(),
91 )
92}
93
94bitflags! {
95 #[derive(Clone, Copy)]
96 pub(crate) struct BoxAreaInclusion: u8 {
97 const Transforms = 1 << 0;
98 const Inlines = 1 << 1;
99 }
100}
101
102pub(crate) fn process_box_area_request(
103 layout_thread: &LayoutThread,
104 stacking_context_tree: &StackingContextTree,
105 node: ServoLayoutNode<'_>,
106 area: BoxAreaType,
107 inclusion: BoxAreaInclusion,
108) -> Option<Rect<Au, CSSPixel>> {
109 node.with_fragments(|fragments| {
112 let mut rects = fragments
113 .iter()
114 .filter(|fragment| {
115 inclusion.contains(BoxAreaInclusion::Inlines) ||
116 fragment
117 .retrieve_box_fragment()
118 .is_none_or(|fragment| !fragment.with_style().is_inline_box())
119 })
120 .filter_map(|node| node.cumulative_box_area_rect(area, layout_thread.into()))
121 .peekable();
122
123 rects.peek()?;
124 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
125
126 if !inclusion.contains(BoxAreaInclusion::Transforms) {
127 return Some(rect_union);
128 }
129
130 let Some(transform) =
131 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, fragments)
132 else {
133 return Some(Rect::new(rect_union.origin, Size2D::zero()));
134 };
135
136 transform_au_rectangle(rect_union, transform)
137 })?
138}
139
140pub(crate) fn process_box_areas_request(
141 layout_thread: &LayoutThread,
142 stacking_context_tree: &StackingContextTree,
143 node: ServoLayoutNode<'_>,
144 area: BoxAreaType,
145) -> CSSPixelRectVec {
146 let fragments = node.fragments_for_pseudo(None);
147 let transform =
148 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, &fragments);
149
150 let rects = fragments
151 .into_iter()
152 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area, layout_thread.into()));
153
154 let Some(transform) = transform else {
155 return rects
156 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
157 .collect();
158 };
159
160 rects
161 .filter_map(move |rect| transform_au_rectangle(rect, transform))
162 .collect()
163}
164
165pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
166 node.fragments_for_pseudo(None)
167 .first()
168 .map(Fragment::client_rect)
169 .unwrap_or_default()
170}
171
172pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
179 let Some(layout_data) = node.inner_layout_data() else {
180 return 1.0;
181 };
182 let layout_box = layout_data.self_box.borrow();
183 let Some(layout_box) = layout_box.as_ref() else {
184 return 1.0;
185 };
186 layout_box
187 .with_base(|base| base.style.effective_zoom.value())
188 .unwrap_or(1.0)
189}
190
191pub fn process_node_scroll_area_request(
193 layout_thread: &LayoutThread,
194 requested_node: Option<ServoLayoutNode<'_>>,
195 fragment_tree: Option<Rc<FragmentTree>>,
196) -> Rect<i32, CSSPixel> {
197 let Some(tree) = fragment_tree else {
198 return Rect::zero();
199 };
200
201 let rect = match requested_node {
202 Some(node) => node
203 .fragments_for_pseudo(None)
204 .first()
205 .map(|fragment| fragment.scrolling_area(layout_thread))
206 .unwrap_or_default(),
207 None => tree
208 .scrollable_overflow()
209 .union(&tree.initial_containing_block),
210 };
211
212 Rect::new(
213 rect.origin.map(Au::to_f32_px),
214 rect.size.to_vector().map(Au::to_f32_px).to_size(),
215 )
216 .round()
217 .to_i32()
218}
219
220pub fn process_resolved_style_request(
223 layout_thread: &LayoutThread,
224 context: &SharedStyleContext,
225 node: ServoLayoutNode<'_>,
226 pseudo: &Option<PseudoElement>,
227 property: &PropertyId,
228) -> String {
229 if node
230 .as_element()
231 .is_none_or(|element| element.style_data().is_none())
232 {
233 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
234 }
235
236 let layout_element = node.as_element().unwrap();
239 let layout_element = match pseudo {
240 Some(pseudo_element_type) => {
241 match layout_element.with_pseudo(*pseudo_element_type) {
242 Some(layout_element) => layout_element,
243 None => {
244 return String::new();
248 },
249 }
250 },
251 None => layout_element,
252 };
253
254 let style = &*layout_element.style(context);
255 let longhand_id = match *property {
256 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
257 Ok(longhand_id) => longhand_id,
258 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
259 },
260 PropertyId::Custom(ref name) => {
261 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
262 },
263 }
264 .to_physical(style.writing_mode);
265
266 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
268 let transform_list = &style.get_box().transform;
269
270 if transform_list.0.is_empty() {
274 return Ok("none".into());
275 }
276
277 let length_rect = box_fragment
282 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
283 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
284
285 let matrix = Matrix3D::from(transform);
290 if !is_3d {
291 Ok(matrix.into_2d()?.to_css_string())
292 } else {
293 Ok(matrix.to_css_string())
294 }
295 };
296
297 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
298 LonghandId::MinWidth
299 if style.get_min_width() == &Size::Auto &&
300 !should_honor_min_size_auto(fragment, style) =>
301 {
302 String::from("0px")
303 },
304 LonghandId::MinHeight
305 if style.get_min_height() == &Size::Auto &&
306 !should_honor_min_size_auto(fragment, style) =>
307 {
308 String::from("0px")
309 },
310 LonghandId::Transform => match serialize_transform_value(None) {
311 Ok(value) => value,
312 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
313 },
314 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
315 };
316
317 if longhand_id == LonghandId::LineHeight {
326 let font = style.get_font();
327 let font_size = font.font_size.computed_size();
328 return match font.line_height {
329 LineHeight::Normal => computed_style(None),
332 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
333 LineHeight::Length(value) => value.0.to_css_string(),
334 };
335 }
336
337 let display = style.get_box().display;
341 if display.is_none() || display.is_contents() {
342 return computed_style(None);
343 }
344
345 let resolve_for_fragment = |fragment: &Fragment| {
346 if let Some(box_fragment) = fragment.retrieve_box_fragment() &&
347 style.get_box().position != Position::Static
348 {
349 let resolved_insets =
350 || box_fragment.calculate_resolved_insets_if_positioned(layout_thread.into());
351 match longhand_id {
352 LonghandId::Top => return resolved_insets().top.to_css_string(),
353 LonghandId::Right => {
354 return resolved_insets().right.to_css_string();
355 },
356 LonghandId::Bottom => {
357 return resolved_insets().bottom.to_css_string();
358 },
359 LonghandId::Left => {
360 return resolved_insets().left.to_css_string();
361 },
362 LonghandId::Transform => {
363 if let Ok(string) = serialize_transform_value(Some(&box_fragment)) {
366 return string;
367 }
368 },
369 _ => {},
370 }
371 }
372
373 if !matches!(
374 fragment,
375 Fragment::LayoutRoot(..) | Fragment::Box(..) | Fragment::Positioning(..)
376 ) {
377 return computed_style(Some(fragment));
378 }
379
380 let specific_layout_info = fragment
386 .retrieve_box_fragment()
387 .and_then(|box_fragment| box_fragment.specific_layout_info().as_deref().cloned());
388 if display.inside() == DisplayInside::Grid &&
389 let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info &&
390 let Some(value) = resolve_grid_template(&info, style, longhand_id)
391 {
392 return value;
393 }
394
395 let content_rect =
403 LazyCell::new(|| fragment.base().map(|base| base.rect()).unwrap_or_default());
404 let margins = LazyCell::new(|| {
405 fragment
406 .retrieve_box_fragment()
407 .map(|fragment| fragment.margin)
408 .unwrap_or_default()
409 });
410 let padding = LazyCell::new(|| {
411 fragment
412 .retrieve_box_fragment()
413 .map(|fragment| fragment.padding)
414 .unwrap_or_default()
415 });
416 match longhand_id {
417 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
418 content_rect.size.width
419 },
420 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
421 content_rect.size.height
422 },
423 LonghandId::MarginBottom => margins.bottom,
424 LonghandId::MarginTop => margins.top,
425 LonghandId::MarginLeft => margins.left,
426 LonghandId::MarginRight => margins.right,
427 LonghandId::PaddingBottom => padding.bottom,
428 LonghandId::PaddingTop => padding.top,
429 LonghandId::PaddingLeft => padding.left,
430 LonghandId::PaddingRight => padding.right,
431 _ => return computed_style(Some(fragment)),
432 }
433 .to_css_string()
434 };
435
436 node.fragments_for_pseudo(*pseudo)
437 .first()
438 .map(resolve_for_fragment)
439 .unwrap_or_else(|| computed_style(None))
440}
441
442fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
443 match fragment {
446 Fragment::LayoutRoot(layout_root) => {
447 resolved_size_should_be_used_value(&layout_root.inner())
448 },
449 Fragment::Box(box_fragment) => !box_fragment.with_style().is_inline_box(),
450 Fragment::Float(_) |
451 Fragment::Positioning(_) |
452 Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
453 Fragment::Image(_) |
454 Fragment::IFrame(_) => true,
455 Fragment::Text(_) => false,
456 }
457}
458
459fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
460 let Some(box_fragment) = fragment.and_then(|fragment| fragment.retrieve_box_fragment()) else {
469 return false;
470 };
471 let flags = box_fragment.base.flags;
472 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
473 style.get_aspect_ratio() != &AspectRatio::auto()
474}
475
476fn resolve_grid_template(
477 grid_info: &SpecificTaffyGridInfo,
478 style: &ComputedValues,
479 longhand_id: LonghandId,
480) -> Option<String> {
481 let (track_info, computed_value) = match longhand_id {
482 LonghandId::GridTemplateRows => (
483 &grid_info.info.rows,
484 &style.get_position().grid_template_rows,
485 ),
486 LonghandId::GridTemplateColumns => (
487 &grid_info.info.columns,
488 &style.get_position().grid_template_columns,
489 ),
490 _ => return None,
491 };
492
493 match computed_value {
494 GenericGridTemplateComponent::None | GenericGridTemplateComponent::TrackList(_) => {
498 (!track_info.positions.is_empty()).then(|| track_info.to_track_list_string())
499 },
500
501 GenericGridTemplateComponent::Subgrid(_) => None,
509 }
510}
511
512#[expect(unsafe_code)]
513pub fn process_resolved_style_request_for_unstyled_node(
514 context: &SharedStyleContext,
515 node: ServoLayoutNode<'_>,
516 pseudo: &Option<PseudoElement>,
517 property: &PropertyId,
518) -> String {
519 if pseudo.is_some() {
521 return String::new();
522 }
523
524 let mut tlc = ThreadLocalStyleContext::new();
525 let mut context = StyleContext {
526 shared: context,
527 thread_local: &mut tlc,
528 };
529
530 let element = node.as_element().unwrap();
531 let styles = resolve_style(
532 &mut context,
533 unsafe { element.dangerous_style_element() },
534 RuleInclusion::All,
535 pseudo.as_ref(),
536 None,
537 );
538 let style = styles.primary();
539 let longhand_id = match *property {
540 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
541 Ok(longhand_id) => longhand_id,
542 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
543 },
544 PropertyId::Custom(ref name) => {
545 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
546 },
547 };
548
549 match longhand_id {
550 LonghandId::MinWidth if style.get_min_width() == &Size::Auto => String::from("0px"),
553 LonghandId::MinHeight if style.get_min_height() == &Size::Auto => String::from("0px"),
554
555 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
558 }
559}
560
561fn shorthand_to_css_string(
562 id: style::properties::ShorthandId,
563 style: &style::properties::ComputedValues,
564) -> String {
565 use style::values::resolved::Context;
566 let mut block = PropertyDeclarationBlock::new();
567 let mut dest = String::new();
568 for longhand in id.longhands() {
569 block.push(
570 style.computed_or_resolved_declaration(
571 longhand,
572 Some(&mut Context {
573 style,
574 for_property: PropertyId::NonCustom(longhand.into()),
575 current_longhand: None,
576 }),
577 ),
578 Importance::Normal,
579 );
580 }
581 match block.shorthand_to_css(id, &mut dest) {
582 Ok(_) => dest,
583 Err(_) => String::new(),
584 }
585}
586
587struct OffsetParentFragments {
588 parent: Arc<BoxFragment>,
589 grandparent: Option<Fragment>,
590}
591
592impl OffsetParentFragments {
593 fn grandparent_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
594 self.grandparent
595 .as_ref()
596 .and_then(|grandparent| grandparent.retrieve_box_fragment())
597 }
598}
599#[expect(unsafe_code)]
601fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
602 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
608 let flags = fragment.base()?.flags;
609 if flags.intersects(
610 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
611 ) {
612 return None;
613 }
614
615 if fragment
616 .retrieve_box_fragment()
617 .is_some_and(|fragment| fragment.style().get_box().position == Position::Fixed)
618 {
619 return None;
620 }
621
622 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
629 while let Some(parent_node) = maybe_parent_node {
630 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
631
632 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
633 let Some(parent_fragment) = parent_fragment.retrieve_box_fragment() else {
634 continue;
635 };
636
637 let grandparent_fragment =
638 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
639
640 if parent_fragment.style().get_box().position != Position::Static {
641 return Some(OffsetParentFragments {
642 parent: parent_fragment.clone(),
643 grandparent: grandparent_fragment,
644 });
645 }
646
647 let flags = parent_fragment.base.flags;
648 if flags.intersects(
649 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
650 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
651 ) {
652 return Some(OffsetParentFragments {
653 parent: parent_fragment.clone(),
654 grandparent: grandparent_fragment,
655 });
656 }
657 }
658 }
659
660 None
661}
662
663#[inline]
664pub fn process_offset_parent_query(
665 layout_thread: &LayoutThread,
666 scroll_tree: &ScrollTree,
667 node: ServoLayoutNode<'_>,
668) -> Option<OffsetParentResponse> {
669 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
686 let mut border_box =
687 fragment.cumulative_box_area_rect(BoxAreaType::Border, layout_thread.into())?;
688 let cumulative_sticky_offsets = fragment
689 .retrieve_box_fragment()
690 .and_then(|box_fragment| box_fragment.spatial_tree_node())
691 .map(|node_id| {
692 scroll_tree
693 .cumulative_sticky_offsets(node_id)
694 .map(Au::from_f32_px)
695 .cast_unit()
696 });
697 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
698
699 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
704 return Some(OffsetParentResponse {
705 node_address: None,
706 rect: border_box,
707 });
708 };
709
710 let parent_fragment = &offset_parent_fragment.parent;
711 let parent_is_static_body_element = parent_fragment
712 .base
713 .flags
714 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
715 parent_fragment.style().get_box().position == Position::Static;
716
717 let parent_offset_rect = if parent_is_static_body_element {
734 if let Some(grandparent_fragment) = offset_parent_fragment.grandparent_box_fragment() {
735 grandparent_fragment.offset_by_containing_block(
736 &grandparent_fragment.border_rect(),
737 layout_thread.into(),
738 )
739 } else {
740 parent_fragment
741 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
742 }
743 } else {
744 parent_fragment
745 .offset_by_containing_block(&parent_fragment.padding_rect(), layout_thread.into())
746 }
747 .translate(
748 cumulative_sticky_offsets
749 .and_then(|_| parent_fragment.spatial_tree_node())
750 .map(|node_id| {
751 scroll_tree
752 .cumulative_sticky_offsets(node_id)
753 .map(Au::from_f32_px)
754 .cast_unit()
755 })
756 .unwrap_or_default(),
757 );
758
759 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
760
761 Some(OffsetParentResponse {
762 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
763 rect: border_box,
764 })
765}
766
767fn style_and_flags_for_node(
768 node: &ServoLayoutNode,
769) -> Option<(ServoArc<ComputedValues>, FragmentFlags)> {
770 let layout_data = node.inner_layout_data()?;
771 let layout_box = layout_data.self_box.borrow();
772 let layout_box = layout_box.as_ref()?;
773
774 layout_box.with_base(|base| (base.style.clone(), base.base_fragment_info.flags))
775}
776
777fn is_containing_block_for_position(
778 position: Position,
779 ancestor_style: &ServoArc<ComputedValues>,
780 ancestor_flags: FragmentFlags,
781) -> bool {
782 match position {
783 Position::Static | Position::Relative | Position::Sticky => {
784 !ancestor_style.is_inline_box(ancestor_flags)
785 },
786 Position::Absolute => {
787 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
788 },
789 Position::Fixed => {
790 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
791 },
792 }
793}
794
795fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
796 let (style, _flags) = style_and_flags_for_node(&node)?;
797
798 let mut current_position_value = style.get_box().position;
799 let mut current_ancestor = node;
800
801 #[expect(unsafe_code)]
802 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
803 current_ancestor = ancestor;
804
805 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
806 continue;
807 };
808
809 if is_containing_block_for_position(current_position_value, &ancestor_style, ancestor_flags)
810 {
811 return Some(ancestor);
812 }
813
814 current_position_value = ancestor_style.get_box().position;
815 }
816 None
817}
818
819#[inline]
823pub(crate) fn process_scroll_container_query(
824 node: Option<ServoLayoutNode<'_>>,
825 query_flags: ScrollContainerQueryFlags,
826 viewport_overflow: AxesOverflow,
827) -> Option<ScrollContainerResponse> {
828 let Some(node) = node else {
829 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
830 };
831
832 let (style, flags) = style_and_flags_for_node(&node)?;
835
836 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
842 flags.intersects(
843 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
844 )
845 {
846 return None;
847 }
848
849 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
850 style.establishes_scroll_container(flags)
851 {
852 return Some(ScrollContainerResponse::Element(
853 node.opaque().into(),
854 style.effective_overflow(flags),
855 ));
856 }
857
858 let mut current_position_value = style.get_box().position;
878 let mut current_ancestor = node;
879
880 #[expect(unsafe_code)]
881 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
882 current_ancestor = ancestor;
883
884 let Some((ancestor_style, ancestor_flags)) = style_and_flags_for_node(&ancestor) else {
885 continue;
886 };
887
888 if !is_containing_block_for_position(
889 current_position_value,
890 &ancestor_style,
891 ancestor_flags,
892 ) {
893 continue;
894 }
895
896 if ancestor_style.establishes_scroll_container(ancestor_flags) {
897 return Some(ScrollContainerResponse::Element(
898 ancestor.opaque().into(),
899 ancestor_style.effective_overflow(ancestor_flags),
900 ));
901 }
902
903 current_position_value = ancestor_style.get_box().position;
904 }
905
906 match current_position_value {
907 Position::Fixed => None,
908 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
909 }
910}
911
912pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
914 let mut results = Vec::new();
920 let mut max_req_line_break_count = 0;
921
922 let mut state = Default::default();
924 for child in node.dom_children() {
925 let mut current = rendered_text_collection_steps(child, &mut state);
927 results.append(&mut current);
929 }
930
931 let mut output = String::new();
932 for item in results {
933 match item {
934 InnerOrOuterTextItem::Text(s) => {
935 if !s.is_empty() {
937 if max_req_line_break_count > 0 {
938 output.extend(std::iter::repeat_n('\u{000A}', max_req_line_break_count));
940 max_req_line_break_count = 0;
941 }
942 output.push_str(&s);
943 }
944 },
945 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
946 if output.is_empty() {
948 continue;
950 }
951 if count > max_req_line_break_count {
955 max_req_line_break_count = count;
956 }
957 },
958 }
959 }
960 output
961}
962
963enum InnerOrOuterTextItem {
964 Text(Cow<'static, str>),
965 RequiredLineBreakCount(usize),
966}
967
968#[derive(Clone)]
969struct RenderedTextCollectionState {
970 first_table_row: bool,
972 first_table_cell: bool,
974 within_table: bool,
977 may_start_with_whitespace: bool,
979 did_truncate_trailing_white_space: bool,
982 within_table_content: bool,
985}
986
987impl Default for RenderedTextCollectionState {
988 fn default() -> Self {
989 RenderedTextCollectionState {
990 first_table_row: true,
991 first_table_cell: true,
992 may_start_with_whitespace: true,
993 did_truncate_trailing_white_space: false,
994 within_table: false,
995 within_table_content: false,
996 }
997 }
998}
999
1000#[expect(unsafe_code)]
1002fn rendered_text_collection_steps(
1003 node: ServoLayoutNode<'_>,
1004 state: &mut RenderedTextCollectionState,
1005) -> Vec<InnerOrOuterTextItem> {
1006 let mut items = vec![];
1010 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
1011 return items;
1012 }
1013
1014 match node.type_id() {
1015 Some(LayoutNodeType::Text) => {
1016 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
1017 match parent_node.type_id() {
1018 Some(
1020 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1021 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1022 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1023 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1024 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1025 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1026 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1027 ) => {
1028 return items;
1029 },
1030 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1034 if let Some(grandparent_node) =
1035 unsafe { parent_node.dangerous_dom_parent() }
1036 {
1037 if !matches!(
1038 grandparent_node.type_id(),
1039 Some(LayoutNodeType::Element(
1040 LayoutElementType::HTMLSelectElement
1041 ))
1042 ) {
1043 return items;
1044 }
1045 } else {
1046 return items;
1047 }
1048 },
1049 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1050 return items;
1051 },
1052 _ => {},
1053 }
1054
1055 if state.within_table && !state.within_table_content {
1059 return items;
1060 }
1061
1062 let Some(parent_element) = parent_node.as_element() else {
1063 return items;
1064 };
1065 let Some(style_data) = parent_element.style_data() else {
1066 return items;
1067 };
1068
1069 let element_data = style_data.element_data.borrow();
1070 let Some(style) = element_data.styles.get_primary() else {
1071 return items;
1072 };
1073
1074 if style.get_inherited_box().visibility != Visibility::Visible {
1080 return items;
1081 }
1082
1083 let display = style.get_box().display;
1087 if display == Display::None {
1088 match parent_element.type_id() {
1089 Some(
1092 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1093 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1094 ) => {},
1095 _ => {
1096 return items;
1097 },
1098 }
1099 }
1100
1101 let text_content = node.text_content();
1102
1103 let white_space_collapse = style.get_white_space_collapse();
1104 let preserve_whitespace =
1105 white_space_collapse == &WhiteSpaceCollapseValue::Preserve;
1106 let is_inline = matches!(
1107 display,
1108 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1109 );
1110
1111 let trim_leading_white_space =
1115 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1116 let on_word_boundary = true;
1119
1120 let mut transformed_text = String::with_capacity(text_content.len());
1128 for iteration in TextTransformationIterator::new(
1129 &text_content,
1130 style,
1131 trim_leading_white_space,
1132 on_word_boundary,
1133 ) {
1134 transformed_text.extend(iteration.characters());
1135 }
1136
1137 let is_preformatted_element =
1138 white_space_collapse == &WhiteSpaceCollapseValue::Preserve;
1139
1140 let is_final_character_whitespace = transformed_text
1141 .chars()
1142 .next_back()
1143 .filter(char::is_ascii_whitespace)
1144 .is_some();
1145
1146 let is_first_character_whitespace = transformed_text
1147 .chars()
1148 .next()
1149 .filter(char::is_ascii_whitespace)
1150 .is_some();
1151
1152 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1156 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1157 };
1158
1159 if !transformed_text.is_empty() {
1160 if is_final_character_whitespace && !is_preformatted_element {
1163 state.may_start_with_whitespace = false;
1164 state.did_truncate_trailing_white_space = true;
1165 transformed_text.pop();
1166 } else {
1167 state.may_start_with_whitespace = is_final_character_whitespace;
1168 state.did_truncate_trailing_white_space = false;
1169 }
1170 items.push(InnerOrOuterTextItem::Text(Cow::Owned(transformed_text)));
1171 }
1172 } else {
1173 items.push(InnerOrOuterTextItem::Text(Cow::Owned(
1176 node.text_content().deref().into(),
1177 )));
1178 }
1179 },
1180 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1181 state.did_truncate_trailing_white_space = false;
1184 state.may_start_with_whitespace = true;
1185 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed("\u{000A}")));
1186 },
1187 _ => {
1188 let Some(element) = node.as_element() else {
1191 return items;
1192 };
1193 let Some(style_data) = element.style_data() else {
1194 return items;
1195 };
1196
1197 let element_data = style_data.element_data.borrow();
1198 let Some(style) = element_data.styles.get_primary() else {
1199 return items;
1200 };
1201 let inherited_box = style.get_inherited_box();
1202
1203 if inherited_box.visibility != Visibility::Visible {
1204 for child in node.dom_children() {
1208 items.append(&mut rendered_text_collection_steps(child, state));
1209 }
1210 return items;
1211 }
1212
1213 let style_box = style.get_box();
1214 let display = style_box.display;
1215 let mut surrounding_line_breaks = 0;
1216
1217 if style_box.position == Position::Absolute || style_box.float != Float::None {
1219 surrounding_line_breaks = 1;
1220 }
1221
1222 match display {
1225 Display::Table => {
1226 surrounding_line_breaks = 1;
1227 state.within_table = true;
1228 },
1229 Display::TableCell => {
1234 if !state.first_table_cell {
1235 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1236 "\u{0009}", )));
1238 state.did_truncate_trailing_white_space = false;
1240 }
1241 state.first_table_cell = false;
1242 state.within_table_content = true;
1243 },
1244 Display::TableRow => {
1249 if !state.first_table_row {
1250 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1251 "\u{000A}", )));
1253 state.did_truncate_trailing_white_space = false;
1255 }
1256 state.first_table_row = false;
1257 state.first_table_cell = true;
1258 },
1259 Display::Block => {
1262 surrounding_line_breaks = 1;
1263 },
1264 Display::TableCaption => {
1265 surrounding_line_breaks = 1;
1266 state.within_table_content = true;
1267 },
1268 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock
1272 if state.did_truncate_trailing_white_space =>
1273 {
1274 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1275 state.did_truncate_trailing_white_space = false;
1276 state.may_start_with_whitespace = true;
1277 },
1278 _ => {},
1279 }
1280
1281 match node.type_id() {
1282 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1285 surrounding_line_breaks = 2;
1286 },
1287 Some(
1290 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1291 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1292 ) => {
1293 surrounding_line_breaks = 1;
1294 },
1295 _ => {},
1296 }
1297
1298 if surrounding_line_breaks > 0 {
1299 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1300 surrounding_line_breaks,
1301 ));
1302 state.did_truncate_trailing_white_space = false;
1303 state.may_start_with_whitespace = true;
1304 }
1305
1306 match node.type_id() {
1307 Some(
1312 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1313 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1314 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1315 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1316 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1317 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1318 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1319 ) => {
1320 if display != Display::Block && state.did_truncate_trailing_white_space {
1321 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1322 state.did_truncate_trailing_white_space = false;
1323 };
1324 state.may_start_with_whitespace = false;
1325 },
1326 _ => {
1327 for child in node.dom_children() {
1330 items.append(&mut rendered_text_collection_steps(child, state));
1331 }
1332 },
1333 }
1334
1335 match display {
1338 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1339 state.did_truncate_trailing_white_space = false;
1340 state.may_start_with_whitespace = false;
1341 },
1342 Display::Table => {
1343 state.within_table = false;
1344 },
1345 Display::TableCell | Display::TableCaption => {
1346 state.within_table_content = false;
1347 },
1348 _ => {},
1349 }
1350
1351 if surrounding_line_breaks > 0 {
1352 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1353 surrounding_line_breaks,
1354 ));
1355 state.did_truncate_trailing_white_space = false;
1356 state.may_start_with_whitespace = true;
1357 }
1358 },
1359 };
1360 items
1361}
1362
1363pub fn find_character_offset_in_fragment_descendants(
1364 node: &ServoLayoutNode,
1365 stacking_context_tree: &StackingContextTree,
1366 point_in_viewport: Point2D<Au, CSSPixel>,
1367) -> Option<(OpaqueNode, Utf32CodeUnits)> {
1368 let mut search = ClosestFragmentSearch::default();
1369 for fragment in &node.fragments_for_pseudo(None) {
1370 if let Some(point_in_fragment) =
1371 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1372 {
1373 search.collect_relevant_children(fragment, point_in_fragment);
1374 }
1375 }
1376 search.into_dom_position()
1377}
1378
1379pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1380 let containing_block = containing_block_for_node(node);
1381 containing_block.map(|node| node.opaque().into())
1382}
1383
1384pub fn process_containing_block_descendant_query(
1385 possible_ancestor: ServoLayoutNode,
1386 mut possible_descendant: ServoLayoutNode,
1387) -> bool {
1388 while let Some(establishing_node) = containing_block_for_node(possible_descendant) {
1389 if establishing_node == possible_ancestor {
1390 return true;
1391 }
1392 possible_descendant = establishing_node;
1393 }
1394 false
1395}
1396
1397pub fn process_resolved_font_style_query<'dom, E>(
1398 context: &SharedStyleContext,
1399 node: E,
1400 value: &str,
1401 url_data: ServoUrl,
1402 shared_lock: &SharedRwLock,
1403) -> Option<ServoArc<Font>>
1404where
1405 E: LayoutNode<'dom>,
1406{
1407 fn create_font_declaration(
1408 value: &str,
1409 url_data: &ServoUrl,
1410 quirks_mode: QuirksMode,
1411 ) -> Option<PropertyDeclarationBlock> {
1412 let mut declarations = SourcePropertyDeclaration::default();
1413 let result = parse_one_declaration_into(
1414 &mut declarations,
1415 PropertyId::NonCustom(ShorthandId::Font.into()),
1416 value,
1417 Origin::Author,
1418 &UrlExtraData(url_data.get_arc()),
1419 None,
1420 ParsingMode::DEFAULT,
1421 quirks_mode,
1422 CssRuleType::Style,
1423 );
1424 let declarations = match result {
1425 Ok(()) => {
1426 let mut block = PropertyDeclarationBlock::new();
1427 block.extend(declarations.drain(), Importance::Normal);
1428 block
1429 },
1430 Err(_) => return None,
1431 };
1432 Some(declarations)
1434 }
1435 fn resolve_for_declarations<'dom, E>(
1436 context: &SharedStyleContext,
1437 parent_style: Option<&ComputedValues>,
1438 declarations: PropertyDeclarationBlock,
1439 shared_lock: &SharedRwLock,
1440 ) -> ServoArc<ComputedValues>
1441 where
1442 E: LayoutNode<'dom>,
1443 {
1444 let parent_style = match parent_style {
1445 Some(parent) => parent,
1446 None => context.stylist.device().default_computed_values(),
1447 };
1448 context
1449 .stylist
1450 .compute_for_declarations::<DangerousStyleElementOf<'dom, E::ConcreteTypeBundle>>(
1451 &context.guards,
1452 parent_style,
1453 ServoArc::new(shared_lock.wrap(declarations)),
1454 )
1455 }
1456
1457 let quirks_mode = context.quirks_mode();
1460 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1461
1462 let element = node.as_element().unwrap();
1466 let parent_style = if node.is_connected() {
1467 if element.style_data().is_some() {
1468 element.style(context)
1469 } else {
1470 let mut tlc = ThreadLocalStyleContext::new();
1471 let mut context = StyleContext {
1472 shared: context,
1473 thread_local: &mut tlc,
1474 };
1475 #[expect(unsafe_code)]
1476 let styles = resolve_style(
1477 &mut context,
1478 unsafe { element.dangerous_style_element() },
1479 RuleInclusion::All,
1480 None,
1481 None,
1482 );
1483 styles.primary().clone()
1484 }
1485 } else {
1486 let default_declarations =
1487 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1488 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1489 };
1490
1491 let computed_values =
1493 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1494
1495 Some(computed_values.clone_font())
1496}
1497
1498pub(crate) fn transform_au_rectangle(
1499 rect_to_transform: Rect<Au, CSSPixel>,
1500 transform: FastLayoutTransform,
1501) -> Option<Rect<Au, CSSPixel>> {
1502 transform_f32_rectangle(
1503 au_rect_to_f32_rect(rect_to_transform).cast_unit(),
1504 transform,
1505 )
1506 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1507}
1508
1509pub(crate) fn transform_f32_rectangle(
1510 rect_to_transform: Rect<f32, LayoutPixel>,
1511 transform: FastLayoutTransform,
1512) -> Option<Rect<f32, LayoutPixel>> {
1513 match transform {
1514 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1515 FastLayoutTransform::Transform { transform, .. } => {
1516 transform.outer_transformed_rect(&rect_to_transform)
1517 },
1518 }
1519}
1520
1521pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1522 let fragments = node.fragments_for_pseudo(None);
1523 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1524
1525 Some(
1526 box_fragment
1527 .style()
1528 .effective_overflow(box_fragment.base.flags),
1529 )
1530}