layout/display_list/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use base::id::ScrollTreeNodeId;
10use clip::{Clip, ClipId};
11use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
12use fonts::GlyphStore;
13use gradient::WebRenderGradient;
14use net_traits::image_cache::Image as CachedImage;
15use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
16use paint_api::largest_contentful_paint_candidate::LCPCandidateID;
17use servo_arc::Arc as ServoArc;
18use servo_config::opts::DiagnosticsLogging;
19use servo_config::pref;
20use servo_geometry::MaxRect;
21use style::Zero;
22use style::color::{AbsoluteColor, ColorSpace};
23use style::computed_values::border_image_outset::T as BorderImageOutset;
24use style::computed_values::text_decoration_style::{
25    T as ComputedTextDecorationStyle, T as TextDecorationStyle,
26};
27use style::dom::OpaqueNode;
28use style::properties::ComputedValues;
29use style::properties::longhands::visibility::computed_value::T as Visibility;
30use style::properties::style_structs::Border;
31use style::values::computed::{
32    BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
33    NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
34};
35use style::values::generics::NonNegative;
36use style::values::generics::color::ColorOrAuto;
37use style::values::generics::rect::Rect as StyleRect;
38use style::values::specified::text::TextDecorationLine;
39use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
40use webrender_api::units::{
41    DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
42};
43use webrender_api::{
44    self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
45    ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
46    NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding,
47    SpatialId, SpatialTreeItemKey, units,
48};
49use wr::units::LayoutVector2D;
50
51use crate::cell::ArcRefCell;
52use crate::context::{ImageResolver, ResolvedImage};
53pub(crate) use crate::display_list::conversions::ToWebRender;
54use crate::display_list::stacking_context::StackingContextSection;
55use crate::fragment_tree::{
56    BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
57    TextFragment,
58};
59use crate::geom::{
60    LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
61};
62use crate::replaced::NaturalSizes;
63use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
64
65mod background;
66mod clip;
67mod conversions;
68mod gradient;
69mod hit_test;
70mod paint_timing_handler;
71mod stacking_context;
72
73use background::BackgroundPainter;
74pub(crate) use hit_test::HitTest;
75pub(crate) use paint_timing_handler::PaintTimingHandler;
76pub(crate) use stacking_context::*;
77
78const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
79
80pub(crate) struct DisplayListBuilder<'a> {
81    /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This
82    /// allows only passing the builder instead passing the containing
83    /// [stacking_context::StackingContextContent::Fragment] as an argument to display
84    /// list building functions.
85    current_scroll_node_id: ScrollTreeNodeId,
86
87    /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This is necessary in addition
88    /// to the [Self::current_scroll_node_id], because some pieces of fragments as backgrounds with
89    /// `background-attachment: fixed` need to not scroll while the rest of the fragment does.
90    current_reference_frame_scroll_node_id: ScrollTreeNodeId,
91
92    /// The current [`ClipId`] for this [DisplayListBuilder]. This allows
93    /// only passing the builder instead passing the containing
94    /// [stacking_context::StackingContextContent::Fragment] as an argument to display
95    /// list building functions.
96    current_clip_id: ClipId,
97
98    /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`].
99    pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
100
101    /// The [`PaintDisplayListInfo`] used to collect display list items and metadata.
102    pub paint_info: &'a mut PaintDisplayListInfo,
103
104    /// Data about the fragments that are highlighted by the inspector, if any.
105    ///
106    /// This data is collected during the traversal of the fragment tree and used
107    /// to paint the highlight at the very end.
108    inspector_highlight: Option<InspectorHighlight>,
109
110    /// Whether or not the `<body>` element should be painted. This is false if the root `<html>`
111    /// element inherits the `<body>`'s background to paint the page canvas background.
112    /// See <https://drafts.csswg.org/css-backgrounds/#body-background>.
113    paint_body_background: bool,
114
115    /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender
116    /// display list.
117    clip_map: Vec<ClipChainId>,
118
119    /// An [`ImageResolver`] to use during display list construction.
120    image_resolver: Arc<ImageResolver>,
121
122    /// The device pixel ratio used for this `Document`'s display list.
123    device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
124
125    /// Handler for all Paint Timings
126    paint_timing_handler: &'a mut PaintTimingHandler,
127}
128
129struct InspectorHighlight {
130    /// The node that should be highlighted
131    tag: Tag,
132
133    /// Accumulates information about the fragments that belong to the highlighted node.
134    ///
135    /// This information is collected as the fragment tree is traversed to build the
136    /// display list.
137    state: Option<HighlightTraversalState>,
138}
139
140struct HighlightTraversalState {
141    /// The smallest rectangle that fully encloses all fragments created by the highlighted
142    /// dom node, if any.
143    content_box: Rect<Au, StyloCSSPixel>,
144
145    spatial_id: SpatialId,
146
147    clip_chain_id: ClipChainId,
148
149    /// When the highlighted fragment is a box fragment we remember the information
150    /// needed to paint padding, border and margin areas.
151    maybe_box_fragment: Option<ArcRefCell<BoxFragment>>,
152}
153
154impl InspectorHighlight {
155    fn for_node(node: OpaqueNode) -> Self {
156        Self {
157            tag: Tag {
158                node,
159                // TODO: Support highlighting pseudo-elements.
160                pseudo_element_chain: Default::default(),
161            },
162            state: None,
163        }
164    }
165}
166
167impl DisplayListBuilder<'_> {
168    pub(crate) fn build(
169        stacking_context_tree: &mut StackingContextTree,
170        fragment_tree: &FragmentTree,
171        image_resolver: Arc<ImageResolver>,
172        device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
173        highlighted_dom_node: Option<OpaqueNode>,
174        debug: &DiagnosticsLogging,
175        paint_timing_handler: &mut PaintTimingHandler,
176    ) -> BuiltDisplayList {
177        // Build the rest of the display list which inclues all of the WebRender primitives.
178        let paint_info = &mut stacking_context_tree.paint_info;
179        let pipeline_id = paint_info.pipeline_id;
180        let mut webrender_display_list_builder =
181            webrender_api::DisplayListBuilder::new(pipeline_id);
182        webrender_display_list_builder.begin();
183
184        // `dump_serialized_display_list` doesn't actually print anything. It sets up
185        // the display list for printing the serialized version when `finalize()` is called.
186        // We need to call this before adding any display items so that they are printed
187        // during `finalize()`.
188        if debug.display_list {
189            webrender_display_list_builder.dump_serialized_display_list();
190        }
191
192        let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
193        let mut builder = DisplayListBuilder {
194            current_scroll_node_id: paint_info.root_reference_frame_id,
195            current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
196            current_clip_id: ClipId::INVALID,
197            webrender_display_list_builder: &mut webrender_display_list_builder,
198            paint_info,
199            inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
200            paint_body_background: true,
201            clip_map: Default::default(),
202            image_resolver,
203            device_pixel_ratio,
204            paint_timing_handler,
205        };
206
207        builder.add_all_spatial_nodes();
208
209        for clip in stacking_context_tree.clip_store.0.iter() {
210            builder.add_clip_to_display_list(clip);
211        }
212
213        // Add a single hit test that covers the entire viewport, so that WebRender knows
214        // which pipeline it hits when doing hit testing.
215        let pipeline_id = builder.paint_info.pipeline_id;
216        let viewport_size = builder.paint_info.viewport_details.size;
217        let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
218        builder.wr().push_hit_test(
219            viewport_rect,
220            ClipChainId::INVALID,
221            SpatialId::root_reference_frame(pipeline_id),
222            PrimitiveFlags::default(),
223            (0, 0), /* tag */
224        );
225
226        // Paint the canvas’ background (if any) before/under everything else
227        stacking_context_tree
228            .root_stacking_context
229            .build_canvas_background_display_list(&mut builder, fragment_tree);
230        stacking_context_tree
231            .root_stacking_context
232            .build_display_list(&mut builder);
233        builder.paint_dom_inspector_highlight();
234
235        webrender_display_list_builder.end().1
236    }
237
238    fn wr(&mut self) -> &mut wr::DisplayListBuilder {
239        self.webrender_display_list_builder
240    }
241
242    fn pipeline_id(&mut self) -> wr::PipelineId {
243        self.paint_info.pipeline_id
244    }
245
246    fn mark_is_contentful(&mut self) {
247        self.paint_info.is_contentful = true;
248    }
249
250    fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
251        self.paint_info.scroll_tree.webrender_id(id)
252    }
253
254    fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
255        match id {
256            ClipId::INVALID => ClipChainId::INVALID,
257            _ => *self
258                .clip_map
259                .get(id.0)
260                .expect("Should never try to get clip before adding it to WebRender display list"),
261        }
262    }
263
264    pub(crate) fn add_all_spatial_nodes(&mut self) {
265        // A count of the number of SpatialTree nodes pushed to the WebRender display
266        // list. This is merely to ensure that the currently-unused SpatialTreeItemKey
267        // produced for every SpatialTree node is unique.
268        let mut spatial_tree_count = 0;
269        let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
270        let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
271
272        mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
273        mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
274
275        let pipeline_id = self.pipeline_id();
276        let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
277
278        for node in scroll_tree.nodes.iter().skip(2) {
279            let parent_scroll_node_id = node
280                .parent
281                .expect("Should have already added root reference frame");
282            let parent_spatial_node_id = mapping
283                .get(parent_scroll_node_id.index)
284                .expect("Should add spatial nodes to display list in order");
285
286            // Produce a new SpatialTreeItemKey. This is currently unused by WebRender,
287            // but has to be unique to the entire scene.
288            spatial_tree_count += 1;
289            let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
290
291            mapping.push(match &node.info {
292                SpatialTreeNodeInfo::ReferenceFrame(info) => {
293                    let spatial_id = self.wr().push_reference_frame(
294                        info.origin,
295                        *parent_spatial_node_id,
296                        info.transform_style,
297                        PropertyBinding::Value(*info.transform.to_transform()),
298                        info.kind,
299                        spatial_tree_item_key,
300                    );
301                    self.wr().pop_reference_frame();
302                    spatial_id
303                },
304                SpatialTreeNodeInfo::Scroll(info) => {
305                    self.wr().define_scroll_frame(
306                        *parent_spatial_node_id,
307                        info.external_id,
308                        info.content_rect,
309                        info.clip_rect,
310                        LayoutVector2D::zero(), /* external_scroll_offset */
311                        0,                      /* scroll_offset_generation */
312                        wr::HasScrollLinkedEffect::No,
313                        spatial_tree_item_key,
314                    )
315                },
316                SpatialTreeNodeInfo::Sticky(info) => {
317                    self.wr().define_sticky_frame(
318                        *parent_spatial_node_id,
319                        info.frame_rect,
320                        info.margins,
321                        info.vertical_offset_bounds,
322                        info.horizontal_offset_bounds,
323                        LayoutVector2D::zero(), /* previously_applied_offset */
324                        spatial_tree_item_key,
325                        None, /* transform */
326                    )
327                },
328            });
329        }
330
331        scroll_tree.update_mapping(mapping);
332        self.paint_info.scroll_tree = scroll_tree;
333    }
334
335    /// Add the given [`Clip`] to the WebRender display list and create a mapping from
336    /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens:
337    ///  - When WebRender display list construction starts: All clips created during the
338    ///    `StackingContextTree` construction are added in one batch. These clips are used
339    ///    for things such as `overflow: scroll` elements.
340    ///  - When a clip is added during WebRender display list construction for individual
341    ///    items. In that case, this is called by [`Self::maybe_create_clip`].
342    pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
343        assert_eq!(
344            clip.id.0,
345            self.clip_map.len(),
346            "Clips should be added in order"
347        );
348
349        let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
350        let new_clip_id = if clip.radii.is_zero() {
351            self.wr().define_clip_rect(spatial_id, clip.rect)
352        } else {
353            self.wr().define_clip_rounded_rect(
354                spatial_id,
355                ComplexClipRegion {
356                    rect: clip.rect,
357                    radii: clip.radii,
358                    mode: ClipMode::Clip,
359                },
360            )
361        };
362
363        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
364        // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
365        // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
366        // leads to a crash.
367        let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
368            ClipChainId::INVALID => None,
369            parent => Some(parent),
370        };
371        let clip_chain_id = self
372            .wr()
373            .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
374        self.clip_map.push(clip_chain_id);
375        clip_chain_id
376    }
377
378    /// Add a new clip to the WebRender display list being built. This only happens during
379    /// WebRender display list building and these clips should be added after all clips
380    /// from the `StackingContextTree` have already been processed.
381    fn maybe_create_clip(
382        &mut self,
383        radii: wr::BorderRadius,
384        rect: units::LayoutRect,
385        force_clip_creation: bool,
386    ) -> Option<ClipChainId> {
387        if radii.is_zero() && !force_clip_creation {
388            return None;
389        }
390
391        Some(self.add_clip_to_display_list(&Clip {
392            id: ClipId(self.clip_map.len()),
393            radii,
394            rect,
395            parent_scroll_node_id: self.current_scroll_node_id,
396            parent_clip_id: self.current_clip_id,
397        }))
398    }
399
400    fn common_properties(
401        &self,
402        clip_rect: units::LayoutRect,
403        style: &ComputedValues,
404    ) -> wr::CommonItemProperties {
405        // TODO(mrobinson): We should take advantage of this field to pass hit testing
406        // information. This will allow us to avoid creating hit testing display items
407        // for fragments that paint their entire border rectangle.
408        wr::CommonItemProperties {
409            clip_rect,
410            spatial_id: self.spatial_id(self.current_scroll_node_id),
411            clip_chain_id: self.clip_chain_id(self.current_clip_id),
412            flags: style.get_webrender_primitive_flags(),
413        }
414    }
415
416    /// Draw highlights around the node that is currently hovered in the devtools.
417    fn paint_dom_inspector_highlight(&mut self) {
418        let Some(highlight) = self
419            .inspector_highlight
420            .take()
421            .and_then(|highlight| highlight.state)
422        else {
423            return;
424        };
425
426        const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
427            r: 0.23,
428            g: 0.7,
429            b: 0.87,
430            a: 0.5,
431        };
432
433        const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
434            r: 0.49,
435            g: 0.3,
436            b: 0.7,
437            a: 0.5,
438        };
439
440        const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
441            r: 0.2,
442            g: 0.2,
443            b: 0.2,
444            a: 0.5,
445        };
446
447        const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
448            r: 1.,
449            g: 0.93,
450            b: 0.,
451            a: 0.5,
452        };
453
454        // Highlight content box
455        let content_box = highlight.content_box.to_webrender();
456        let properties = wr::CommonItemProperties {
457            clip_rect: content_box,
458            spatial_id: highlight.spatial_id,
459            clip_chain_id: highlight.clip_chain_id,
460            flags: wr::PrimitiveFlags::default(),
461        };
462
463        self.wr()
464            .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
465
466        // Highlight margin, border and padding
467        if let Some(box_fragment) = highlight.maybe_box_fragment {
468            let mut paint_highlight =
469                |color: webrender_api::ColorF,
470                 fragment_relative_bounds: PhysicalRect<Au>,
471                 widths: webrender_api::units::LayoutSideOffsets| {
472                    if widths.is_zero() {
473                        return;
474                    }
475
476                    let bounds = box_fragment
477                        .borrow()
478                        .offset_by_containing_block(&fragment_relative_bounds)
479                        .to_webrender();
480
481                    // We paint each highlighted area as if it was a border for simplicity
482                    let border_style = wr::BorderSide {
483                        color,
484                        style: wr::BorderStyle::Solid,
485                    };
486
487                    let details = wr::BorderDetails::Normal(wr::NormalBorder {
488                        top: border_style,
489                        right: border_style,
490                        bottom: border_style,
491                        left: border_style,
492                        radius: webrender_api::BorderRadius::default(),
493                        do_aa: true,
494                    });
495
496                    let common = wr::CommonItemProperties {
497                        clip_rect: bounds,
498                        spatial_id: highlight.spatial_id,
499                        clip_chain_id: highlight.clip_chain_id,
500                        flags: wr::PrimitiveFlags::default(),
501                    };
502                    self.wr().push_border(&common, bounds, widths, details)
503                };
504
505            let box_fragment = box_fragment.borrow();
506            paint_highlight(
507                PADDING_BOX_HIGHLIGHT_COLOR,
508                box_fragment.padding_rect(),
509                box_fragment.padding.to_webrender(),
510            );
511            paint_highlight(
512                BORDER_BOX_HIGHLIGHT_COLOR,
513                box_fragment.border_rect(),
514                box_fragment.border.to_webrender(),
515            );
516            paint_highlight(
517                MARGIN_BOX_HIGHLIGHT_COLOR,
518                box_fragment.margin_rect(),
519                box_fragment.margin.to_webrender(),
520            );
521        }
522    }
523
524    fn check_for_contentful_paint(&mut self, bounds: LayoutRect, clip_rect: LayoutRect) {
525        if self
526            .paint_timing_handler
527            .check_bounding_rect(bounds, clip_rect)
528        {
529            self.mark_is_contentful();
530        }
531    }
532
533    fn check_for_lcp_candidate(
534        &mut self,
535        lcp_candidate_id: LCPCandidateID,
536        clip_rect: LayoutRect,
537        bounds: LayoutRect,
538    ) {
539        if !pref!(largest_contentful_paint_enabled) {
540            return;
541        }
542
543        let transform = self
544            .paint_info
545            .scroll_tree
546            .cumulative_node_to_root_transform(self.current_scroll_node_id);
547
548        self.paint_timing_handler.update_lcp_candidate(
549            lcp_candidate_id,
550            bounds,
551            clip_rect,
552            transform,
553        );
554    }
555}
556
557impl InspectorHighlight {
558    fn register_fragment_of_highlighted_dom_node(
559        &mut self,
560        fragment: &Fragment,
561        spatial_id: SpatialId,
562        clip_chain_id: ClipChainId,
563        containing_block: &PhysicalRect<Au>,
564    ) {
565        let state = self.state.get_or_insert(HighlightTraversalState {
566            content_box: Rect::zero(),
567            spatial_id,
568            clip_chain_id,
569            maybe_box_fragment: None,
570        });
571
572        // We only need to highlight the first `SpatialId`. Typically this will include the bottommost
573        // fragment for a node, which generally surrounds the entire content.
574        if spatial_id != state.spatial_id {
575            return;
576        }
577
578        if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
579            debug_assert_eq!(
580                clip_chain_id, state.clip_chain_id,
581                "Fragments of the same node must either have no clip chain or the same one"
582            );
583        }
584
585        let Some(fragment_relative_rect) = fragment.base().map(|base| base.rect) else {
586            return;
587        };
588        state.maybe_box_fragment = match fragment {
589            Fragment::Box(fragment) | Fragment::Float(fragment) => Some(fragment.clone()),
590            _ => None,
591        };
592
593        state.content_box = state
594            .content_box
595            .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
596    }
597}
598
599impl Fragment {
600    pub(crate) fn build_display_list(
601        &self,
602        builder: &mut DisplayListBuilder,
603        containing_block: &PhysicalRect<Au>,
604        section: StackingContextSection,
605        is_hit_test_for_scrollable_overflow: bool,
606        is_collapsed_table_borders: bool,
607        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
608    ) {
609        let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
610        let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
611        if let Some(inspector_highlight) = &mut builder.inspector_highlight {
612            if self.tag() == Some(inspector_highlight.tag) {
613                inspector_highlight.register_fragment_of_highlighted_dom_node(
614                    self,
615                    spatial_id,
616                    clip_chain_id,
617                    containing_block,
618                );
619            }
620        }
621
622        match self {
623            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
624                let box_fragment = &*box_fragment.borrow();
625                match box_fragment.style().get_inherited_box().visibility {
626                    Visibility::Visible => BuilderForBoxFragment::new(
627                        box_fragment,
628                        containing_block,
629                        is_hit_test_for_scrollable_overflow,
630                        is_collapsed_table_borders,
631                    )
632                    .build(builder, section),
633                    Visibility::Hidden => (),
634                    Visibility::Collapse => (),
635                }
636            },
637            Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
638            Fragment::Image(image) => {
639                let image = image.borrow();
640                let style = image.base.style();
641                match style.get_inherited_box().visibility {
642                    Visibility::Visible => {
643                        let image_rendering =
644                            style.get_inherited_box().image_rendering.to_webrender();
645                        let rect = image
646                            .base
647                            .rect
648                            .translate(containing_block.origin.to_vector())
649                            .to_webrender();
650                        let clip = image
651                            .clip
652                            .translate(containing_block.origin.to_vector())
653                            .to_webrender();
654                        let common = builder.common_properties(clip, &style);
655
656                        if let Some(image_key) = image.image_key {
657                            builder.wr().push_image(
658                                &common,
659                                rect,
660                                image_rendering,
661                                wr::AlphaType::PremultipliedAlpha,
662                                image_key,
663                                wr::ColorF::WHITE,
664                            );
665
666                            // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
667                            // An element target is contentful when one or more of the following apply:
668                            // > target is a replaced element representing an available image.
669                            builder.check_for_contentful_paint(rect, common.clip_rect);
670                        }
671
672                        if image.showing_broken_image_icon {
673                            self.build_display_list_for_broken_image_border(
674                                builder,
675                                containing_block,
676                                &common,
677                            );
678                        }
679
680                        let lcp_candidate_id = image
681                            .base
682                            .tag
683                            .map(|tag| LCPCandidateID(tag.node.id()))
684                            .unwrap_or(LCPCandidateID(0));
685                        builder.check_for_lcp_candidate(lcp_candidate_id, common.clip_rect, rect);
686                    },
687                    Visibility::Hidden => (),
688                    Visibility::Collapse => (),
689                }
690            },
691            Fragment::IFrame(iframe) => {
692                let iframe = iframe.borrow();
693                let style = iframe.base.style();
694                match style.get_inherited_box().visibility {
695                    Visibility::Visible => {
696                        // From <https://www.w3.org/TR/paint-timing/#mark-paint-timing>:
697                        // > A parent frame should not be aware of the paint events from its child iframes, and
698                        // > vice versa. This means that a frame that contains just iframes will have first paint
699                        // > (due to the enclosing boxes of the iframes) but no first contentful paint.
700                        let rect = iframe
701                            .base
702                            .rect
703                            .translate(containing_block.origin.to_vector());
704
705                        let common = builder.common_properties(rect.to_webrender(), &style);
706                        builder.wr().push_iframe(
707                            rect.to_webrender(),
708                            common.clip_rect,
709                            &wr::SpaceAndClipInfo {
710                                spatial_id: common.spatial_id,
711                                clip_chain_id: common.clip_chain_id,
712                            },
713                            iframe.pipeline_id.into(),
714                            true,
715                        );
716                    },
717                    Visibility::Hidden => (),
718                    Visibility::Collapse => (),
719                }
720            },
721            Fragment::Text(text) => {
722                let text = &*text.borrow();
723                match text.base.style().get_inherited_box().visibility {
724                    Visibility::Visible => self.build_display_list_for_text_fragment(
725                        text,
726                        builder,
727                        containing_block,
728                        text_decorations,
729                    ),
730                    Visibility::Hidden => (),
731                    Visibility::Collapse => (),
732                }
733            },
734        }
735    }
736
737    fn build_display_list_for_text_fragment(
738        &self,
739        fragment: &TextFragment,
740        builder: &mut DisplayListBuilder,
741        containing_block: &PhysicalRect<Au>,
742        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
743    ) {
744        // NB: The order of painting text components (CSS Text Decoration Module Level 3) is:
745        // shadows, underline, overline, text, text-emphasis, and then line-through.
746        let rect = fragment
747            .base
748            .rect
749            .translate(containing_block.origin.to_vector());
750        let mut baseline_origin = rect.origin;
751        baseline_origin.y += fragment.font_metrics.ascent;
752        let include_whitespace =
753            fragment.offsets.is_some() || text_decorations.iter().any(|item| !item.line.is_empty());
754
755        let (glyphs, largest_advance) = glyphs(
756            &fragment.glyphs,
757            baseline_origin,
758            fragment.justification_adjustment,
759            include_whitespace,
760        );
761
762        if glyphs.is_empty() {
763            return;
764        }
765
766        let parent_style = fragment.base.style();
767        let color = parent_style.clone_color();
768        let font_metrics = &fragment.font_metrics;
769        let dppx = builder.device_pixel_ratio.get();
770
771        // Gecko gets the text bounding box based on the ink overflow bounds. Since
772        // we don't need to calculate this yet (as we do not implement `contain:
773        // paint`), we just need to make sure these boundaries are big enough to
774        // contain the inked portion of the glyphs. We assume that the descent and
775        // ascent are big enough and then just expand the advance-based boundaries by
776        // twice the size of the biggest advance in the advance dimention.
777        let glyph_bounds = rect
778            .inflate(largest_advance.scale_by(2.0), Au::zero())
779            .to_webrender();
780        let common = builder.common_properties(glyph_bounds, &parent_style);
781
782        // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to
783        // back).
784        let shadows = &parent_style.get_inherited_text().text_shadow;
785        for shadow in shadows.0.iter().rev() {
786            builder.wr().push_shadow(
787                &wr::SpaceAndClipInfo {
788                    spatial_id: common.spatial_id,
789                    clip_chain_id: common.clip_chain_id,
790                },
791                wr::Shadow {
792                    offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
793                    color: rgba(shadow.color.resolve_to_absolute(&color)),
794                    blur_radius: shadow.blur.px(),
795                },
796                true, /* should_inflate */
797            );
798        }
799
800        for text_decoration in text_decorations.iter() {
801            if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
802                let mut rect = rect;
803                rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
804                rect.size.height =
805                    Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
806
807                self.build_display_list_for_text_decoration(
808                    &parent_style,
809                    builder,
810                    &rect,
811                    text_decoration,
812                    TextDecorationLine::UNDERLINE,
813                );
814            }
815        }
816
817        for text_decoration in text_decorations.iter() {
818            if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
819                let mut rect = rect;
820                rect.size.height =
821                    Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
822                self.build_display_list_for_text_decoration(
823                    &parent_style,
824                    builder,
825                    &rect,
826                    text_decoration,
827                    TextDecorationLine::OVERLINE,
828                );
829            }
830        }
831
832        self.build_display_list_for_text_selection(
833            fragment,
834            builder,
835            containing_block,
836            fragment.base.rect.min_x(),
837            fragment.justification_adjustment,
838        );
839
840        builder.wr().push_text(
841            &common,
842            glyph_bounds,
843            &glyphs,
844            fragment.font_key,
845            rgba(color),
846            None,
847        );
848
849        // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
850        // An element target is contentful when one or more of the following apply:
851        // > target has a text node child, representing non-empty text, and the node’s used opacity is greater than zero.
852        // TODO(shubhamg13): Consider opacity in separate PR
853        builder.check_for_contentful_paint(glyph_bounds, common.clip_rect);
854
855        for text_decoration in text_decorations.iter() {
856            if text_decoration
857                .line
858                .contains(TextDecorationLine::LINE_THROUGH)
859            {
860                let mut rect = rect;
861                rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
862                rect.size.height =
863                    Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
864                self.build_display_list_for_text_decoration(
865                    &parent_style,
866                    builder,
867                    &rect,
868                    text_decoration,
869                    TextDecorationLine::LINE_THROUGH,
870                );
871            }
872        }
873
874        if !shadows.0.is_empty() {
875            builder.wr().pop_all_shadows();
876        }
877    }
878
879    fn build_display_list_for_text_decoration(
880        &self,
881        parent_style: &ServoArc<ComputedValues>,
882        builder: &mut DisplayListBuilder,
883        rect: &PhysicalRect<Au>,
884        text_decoration: &FragmentTextDecoration,
885        line: TextDecorationLine,
886    ) {
887        if text_decoration.style == ComputedTextDecorationStyle::MozNone {
888            return;
889        }
890
891        let mut rect = rect.to_webrender();
892        let line_thickness = rect.height().ceil();
893
894        if text_decoration.style == ComputedTextDecorationStyle::Wavy {
895            rect = rect.inflate(0.0, line_thickness * 1.0);
896        }
897
898        let common_properties = builder.common_properties(rect, parent_style);
899        builder.wr().push_line(
900            &common_properties,
901            &rect,
902            line_thickness,
903            wr::LineOrientation::Horizontal,
904            &rgba(text_decoration.color),
905            text_decoration.style.to_webrender(),
906        );
907
908        if text_decoration.style == TextDecorationStyle::Double {
909            let half_height = (rect.height() / 2.0).floor().max(1.0);
910            let y_offset = match line {
911                TextDecorationLine::OVERLINE => -rect.height() - half_height,
912                _ => rect.height() + half_height,
913            };
914            let rect = rect.translate(Vector2D::new(0.0, y_offset));
915            let common_properties = builder.common_properties(rect, parent_style);
916            builder.wr().push_line(
917                &common_properties,
918                &rect,
919                line_thickness,
920                wr::LineOrientation::Horizontal,
921                &rgba(text_decoration.color),
922                text_decoration.style.to_webrender(),
923            );
924        }
925    }
926
927    fn build_display_list_for_broken_image_border(
928        &self,
929        builder: &mut DisplayListBuilder,
930        containing_block: &PhysicalRect<Au>,
931        common: &CommonItemProperties,
932    ) {
933        let border_side = BorderSide {
934            color: ColorF::BLACK,
935            style: wr::BorderStyle::Inset,
936        };
937        builder.wr().push_border(
938            common,
939            containing_block.to_webrender(),
940            LayoutSideOffsets::new_all_same(1.0),
941            BorderDetails::Normal(NormalBorder {
942                left: border_side,
943                right: border_side,
944                top: border_side,
945                bottom: border_side,
946                radius: BorderRadius::zero(),
947                do_aa: true,
948            }),
949        );
950    }
951
952    // TODO: This caret/text selection implementation currently does not account for vertical text
953    // and RTL text properly.
954    fn build_display_list_for_text_selection(
955        &self,
956        fragment: &TextFragment,
957        builder: &mut DisplayListBuilder<'_>,
958        containing_block_rect: &PhysicalRect<Au>,
959        fragment_x_offset: Au,
960        justification_adjustment: Au,
961    ) {
962        let Some(offsets) = fragment.offsets.as_ref() else {
963            return;
964        };
965
966        let shared_selection = offsets.shared_selection.borrow();
967        if !shared_selection.enabled {
968            return;
969        }
970
971        if offsets.character_range.start > shared_selection.character_range.end ||
972            offsets.character_range.end < shared_selection.character_range.start
973        {
974            return;
975        }
976
977        let mut current_character_index = offsets.character_range.start;
978        let mut current_advance = Au::zero();
979        let mut start_advance = None;
980        let mut end_advance = None;
981        for glyph_store in fragment.glyphs.iter() {
982            let glyph_store_character_count = glyph_store.total_characters();
983            if current_character_index + glyph_store_character_count <
984                shared_selection.character_range.start
985            {
986                current_advance += glyph_store.total_advance() +
987                    (justification_adjustment * glyph_store.total_word_separators() as i32);
988                current_character_index += glyph_store_character_count;
989                continue;
990            }
991
992            if current_character_index >= shared_selection.character_range.end {
993                break;
994            }
995
996            for glyph in glyph_store.glyphs() {
997                if current_character_index >= shared_selection.character_range.start {
998                    start_advance = start_advance.or(Some(current_advance));
999                }
1000
1001                current_character_index += glyph.character_count();
1002                current_advance += glyph.advance();
1003                if glyph.char_is_word_separator() {
1004                    current_advance += justification_adjustment;
1005                }
1006
1007                if current_character_index <= shared_selection.character_range.end {
1008                    end_advance = Some(current_advance);
1009                }
1010            }
1011        }
1012
1013        let start_x = start_advance.unwrap_or(current_advance);
1014        let end_x = end_advance.unwrap_or(current_advance);
1015
1016        let parent_style = fragment.base.style();
1017        if !shared_selection.range.is_empty() {
1018            let selection_rect = Rect::new(
1019                containing_block_rect.origin +
1020                    Vector2D::new(fragment_x_offset + start_x, Au::zero()),
1021                Size2D::new(end_x - start_x, containing_block_rect.height()),
1022            )
1023            .to_webrender();
1024
1025            if let Some(selection_color) = fragment
1026                .selected_style
1027                .borrow()
1028                .clone_background_color()
1029                .as_absolute()
1030            {
1031                let selection_common = builder.common_properties(selection_rect, &parent_style);
1032                builder
1033                    .wr()
1034                    .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1035            }
1036            return;
1037        }
1038
1039        let insertion_point_rect = Rect::new(
1040            containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1041            Size2D::new(
1042                INSERTION_POINT_LOGICAL_WIDTH,
1043                containing_block_rect.height(),
1044            ),
1045        )
1046        .to_webrender();
1047
1048        let color = parent_style.clone_color();
1049        let caret_color = match parent_style.clone_caret_color().0 {
1050            ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1051            ColorOrAuto::Auto => color,
1052        };
1053        let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
1054        builder.wr().push_rect(
1055            &insertion_point_common,
1056            insertion_point_rect,
1057            rgba(caret_color),
1058        );
1059    }
1060}
1061
1062struct BuilderForBoxFragment<'a> {
1063    fragment: &'a BoxFragment,
1064    containing_block: &'a PhysicalRect<Au>,
1065    border_rect: units::LayoutRect,
1066    margin_rect: OnceCell<units::LayoutRect>,
1067    padding_rect: OnceCell<units::LayoutRect>,
1068    content_rect: OnceCell<units::LayoutRect>,
1069    border_radius: wr::BorderRadius,
1070    border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1071    padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1072    content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1073    is_hit_test_for_scrollable_overflow: bool,
1074    is_collapsed_table_borders: bool,
1075}
1076
1077impl<'a> BuilderForBoxFragment<'a> {
1078    fn new(
1079        fragment: &'a BoxFragment,
1080        containing_block: &'a PhysicalRect<Au>,
1081        is_hit_test_for_scrollable_overflow: bool,
1082        is_collapsed_table_borders: bool,
1083    ) -> Self {
1084        let border_rect = fragment
1085            .border_rect()
1086            .translate(containing_block.origin.to_vector());
1087        Self {
1088            fragment,
1089            containing_block,
1090            border_rect: border_rect.to_webrender(),
1091            border_radius: fragment.border_radius(),
1092            margin_rect: OnceCell::new(),
1093            padding_rect: OnceCell::new(),
1094            content_rect: OnceCell::new(),
1095            border_edge_clip_chain_id: RefCell::new(None),
1096            padding_edge_clip_chain_id: RefCell::new(None),
1097            content_edge_clip_chain_id: RefCell::new(None),
1098            is_hit_test_for_scrollable_overflow,
1099            is_collapsed_table_borders,
1100        }
1101    }
1102
1103    fn content_rect(&self) -> &units::LayoutRect {
1104        self.content_rect.get_or_init(|| {
1105            self.fragment
1106                .content_rect()
1107                .translate(self.containing_block.origin.to_vector())
1108                .to_webrender()
1109        })
1110    }
1111
1112    fn padding_rect(&self) -> &units::LayoutRect {
1113        self.padding_rect.get_or_init(|| {
1114            self.fragment
1115                .padding_rect()
1116                .translate(self.containing_block.origin.to_vector())
1117                .to_webrender()
1118        })
1119    }
1120
1121    fn margin_rect(&self) -> &units::LayoutRect {
1122        self.margin_rect.get_or_init(|| {
1123            self.fragment
1124                .margin_rect()
1125                .translate(self.containing_block.origin.to_vector())
1126                .to_webrender()
1127        })
1128    }
1129
1130    fn border_edge_clip(
1131        &self,
1132        builder: &mut DisplayListBuilder,
1133        force_clip_creation: bool,
1134    ) -> Option<ClipChainId> {
1135        if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1136            return Some(clip);
1137        }
1138
1139        let maybe_clip =
1140            builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
1141        *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1142        maybe_clip
1143    }
1144
1145    fn padding_edge_clip(
1146        &self,
1147        builder: &mut DisplayListBuilder,
1148        force_clip_creation: bool,
1149    ) -> Option<ClipChainId> {
1150        if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1151            return Some(clip);
1152        }
1153
1154        let radii = offset_radii(self.border_radius, -self.fragment.border.to_webrender());
1155        let maybe_clip =
1156            builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
1157        *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1158        maybe_clip
1159    }
1160
1161    fn content_edge_clip(
1162        &self,
1163        builder: &mut DisplayListBuilder,
1164        force_clip_creation: bool,
1165    ) -> Option<ClipChainId> {
1166        if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1167            return Some(clip);
1168        }
1169
1170        let radii = offset_radii(
1171            self.border_radius,
1172            -(self.fragment.border + self.fragment.padding).to_webrender(),
1173        );
1174        let maybe_clip =
1175            builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
1176        *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1177        maybe_clip
1178    }
1179
1180    fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
1181        if self.is_hit_test_for_scrollable_overflow &&
1182            self.fragment.style().get_inherited_ui().pointer_events !=
1183                style::computed_values::pointer_events::T::None
1184        {
1185            self.build_hit_test(
1186                builder,
1187                self.fragment
1188                    .scrollable_overflow()
1189                    .translate(self.containing_block.origin.to_vector())
1190                    .to_webrender(),
1191            );
1192            return;
1193        }
1194        if self.is_collapsed_table_borders {
1195            self.build_collapsed_table_borders(builder);
1196            return;
1197        }
1198
1199        if section == StackingContextSection::Outline {
1200            self.build_outline(builder);
1201            return;
1202        }
1203
1204        if self
1205            .fragment
1206            .base
1207            .flags
1208            .contains(FragmentFlags::DO_NOT_PAINT)
1209        {
1210            return;
1211        }
1212
1213        self.build_background(builder);
1214        self.build_box_shadow(builder);
1215        self.build_border(builder);
1216    }
1217
1218    fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
1219        let external_scroll_node_id = builder
1220            .paint_info
1221            .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
1222
1223        let mut common = builder.common_properties(rect, &self.fragment.style());
1224        if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
1225            common.clip_chain_id = clip_chain_id;
1226        }
1227        builder.wr().push_hit_test(
1228            common.clip_rect,
1229            common.clip_chain_id,
1230            common.spatial_id,
1231            common.flags,
1232            (external_scroll_node_id.0, 0), /* tag */
1233        );
1234    }
1235
1236    fn build_background_for_painter(
1237        &mut self,
1238        builder: &mut DisplayListBuilder,
1239        painter: &BackgroundPainter,
1240    ) {
1241        let b = painter.style.get_background();
1242        let background_color = painter.style.resolve_color(&b.background_color);
1243        if background_color.alpha > 0.0 {
1244            // https://drafts.csswg.org/css-backgrounds/#background-color
1245            // “The background color is clipped according to the background-clip
1246            //  value associated with the bottom-most background image layer.”
1247            let layer_index = b.background_image.0.len() - 1;
1248            let bounds = painter.painting_area(self, builder, layer_index);
1249            let common = painter.common_properties(self, builder, layer_index, bounds);
1250            builder
1251                .wr()
1252                .push_rect(&common, bounds, rgba(background_color))
1253        }
1254
1255        self.build_background_image(builder, painter);
1256    }
1257
1258    fn build_background(&mut self, builder: &mut DisplayListBuilder) {
1259        let flags = self.fragment.base.flags;
1260
1261        // The root element's background is painted separately as it might inherit the `<body>`'s
1262        // background.
1263        if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1264            return;
1265        }
1266        // If the `<body>` background was inherited by the root element, don't paint it again here.
1267        if !builder.paint_body_background &&
1268            flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1269        {
1270            return;
1271        }
1272
1273        // If this BoxFragment does not paint a background, do nothing.
1274        if let BackgroundMode::None = self.fragment.background_mode {
1275            return;
1276        }
1277
1278        // Paint all extra backgrounds for this BoxFragment. These are painted first, as that's
1279        // the order that they are expected to be painted for table cells (where this feature
1280        // is used).
1281        if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1282            for extra_background in extra_backgrounds {
1283                let positioning_area = extra_background.rect;
1284                let painter = BackgroundPainter {
1285                    style: &extra_background.style.borrow_mut(),
1286                    painting_area_override: None,
1287                    positioning_area_override: Some(
1288                        positioning_area
1289                            .translate(self.containing_block.origin.to_vector())
1290                            .to_webrender(),
1291                    ),
1292                };
1293                self.build_background_for_painter(builder, &painter);
1294            }
1295        }
1296
1297        let painter = BackgroundPainter {
1298            style: &self.fragment.style(),
1299            painting_area_override: None,
1300            positioning_area_override: None,
1301        };
1302        self.build_background_for_painter(builder, &painter);
1303    }
1304
1305    fn build_background_image(
1306        &mut self,
1307        builder: &mut DisplayListBuilder,
1308        painter: &BackgroundPainter,
1309    ) {
1310        let style = painter.style;
1311        let b = style.get_background();
1312        let node = self.fragment.base.tag.map(|tag| tag.node);
1313        // Reverse because the property is top layer first, we want to paint bottom layer first.
1314        for (index, image) in b.background_image.0.iter().enumerate().rev() {
1315            match builder.image_resolver.resolve_image(node, image) {
1316                Err(_) => {},
1317                Ok(ResolvedImage::Gradient(gradient)) => {
1318                    let intrinsic = NaturalSizes::empty();
1319                    let Some(layer) =
1320                        &background::layout_layer(self, painter, builder, index, intrinsic)
1321                    else {
1322                        continue;
1323                    };
1324
1325                    match gradient::build(style, gradient, layer.tile_size, builder) {
1326                        WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
1327                            &layer.common,
1328                            layer.bounds,
1329                            linear_gradient,
1330                            layer.tile_size,
1331                            layer.tile_spacing,
1332                        ),
1333                        WebRenderGradient::Radial(radial_gradient) => {
1334                            builder.wr().push_radial_gradient(
1335                                &layer.common,
1336                                layer.bounds,
1337                                radial_gradient,
1338                                layer.tile_size,
1339                                layer.tile_spacing,
1340                            )
1341                        },
1342                        WebRenderGradient::Conic(conic_gradient) => {
1343                            builder.wr().push_conic_gradient(
1344                                &layer.common,
1345                                layer.bounds,
1346                                conic_gradient,
1347                                layer.tile_size,
1348                                layer.tile_spacing,
1349                            )
1350                        },
1351                    }
1352                },
1353                Ok(ResolvedImage::Image { image, size }) => {
1354                    // FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
1355                    let dppx = 1.0;
1356                    let intrinsic =
1357                        NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1358                    let layer = background::layout_layer(self, painter, builder, index, intrinsic);
1359                    let image_wr_key = match image {
1360                        CachedImage::Raster(raster_image) => raster_image.id,
1361                        CachedImage::Vector(vector_image) => {
1362                            let scale = builder.device_pixel_ratio.get();
1363                            let default_size: DeviceIntSize =
1364                                Size2D::new(size.width * scale, size.height * scale).to_i32();
1365                            let layer_size = layer.as_ref().map(|layer| {
1366                                Size2D::new(
1367                                    layer.tile_size.width * scale,
1368                                    layer.tile_size.height * scale,
1369                                )
1370                                .to_i32()
1371                            });
1372
1373                            node.and_then(|node| {
1374                                let size = layer_size.unwrap_or(default_size);
1375                                builder.image_resolver.rasterize_vector_image(
1376                                    vector_image.id,
1377                                    size,
1378                                    node,
1379                                    vector_image.svg_id,
1380                                )
1381                            })
1382                            .and_then(|rasterized_image| rasterized_image.id)
1383                        },
1384                    };
1385
1386                    let Some(image_key) = image_wr_key else {
1387                        continue;
1388                    };
1389
1390                    if let Some(layer) = layer {
1391                        if layer.repeat {
1392                            builder.wr().push_repeating_image(
1393                                &layer.common,
1394                                layer.bounds,
1395                                layer.tile_size,
1396                                layer.tile_spacing,
1397                                style.clone_image_rendering().to_webrender(),
1398                                wr::AlphaType::PremultipliedAlpha,
1399                                image_key,
1400                                wr::ColorF::WHITE,
1401                            )
1402                        } else {
1403                            builder.wr().push_image(
1404                                &layer.common,
1405                                layer.bounds,
1406                                style.clone_image_rendering().to_webrender(),
1407                                wr::AlphaType::PremultipliedAlpha,
1408                                image_key,
1409                                wr::ColorF::WHITE,
1410                            )
1411                        }
1412
1413                        // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
1414                        // An element target is contentful when one or more of the following apply:
1415                        // > target has a background-image which is a contentful image, and its used
1416                        // > background-size has non-zero width and height values.
1417                        builder.check_for_contentful_paint(layer.bounds, layer.common.clip_rect);
1418
1419                        let lcp_candidate_id = self
1420                            .fragment
1421                            .base
1422                            .tag
1423                            .map(|tag| LCPCandidateID(tag.node.id()))
1424                            .unwrap_or(LCPCandidateID(0));
1425                        builder.check_for_lcp_candidate(
1426                            lcp_candidate_id,
1427                            layer.common.clip_rect,
1428                            layer.bounds,
1429                        );
1430                    }
1431                },
1432            }
1433        }
1434    }
1435
1436    fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1437        wr::BorderSide {
1438            color: rgba(style_color.color),
1439            style: match style_color.style {
1440                BorderStyle::None => wr::BorderStyle::None,
1441                BorderStyle::Solid => wr::BorderStyle::Solid,
1442                BorderStyle::Double => wr::BorderStyle::Double,
1443                BorderStyle::Dotted => wr::BorderStyle::Dotted,
1444                BorderStyle::Dashed => wr::BorderStyle::Dashed,
1445                BorderStyle::Hidden => wr::BorderStyle::Hidden,
1446                BorderStyle::Groove => wr::BorderStyle::Groove,
1447                BorderStyle::Ridge => wr::BorderStyle::Ridge,
1448                BorderStyle::Inset => wr::BorderStyle::Inset,
1449                BorderStyle::Outset => wr::BorderStyle::Outset,
1450            },
1451        }
1452    }
1453
1454    fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
1455        let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1456            self.fragment.specific_layout_info()
1457        else {
1458            return;
1459        };
1460        let mut common =
1461            builder.common_properties(units::LayoutRect::default(), &self.fragment.style());
1462        let radius = wr::BorderRadius::default();
1463        let mut column_sum = Au::zero();
1464        for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1465            let mut row_sum = Au::zero();
1466            for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1467                let left_border = &table_info.collapsed_borders.x[x][y];
1468                let right_border = &table_info.collapsed_borders.x[x + 1][y];
1469                let top_border = &table_info.collapsed_borders.y[y][x];
1470                let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1471                let details = wr::BorderDetails::Normal(wr::NormalBorder {
1472                    left: self.build_border_side(left_border.style_color.clone()),
1473                    right: self.build_border_side(right_border.style_color.clone()),
1474                    top: self.build_border_side(top_border.style_color.clone()),
1475                    bottom: self.build_border_side(bottom_border.style_color.clone()),
1476                    radius,
1477                    do_aa: true,
1478                });
1479                let mut border_widths = PhysicalSides::new(
1480                    top_border.width,
1481                    right_border.width,
1482                    bottom_border.width,
1483                    left_border.width,
1484                );
1485                let left_adjustment = if x == 0 {
1486                    -border_widths.left / 2
1487                } else {
1488                    std::mem::take(&mut border_widths.left) / 2
1489                };
1490                let top_adjustment = if y == 0 {
1491                    -border_widths.top / 2
1492                } else {
1493                    std::mem::take(&mut border_widths.top) / 2
1494                };
1495                let origin =
1496                    PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1497                let size = PhysicalSize::new(
1498                    *column_size - left_adjustment + border_widths.right / 2,
1499                    *row_size - top_adjustment + border_widths.bottom / 2,
1500                );
1501                let border_rect = PhysicalRect::new(origin, size)
1502                    .translate(self.fragment.content_rect().origin.to_vector())
1503                    .translate(self.containing_block.origin.to_vector())
1504                    .to_webrender();
1505                common.clip_rect = border_rect;
1506                builder.wr().push_border(
1507                    &common,
1508                    border_rect,
1509                    border_widths.to_webrender(),
1510                    details,
1511                );
1512                row_sum += *row_size;
1513            }
1514            column_sum += *column_size;
1515        }
1516    }
1517
1518    fn build_border(&mut self, builder: &mut DisplayListBuilder) {
1519        if self.fragment.has_collapsed_borders() {
1520            // Avoid painting borders for tables and table parts in collapsed-borders mode,
1521            // since the resulting collapsed borders are painted on their own in a special way.
1522            return;
1523        }
1524
1525        let style = self.fragment.style();
1526        let border = style.get_border();
1527        let border_widths = self.fragment.border.to_webrender();
1528
1529        if border_widths == SideOffsets2D::zero() {
1530            return;
1531        }
1532
1533        // `border-image` replaces an element's border entirely.
1534        let common = builder.common_properties(self.border_rect, &style);
1535        if self.build_border_image(builder, &common, border, border_widths) {
1536            return;
1537        }
1538
1539        let current_color = style.get_inherited_text().clone_color();
1540        let style_color = BorderStyleColor::from_border(border, &current_color);
1541        let details = wr::BorderDetails::Normal(wr::NormalBorder {
1542            top: self.build_border_side(style_color.top),
1543            right: self.build_border_side(style_color.right),
1544            bottom: self.build_border_side(style_color.bottom),
1545            left: self.build_border_side(style_color.left),
1546            radius: self.border_radius,
1547            do_aa: true,
1548        });
1549        builder
1550            .wr()
1551            .push_border(&common, self.border_rect, border_widths, details)
1552    }
1553
1554    /// Add a display item for image borders if necessary.
1555    fn build_border_image(
1556        &self,
1557        builder: &mut DisplayListBuilder,
1558        common: &CommonItemProperties,
1559        border: &Border,
1560        border_widths: SideOffsets2D<f32, LayoutPixel>,
1561    ) -> bool {
1562        let style = self.fragment.style();
1563        let border_style_struct = style.get_border();
1564        let border_image_outset =
1565            resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1566        let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1567        let border_image_size = border_image_area.size;
1568        let border_image_widths = resolve_border_image_width(
1569            &border_style_struct.border_image_width,
1570            border_widths,
1571            border_image_size,
1572        );
1573        let border_image_repeat = &border_style_struct.border_image_repeat;
1574        let border_image_fill = border_style_struct.border_image_slice.fill;
1575        let border_image_slice = &border_style_struct.border_image_slice.offsets;
1576
1577        let stops = Vec::new();
1578        let mut width = border_image_size.width;
1579        let mut height = border_image_size.height;
1580        let node = self.fragment.base.tag.map(|tag| tag.node);
1581        let source = match builder
1582            .image_resolver
1583            .resolve_image(node, &border.border_image_source)
1584        {
1585            Err(_) => return false,
1586            Ok(ResolvedImage::Image { image, size }) => {
1587                let Some(image) = image.as_raster_image() else {
1588                    return false;
1589                };
1590
1591                let Some(key) = image.id else {
1592                    return false;
1593                };
1594
1595                // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
1596                // An element target is contentful when one or more of the following apply:
1597                // > target has a background-image which is a contentful image,
1598                // > and its used background-size has non-zero width and height values.
1599                builder.check_for_contentful_paint(
1600                    Box2D::from_size(size.cast_unit()),
1601                    common.clip_rect,
1602                );
1603                width = size.width;
1604                height = size.height;
1605                let image_rendering = style.clone_image_rendering().to_webrender();
1606                NinePatchBorderSource::Image(key, image_rendering)
1607            },
1608            Ok(ResolvedImage::Gradient(gradient)) => {
1609                match gradient::build(&style, gradient, border_image_size, builder) {
1610                    WebRenderGradient::Linear(gradient) => {
1611                        NinePatchBorderSource::Gradient(gradient)
1612                    },
1613                    WebRenderGradient::Radial(gradient) => {
1614                        NinePatchBorderSource::RadialGradient(gradient)
1615                    },
1616                    WebRenderGradient::Conic(gradient) => {
1617                        NinePatchBorderSource::ConicGradient(gradient)
1618                    },
1619                }
1620            },
1621        };
1622
1623        let size = Size2D::new(width as i32, height as i32);
1624
1625        // If the size of the border is zero or the size of the border image is zero, just
1626        // don't render anything. Zero-sized gradients cause problems in WebRender.
1627        if size.is_empty() || border_image_size.is_empty() {
1628            return true;
1629        }
1630
1631        let details = BorderDetails::NinePatch(NinePatchBorder {
1632            source,
1633            width: size.width,
1634            height: size.height,
1635            slice: resolve_border_image_slice(border_image_slice, size),
1636            fill: border_image_fill,
1637            repeat_horizontal: border_image_repeat.0.to_webrender(),
1638            repeat_vertical: border_image_repeat.1.to_webrender(),
1639        });
1640        builder.wr().push_border(
1641            common,
1642            border_image_area.to_box2d(),
1643            border_image_widths,
1644            details,
1645        );
1646        builder.wr().push_stops(&stops);
1647        true
1648    }
1649
1650    fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
1651        let style = self.fragment.style();
1652        let outline = style.get_outline();
1653        if outline.outline_style.none_or_hidden() {
1654            return;
1655        }
1656        let width = outline.outline_width.0.to_f32_px();
1657        if width == 0.0 {
1658            return;
1659        }
1660        // <https://drafts.csswg.org/css-ui-3/#outline-offset>
1661        // > Negative values must cause the outline to shrink into the border box. Both
1662        // > the height and the width of outside of the shape drawn by the outline should
1663        // > not become smaller than twice the computed value of the outline-width
1664        // > property, to make sure that an outline can be rendered even with large
1665        // > negative values. User agents should apply this constraint independently in
1666        // > each dimension. If the outline is drawn as multiple disconnected shapes, this
1667        // > constraint applies to each shape separately.
1668        let offset = outline.outline_offset.to_f32_px() + width;
1669        let outline_rect = self.border_rect.inflate(
1670            offset.max(-self.border_rect.width() / 2.0 + width),
1671            offset.max(-self.border_rect.height() / 2.0 + width),
1672        );
1673        let common = builder.common_properties(outline_rect, &style);
1674        let widths = SideOffsets2D::new_all_same(width);
1675        let border_style = match outline.outline_style {
1676            // TODO: treating 'auto' as 'solid' is allowed by the spec,
1677            // but we should do something better.
1678            OutlineStyle::Auto => BorderStyle::Solid,
1679            OutlineStyle::BorderStyle(s) => s,
1680        };
1681        let side = self.build_border_side(BorderStyleColor {
1682            style: border_style,
1683            color: style.resolve_color(&outline.outline_color),
1684        });
1685        let details = wr::BorderDetails::Normal(wr::NormalBorder {
1686            top: side,
1687            right: side,
1688            bottom: side,
1689            left: side,
1690            radius: offset_radii(self.border_radius, SideOffsets2D::new_all_same(offset)),
1691            do_aa: true,
1692        });
1693        builder
1694            .wr()
1695            .push_border(&common, outline_rect, widths, details)
1696    }
1697
1698    fn build_box_shadow(&self, builder: &mut DisplayListBuilder<'_>) {
1699        let style = self.fragment.style();
1700        let box_shadows = &style.get_effects().box_shadow.0;
1701        if box_shadows.is_empty() {
1702            return;
1703        }
1704
1705        // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
1706        let common = builder.common_properties(MaxRect::max_rect(), &style);
1707        for box_shadow in box_shadows.iter().rev() {
1708            let (rect, clip_mode) = if box_shadow.inset {
1709                (*self.padding_rect(), BoxShadowClipMode::Inset)
1710            } else {
1711                (self.border_rect, BoxShadowClipMode::Outset)
1712            };
1713
1714            builder.wr().push_box_shadow(
1715                &common,
1716                rect,
1717                LayoutVector2D::new(
1718                    box_shadow.base.horizontal.px(),
1719                    box_shadow.base.vertical.px(),
1720                ),
1721                rgba(style.resolve_color(&box_shadow.base.color)),
1722                box_shadow.base.blur.px(),
1723                box_shadow.spread.px(),
1724                self.border_radius,
1725                clip_mode,
1726            );
1727        }
1728    }
1729}
1730
1731fn rgba(color: AbsoluteColor) -> wr::ColorF {
1732    let rgba = color.to_color_space(ColorSpace::Srgb);
1733    wr::ColorF::new(
1734        rgba.components.0.clamp(0.0, 1.0),
1735        rgba.components.1.clamp(0.0, 1.0),
1736        rgba.components.2.clamp(0.0, 1.0),
1737        rgba.alpha,
1738    )
1739}
1740
1741fn glyphs(
1742    glyph_runs: &[Arc<GlyphStore>],
1743    mut baseline_origin: PhysicalPoint<Au>,
1744    justification_adjustment: Au,
1745    include_whitespace: bool,
1746) -> (Vec<GlyphInstance>, Au) {
1747    let mut glyphs = vec![];
1748    let mut largest_advance = Au::zero();
1749
1750    for run in glyph_runs {
1751        for glyph in run.glyphs() {
1752            if !run.is_whitespace() || include_whitespace {
1753                let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1754                let point = LayoutPoint::new(
1755                    baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1756                    baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1757                );
1758                let glyph_instance = GlyphInstance {
1759                    index: glyph.id(),
1760                    point,
1761                };
1762                glyphs.push(glyph_instance);
1763            }
1764
1765            if glyph.char_is_word_separator() {
1766                baseline_origin.x += justification_adjustment;
1767            }
1768
1769            let advance = glyph.advance();
1770            baseline_origin.x += advance;
1771            largest_advance.max_assign(advance);
1772        }
1773    }
1774    (glyphs, largest_advance)
1775}
1776
1777/// Given a set of corner radii for a rectangle, this function returns the corresponding radii
1778/// for the [outer rectangle][`Rect::outer_rect`] resulting from expanding the original
1779/// rectangle by the given offsets.
1780fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
1781    let expand = |radius: &mut f32, offset: f32| {
1782        // For negative offsets, just shrink the radius by that amount.
1783        if offset < 0.0 {
1784            *radius = (*radius + offset).max(0.0);
1785            return;
1786        }
1787
1788        // For positive offsets, expand the radius by that amount. But only if the
1789        // radius is positive, in order to preserve sharp corners.
1790        // TODO: this behavior is not continuous, we should use this algorithm instead:
1791        // https://github.com/w3c/csswg-drafts/issues/7103#issuecomment-3357331922
1792        if *radius > 0.0 {
1793            *radius += offset;
1794        }
1795    };
1796    if offsets.left != 0.0 {
1797        expand(&mut radii.top_left.width, offsets.left);
1798        expand(&mut radii.bottom_left.width, offsets.left);
1799    }
1800    if offsets.right != 0.0 {
1801        expand(&mut radii.top_right.width, offsets.right);
1802        expand(&mut radii.bottom_right.width, offsets.right);
1803    }
1804    if offsets.top != 0.0 {
1805        expand(&mut radii.top_left.height, offsets.top);
1806        expand(&mut radii.top_right.height, offsets.top);
1807    }
1808    if offsets.bottom != 0.0 {
1809        expand(&mut radii.bottom_right.height, offsets.bottom);
1810        expand(&mut radii.bottom_left.height, offsets.bottom);
1811    }
1812    radii
1813}
1814
1815/// Resolve the WebRender border-image outset area from the style values.
1816fn resolve_border_image_outset(
1817    outset: BorderImageOutset,
1818    border: SideOffsets2D<f32, LayoutPixel>,
1819) -> SideOffsets2D<f32, LayoutPixel> {
1820    fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
1821        match outset {
1822            NonNegativeLengthOrNumber::Length(length) => length.px(),
1823            NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
1824        }
1825    }
1826
1827    SideOffsets2D::new(
1828        image_outset_for_side(outset.0, border.top),
1829        image_outset_for_side(outset.1, border.right),
1830        image_outset_for_side(outset.2, border.bottom),
1831        image_outset_for_side(outset.3, border.left),
1832    )
1833}
1834
1835/// Resolve the WebRender border-image width from the style values.
1836fn resolve_border_image_width(
1837    width: &BorderImageWidth,
1838    border: SideOffsets2D<f32, LayoutPixel>,
1839    border_area: Size2D<f32, LayoutPixel>,
1840) -> SideOffsets2D<f32, LayoutPixel> {
1841    fn image_width_for_side(
1842        border_image_width: &BorderImageSideWidth,
1843        border_width: f32,
1844        total_length: f32,
1845    ) -> f32 {
1846        match border_image_width {
1847            BorderImageSideWidth::LengthPercentage(v) => {
1848                v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
1849            },
1850            BorderImageSideWidth::Number(x) => border_width * x.0,
1851            BorderImageSideWidth::Auto => border_width,
1852        }
1853    }
1854
1855    SideOffsets2D::new(
1856        image_width_for_side(&width.0, border.top, border_area.height),
1857        image_width_for_side(&width.1, border.right, border_area.width),
1858        image_width_for_side(&width.2, border.bottom, border_area.height),
1859        image_width_for_side(&width.3, border.left, border_area.width),
1860    )
1861}
1862
1863/// Resolve the WebRender border-image slice from the style values.
1864fn resolve_border_image_slice(
1865    border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
1866    size: Size2D<i32, UnknownUnit>,
1867) -> SideOffsets2D<i32, DevicePixel> {
1868    fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
1869        match value.0 {
1870            NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
1871            NumberOrPercentage::Number(n) => n.round() as i32,
1872        }
1873    }
1874
1875    SideOffsets2D::new(
1876        resolve_percentage(border_image_slice.0, size.height),
1877        resolve_percentage(border_image_slice.1, size.width),
1878        resolve_percentage(border_image_slice.2, size.height),
1879        resolve_percentage(border_image_slice.3, size.width),
1880    )
1881}
1882
1883pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
1884    // Normalize radii that add up to > 100%.
1885    // https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
1886    // > Let f = min(L_i/S_i), where i ∈ {top, right, bottom, left},
1887    // > S_i is the sum of the two corresponding radii of the corners on side i,
1888    // > and L_top = L_bottom = the width of the box,
1889    // > and L_left = L_right = the height of the box.
1890    // > If f < 1, then all corner radii are reduced by multiplying them by f.
1891    let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
1892        .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
1893        .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
1894        .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
1895    if f < 1.0 {
1896        radius.top_left *= f;
1897        radius.top_right *= f;
1898        radius.bottom_right *= f;
1899        radius.bottom_left *= f;
1900    }
1901}
1902
1903/// <https://drafts.csswg.org/css-shapes-1/#valdef-shape-box-margin-box>
1904/// > The corner radii of this shape are determined by the corresponding
1905/// > border-radius and margin values. If the ratio of border-radius/margin is 1 or more,
1906/// > or margin is negative or zero, then the margin box corner radius is
1907/// > max(border-radius + margin, 0). If the ratio of border-radius/margin is less than 1,
1908/// > and margin is positive, then the margin box corner radius is
1909/// > border-radius + margin * (1 + (ratio-1)^3).
1910pub(super) fn compute_margin_box_radius(
1911    radius: wr::BorderRadius,
1912    layout_rect: LayoutSize,
1913    fragment: &BoxFragment,
1914) -> wr::BorderRadius {
1915    let style = fragment.style();
1916    let margin = style.physical_margin();
1917    let adjust_radius = |radius: f32, margin: f32| -> f32 {
1918        if margin <= 0. || (radius / margin) >= 1. {
1919            (radius + margin).max(0.)
1920        } else {
1921            radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
1922        }
1923    };
1924    let compute_margin_radius = |radius: LayoutSize,
1925                                 layout_rect: LayoutSize,
1926                                 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
1927     -> LayoutSize {
1928        let zero = LengthPercentage::zero();
1929        let width = margin
1930            .width
1931            .auto_is(|| &zero)
1932            .to_used_value(Au::from_f32_px(layout_rect.width));
1933        let height = margin
1934            .height
1935            .auto_is(|| &zero)
1936            .to_used_value(Au::from_f32_px(layout_rect.height));
1937        LayoutSize::new(
1938            adjust_radius(radius.width, width.to_f32_px()),
1939            adjust_radius(radius.height, height.to_f32_px()),
1940        )
1941    };
1942    wr::BorderRadius {
1943        top_left: compute_margin_radius(
1944            radius.top_left,
1945            layout_rect,
1946            Size2D::new(margin.left, margin.top),
1947        ),
1948        top_right: compute_margin_radius(
1949            radius.top_right,
1950            layout_rect,
1951            Size2D::new(margin.right, margin.top),
1952        ),
1953        bottom_left: compute_margin_radius(
1954            radius.bottom_left,
1955            layout_rect,
1956            Size2D::new(margin.left, margin.bottom),
1957        ),
1958        bottom_right: compute_margin_radius(
1959            radius.bottom_right,
1960            layout_rect,
1961            Size2D::new(margin.right, margin.bottom),
1962        ),
1963    }
1964}
1965
1966impl BoxFragment {
1967    fn border_radius(&self) -> BorderRadius {
1968        let style = self.style();
1969        let border = style.get_border();
1970        if border.border_top_left_radius.0.is_zero() &&
1971            border.border_top_right_radius.0.is_zero() &&
1972            border.border_bottom_right_radius.0.is_zero() &&
1973            border.border_bottom_left_radius.0.is_zero()
1974        {
1975            return BorderRadius::zero();
1976        }
1977
1978        let border_rect = self.border_rect();
1979        let resolve =
1980            |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
1981        let corner = |corner: &style::values::computed::BorderCornerRadius| {
1982            Size2D::new(
1983                resolve(&corner.0.width.0, border_rect.size.width),
1984                resolve(&corner.0.height.0, border_rect.size.height),
1985            )
1986        };
1987
1988        let mut radius = wr::BorderRadius {
1989            top_left: corner(&border.border_top_left_radius),
1990            top_right: corner(&border.border_top_right_radius),
1991            bottom_right: corner(&border.border_bottom_right_radius),
1992            bottom_left: corner(&border.border_bottom_left_radius),
1993        };
1994
1995        normalize_radii(&border_rect.to_webrender(), &mut radius);
1996        radius
1997    }
1998}