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