Skip to main content

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