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