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, ScrollContainerQueryFlags,
19 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::Zero;
28use style::computed_values::display::T as Display;
29use style::computed_values::position::T as Position;
30use style::computed_values::visibility::T as Visibility;
31use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
32use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
33use style::dom::{NodeInfo, OpaqueNode};
34use style::properties::style_structs::Font;
35use style::properties::{
36 ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
37 PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into,
38};
39use style::selector_parser::PseudoElement;
40use style::shared_lock::SharedRwLock;
41use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
42use style::stylist::RuleInclusion;
43use style::traversal::resolve_style;
44use style::values::computed::transform::Matrix3D;
45use style::values::computed::{Float, Size};
46use style::values::generics::font::LineHeight;
47use style::values::generics::position::AspectRatio;
48use style::values::specified::GenericGridTemplateComponent;
49use style::values::specified::box_::DisplayInside;
50use style_traits::{CSSPixel, ParsingMode, ToCss};
51use webrender_api::units::LayoutPixel;
52
53use crate::cell::RefOrAtomicRef;
54use crate::display_list::{ClosestFragmentSearch, StackingContextTree, au_rect_to_length_rect};
55use crate::dom::NodeExt;
56use crate::flow::inline::text_transform::TextTransformationIterator;
57use crate::fragment_tree::{
58 BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
59};
60use crate::geom::{AuOrAuto, LengthPercentageOrAuto, PhysicalRect, PhysicalSides};
61use crate::layout_impl::LayoutThread;
62use crate::style_ext::ComputedValuesExt;
63use crate::taffy::SpecificTaffyGridInfo;
64
65fn root_transform_for_fragments(
67 scroll_tree: &ScrollTree,
68 fragments: &[Fragment],
69) -> Option<FastLayoutTransform> {
70 let box_fragment = fragments
71 .first()
72 .and_then(Fragment::retrieve_box_fragment)?;
73 let scroll_tree_node_id = box_fragment.spatial_tree_node()?;
74 Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
75}
76
77pub(crate) fn process_padding_request(node: ServoLayoutNode<'_>) -> Option<PhysicalSides<Au>> {
78 let fragments = node.fragments_for_pseudo(None);
79 let fragment = fragments.first()?;
80 Some(
81 fragment
82 .retrieve_box_fragment()
83 .map(|box_fragment| box_fragment.padding)
84 .unwrap_or_default(),
85 )
86}
87
88bitflags! {
89 #[derive(Clone, Copy)]
90 pub(crate) struct BoxAreaInclusion: u8 {
91 const Transforms = 1 << 0;
92 const Inlines = 1 << 1;
93 }
94}
95
96pub(crate) fn process_box_area_request(
97 layout_thread: &LayoutThread,
98 stacking_context_tree: &StackingContextTree,
99 node: ServoLayoutNode<'_>,
100 area: BoxAreaType,
101 inclusion: BoxAreaInclusion,
102) -> Option<Rect<Au, CSSPixel>> {
103 node.with_fragments(|fragments| {
106 let mut rects = fragments
107 .iter()
108 .filter(|fragment| {
109 inclusion.contains(BoxAreaInclusion::Inlines) ||
110 fragment
111 .retrieve_box_fragment()
112 .is_none_or(|fragment| !fragment.with_style().is_inline_box())
113 })
114 .filter_map(|node| node.cumulative_box_area_rect(area, layout_thread.into()))
115 .peekable();
116
117 rects.peek()?;
118 let rect_union = rects.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect));
119
120 if !inclusion.contains(BoxAreaInclusion::Transforms) {
121 return Some(rect_union);
122 }
123
124 let Some(transform) =
125 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, fragments)
126 else {
127 return Some(Rect::new(rect_union.origin, Size2D::zero()));
128 };
129
130 transform_au_rectangle(rect_union, transform)
131 })?
132}
133
134pub(crate) fn process_box_areas_request(
135 layout_thread: &LayoutThread,
136 stacking_context_tree: &StackingContextTree,
137 node: ServoLayoutNode<'_>,
138 area: BoxAreaType,
139) -> CSSPixelRectVec {
140 let fragments = node.fragments_for_pseudo(None);
141 let transform =
142 root_transform_for_fragments(&stacking_context_tree.paint_info.scroll_tree, &fragments);
143
144 let rects = fragments
145 .into_iter()
146 .filter_map(move |fragment| fragment.cumulative_box_area_rect(area, layout_thread.into()));
147
148 let Some(transform) = transform else {
149 return rects
150 .map(|rect| Rect::new(rect.origin, Size2D::zero()))
151 .collect();
152 };
153
154 rects
155 .filter_map(move |rect| transform_au_rectangle(rect, transform))
156 .collect()
157}
158
159pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
160 node.fragments_for_pseudo(None)
161 .first()
162 .map(Fragment::client_rect)
163 .unwrap_or_default()
164}
165
166pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
173 let Some(layout_data) = node.inner_layout_data() else {
174 return 1.0;
175 };
176 let layout_box = layout_data.self_box.borrow();
177 let Some(layout_box) = layout_box.as_ref() else {
178 return 1.0;
179 };
180 layout_box
181 .with_base(|base| base.style.effective_zoom.value())
182 .unwrap_or(1.0)
183}
184
185pub fn process_node_scroll_area_request(
187 layout_thread: &LayoutThread,
188 requested_node: Option<ServoLayoutNode<'_>>,
189 fragment_tree: Option<Rc<FragmentTree>>,
190) -> Rect<i32, CSSPixel> {
191 let Some(tree) = fragment_tree else {
192 return Rect::zero();
193 };
194
195 let rect = match requested_node {
196 Some(node) => node
197 .fragments_for_pseudo(None)
198 .first()
199 .map(|fragment| fragment.scrolling_area(layout_thread))
200 .unwrap_or_default(),
201 None => tree
202 .scrollable_overflow()
203 .union(&tree.initial_containing_block),
204 };
205
206 Rect::new(
207 rect.origin.map(Au::to_f32_px),
208 rect.size.to_vector().map(Au::to_f32_px).to_size(),
209 )
210 .round()
211 .to_i32()
212}
213
214pub fn process_resolved_style_request(
217 context: &SharedStyleContext,
218 initial_containing_block: Option<&PhysicalRect<Au>>,
219 node: ServoLayoutNode<'_>,
220 pseudo: &Option<PseudoElement>,
221 property: &PropertyId,
222) -> String {
223 if node
224 .as_element()
225 .is_none_or(|element| element.style_data().is_none())
226 {
227 return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
228 }
229
230 let layout_element = node.as_element().unwrap();
233 let layout_element = match pseudo {
234 Some(pseudo_element_type) => {
235 match layout_element.with_pseudo(*pseudo_element_type) {
236 Some(layout_element) => layout_element,
237 None => {
238 return String::new();
242 },
243 }
244 },
245 None => layout_element,
246 };
247 let node = layout_element.as_node();
248
249 let style = &*layout_element.style(context);
250 let longhand_id = match *property {
251 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
252 Ok(longhand_id) => longhand_id,
253 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
254 },
255 PropertyId::Custom(ref name) => {
256 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
257 },
258 }
259 .to_physical(style.writing_mode);
260
261 let serialize_transform_value = |box_fragment: Option<&BoxFragment>| -> Result<String, ()> {
263 let transform_list = &style.get_box().transform;
264
265 if transform_list.0.is_empty() {
269 return Ok("none".into());
270 }
271
272 let length_rect = box_fragment
277 .map(|box_fragment| au_rect_to_length_rect(&box_fragment.border_rect()).to_untyped());
278 let (transform, is_3d) = transform_list.to_transform_3d_matrix(length_rect.as_ref())?;
279
280 let matrix = Matrix3D::from(transform);
285 if !is_3d {
286 Ok(matrix.into_2d()?.to_css_string())
287 } else {
288 Ok(matrix.to_css_string())
289 }
290 };
291
292 let computed_style = |fragment: Option<&Fragment>| match longhand_id {
293 LonghandId::MinWidth
294 if style.get_min_width() == &Size::Auto &&
295 !should_honor_min_size_auto(fragment, style) =>
296 {
297 String::from("0px")
298 },
299 LonghandId::MinHeight
300 if style.get_min_height() == &Size::Auto &&
301 !should_honor_min_size_auto(fragment, style) =>
302 {
303 String::from("0px")
304 },
305 LonghandId::Transform => match serialize_transform_value(None) {
306 Ok(value) => value,
307 Err(..) => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
308 },
309 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
310 };
311
312 if longhand_id == LonghandId::LineHeight {
321 let font = style.get_font();
322 let font_size = font.font_size.computed_size();
323 return match font.line_height {
324 LineHeight::Normal => computed_style(None),
327 LineHeight::Number(value) => (font_size * value.0).to_css_string(),
328 LineHeight::Length(value) => value.0.to_css_string(),
329 };
330 }
331
332 let display = style.get_box().display;
336 if display.is_none() || display.is_contents() {
337 return computed_style(None);
338 }
339
340 let resolve_for_fragment = |fragment: &Fragment| {
341 if let Some(box_fragment) = fragment.retrieve_box_fragment() &&
342 let Some(initial_containing_block) = initial_containing_block &&
343 matches!(
344 longhand_id,
345 LonghandId::Top | LonghandId::Right | LonghandId::Bottom | LonghandId::Left,
346 ) &&
347 let Some(insets) = box_fragment.resolved_insets_for_non_static(
348 node,
349 style,
350 initial_containing_block,
351 )
352 {
353 match longhand_id {
354 LonghandId::Top => return insets.top.to_css_string(),
355 LonghandId::Right => return insets.right.to_css_string(),
356 LonghandId::Bottom => return insets.bottom.to_css_string(),
357 LonghandId::Left => return insets.left.to_css_string(),
358 _ => unreachable!("Should be impossible by matches! above"),
359 }
360 }
361
362 if let Some(box_fragment) = fragment.retrieve_box_fragment() &&
365 longhand_id == LonghandId::Transform &&
366 let Ok(string) = serialize_transform_value(Some(&box_fragment))
367 {
368 return string;
369 }
370
371 if !matches!(
372 fragment,
373 Fragment::LayoutRoot(..) | Fragment::Box(..) | Fragment::Positioning(..)
374 ) {
375 return computed_style(Some(fragment));
376 }
377
378 let specific_layout_info = fragment
384 .retrieve_box_fragment()
385 .and_then(|box_fragment| box_fragment.specific_layout_info().as_deref().cloned());
386 if display.inside() == DisplayInside::Grid &&
387 let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info &&
388 let Some(value) = resolve_grid_template(&info, style, longhand_id)
389 {
390 return value;
391 }
392
393 let content_rect =
401 LazyCell::new(|| fragment.base().map(|base| base.rect()).unwrap_or_default());
402 let margins = LazyCell::new(|| {
403 fragment
404 .retrieve_box_fragment()
405 .map(|fragment| fragment.margin)
406 .unwrap_or_default()
407 });
408 let padding = LazyCell::new(|| {
409 fragment
410 .retrieve_box_fragment()
411 .map(|fragment| fragment.padding)
412 .unwrap_or_default()
413 });
414 match longhand_id {
415 LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
416 content_rect.size.width
417 },
418 LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
419 content_rect.size.height
420 },
421 LonghandId::MarginBottom => margins.bottom,
422 LonghandId::MarginTop => margins.top,
423 LonghandId::MarginLeft => margins.left,
424 LonghandId::MarginRight => margins.right,
425 LonghandId::PaddingBottom => padding.bottom,
426 LonghandId::PaddingTop => padding.top,
427 LonghandId::PaddingLeft => padding.left,
428 LonghandId::PaddingRight => padding.right,
429 _ => return computed_style(Some(fragment)),
430 }
431 .to_css_string()
432 };
433
434 node.fragments_for_pseudo(*pseudo)
435 .first()
436 .map(resolve_for_fragment)
437 .unwrap_or_else(|| computed_style(None))
438}
439
440fn resolved_size_should_be_used_value(fragment: &Fragment) -> bool {
441 match fragment {
444 Fragment::LayoutRoot(layout_root) => {
445 resolved_size_should_be_used_value(&layout_root.inner())
446 },
447 Fragment::Box(box_fragment) => !box_fragment.with_style().is_inline_box(),
448 Fragment::Float(_) |
449 Fragment::Positioning(_) |
450 Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
451 Fragment::Image(_) |
452 Fragment::IFrame(_) => true,
453 Fragment::Text(_) => false,
454 }
455}
456
457fn should_honor_min_size_auto(fragment: Option<&Fragment>, style: &ComputedValues) -> bool {
458 let Some(box_fragment) = fragment.and_then(|fragment| fragment.retrieve_box_fragment()) else {
467 return false;
468 };
469 let flags = box_fragment.base.flags;
470 flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) ||
471 style.get_aspect_ratio() != &AspectRatio::auto()
472}
473
474fn resolve_grid_template(
475 grid_info: &SpecificTaffyGridInfo,
476 style: &ComputedValues,
477 longhand_id: LonghandId,
478) -> Option<String> {
479 let (track_info, computed_value) = match longhand_id {
480 LonghandId::GridTemplateRows => (
481 &grid_info.info.rows,
482 &style.get_position().grid_template_rows,
483 ),
484 LonghandId::GridTemplateColumns => (
485 &grid_info.info.columns,
486 &style.get_position().grid_template_columns,
487 ),
488 _ => return None,
489 };
490
491 match computed_value {
492 GenericGridTemplateComponent::None | GenericGridTemplateComponent::TrackList(_) => {
496 (!track_info.positions.is_empty()).then(|| track_info.to_track_list_string(1.0))
497 },
498
499 GenericGridTemplateComponent::Subgrid(_) => None,
507 }
508}
509
510#[expect(unsafe_code)]
511pub fn process_resolved_style_request_for_unstyled_node(
512 context: &SharedStyleContext,
513 node: ServoLayoutNode<'_>,
514 pseudo: &Option<PseudoElement>,
515 property: &PropertyId,
516) -> String {
517 if pseudo.is_some() {
519 return String::new();
520 }
521
522 let mut tlc = ThreadLocalStyleContext::new();
523 let mut context = StyleContext {
524 shared: context,
525 thread_local: &mut tlc,
526 };
527
528 let element = node.as_element().unwrap();
529 let styles = resolve_style(
530 &mut context,
531 unsafe { element.dangerous_style_element() },
532 RuleInclusion::All,
533 pseudo.as_ref(),
534 None,
535 );
536 let style = styles.primary();
537 let longhand_id = match *property {
538 PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
539 Ok(longhand_id) => longhand_id,
540 Err(shorthand_id) => return shorthand_to_css_string(shorthand_id, style),
541 },
542 PropertyId::Custom(ref name) => {
543 return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
544 },
545 };
546
547 match longhand_id {
548 LonghandId::MinWidth if style.get_min_width() == &Size::Auto => String::from("0px"),
551 LonghandId::MinHeight if style.get_min_height() == &Size::Auto => String::from("0px"),
552
553 _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
556 }
557}
558
559fn shorthand_to_css_string(
560 id: style::properties::ShorthandId,
561 style: &style::properties::ComputedValues,
562) -> String {
563 use style::values::resolved::Context;
564 let mut block = PropertyDeclarationBlock::new();
565 let mut dest = String::new();
566 for longhand in id.longhands() {
567 block.push(
568 style.computed_or_resolved_declaration(
569 longhand,
570 Some(&mut Context {
571 style,
572 for_property: PropertyId::NonCustom(longhand.into()),
573 current_longhand: None,
574 }),
575 ),
576 Importance::Normal,
577 );
578 }
579 match block.shorthand_to_css(id, &mut dest) {
580 Ok(_) => dest,
581 Err(_) => String::new(),
582 }
583}
584
585struct OffsetParentFragments {
586 parent: Arc<BoxFragment>,
587 grandparent: Option<Fragment>,
588}
589
590impl OffsetParentFragments {
591 fn grandparent_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
592 self.grandparent
593 .as_ref()
594 .and_then(|grandparent| grandparent.retrieve_box_fragment())
595 }
596}
597#[expect(unsafe_code)]
599fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
600 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
606 let flags = fragment.base()?.flags;
607 if flags.intersects(
608 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
609 ) {
610 return None;
611 }
612
613 if fragment
614 .retrieve_box_fragment()
615 .is_some_and(|fragment| fragment.style().get_box().position == Position::Fixed)
616 {
617 return None;
618 }
619
620 let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
627 while let Some(parent_node) = maybe_parent_node {
628 maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
629
630 if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
631 let Some(parent_fragment) = parent_fragment.retrieve_box_fragment() else {
632 continue;
633 };
634
635 let grandparent_fragment =
636 maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
637
638 if parent_fragment.style().get_box().position != Position::Static {
639 return Some(OffsetParentFragments {
640 parent: parent_fragment.clone(),
641 grandparent: grandparent_fragment,
642 });
643 }
644
645 let flags = parent_fragment.base.flags;
646 if flags.intersects(
647 FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
648 FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
649 ) {
650 return Some(OffsetParentFragments {
651 parent: parent_fragment.clone(),
652 grandparent: grandparent_fragment,
653 });
654 }
655 }
656 }
657
658 None
659}
660
661#[inline]
662pub fn process_offset_parent_query(
663 layout_thread: &LayoutThread,
664 scroll_tree: &ScrollTree,
665 node: ServoLayoutNode<'_>,
666) -> Option<OffsetParentResponse> {
667 let fragment = node.fragments_for_pseudo(None).first().cloned()?;
684 let mut border_box =
685 fragment.cumulative_box_area_rect(BoxAreaType::Border, layout_thread.into())?;
686 let cumulative_sticky_offsets = fragment
687 .retrieve_box_fragment()
688 .and_then(|box_fragment| box_fragment.spatial_tree_node())
689 .map(|node_id| {
690 scroll_tree
691 .cumulative_sticky_offsets(node_id)
692 .map(Au::from_f32_px)
693 .cast_unit()
694 });
695 border_box = border_box.translate(cumulative_sticky_offsets.unwrap_or_default());
696
697 let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
702 return Some(OffsetParentResponse {
703 node_address: None,
704 rect: border_box,
705 });
706 };
707
708 let parent_fragment = &offset_parent_fragment.parent;
709 let parent_is_static_body_element = parent_fragment
710 .base
711 .flags
712 .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
713 parent_fragment.style().get_box().position == Position::Static;
714
715 let parent_offset_rect = if parent_is_static_body_element {
732 if let Some(grandparent_fragment) = offset_parent_fragment.grandparent_box_fragment() {
733 grandparent_fragment.offset_by_cumulative_content_offset(
734 &grandparent_fragment.border_rect(),
735 layout_thread.into(),
736 )
737 } else {
738 parent_fragment.offset_by_cumulative_content_offset(
739 &parent_fragment.padding_rect(),
740 layout_thread.into(),
741 )
742 }
743 } else {
744 parent_fragment.offset_by_cumulative_content_offset(
745 &parent_fragment.padding_rect(),
746 layout_thread.into(),
747 )
748 }
749 .translate(
750 cumulative_sticky_offsets
751 .and_then(|_| parent_fragment.spatial_tree_node())
752 .map(|node_id| {
753 scroll_tree
754 .cumulative_sticky_offsets(node_id)
755 .map(Au::from_f32_px)
756 .cast_unit()
757 })
758 .unwrap_or_default(),
759 );
760
761 border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
762
763 Some(OffsetParentResponse {
764 node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
765 rect: border_box,
766 })
767}
768
769fn is_containing_block_for_position(
770 position: Position,
771 ancestor_style: &ServoArc<ComputedValues>,
772 ancestor_flags: FragmentFlags,
773) -> bool {
774 match position {
775 Position::Static | Position::Relative | Position::Sticky => {
776 !ancestor_style.is_inline_box(ancestor_flags)
777 },
778 Position::Absolute => {
779 ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
780 },
781 Position::Fixed => {
782 ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
783 },
784 }
785}
786
787fn containing_block_for_node<'a>(node: ServoLayoutNode<'a>) -> Option<ServoLayoutNode<'a>> {
788 let node_style = node.with_layout_box_base(|base| base.style.clone())?;
789 let position = node_style.get_box().position;
790 let mut current_ancestor = node;
791
792 #[expect(unsafe_code)]
793 while let Some(ancestor) =
794 unsafe { current_ancestor.dangerous_flat_tree_parent_considering_pseudos() }
795 {
796 current_ancestor = ancestor;
797
798 let Some((ancestor_style, ancestor_flags)) = ancestor
799 .with_layout_box_base(|base| (base.style.clone(), base.base_fragment_info.flags))
800 else {
801 continue;
802 };
803
804 if is_containing_block_for_position(position, &ancestor_style, ancestor_flags) {
805 return Some(ancestor);
806 }
807 }
808 None
809}
810
811fn containing_block_fragment(
812 node: ServoLayoutNode,
813 fragment: &BoxFragment,
814) -> Option<Arc<BoxFragment>> {
815 let containing_block_node = containing_block_for_node(node)?;
816 containing_block_node.with_layout_box_base(|layout_box_base| {
817 let fragments = layout_box_base.fragments();
818
819 if fragments.len() > 1 {
822 for potential_fragment in fragments.iter() {
823 if let Some(potential_box_fragment) = potential_fragment.retrieve_box_fragment() &&
824 potential_box_fragment
825 .children
826 .iter()
827 .any(|child_fragment| child_fragment.is_box_fragment(fragment))
828 {
829 return Some(potential_box_fragment.clone());
830 }
831 }
832 }
833
834 Some(fragments.first()?.retrieve_box_fragment()?.clone())
837 }).flatten()
838}
839
840#[inline]
844pub(crate) fn process_scroll_container_query(
845 node: Option<ServoLayoutNode<'_>>,
846 query_flags: ScrollContainerQueryFlags,
847 viewport_overflow: AxesOverflow,
848) -> Option<ScrollContainerResponse> {
849 let Some(node) = node else {
850 return Some(ScrollContainerResponse::Viewport(viewport_overflow));
851 };
852
853 let (style, flags) =
856 node.with_layout_box_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
857
858 if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
864 flags.intersects(
865 FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
866 )
867 {
868 return None;
869 }
870
871 if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
872 style.establishes_scroll_container(flags)
873 {
874 return Some(ScrollContainerResponse::Element(
875 node.opaque().into(),
876 style.effective_overflow(flags),
877 ));
878 }
879
880 let mut current_position_value = style.get_box().position;
900 let mut current_ancestor = node;
901
902 #[expect(unsafe_code)]
903 while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
904 current_ancestor = ancestor;
905
906 let Some((ancestor_style, ancestor_flags)) = ancestor
907 .with_layout_box_base(|base| (base.style.clone(), base.base_fragment_info.flags))
908 else {
909 continue;
910 };
911
912 if !is_containing_block_for_position(
913 current_position_value,
914 &ancestor_style,
915 ancestor_flags,
916 ) {
917 continue;
918 }
919
920 if ancestor_style.establishes_scroll_container(ancestor_flags) {
921 return Some(ScrollContainerResponse::Element(
922 ancestor.opaque().into(),
923 ancestor_style.effective_overflow(ancestor_flags),
924 ));
925 }
926
927 current_position_value = ancestor_style.get_box().position;
928 }
929
930 match current_position_value {
931 Position::Fixed => None,
932 _ => Some(ScrollContainerResponse::Viewport(viewport_overflow)),
933 }
934}
935
936pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
938 let mut results = Vec::new();
944 let mut max_req_line_break_count = 0;
945
946 let mut state = Default::default();
948 for child in node.dom_children() {
949 let mut current = rendered_text_collection_steps(child, &mut state);
951 results.append(&mut current);
953 }
954
955 let mut output = String::new();
956 for item in results {
957 match item {
958 InnerOrOuterTextItem::Text(s) => {
959 if !s.is_empty() {
961 if max_req_line_break_count > 0 {
962 output.extend(std::iter::repeat_n('\u{000A}', max_req_line_break_count));
964 max_req_line_break_count = 0;
965 }
966 output.push_str(&s);
967 }
968 },
969 InnerOrOuterTextItem::RequiredLineBreakCount(count) => {
970 if output.is_empty() {
972 continue;
974 }
975 if count > max_req_line_break_count {
979 max_req_line_break_count = count;
980 }
981 },
982 }
983 }
984 output
985}
986
987enum InnerOrOuterTextItem {
988 Text(Cow<'static, str>),
989 RequiredLineBreakCount(usize),
990}
991
992#[derive(Clone)]
993struct RenderedTextCollectionState {
994 first_table_row: bool,
996 first_table_cell: bool,
998 within_table: bool,
1001 may_start_with_whitespace: bool,
1003 did_truncate_trailing_white_space: bool,
1006 within_table_content: bool,
1009}
1010
1011impl Default for RenderedTextCollectionState {
1012 fn default() -> Self {
1013 RenderedTextCollectionState {
1014 first_table_row: true,
1015 first_table_cell: true,
1016 may_start_with_whitespace: true,
1017 did_truncate_trailing_white_space: false,
1018 within_table: false,
1019 within_table_content: false,
1020 }
1021 }
1022}
1023
1024#[expect(unsafe_code)]
1026fn rendered_text_collection_steps(
1027 node: ServoLayoutNode<'_>,
1028 state: &mut RenderedTextCollectionState,
1029) -> Vec<InnerOrOuterTextItem> {
1030 let mut items = vec![];
1034 if !node.is_connected() || !(node.is_element() || node.is_text_node()) {
1035 return items;
1036 }
1037
1038 match node.type_id() {
1039 Some(LayoutNodeType::Text) => {
1040 if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
1041 match parent_node.type_id() {
1042 Some(
1044 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1045 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1046 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1047 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1048 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1049 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1050 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1051 ) => {
1052 return items;
1053 },
1054 Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
1058 if let Some(grandparent_node) =
1059 unsafe { parent_node.dangerous_dom_parent() }
1060 {
1061 if !matches!(
1062 grandparent_node.type_id(),
1063 Some(LayoutNodeType::Element(
1064 LayoutElementType::HTMLSelectElement
1065 ))
1066 ) {
1067 return items;
1068 }
1069 } else {
1070 return items;
1071 }
1072 },
1073 Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
1074 return items;
1075 },
1076 _ => {},
1077 }
1078
1079 if state.within_table && !state.within_table_content {
1083 return items;
1084 }
1085
1086 let Some(parent_element) = parent_node.as_element() else {
1087 return items;
1088 };
1089 let Some(style_data) = parent_element.style_data() else {
1090 return items;
1091 };
1092
1093 let element_data = style_data.element_data.borrow();
1094 let Some(style) = element_data.styles.get_primary() else {
1095 return items;
1096 };
1097
1098 if style.get_inherited_box().visibility != Visibility::Visible {
1104 return items;
1105 }
1106
1107 let display = style.get_box().display;
1111 if display == Display::None {
1112 match parent_element.type_id() {
1113 Some(
1116 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) |
1117 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement),
1118 ) => {},
1119 _ => {
1120 return items;
1121 },
1122 }
1123 }
1124
1125 let text_content = node.text_content();
1126
1127 let white_space_collapse = style.get_white_space_collapse();
1128 let preserve_whitespace =
1129 white_space_collapse == &WhiteSpaceCollapseValue::Preserve;
1130 let is_inline = matches!(
1131 display,
1132 Display::InlineBlock | Display::InlineFlex | Display::InlineGrid
1133 );
1134
1135 let trim_leading_white_space =
1139 !preserve_whitespace && (state.may_start_with_whitespace || is_inline);
1140 let on_word_boundary = true;
1143
1144 let mut transformed_text = String::with_capacity(text_content.len());
1152 for iteration in TextTransformationIterator::new(
1153 &text_content,
1154 style,
1155 trim_leading_white_space,
1156 on_word_boundary,
1157 ) {
1158 transformed_text.extend(iteration.characters());
1159 }
1160
1161 let is_preformatted_element =
1162 white_space_collapse == &WhiteSpaceCollapseValue::Preserve;
1163
1164 let is_final_character_whitespace = transformed_text
1165 .chars()
1166 .next_back()
1167 .filter(char::is_ascii_whitespace)
1168 .is_some();
1169
1170 let is_first_character_whitespace = transformed_text
1171 .chars()
1172 .next()
1173 .filter(char::is_ascii_whitespace)
1174 .is_some();
1175
1176 if state.did_truncate_trailing_white_space && !is_first_character_whitespace {
1180 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1181 };
1182
1183 if !transformed_text.is_empty() {
1184 if is_final_character_whitespace && !is_preformatted_element {
1187 state.may_start_with_whitespace = false;
1188 state.did_truncate_trailing_white_space = true;
1189 transformed_text.pop();
1190 } else {
1191 state.may_start_with_whitespace = is_final_character_whitespace;
1192 state.did_truncate_trailing_white_space = false;
1193 }
1194 items.push(InnerOrOuterTextItem::Text(Cow::Owned(transformed_text)));
1195 }
1196 } else {
1197 items.push(InnerOrOuterTextItem::Text(Cow::Owned(
1200 node.text_content().deref().into(),
1201 )));
1202 }
1203 },
1204 Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
1205 state.did_truncate_trailing_white_space = false;
1208 state.may_start_with_whitespace = true;
1209 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed("\u{000A}")));
1210 },
1211 _ => {
1212 let Some(element) = node.as_element() else {
1215 return items;
1216 };
1217 let Some(style_data) = element.style_data() else {
1218 return items;
1219 };
1220
1221 let element_data = style_data.element_data.borrow();
1222 let Some(style) = element_data.styles.get_primary() else {
1223 return items;
1224 };
1225 let inherited_box = style.get_inherited_box();
1226
1227 if inherited_box.visibility != Visibility::Visible {
1228 for child in node.dom_children() {
1232 items.append(&mut rendered_text_collection_steps(child, state));
1233 }
1234 return items;
1235 }
1236
1237 let style_box = style.get_box();
1238 let display = style_box.display;
1239 let mut surrounding_line_breaks = 0;
1240
1241 if style_box.position == Position::Absolute || style_box.float != Float::None {
1243 surrounding_line_breaks = 1;
1244 }
1245
1246 match display {
1249 Display::Table => {
1250 surrounding_line_breaks = 1;
1251 state.within_table = true;
1252 },
1253 Display::TableCell => {
1258 if !state.first_table_cell {
1259 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1260 "\u{0009}", )));
1262 state.did_truncate_trailing_white_space = false;
1264 }
1265 state.first_table_cell = false;
1266 state.within_table_content = true;
1267 },
1268 Display::TableRow => {
1273 if !state.first_table_row {
1274 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(
1275 "\u{000A}", )));
1277 state.did_truncate_trailing_white_space = false;
1279 }
1280 state.first_table_row = false;
1281 state.first_table_cell = true;
1282 },
1283 Display::Block => {
1286 surrounding_line_breaks = 1;
1287 },
1288 Display::TableCaption => {
1289 surrounding_line_breaks = 1;
1290 state.within_table_content = true;
1291 },
1292 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock
1296 if state.did_truncate_trailing_white_space =>
1297 {
1298 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1299 state.did_truncate_trailing_white_space = false;
1300 state.may_start_with_whitespace = true;
1301 },
1302 _ => {},
1303 }
1304
1305 match node.type_id() {
1306 Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
1309 surrounding_line_breaks = 2;
1310 },
1311 Some(
1314 LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
1315 LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
1316 ) => {
1317 surrounding_line_breaks = 1;
1318 },
1319 _ => {},
1320 }
1321
1322 if surrounding_line_breaks > 0 {
1323 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1324 surrounding_line_breaks,
1325 ));
1326 state.did_truncate_trailing_white_space = false;
1327 state.may_start_with_whitespace = true;
1328 }
1329
1330 match node.type_id() {
1331 Some(
1336 LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
1337 LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
1338 LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
1339 LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
1340 LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
1341 LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
1342 LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
1343 ) => {
1344 if display != Display::Block && state.did_truncate_trailing_white_space {
1345 items.push(InnerOrOuterTextItem::Text(Cow::Borrowed(" ")));
1346 state.did_truncate_trailing_white_space = false;
1347 };
1348 state.may_start_with_whitespace = false;
1349 },
1350 _ => {
1351 for child in node.dom_children() {
1354 items.append(&mut rendered_text_collection_steps(child, state));
1355 }
1356 },
1357 }
1358
1359 match display {
1362 Display::InlineFlex | Display::InlineGrid | Display::InlineBlock => {
1363 state.did_truncate_trailing_white_space = false;
1364 state.may_start_with_whitespace = false;
1365 },
1366 Display::Table => {
1367 state.within_table = false;
1368 },
1369 Display::TableCell | Display::TableCaption => {
1370 state.within_table_content = false;
1371 },
1372 _ => {},
1373 }
1374
1375 if surrounding_line_breaks > 0 {
1376 items.push(InnerOrOuterTextItem::RequiredLineBreakCount(
1377 surrounding_line_breaks,
1378 ));
1379 state.did_truncate_trailing_white_space = false;
1380 state.may_start_with_whitespace = true;
1381 }
1382 },
1383 };
1384 items
1385}
1386
1387pub fn find_character_offset_in_fragment_descendants(
1388 node: &ServoLayoutNode,
1389 stacking_context_tree: &StackingContextTree,
1390 point_in_viewport: Point2D<Au, CSSPixel>,
1391) -> Option<(OpaqueNode, Utf32CodeUnits)> {
1392 let mut search = ClosestFragmentSearch::default();
1393 for fragment in &node.fragments_for_pseudo(None) {
1394 if let Some(point_in_fragment) =
1395 stacking_context_tree.offset_in_fragment(fragment, point_in_viewport)
1396 {
1397 search.collect_relevant_children(fragment, point_in_fragment);
1398 }
1399 }
1400 search.into_dom_position()
1401}
1402
1403pub fn process_containing_block_query(node: ServoLayoutNode) -> Option<UntrustedNodeAddress> {
1404 let containing_block = containing_block_for_node(node);
1405 containing_block.map(|node| node.opaque().into())
1406}
1407
1408pub fn process_containing_block_descendant_query(
1409 possible_ancestor: ServoLayoutNode,
1410 mut possible_descendant: ServoLayoutNode,
1411) -> bool {
1412 while let Some(establishing_node) = containing_block_for_node(possible_descendant) {
1413 if establishing_node == possible_ancestor {
1414 return true;
1415 }
1416 possible_descendant = establishing_node;
1417 }
1418 false
1419}
1420
1421pub fn process_resolved_font_style_query<'dom, E>(
1422 context: &SharedStyleContext,
1423 node: E,
1424 value: &str,
1425 url_data: ServoUrl,
1426 shared_lock: &SharedRwLock,
1427) -> Option<ServoArc<Font>>
1428where
1429 E: LayoutNode<'dom>,
1430{
1431 fn create_font_declaration(
1432 value: &str,
1433 url_data: &ServoUrl,
1434 quirks_mode: QuirksMode,
1435 ) -> Option<PropertyDeclarationBlock> {
1436 let mut declarations = SourcePropertyDeclaration::default();
1437 let result = parse_one_declaration_into(
1438 &mut declarations,
1439 PropertyId::NonCustom(ShorthandId::Font.into()),
1440 value,
1441 Origin::Author,
1442 &UrlExtraData(url_data.get_arc()),
1443 None,
1444 ParsingMode::DEFAULT,
1445 quirks_mode,
1446 CssRuleType::Style,
1447 );
1448 let declarations = match result {
1449 Ok(()) => {
1450 let mut block = PropertyDeclarationBlock::new();
1451 block.extend(declarations.drain(), Importance::Normal);
1452 block
1453 },
1454 Err(_) => return None,
1455 };
1456 Some(declarations)
1458 }
1459 fn resolve_for_declarations<'dom, E>(
1460 context: &SharedStyleContext,
1461 parent_style: Option<&ComputedValues>,
1462 declarations: PropertyDeclarationBlock,
1463 shared_lock: &SharedRwLock,
1464 ) -> ServoArc<ComputedValues>
1465 where
1466 E: LayoutNode<'dom>,
1467 {
1468 let parent_style = match parent_style {
1469 Some(parent) => parent,
1470 None => context.stylist.device().default_computed_values(),
1471 };
1472 context
1473 .stylist
1474 .compute_for_declarations::<DangerousStyleElementOf<'dom, E::ConcreteTypeBundle>>(
1475 &context.guards,
1476 parent_style,
1477 ServoArc::new(shared_lock.wrap(declarations)),
1478 )
1479 }
1480
1481 let quirks_mode = context.quirks_mode();
1484 let declarations = create_font_declaration(value, &url_data, quirks_mode)?;
1485
1486 let element = node.as_element().unwrap();
1490 let parent_style = if node.is_connected() {
1491 if element.style_data().is_some() {
1492 element.style(context)
1493 } else {
1494 let mut tlc = ThreadLocalStyleContext::new();
1495 let mut context = StyleContext {
1496 shared: context,
1497 thread_local: &mut tlc,
1498 };
1499 #[expect(unsafe_code)]
1500 let styles = resolve_style(
1501 &mut context,
1502 unsafe { element.dangerous_style_element() },
1503 RuleInclusion::All,
1504 None,
1505 None,
1506 );
1507 styles.primary().clone()
1508 }
1509 } else {
1510 let default_declarations =
1511 create_font_declaration("10px sans-serif", &url_data, quirks_mode).unwrap();
1512 resolve_for_declarations::<E>(context, None, default_declarations, shared_lock)
1513 };
1514
1515 let computed_values =
1517 resolve_for_declarations::<E>(context, Some(&*parent_style), declarations, shared_lock);
1518
1519 Some(computed_values.clone_font())
1520}
1521
1522pub(crate) fn transform_au_rectangle(
1523 rect_to_transform: Rect<Au, CSSPixel>,
1524 transform: FastLayoutTransform,
1525) -> Option<Rect<Au, CSSPixel>> {
1526 transform_f32_rectangle(
1527 au_rect_to_f32_rect(rect_to_transform).cast_unit(),
1528 transform,
1529 )
1530 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
1531}
1532
1533pub(crate) fn transform_f32_rectangle(
1534 rect_to_transform: Rect<f32, LayoutPixel>,
1535 transform: FastLayoutTransform,
1536) -> Option<Rect<f32, LayoutPixel>> {
1537 match transform {
1538 FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
1539 FastLayoutTransform::Transform { transform, .. } => {
1540 transform.outer_transformed_rect(&rect_to_transform)
1541 },
1542 }
1543}
1544
1545pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
1546 let fragments = node.fragments_for_pseudo(None);
1547 let box_fragment = fragments.first()?.retrieve_box_fragment()?;
1548
1549 Some(
1550 box_fragment
1551 .style()
1552 .effective_overflow(box_fragment.base.flags),
1553 )
1554}
1555
1556impl BoxFragment {
1557 fn resolved_insets_for_non_static(
1558 &self,
1559 node: ServoLayoutNode,
1560 style: &ComputedValues,
1561 initial_containing_block: &PhysicalRect<Au>,
1562 ) -> Option<PhysicalSides<AuOrAuto>> {
1563 match style.get_box().position {
1564 Position::Static => None,
1565 Position::Relative => {
1566 self.resolved_insets_for_relative(node, style, initial_containing_block)
1567 },
1568 Position::Absolute | Position::Fixed => {
1569 self.resolved_insets_for_absolute(node, style, initial_containing_block)
1570 },
1571 Position::Sticky => Some(**self.resolved_sticky_insets()?),
1572 }
1573 }
1574
1575 fn resolved_insets_for_relative(
1576 &self,
1577 node: ServoLayoutNode,
1578 style: &ComputedValues,
1579 initial_containing_block: &PhysicalRect<Au>,
1580 ) -> Option<PhysicalSides<AuOrAuto>> {
1581 let get_resolved_axis =
1582 |start: &LengthPercentageOrAuto, end: &LengthPercentageOrAuto, container_length: Au| {
1583 let start = start.map(|value| value.to_used_value(container_length));
1584 let end = end.map(|value| value.to_used_value(container_length));
1585 match (start.non_auto(), end.non_auto()) {
1586 (None, None) => (Au::zero(), Au::zero()),
1587 (None, Some(end)) => (-end, end),
1588 (Some(start), None) => (start, -start),
1589 (Some(start), Some(end)) => (start, end),
1592 }
1593 };
1594
1595 let insets = style.physical_box_offsets();
1602 let containing_block_rect_size = containing_block_fragment(node, self)
1603 .map(|fragment| fragment.content_rect())
1604 .unwrap_or(*initial_containing_block)
1605 .size;
1606 let (left, right) = get_resolved_axis(
1607 &insets.left,
1608 &insets.right,
1609 containing_block_rect_size.width,
1610 );
1611 let (top, bottom) = get_resolved_axis(
1612 &insets.top,
1613 &insets.bottom,
1614 containing_block_rect_size.height,
1615 );
1616 Some(au_or_auto_sides(PhysicalSides::new(
1617 top, right, bottom, left,
1618 )))
1619 }
1620
1621 fn resolved_insets_for_absolute(
1622 &self,
1623 node: ServoLayoutNode,
1624 style: &ComputedValues,
1625 initial_containing_block: &PhysicalRect<Au>,
1626 ) -> Option<PhysicalSides<AuOrAuto>> {
1627 let insets = style.physical_box_offsets();
1628 let containing_block_rect = containing_block_fragment(node, self)
1629 .map(|fragment| fragment.containing_block_rect_for_absolute(style))
1630 .unwrap_or(*initial_containing_block);
1631
1632 let margin_rect = self.margin_rect();
1633 let (top, bottom) = match (&insets.top, &insets.bottom) {
1634 (
1635 LengthPercentageOrAuto::LengthPercentage(top),
1636 LengthPercentageOrAuto::LengthPercentage(bottom),
1637 ) => (
1638 top.to_used_value(containing_block_rect.size.height),
1639 bottom.to_used_value(containing_block_rect.size.height),
1640 ),
1641 _ => (
1642 margin_rect.min_y() - containing_block_rect.min_y(),
1643 containing_block_rect.max_y() - margin_rect.max_y(),
1644 ),
1645 };
1646 let (left, right) = match (&insets.left, &insets.right) {
1647 (
1648 LengthPercentageOrAuto::LengthPercentage(left),
1649 LengthPercentageOrAuto::LengthPercentage(right),
1650 ) => (
1651 left.to_used_value(containing_block_rect.size.width),
1652 right.to_used_value(containing_block_rect.size.width),
1653 ),
1654 _ => (
1655 margin_rect.min_x() - containing_block_rect.min_x(),
1656 containing_block_rect.max_x() - margin_rect.max_x(),
1657 ),
1658 };
1659
1660 Some(au_or_auto_sides(PhysicalSides::new(
1661 top, right, bottom, left,
1662 )))
1663 }
1664}
1665
1666fn au_or_auto_sides(sides: PhysicalSides<Au>) -> PhysicalSides<AuOrAuto> {
1667 PhysicalSides::new(
1668 AuOrAuto::LengthPercentage(sides.top),
1669 AuOrAuto::LengthPercentage(sides.right),
1670 AuOrAuto::LengthPercentage(sides.bottom),
1671 AuOrAuto::LengthPercentage(sides.left),
1672 )
1673}