Skip to main content

layout/display_list/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{OnceCell, RefCell};
6use std::sync::atomic::Ordering;
7use std::sync::{Arc, LazyLock};
8
9use app_units::{AU_PER_PX, Au};
10use clip::Clip;
11pub(crate) use clip::ClipId;
12use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
13use fonts::{FontMetrics, ShapedTextSlice};
14use gradient::WebRenderGradient;
15use layout_api::ReflowStatistics;
16use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
17use servo_arc::Arc as ServoArc;
18use servo_base::cross_process_instant::CrossProcessInstant;
19use servo_base::id::{PipelineId, ScrollTreeNodeId};
20use servo_config::opts::{DiagnosticsLogging, DiagnosticsLoggingOption};
21use servo_config::{pref, prefs};
22use servo_url::ServoUrl;
23use style::Zero;
24use style::color::{AbsoluteColor, ColorSpace};
25use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
26use style::computed_values::border_image_outset::T as BorderImageOutset;
27use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
28use style::computed_values::overflow_x::T as ComputedOverflow;
29use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
30use style::computed_values::text_decoration_thickness::T as TextDecorationThickness;
31use style::dom::OpaqueNode;
32use style::properties::ComputedValues;
33use style::properties::longhands::visibility::computed_value::T as Visibility;
34use style::properties::style_structs::Border;
35use style::values::computed::basic_shape::ClipPath as ComputedClipPath;
36use style::values::computed::{
37    BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
38    NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
39};
40use style::values::generics::NonNegative;
41use style::values::generics::color::ColorOrAuto;
42use style::values::generics::rect::Rect as StyleRect;
43use style::values::specified::text::TextDecorationLine;
44use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
45use webrender_api::units::{
46    DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
47};
48use webrender_api::{
49    self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
50    ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
51    MixBlendMode, NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags,
52    PropertyBinding, PropertyBindingKey, RasterSpace, SpatialId, StackingContextFlags,
53    TransformStyle, units,
54};
55use wr::units::LayoutVector2D;
56
57use crate::context::{ImageResolver, ResolvedImage};
58use crate::display_list::background::BackgroundPainter;
59use crate::display_list::conversions::FilterToWebRender;
60pub(crate) use crate::display_list::conversions::ToWebRender;
61use crate::display_list::paint_traversal::{PaintTraversal, PaintTraversalHandler, TraversalState};
62use crate::fragment_tree::{
63    BackgroundMode, BaseFragment, BoxFragment, BoxFragmentWithStyle,
64    CumulativeContentRectCalculation, Fragment, FragmentFlags, FragmentStatus, FragmentTree,
65    IFrameFragment, ImageFragment, PositioningFragment, SpecificLayoutInfo, Tag, TextFragment,
66};
67use crate::geom::{
68    LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
69};
70use crate::replaced::NaturalSizes;
71use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
72
73mod background;
74mod clip;
75mod conversions;
76mod gradient;
77mod hit_test;
78mod paint_timing_handler;
79mod paint_traversal;
80mod painted_region;
81mod stacking_context;
82
83pub(crate) use hit_test::{ClosestFragmentSearch, HitTest};
84pub(crate) use paint_timing_handler::{
85    ContainerTimingRootChain, ContainerTimingRoots, PaintTimingHandler,
86};
87pub(crate) use stacking_context::*;
88
89const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
90
91/// A color to use for the selection rectangle when the document isn't focused (does not
92/// have system focus or its frame itself does not have focus).
93///
94/// TODO: This should eventually come from the system theme.
95static UNFOCUSED_SELECTION_COLOR: LazyLock<AbsoluteColor> =
96    LazyLock::new(|| AbsoluteColor::srgb_legacy(200, 200, 200, 1.0));
97
98pub(crate) struct DisplayListBuilder<'a> {
99    /// The [`FragmentTree`] that we are building a display list for.
100    fragment_tree: &'a FragmentTree,
101
102    /// The current [`ScrollTreeNodeId`] for this [`DisplayListBuilder`]. This is
103    /// necessary because some pieces of fragments as backgrounds with
104    /// `background-attachment: fixed` need to not scroll while the rest of the fragment
105    /// does.
106    current_reference_frame_scroll_node_id: ScrollTreeNodeId,
107
108    /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`].
109    pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
110
111    /// The [`PaintDisplayListInfo`] used to collect display list items and metadata.
112    pub paint_info: &'a mut PaintDisplayListInfo,
113
114    /// Data about the fragments that are highlighted by the inspector, if any.
115    ///
116    /// This data is collected during the traversal of the fragment tree and used
117    /// to paint the highlight at the very end.
118    inspector_highlight: Option<InspectorHighlight>,
119
120    /// Whether or not the `<body>` element should be painted. This is false if the root `<html>`
121    /// element inherits the `<body>`'s background to paint the page canvas background.
122    /// See <https://drafts.csswg.org/css-backgrounds/#body-background>.
123    paint_body_background: bool,
124
125    /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender
126    /// display list.
127    clip_map: Vec<ClipChainId>,
128
129    /// An [`ImageResolver`] to use during display list construction.
130    image_resolver: Arc<ImageResolver>,
131
132    /// The device pixel ratio used for this `Document`'s display list.
133    device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
134
135    /// Handler for all Paint Timings
136    paint_timing_handler: &'a mut PaintTimingHandler,
137
138    /// Statistics collected about the reflow, in order to write tests for incremental layout.
139    reflow_statistics: &'a mut ReflowStatistics,
140
141    /// Whether the `largest_contentul_paint_enabled` preference is enabled.
142    largest_contentful_paint_enabled: bool,
143
144    /// Whether the `container_timing_enabled` preference is enabled.
145    container_timing_enabled: bool,
146
147    /// The background color used for the shell.
148    shell_background_color: AbsoluteColor,
149
150    /// Whether or not the `WebView` this display list is being rendered for has system focus
151    /// and is the focused frame in the frame tree.
152    frame_focused: bool,
153
154    /// Glyphs of the text fragment being built, reused across fragments to avoid
155    /// an allocation per fragment.
156    glyph_buffer: Vec<GlyphInstance>,
157}
158
159struct InspectorHighlight {
160    /// The node that should be highlighted
161    tag: Tag,
162
163    /// Accumulates information about the fragments that belong to the highlighted node.
164    ///
165    /// This information is collected as the fragment tree is traversed to build the
166    /// display list.
167    state: Option<HighlightTraversalState>,
168}
169
170struct HighlightTraversalState {
171    /// The smallest rectangle that fully encloses all fragments created by the highlighted
172    /// dom node, if any.
173    content_box: Rect<Au, StyloCSSPixel>,
174
175    spatial_id: SpatialId,
176
177    clip_chain_id: ClipChainId,
178
179    /// When the highlighted fragment is a box fragment we remember the information
180    /// needed to paint padding, border and margin areas.
181    maybe_box_fragment: Option<Arc<BoxFragment>>,
182}
183
184impl InspectorHighlight {
185    fn for_node(node: OpaqueNode) -> Self {
186        Self {
187            tag: Tag {
188                node,
189                // TODO: Support highlighting pseudo-elements.
190                pseudo_element_chain: Default::default(),
191            },
192            state: None,
193        }
194    }
195}
196
197impl DisplayListBuilder<'_> {
198    #[expect(clippy::too_many_arguments)]
199    pub(crate) fn build(
200        stacking_context_tree: &mut StackingContextTree,
201        fragment_tree: &FragmentTree,
202        image_resolver: Arc<ImageResolver>,
203        device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
204        highlighted_dom_node: Option<OpaqueNode>,
205        debug: &DiagnosticsLogging,
206        paint_timing_handler: &mut PaintTimingHandler,
207        reflow_statistics: &mut ReflowStatistics,
208        frame_focused: bool,
209    ) -> BuiltDisplayList {
210        // Build the rest of the display list which inclues all of the WebRender primitives.
211        let paint_info = &mut stacking_context_tree.paint_info;
212        let pipeline_id = paint_info.pipeline_id;
213        let mut webrender_display_list_builder =
214            webrender_api::DisplayListBuilder::new(pipeline_id);
215        webrender_display_list_builder.begin();
216
217        // `dump_serialized_display_list` doesn't actually print anything. It sets up
218        // the display list for printing the serialized version when `finalize()` is called.
219        // We need to call this before adding any display items so that they are printed
220        // during `finalize()`.
221        if debug.is_enabled(DiagnosticsLoggingOption::DisplayList) {
222            webrender_display_list_builder.dump_serialized_display_list();
223        }
224
225        let shell_background_color = {
226            let default_background_color = pref!(shell_background_color_rgba);
227            AbsoluteColor::new(
228                ColorSpace::Srgb,
229                default_background_color[0] as f32,
230                default_background_color[1] as f32,
231                default_background_color[2] as f32,
232                default_background_color[3] as f32,
233            )
234            .into_srgb_legacy()
235        };
236
237        let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
238        let mut builder = DisplayListBuilder {
239            fragment_tree,
240            current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
241            webrender_display_list_builder: &mut webrender_display_list_builder,
242            paint_info,
243            inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
244            paint_body_background: true,
245            clip_map: Default::default(),
246            image_resolver,
247            device_pixel_ratio,
248            paint_timing_handler,
249            reflow_statistics,
250            largest_contentful_paint_enabled: pref!(largest_contentful_paint_enabled),
251            container_timing_enabled: pref!(container_timing_enabled),
252            shell_background_color,
253            frame_focused,
254            glyph_buffer: Vec::new(),
255        };
256
257        // Clear any caret color from previous display list constructions.
258        builder.paint_info.caret_property_binding = None;
259
260        builder.add_all_spatial_nodes();
261
262        for clip in stacking_context_tree.clip_store.0.iter() {
263            builder.add_clip_to_display_list(clip);
264        }
265
266        // Add a single hit test that covers the entire viewport, so that WebRender knows
267        // which pipeline it hits when doing hit testing.
268        let pipeline_id = builder.paint_info.pipeline_id;
269        let viewport_size = builder.paint_info.viewport_details.size;
270        let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
271        builder.wr().push_hit_test(
272            viewport_rect,
273            ClipChainId::INVALID,
274            SpatialId::root_reference_frame(pipeline_id),
275            PrimitiveFlags::default(),
276            (0, 0), /* tag */
277        );
278
279        PaintTraversal::traverse(&stacking_context_tree.root_stacking_context, &mut builder);
280        builder.paint_dom_inspector_highlight();
281
282        webrender_display_list_builder.end().1
283    }
284
285    fn wr(&mut self) -> &mut wr::DisplayListBuilder {
286        self.webrender_display_list_builder
287    }
288
289    fn pipeline_id(&self) -> wr::PipelineId {
290        self.paint_info.pipeline_id
291    }
292
293    fn mark_is_paintable(&mut self) {
294        self.paint_timing_handler.mark_document_is_paintable();
295    }
296
297    fn mark_is_contentful(&mut self) {
298        self.paint_timing_handler.mark_document_is_contentful();
299    }
300
301    fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
302        self.paint_info.scroll_tree.webrender_id(id)
303    }
304
305    fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
306        match id {
307            ClipId::INVALID => ClipChainId::INVALID,
308            _ => *self
309                .clip_map
310                .get(id.0)
311                .expect("Should never try to get clip before adding it to WebRender display list"),
312        }
313    }
314
315    pub(crate) fn add_all_spatial_nodes(&mut self) {
316        // A count of the number of SpatialTree nodes pushed to the WebRender display
317        // list. This is merely to ensure that the currently-unused SpatialTreeItemKey
318        // produced for every SpatialTree node is unique.
319        let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
320        let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
321
322        mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
323        mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
324
325        for node in scroll_tree.nodes.iter().skip(2) {
326            let parent_scroll_node_id = node
327                .parent
328                .expect("Should have already added root reference frame");
329            let parent_spatial_node_id = mapping
330                .get(parent_scroll_node_id.index)
331                .expect("Should add spatial nodes to display list in order");
332
333            mapping.push(match &node.info {
334                SpatialTreeNodeInfo::ReferenceFrame(info) => {
335                    let spatial_id = self.wr().push_reference_frame(
336                        info.origin,
337                        *parent_spatial_node_id,
338                        info.transform_style,
339                        PropertyBinding::Value(*info.transform.to_transform()),
340                        info.kind,
341                    );
342                    self.wr().pop_reference_frame();
343                    spatial_id
344                },
345                SpatialTreeNodeInfo::Scroll(info) => {
346                    self.wr().define_scroll_frame(
347                        *parent_spatial_node_id,
348                        info.external_id,
349                        info.content_rect,
350                        info.clip_rect,
351                        LayoutVector2D::zero(), /* external_scroll_offset */
352                        0,                      /* scroll_offset_generation */
353                        wr::HasScrollLinkedEffect::No,
354                    )
355                },
356                SpatialTreeNodeInfo::Sticky(info) => {
357                    self.wr().define_sticky_frame(
358                        *parent_spatial_node_id,
359                        info.frame_rect,
360                        info.margins,
361                        info.vertical_offset_bounds,
362                        info.horizontal_offset_bounds,
363                        LayoutVector2D::zero(), /* previously_applied_offset */
364                        None,                   /* transform */
365                    )
366                },
367            });
368        }
369
370        scroll_tree.update_mapping(mapping);
371        self.paint_info.scroll_tree = scroll_tree;
372    }
373
374    /// Add the given [`Clip`] to the WebRender display list and create a mapping from
375    /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens:
376    ///  - When WebRender display list construction starts: All clips created during the
377    ///    `StackingContextTree` construction are added in one batch. These clips are used
378    ///    for things such as `overflow: scroll` elements.
379    ///  - When a clip is added during WebRender display list construction for individual
380    ///    items. In that case, this is called by [`Self::maybe_create_clip`].
381    pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
382        assert_eq!(
383            clip.id.0,
384            self.clip_map.len(),
385            "Clips should be added in order"
386        );
387
388        let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
389        let new_clip_id = if clip.radii.is_zero() {
390            self.wr().define_clip_rect(spatial_id, clip.rect)
391        } else {
392            self.wr().define_clip_rounded_rect(
393                spatial_id,
394                ComplexClipRegion {
395                    rect: clip.rect,
396                    radii: clip.radii,
397                    mode: ClipMode::Clip,
398                },
399            )
400        };
401
402        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
403        // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
404        // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
405        // leads to a crash.
406        let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
407            ClipChainId::INVALID => None,
408            parent => Some(parent),
409        };
410        let clip_chain_id = self
411            .wr()
412            .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
413        self.clip_map.push(clip_chain_id);
414        clip_chain_id
415    }
416
417    /// Add a new clip to the WebRender display list being built. This only happens during
418    /// WebRender display list building and these clips should be added after all clips
419    /// from the `StackingContextTree` have already been processed.
420    fn maybe_create_clip(
421        &mut self,
422        state: &TraversalState,
423        radii: wr::BorderRadius,
424        rect: units::LayoutRect,
425        force_clip_creation: bool,
426    ) -> Option<ClipChainId> {
427        if radii.is_zero() && !force_clip_creation {
428            return None;
429        }
430
431        Some(self.add_clip_to_display_list(&Clip {
432            id: ClipId(self.clip_map.len()),
433            radii,
434            rect,
435            parent_scroll_node_id: state.spatial_id,
436            parent_clip_id: state.clip_id,
437        }))
438    }
439
440    fn push_webrender_stacking_context_for_transform_if_necessary(
441        &mut self,
442        stacking_context: &StackingContext,
443    ) -> bool {
444        let StackingContextFragments::Fragment(fragment) = &stacking_context.fragment else {
445            return false;
446        };
447
448        let style = fragment.style();
449        let transform_style = style
450            .used_transform_style(fragment.base.flags)
451            .to_webrender();
452
453        if !style.has_effective_transform_or_perspective(FragmentFlags::empty()) &&
454            (transform_style == TransformStyle::Flat &&
455                !stacking_context.participates_in_a_3d_rendering_context)
456        {
457            return false;
458        }
459
460        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID
461        // should be used for primitives, but `None` is used for stacking contexts and
462        // clip chains. We convert to the `Option<ClipChainId>` representation here. Just
463        // passing Some(ClipChainId::INVALID) causes a panic.
464        let clip_chain_id = match stacking_context.clip_id {
465            ClipId::INVALID => None,
466            clip_id => Some(self.clip_chain_id(clip_id)),
467        };
468        let spatial_id = self.spatial_id(stacking_context.scroll_tree_node_id);
469
470        self.wr().push_stacking_context(
471            spatial_id,
472            style.webrender_primitive_flags(),
473            clip_chain_id,
474            transform_style,
475            MixBlendMode::Normal,
476            &[], // filters
477            &[], // filter_datas
478            wr::RasterSpace::Screen,
479            StackingContextFlags::empty(),
480            None, // snapshot
481        );
482        true
483    }
484
485    fn push_webrender_stacking_context_if_necessary(
486        &mut self,
487        stacking_context: &StackingContext,
488    ) -> bool {
489        if stacking_context.context_type == StackingContextType::StackingContainer {
490            return false;
491        }
492
493        let mut is_blend_container = stacking_context.children.iter().any(|child| {
494            child.fragment().is_some_and(|fragment| {
495                fragment.style().get_mix_blend_mode() != &ComputedMixBlendMode::Normal
496            })
497        });
498
499        let mix_blend_mode;
500        let mut filters: Vec<_>;
501        let mut stacking_context_flags = StackingContextFlags::empty();
502        match &stacking_context.fragment {
503            StackingContextFragments::Fragment(fragment) => {
504                let style = fragment.style();
505                let effects = style.get_effects();
506
507                mix_blend_mode = effects.mix_blend_mode.to_webrender();
508
509                // Do not create another blend container stacking context started by the root
510                // element, because the root background is painted above of it (at the root
511                // stacking context, which sits above the root fragment).
512                //
513                // TODO: Would it be cleaner to paint the root background at the root fragment
514                // instead of the root stacking context?
515                is_blend_container &= !fragment.base.flags.contains(FragmentFlags::IS_ROOT_ELEMENT);
516
517                // WebRender only uses the stacking context to apply certain effects. If we don't
518                // actually need to create a stacking context, just avoid creating one.
519                if !is_blend_container &&
520                    effects.filter.0.is_empty() &&
521                    effects.backdrop_filter.0.is_empty() &&
522                    effects.opacity == 1.0 &&
523                    effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
524                    style.get_svg().clip_path == ComputedClipPath::None
525                {
526                    return false;
527                }
528
529                // Create the filter pipeline.
530                let current_color = &style.get_inherited_text().color;
531                filters = effects
532                    .filter
533                    .0
534                    .iter()
535                    .map(|filter| FilterToWebRender::to_webrender(filter, current_color))
536                    .collect();
537                if effects.opacity != 1.0 {
538                    filters.push(wr::FilterOp::Opacity(
539                        effects.opacity.into(),
540                        effects.opacity,
541                    ));
542                }
543            },
544            // WebRender only needs a stacking context at the root when the root stacking
545            // context itself is a blend container.
546            StackingContextFragments::Root if is_blend_container => {
547                mix_blend_mode = MixBlendMode::Normal;
548                filters = Vec::new();
549            },
550            _ => return false,
551        };
552
553        if is_blend_container {
554            stacking_context_flags.insert(StackingContextFlags::IS_BLEND_CONTAINER);
555        }
556
557        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID
558        // should be used for primitives, but `None` is used for stacking contexts and
559        // clip chains. We convert to the `Option<ClipChainId>` representation here. Just
560        // passing Some(ClipChainId::INVALID) causes a panic.
561        let clip_chain_id = match stacking_context.clip_id {
562            ClipId::INVALID => None,
563            clip_id => Some(self.clip_chain_id(clip_id)),
564        };
565        let spatial_id = self.spatial_id(stacking_context.scroll_tree_node_id);
566
567        self.wr().push_stacking_context(
568            spatial_id,
569            PrimitiveFlags::default(),
570            clip_chain_id,
571            TransformStyle::Flat,
572            mix_blend_mode,
573            &filters,
574            &[], // filter_datas
575            wr::RasterSpace::Screen,
576            stacking_context_flags,
577            None, // snapshot
578        );
579
580        true
581    }
582
583    fn common_properties(
584        &self,
585        state: &TraversalState,
586        clip_rect: units::LayoutRect,
587        style: &ComputedValues,
588    ) -> wr::CommonItemProperties {
589        // TODO(mrobinson): We should take advantage of this field to pass hit testing
590        // information. This will allow us to avoid creating hit testing display items
591        // for fragments that paint their entire border rectangle.
592        wr::CommonItemProperties {
593            clip_rect,
594            spatial_id: self.spatial_id(state.spatial_id),
595            clip_chain_id: self.clip_chain_id(state.clip_id),
596            flags: style.webrender_primitive_flags(),
597        }
598    }
599
600    /// Draw highlights around the node that is currently hovered in the devtools.
601    fn paint_dom_inspector_highlight(&mut self) {
602        let Some(highlight) = self
603            .inspector_highlight
604            .take()
605            .and_then(|highlight| highlight.state)
606        else {
607            return;
608        };
609
610        const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
611            r: 0.23,
612            g: 0.7,
613            b: 0.87,
614            a: 0.5,
615        };
616
617        const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
618            r: 0.49,
619            g: 0.3,
620            b: 0.7,
621            a: 0.5,
622        };
623
624        const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
625            r: 0.2,
626            g: 0.2,
627            b: 0.2,
628            a: 0.5,
629        };
630
631        const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
632            r: 1.,
633            g: 0.93,
634            b: 0.,
635            a: 0.5,
636        };
637
638        // Highlight content box
639        let content_box = highlight.content_box.to_webrender();
640        let properties = wr::CommonItemProperties {
641            clip_rect: content_box,
642            spatial_id: highlight.spatial_id,
643            clip_chain_id: highlight.clip_chain_id,
644            flags: wr::PrimitiveFlags::default(),
645        };
646
647        self.wr()
648            .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
649
650        // Highlight margin, border and padding
651        if let Some(box_fragment) = highlight.maybe_box_fragment {
652            let mut paint_highlight =
653                |color: webrender_api::ColorF,
654                 fragment_relative_bounds: PhysicalRect<Au>,
655                 widths: webrender_api::units::LayoutSideOffsets| {
656                    if widths.is_zero() {
657                        return;
658                    }
659
660                    let bounds = box_fragment
661                        .offset_by_cumulative_content_offset(
662                            &fragment_relative_bounds,
663                            CumulativeContentRectCalculation::AlreadyDoneWithStackingContextTree,
664                        )
665                        .to_webrender();
666
667                    // We paint each highlighted area as if it was a border for simplicity
668                    let border_style = wr::BorderSide {
669                        color,
670                        style: wr::BorderStyle::Solid,
671                    };
672
673                    let details = wr::BorderDetails::Normal(wr::NormalBorder {
674                        top: border_style,
675                        right: border_style,
676                        bottom: border_style,
677                        left: border_style,
678                        radius: webrender_api::BorderRadius::default(),
679                        do_aa: true,
680                    });
681
682                    let common = wr::CommonItemProperties {
683                        clip_rect: bounds,
684                        spatial_id: highlight.spatial_id,
685                        clip_chain_id: highlight.clip_chain_id,
686                        flags: wr::PrimitiveFlags::default(),
687                    };
688                    self.wr().push_border(&common, bounds, widths, details)
689                };
690
691            paint_highlight(
692                PADDING_BOX_HIGHLIGHT_COLOR,
693                box_fragment.padding_rect(),
694                box_fragment.padding.to_webrender(),
695            );
696            paint_highlight(
697                BORDER_BOX_HIGHLIGHT_COLOR,
698                box_fragment.border_rect(),
699                box_fragment.border.to_webrender(),
700            );
701            paint_highlight(
702                MARGIN_BOX_HIGHLIGHT_COLOR,
703                box_fragment.margin_rect(),
704                box_fragment.margin.to_webrender(),
705            );
706        }
707    }
708
709    #[expect(clippy::too_many_arguments)]
710    fn collect_image_record(
711        &mut self,
712        state: &TraversalState,
713        bounds: LayoutRect,
714        clip_rect: LayoutRect,
715        tag: Option<Tag>,
716        url: Option<ServoUrl>,
717        natural_width: Option<Au>,
718        natural_height: Option<Au>,
719        load_time: Option<CrossProcessInstant>,
720    ) {
721        if !self.largest_contentful_paint_enabled {
722            return;
723        }
724
725        let transform = self
726            .paint_info
727            .scroll_tree
728            .cumulative_node_to_root_transform(state.spatial_id);
729
730        self.paint_timing_handler.append_image_record(
731            tag,
732            bounds,
733            clip_rect,
734            transform,
735            url,
736            natural_width,
737            natural_height,
738            load_time,
739        );
740    }
741
742    /// Accumulate a fragment painted by the element with the given `tag` into the
743    /// Container Timing state of each of the enclosing `container_timing_roots`.
744    #[expect(clippy::too_many_arguments)]
745    fn collect_container_timing_record(
746        &mut self,
747        state: &TraversalState,
748        container_timing_roots: &ContainerTimingRoots,
749        tag: Option<Tag>,
750        bounds: LayoutRect,
751        clip_rect: LayoutRect,
752        natural_width: Option<Au>,
753        natural_height: Option<Au>,
754    ) {
755        if !self.container_timing_enabled {
756            return;
757        }
758        let Some(container_timing_roots) = container_timing_roots else {
759            return;
760        };
761
762        // Like LCP collection, we skip if there's no containing element tag.
763        let Some(tag) = tag else {
764            return;
765        };
766
767        let transform = self
768            .paint_info
769            .scroll_tree
770            .cumulative_node_to_root_transform(state.spatial_id);
771
772        self.paint_timing_handler.update_container_timing(
773            container_timing_roots,
774            tag.node,
775            bounds,
776            clip_rect,
777            transform,
778            natural_width,
779            natural_height,
780        );
781    }
782
783    fn visit_stacking_context_reference_frame_info(
784        &mut self,
785        stacking_context: &StackingContext,
786    ) -> (usize, Option<ScrollTreeNodeId>) {
787        let Some(reference_frame_info) = &stacking_context.reference_frame_info else {
788            return (0, None);
789        };
790
791        // Note: Reference frames always establish a stacking context, so it is fine to check if
792        // this stacking context establishes a reference frame as well. We don't need to check
793        // every fragment.
794        let old_reference_frame_spatial_id = std::mem::replace(
795            &mut self.current_reference_frame_scroll_node_id,
796            stacking_context.scroll_tree_node_id,
797        );
798
799        if reference_frame_info.captured_clip_id == ClipId::INVALID {
800            return (0, Some(old_reference_frame_spatial_id));
801        }
802
803        // Although there is nothing in the display list API that prevents it, WebRender
804        // expects that reference frames that alter the coordinate space of their contents do
805        // not propagate clips to those contents. In order to achieve this, push an extra
806        // stacking context here that only specifies a clip. This stacking context will contain
807        // all of the contents of the reference frame, but because it is added in the parent
808        // spatial node, it has the coordinate space of the reference frame parent. In addition
809        // to this, reference frames reset the base `ClipId` value for descendants to
810        // `ClipId::INVALID` during stacking context tree construction.
811        let clip_chain_id = Some(self.clip_chain_id(reference_frame_info.captured_clip_id));
812        let spatial_id = self.spatial_id(reference_frame_info.parent_spatial_node_id);
813        self.wr().push_stacking_context(
814            spatial_id,
815            PrimitiveFlags::default(),
816            clip_chain_id,
817            webrender_api::TransformStyle::Flat,
818            webrender_api::MixBlendMode::Normal,
819            &[], // filters,
820            &[], // filter_datas
821            wr::RasterSpace::Screen,
822            wr::StackingContextFlags::empty(),
823            None, // snapshot
824        );
825
826        (1, Some(old_reference_frame_spatial_id))
827    }
828}
829
830impl PaintTraversalHandler for DisplayListBuilder<'_> {
831    /// A tuple composed of the number of real WebRender stacking contexts pushed
832    /// and the previous `Self::current_reference_frame_scroll_node_id` value of
833    /// the `DisplayListBuilder` when a stacking context was visited (or `None` if
834    /// the value was unmodified).
835    type StackingContextState = (usize, Option<ScrollTreeNodeId>);
836
837    fn visit_stacking_context(
838        &mut self,
839        stacking_context: &StackingContext,
840    ) -> Self::StackingContextState {
841        let (mut stacking_contexts_pushed, old_reference_frame) =
842            self.visit_stacking_context_reference_frame_info(stacking_context);
843        if self.push_webrender_stacking_context_for_transform_if_necessary(stacking_context) {
844            stacking_contexts_pushed += 1;
845        }
846        if self.push_webrender_stacking_context_if_necessary(stacking_context) {
847            stacking_contexts_pushed += 1;
848        }
849        (stacking_contexts_pushed, old_reference_frame)
850    }
851
852    fn leave_stacking_context(
853        &mut self,
854        _: &TraversalState,
855        stacking_context_state: Self::StackingContextState,
856    ) {
857        let (stacking_contexts_pushed, old_reference_frame) = stacking_context_state;
858        for _ in 0..stacking_contexts_pushed {
859            self.wr().pop_stacking_context();
860        }
861
862        if let Some(old_reference_frame) = old_reference_frame {
863            self.current_reference_frame_scroll_node_id = old_reference_frame;
864        }
865    }
866
867    fn visit_box(&mut self, state: &TraversalState, fragment: &BoxFragmentWithStyle<'_>) {
868        fragment.base.visit_fragment(self);
869
870        if let Some(mut inspector_highlight) = self.inspector_highlight.take() &&
871            fragment.base.tag == Some(inspector_highlight.tag)
872        {
873            inspector_highlight.register_fragment_of_highlighted_dom_node(self, state, fragment);
874            self.inspector_highlight = Some(inspector_highlight);
875        }
876
877        if fragment.style().get_inherited_box().visibility != Visibility::Visible {
878            return;
879        };
880
881        BuilderForBoxFragment::new(fragment, state.origin).build(self, state)
882    }
883
884    fn visit_iframe(&mut self, state: &TraversalState, fragment: &Arc<IFrameFragment>) {
885        fragment.base.visit_fragment(self);
886
887        let style = fragment.style.borrow();
888        if style.get_inherited_box().visibility != Visibility::Visible {
889            return;
890        }
891
892        let rect = fragment.base.rect().translate(state.origin.to_vector());
893        let common = self.common_properties(state, rect.to_webrender(), &style);
894        self.wr().push_iframe(
895            rect.to_webrender(),
896            common.clip_rect,
897            &wr::SpaceAndClipInfo {
898                spatial_id: common.spatial_id,
899                clip_chain_id: common.clip_chain_id,
900            },
901            fragment.pipeline_id.into(),
902            true,
903        );
904        // From <https://www.w3.org/TR/paint-timing/#mark-paint-timing>:
905        // > A parent frame should not be aware of the paint events from its
906        // > child iframes, and vice versa. This means that a frame that
907        // > contains just iframes will have first paint (due to the enclosing
908        // > boxes of the paintable iframes) but no first contentful paint.
909        self.paint_timing_handler
910            .check_if_paintable(rect.to_webrender(), state.accumulated_opacity);
911    }
912
913    fn visit_image(
914        &mut self,
915        state: &TraversalState,
916        containing_block: PhysicalRect<Au>,
917        fragment: &Arc<ImageFragment>,
918    ) {
919        fragment.base.visit_fragment(self);
920
921        let style = fragment.style.borrow();
922        if style.get_inherited_box().visibility != Visibility::Visible {
923            return;
924        }
925
926        let image_rendering = style.get_inherited_box().image_rendering.to_webrender();
927        let rect = fragment
928            .base
929            .rect()
930            .translate(containing_block.origin.to_vector())
931            .to_webrender();
932        let clip = fragment
933            .clip
934            .translate(containing_block.origin.to_vector())
935            .to_webrender();
936        let common = self.common_properties(state, clip, &style);
937
938        if let Some(image_key) = fragment.image_key {
939            self.wr().push_image(
940                &common,
941                rect,
942                image_rendering,
943                wr::AlphaType::PremultipliedAlpha,
944                image_key,
945                wr::ColorF::WHITE,
946            );
947
948            if self
949                .paint_timing_handler
950                .check_if_paintable(rect, state.accumulated_opacity)
951            {
952                // From <https://www.w3.org/TR/paint-timing/#contentful>:
953                // An element target is contentful when one or more of the following apply:
954                // > target is a replaced element representing an available image.
955                // From: <https://html.spec.whatwg.org/multipage/#img-available>
956                // When an image request's state is either partially available or completely available,
957                // the image request is said to be available.
958                // Hence, Skip Broken Images.
959                if !fragment.showing_broken_image_icon {
960                    self.mark_is_contentful();
961
962                    self.collect_image_record(
963                        state,
964                        rect,
965                        common.clip_rect,
966                        fragment.base.tag,
967                        fragment.url.clone(),
968                        fragment.natural_width,
969                        fragment.natural_height,
970                        fragment.load_time,
971                    );
972
973                    self.collect_container_timing_record(
974                        state,
975                        &state.container_timing_roots,
976                        state.containing_element_tag,
977                        rect,
978                        common.clip_rect,
979                        fragment.natural_width,
980                        fragment.natural_height,
981                    );
982                }
983            }
984        }
985
986        if fragment.showing_broken_image_icon {
987            Fragment::build_display_list_for_broken_image_border(self, &containing_block, &common);
988        }
989
990        if fragment.selected.load(Ordering::Relaxed) {
991            // If there is no selected style, just fall back to using the unfocused selection color,
992            // which is strictly better than not drawing a selection highlight.
993            let selected_style = fragment.selected_style.borrow();
994            let mut background_color =
995                if self.frame_focused && !ServoArc::ptr_eq(&*selected_style, &*style) {
996                    selected_style.resolve_color(&selected_style.get_background().background_color)
997                } else {
998                    *UNFOCUSED_SELECTION_COLOR
999                };
1000
1001            if !background_color.is_transparent() {
1002                background_color.alpha *= 0.5;
1003                self.wr().push_rect(
1004                    &common,
1005                    containing_block.to_webrender(),
1006                    rgba(background_color),
1007                );
1008            }
1009        }
1010    }
1011
1012    fn visit_text(
1013        &mut self,
1014        state: &TraversalState,
1015        line_box_rect: PhysicalRect<Au>,
1016        fragment: &Arc<TextFragment>,
1017    ) {
1018        fragment.base.visit_fragment(self);
1019
1020        let style = fragment.style();
1021        if style.get_inherited_box().visibility != Visibility::Visible {
1022            return;
1023        }
1024        Fragment::build_display_list_for_text_fragment(fragment, self, state, &line_box_rect);
1025    }
1026
1027    fn visit_positioning(&mut self, _state: &TraversalState, fragment: &Arc<PositioningFragment>) {
1028        fragment.base.visit_fragment(self);
1029    }
1030
1031    /// This is an implementation of step 3 from:
1032    /// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-context>:
1033    ///
1034    /// - See also: <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>.
1035    /// - Note: This is only called for the root `StackingContext`.
1036    fn visit_box_for_root_background(&mut self, state: &TraversalState) {
1037        let Some(fragment) = self.fragment_tree.root_box_fragment() else {
1038            return;
1039        };
1040        let fragment = fragment.with_style();
1041
1042        let source_style = {
1043            // > For documents whose root element is an HTML HTML element or an XHTML html element
1044            // > [HTML]: if the computed value of background-image on the root element is none and its
1045            // > background-color is transparent, user agents must instead propagate the computed
1046            // > values of the background properties from that element’s first HTML BODY or XHTML body
1047            // > child element.
1048            let root_fragment_style = fragment.style();
1049            if root_fragment_style.background_is_transparent() {
1050                let body_fragment = self.fragment_tree.body_fragment();
1051                self.paint_body_background = body_fragment.is_none();
1052                body_fragment
1053                    .map(|body_fragment| body_fragment.style().clone())
1054                    .unwrap_or(fragment.style().clone())
1055            } else {
1056                root_fragment_style.clone()
1057            }
1058        };
1059
1060        // This can happen if the root fragment does not have a `<body>` child (either because it is
1061        // `display: none` or `display: contents`) or if the `<body>`'s background is transparent.
1062        if source_style.background_is_transparent() {
1063            return;
1064        }
1065
1066        // The painting area is theoretically the infinite 2D plane,
1067        // but we need a rectangle with finite coordinates.
1068        //
1069        // If the document is smaller than the viewport (and doesn’t scroll),
1070        // we still want to paint the rest of the viewport.
1071        // If it’s larger, we also want to paint areas reachable after scrolling.
1072        let painting_area = self
1073            .fragment_tree
1074            .initial_containing_block
1075            .union(&self.fragment_tree.scrollable_overflow())
1076            .to_webrender();
1077
1078        let background_color =
1079            source_style.resolve_color(&source_style.get_background().background_color);
1080        if background_color.alpha > 0.0 {
1081            let common = self.common_properties(state, painting_area, &source_style);
1082            let color = rgba(background_color);
1083            self.wr().push_rect(&common, painting_area, color);
1084
1085            // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
1086            // First paint ... includes non-default background paint and the enclosing box of an iframe.
1087            // The spec is vague. See also: https://github.com/w3c/paint-timing/issues/122
1088            if background_color != self.shell_background_color {
1089                self.mark_is_paintable();
1090            }
1091        }
1092
1093        let fragment_builder = BuilderForBoxFragment::new(
1094            &fragment,
1095            self.fragment_tree.initial_containing_block.origin,
1096        );
1097        let painter = BackgroundPainter {
1098            style: &source_style,
1099            painting_area_override: Some(painting_area),
1100            positioning_area_override: None,
1101        };
1102        fragment_builder.build_background_image(self, state, &painter);
1103    }
1104
1105    fn visit_box_for_outline(&mut self, state: &TraversalState, fragment: &Arc<BoxFragment>) {
1106        let fragment = fragment.with_style();
1107        if fragment.style().get_inherited_box().visibility != Visibility::Visible {
1108            return;
1109        };
1110        BuilderForBoxFragment::new(&fragment, state.origin).build_outline(self, state)
1111    }
1112
1113    fn visit_box_for_collapsed_table_borders(
1114        &mut self,
1115        state: &TraversalState,
1116        fragment: &BoxFragmentWithStyle<'_>,
1117    ) {
1118        if fragment.style().get_inherited_box().visibility != Visibility::Visible {
1119            return;
1120        };
1121        BuilderForBoxFragment::new(fragment, state.origin)
1122            .build_collapsed_table_borders(self, state)
1123    }
1124}
1125
1126impl InspectorHighlight {
1127    fn register_fragment_of_highlighted_dom_node(
1128        &mut self,
1129        builder: &DisplayListBuilder,
1130        traversal_state: &TraversalState,
1131        fragment: &Arc<BoxFragment>,
1132    ) {
1133        let spatial_id = builder.spatial_id(traversal_state.spatial_id);
1134        let clip_chain_id = builder.clip_chain_id(traversal_state.clip_id);
1135        let state = self.state.get_or_insert_with(|| HighlightTraversalState {
1136            content_box: Rect::zero(),
1137            spatial_id,
1138            clip_chain_id,
1139            maybe_box_fragment: Some(fragment.clone()),
1140        });
1141
1142        // We only need to highlight the first `SpatialId`. Typically this will include the bottommost
1143        // fragment for a node, which generally surrounds the entire content.
1144        if spatial_id != state.spatial_id {
1145            return;
1146        }
1147
1148        if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
1149            debug_assert_eq!(
1150                clip_chain_id, state.clip_chain_id,
1151                "Fragments of the same node must either have no clip chain or the same one"
1152            );
1153        }
1154
1155        state.maybe_box_fragment = Some(fragment.clone());
1156        state.content_box = state.content_box.union(
1157            &fragment
1158                .base
1159                .rect()
1160                .translate(traversal_state.origin.to_vector()),
1161        );
1162    }
1163}
1164
1165impl Fragment {
1166    fn build_display_list_for_text_fragment(
1167        fragment: &TextFragment,
1168        builder: &mut DisplayListBuilder,
1169        state: &TraversalState,
1170        line_box_rect: &PhysicalRect<Au>,
1171    ) {
1172        // NB: The order of painting text components (CSS Text Decoration Module Level 3) is:
1173        // shadows, underline, overline, text, text-emphasis, and then line-through.
1174        let rect = fragment.base.rect().translate(state.origin.to_vector());
1175        let mut baseline_origin = rect.origin;
1176        baseline_origin.y += fragment.font_metrics.ascent;
1177
1178        let (largest_advance, entirely_white_space) = collect_glyphs(
1179            &mut builder.glyph_buffer,
1180            &fragment.glyphs,
1181            baseline_origin,
1182            fragment.justification_adjustment,
1183        );
1184
1185        if builder.glyph_buffer.is_empty() && !fragment.is_empty_for_text_cursor {
1186            return;
1187        }
1188
1189        let parent_style = fragment.style();
1190        let color = parent_style.slow_clone_color();
1191        // Gecko gets the text bounding box based on the ink overflow bounds. Since
1192        // we don't need to calculate this yet (as we do not implement `contain:
1193        // paint`), we just need to make sure these boundaries are big enough to
1194        // contain the inked portion of the glyphs. We assume that the descent and
1195        // ascent are big enough and then just expand the advance-based boundaries by
1196        // twice the size of the biggest advance in the advance dimention.
1197        let glyph_bounds = rect
1198            .inflate(largest_advance.scale_by(2.0), Au::zero())
1199            .to_webrender();
1200        let common = builder.common_properties(state, glyph_bounds, &parent_style);
1201
1202        // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to
1203        // back).
1204        let shadows = &parent_style.get_inherited_text().text_shadow;
1205        for shadow in shadows.0.iter().rev() {
1206            builder.wr().push_shadow(
1207                &wr::SpaceAndClipInfo {
1208                    spatial_id: common.spatial_id,
1209                    clip_chain_id: common.clip_chain_id,
1210                },
1211                wr::Shadow {
1212                    offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
1213                    color: rgba(shadow.color.resolve_to_absolute(&color)),
1214                    blur_radius: shadow.blur.px(),
1215                },
1216                true, /* should_inflate */
1217            );
1218        }
1219
1220        Self::build_display_list_for_text_selection(fragment, builder, state, line_box_rect);
1221
1222        for decoration in [
1223            TextDecorationLine::UNDERLINE,
1224            TextDecorationLine::SPELLING_ERROR,
1225            TextDecorationLine::GRAMMAR_ERROR,
1226            TextDecorationLine::OVERLINE,
1227        ] {
1228            Self::build_display_list_for_text_decoration_line(
1229                state,
1230                &parent_style,
1231                builder,
1232                rect,
1233                &fragment.font_metrics,
1234                decoration,
1235            );
1236        }
1237
1238        builder.webrender_display_list_builder.push_text(
1239            &common,
1240            glyph_bounds,
1241            &builder.glyph_buffer,
1242            fragment.font_key,
1243            rgba(color),
1244            None,
1245        );
1246
1247        if builder
1248            .paint_timing_handler
1249            .check_if_paintable(glyph_bounds, state.accumulated_opacity)
1250        {
1251            // From <https://www.w3.org/TR/paint-timing/#contentful>:
1252            // An element target is contentful when one or more of the following apply:
1253            // > target has a text node child, representing non-empty text, and the node’s
1254            // > used opacity is greater than zero.
1255            if !entirely_white_space && *parent_style.get_opacity() > 0. {
1256                builder.mark_is_contentful();
1257
1258                builder.collect_container_timing_record(
1259                    state,
1260                    &state.container_timing_roots,
1261                    state.containing_element_tag,
1262                    glyph_bounds,
1263                    common.clip_rect,
1264                    None,
1265                    None,
1266                );
1267
1268                // Accumulate this text fragment for LCP by the containing element's tag
1269                if let Some(tag) = state.containing_element_tag &&
1270                    builder.largest_contentful_paint_enabled
1271                {
1272                    let transform = builder
1273                        .paint_info
1274                        .scroll_tree
1275                        .cumulative_node_to_root_transform(state.spatial_id);
1276                    builder.paint_timing_handler.accumulate_text_rect(
1277                        tag,
1278                        rect.to_webrender(),
1279                        transform,
1280                        &parent_style,
1281                    );
1282                }
1283            }
1284        }
1285
1286        Self::build_display_list_for_text_decoration_line(
1287            state,
1288            &parent_style,
1289            builder,
1290            rect,
1291            &fragment.font_metrics,
1292            TextDecorationLine::LINE_THROUGH,
1293        );
1294
1295        if !shadows.0.is_empty() {
1296            builder.wr().pop_all_shadows();
1297        }
1298    }
1299
1300    fn build_display_list_for_text_decoration_line(
1301        state: &TraversalState,
1302        parent_style: &ServoArc<ComputedValues>,
1303        builder: &mut DisplayListBuilder,
1304        rect: PhysicalRect<Au>,
1305        font_metrics: &FontMetrics,
1306        text_decoration_line: TextDecorationLine,
1307    ) {
1308        if state.text_decorations.is_empty() {
1309            return;
1310        }
1311
1312        let font_size = parent_style.get_font().font_size;
1313        let dppx = builder.device_pixel_ratio.get();
1314        let resolve_thickness = |thickness: &TextDecorationThickness| -> Au {
1315            let resolved = match thickness {
1316                TextDecorationThickness::LengthPercentage(length_percentage) => {
1317                    length_percentage.resolve(font_size.computed_size.0).px()
1318                },
1319                TextDecorationThickness::Auto | TextDecorationThickness::FromFont => {
1320                    font_metrics.underline_size.to_f32_px()
1321                },
1322            };
1323
1324            // If zero, return zero.
1325            // Else round down to the nearest physical pixel; floor at 1 physical pixel.
1326            // See: <https://drafts.csswg.org/css-values-4/#snap-as-a-line-width>
1327            if resolved == 0.0 {
1328                Au::zero()
1329            } else {
1330                Au::from_f32_px((resolved * dppx).floor().max(1.0) / dppx)
1331            }
1332        };
1333
1334        for text_decoration in state.text_decorations.iter() {
1335            if !text_decoration.line.contains(text_decoration_line) {
1336                continue;
1337            }
1338
1339            let (color, style, y_offset, height) = match text_decoration_line {
1340                // <https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-underline>
1341                TextDecorationLine::UNDERLINE => (
1342                    text_decoration.color,
1343                    text_decoration.style,
1344                    font_metrics.ascent - font_metrics.underline_offset,
1345                    resolve_thickness(&text_decoration.thickness),
1346                ),
1347                // <https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-spelling-error>
1348                //
1349                // From <https://drafts.csswg.org/css-text-decor-4/#text-decoration-line-property>
1350                // > When spelling-error or grammar-error apply, the user agent must disregard the other
1351                // > sub-properties of text-decoration, as well any other properties typically affecting
1352                // > the appearance of line decorations (such as text-underline-position, color, stroke,
1353                // > or fill) when rendering these decorations. However, the user agent may take into
1354                // > account the accent-color property.
1355                //
1356                // This is why color and style are ignored here and for grammar errors.
1357                TextDecorationLine::SPELLING_ERROR => (
1358                    AbsoluteColor::srgb_legacy(255, 0, 0, 1.0),
1359                    ComputedTextDecorationStyle::Wavy,
1360                    font_metrics.ascent - font_metrics.underline_offset,
1361                    resolve_thickness(&TextDecorationThickness::Auto),
1362                ),
1363                // <https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-grammar-error>
1364                TextDecorationLine::GRAMMAR_ERROR => (
1365                    AbsoluteColor::srgb_legacy(0, 128, 0, 1.0),
1366                    ComputedTextDecorationStyle::Wavy,
1367                    font_metrics.ascent - font_metrics.underline_offset,
1368                    resolve_thickness(&TextDecorationThickness::Auto),
1369                ),
1370                // <https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-line-through>
1371                TextDecorationLine::LINE_THROUGH => (
1372                    text_decoration.color,
1373                    text_decoration.style,
1374                    font_metrics.ascent - font_metrics.strikeout_offset,
1375                    resolve_thickness(&text_decoration.thickness),
1376                ),
1377                // <https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-overline>
1378                TextDecorationLine::OVERLINE => (
1379                    text_decoration.color,
1380                    text_decoration.style,
1381                    Au::zero(),
1382                    resolve_thickness(&text_decoration.thickness),
1383                ),
1384                _ => unreachable!("Got unexpected text decoration line"),
1385            };
1386
1387            if style == ComputedTextDecorationStyle::None {
1388                continue;
1389            }
1390
1391            let mut rect = rect;
1392            rect.origin.y += y_offset;
1393            rect.size.height = height;
1394
1395            let mut rect = rect.to_webrender();
1396            let wavy_line_thickness = rect.height().ceil();
1397            if style == ComputedTextDecorationStyle::Wavy {
1398                rect = rect.inflate(0.0, wavy_line_thickness);
1399            }
1400
1401            let common_properties = builder.common_properties(state, rect, parent_style);
1402
1403            // In Servo, text decorations can span multiple text fragments. In order to have dots,
1404            // dashes, and wavy line segments match up between multiple fragments, this code extends
1405            // the painting rect for the decoration types for which this matters to the origin. As
1406            // the rectangle starts at the origin, all painted decorations will be in phase. As the
1407            // clipping rectangle is left unchanged, the actual painted region remains the size of
1408            // the original rectangle.
1409            if matches!(
1410                style,
1411                ComputedTextDecorationStyle::Dotted |
1412                    ComputedTextDecorationStyle::Dashed |
1413                    ComputedTextDecorationStyle::Wavy,
1414            ) {
1415                rect.min.x = rect.min.x.min(0.0);
1416            }
1417
1418            builder.wr().push_line(
1419                &common_properties,
1420                &rect,
1421                wavy_line_thickness,
1422                wr::LineOrientation::Horizontal,
1423                &rgba(color),
1424                style.to_webrender(),
1425            );
1426
1427            if style == ComputedTextDecorationStyle::Double {
1428                let half_height = (rect.height() / 2.0).floor().max(1.0);
1429                let y_offset = match text_decoration_line {
1430                    TextDecorationLine::OVERLINE => -rect.height() - half_height,
1431                    _ => rect.height() + half_height,
1432                };
1433                let rect = rect.translate(Vector2D::new(0.0, y_offset));
1434                let common_properties = builder.common_properties(state, rect, parent_style);
1435                builder.wr().push_line(
1436                    &common_properties,
1437                    &rect,
1438                    wavy_line_thickness,
1439                    wr::LineOrientation::Horizontal,
1440                    &rgba(color),
1441                    style.to_webrender(),
1442                );
1443            }
1444        }
1445    }
1446
1447    fn build_display_list_for_broken_image_border(
1448        builder: &mut DisplayListBuilder,
1449        containing_block: &PhysicalRect<Au>,
1450        common: &CommonItemProperties,
1451    ) {
1452        let border_side = BorderSide {
1453            color: ColorF::BLACK,
1454            style: wr::BorderStyle::Inset,
1455        };
1456        builder.wr().push_border(
1457            common,
1458            containing_block.to_webrender(),
1459            LayoutSideOffsets::new_all_same(1.0),
1460            BorderDetails::Normal(NormalBorder {
1461                left: border_side,
1462                right: border_side,
1463                top: border_side,
1464                bottom: border_side,
1465                radius: BorderRadius::zero(),
1466                do_aa: true,
1467            }),
1468        );
1469    }
1470
1471    // TODO: This caret/text selection implementation currently does not account for vertical text
1472    // and RTL text properly.
1473    fn build_display_list_for_text_selection(
1474        fragment: &TextFragment,
1475        builder: &mut DisplayListBuilder<'_>,
1476        state: &TraversalState,
1477        line_box_rect: &PhysicalRect<Au>,
1478    ) {
1479        let run_data = &fragment.run_data;
1480        let Some(selection) = *run_data.selection.borrow() else {
1481            return;
1482        };
1483
1484        // The selection character range is in pre-transformed character offsets, so use the
1485        // OffsetMap contained within `run_data` to convert it to post-transformed character
1486        // offsets. This allows updating this selection directly from the DOM (skipping layout).
1487        let selection_character_range = run_data.map_dom_range_to_transformed_range(selection);
1488
1489        if fragment.character_range_in_dom_node.start > selection_character_range.end ||
1490            fragment.character_range_in_dom_node.end < selection_character_range.start
1491        {
1492            return;
1493        }
1494
1495        // When there is an active selection, the line is empty, and there is a forced linebreak,
1496        // layout will push an empty fragment in order to trigger painting of the cursor on an empty line.
1497        // This code ensure that it is only painted if the cursor is on the starting index of the empty
1498        // fragment.
1499        if fragment.is_empty_for_text_cursor &&
1500            !fragment
1501                .character_range_in_dom_node
1502                .contains(&selection_character_range.start)
1503        {
1504            return;
1505        }
1506
1507        let mut current_character_index = fragment.character_range_in_dom_node.start;
1508        let mut current_advance = Au::zero();
1509        let mut start_advance = None;
1510        let mut end_advance = None;
1511        for glyph_store in fragment.glyphs.iter() {
1512            let glyph_store_character_count = glyph_store.character_count();
1513            if current_character_index + glyph_store_character_count <
1514                selection_character_range.start
1515            {
1516                current_advance += glyph_store.total_advance() +
1517                    (fragment.justification_adjustment *
1518                        glyph_store.total_word_separators() as i32);
1519                current_character_index += glyph_store_character_count;
1520                continue;
1521            }
1522
1523            if current_character_index >= selection_character_range.end {
1524                break;
1525            }
1526
1527            for glyph in glyph_store.glyphs() {
1528                if current_character_index >= selection_character_range.start {
1529                    start_advance = start_advance.or(Some(current_advance));
1530                }
1531
1532                current_character_index += glyph.character_count();
1533                current_advance += glyph.advance();
1534                if glyph.char_is_word_separator() {
1535                    current_advance += fragment.justification_adjustment;
1536                }
1537
1538                if current_character_index <= selection_character_range.end {
1539                    end_advance = Some(current_advance);
1540                }
1541            }
1542        }
1543
1544        let start_x = start_advance.unwrap_or(current_advance);
1545        let end_x = end_advance.unwrap_or(current_advance);
1546
1547        let fragment_rect = fragment.base.rect();
1548        let fragment_origin =
1549            (state.origin + fragment_rect.origin.to_vector()) + Vector2D::new(start_x, Au::zero());
1550
1551        let parent_style = fragment.style();
1552        if !selection_character_range.is_empty() {
1553            let selection_rect = Rect::new(
1554                Point2D::new(fragment_origin.x, line_box_rect.min_y()),
1555                Size2D::new(end_x - start_x, line_box_rect.height()),
1556            )
1557            .to_webrender();
1558
1559            let unfocused_color = *UNFOCUSED_SELECTION_COLOR;
1560            let selected_style = fragment.selected_style();
1561            let selection_color = if builder.frame_focused {
1562                selected_style
1563                    .get_background_color()
1564                    .as_absolute()
1565                    .unwrap_or(&unfocused_color)
1566            } else {
1567                &unfocused_color
1568            };
1569
1570            let selection_common = builder.common_properties(state, selection_rect, &parent_style);
1571            builder
1572                .wr()
1573                .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1574
1575            return;
1576        }
1577
1578        if !fragment.run_data.paints_caret {
1579            return;
1580        }
1581
1582        // Never paint a caret when the WebView does not have system focus. This matches
1583        // the behavior of other browsers.
1584        if !builder.frame_focused {
1585            return;
1586        }
1587
1588        let insertion_point_rect = Rect::new(
1589            fragment_origin,
1590            Size2D::new(
1591                INSERTION_POINT_LOGICAL_WIDTH,
1592                fragment.font_metrics.line_gap,
1593            ),
1594        )
1595        .to_webrender();
1596
1597        let color = parent_style.get_color();
1598        let caret_color = match parent_style.get_caret_color().0 {
1599            ColorOrAuto::Color(ref caret_color) => caret_color.resolve_to_absolute(color),
1600            ColorOrAuto::Auto => *color,
1601        };
1602        let insertion_point_common =
1603            builder.common_properties(state, insertion_point_rect, &parent_style);
1604
1605        let caret_color = rgba(caret_color);
1606        let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
1607            // It's okay to always use the same property binding key for this pipeline, as
1608            // there is currently only a single thing that animates in this way (the caret).
1609            // This code should be updated if we ever add more paint-side animations.
1610            let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
1611            let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
1612            builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
1613            PropertyBinding::Binding(property_binding_key, caret_color)
1614        } else {
1615            PropertyBinding::Value(caret_color)
1616        };
1617
1618        builder.wr().push_rect_with_animation(
1619            &insertion_point_common,
1620            insertion_point_rect,
1621            property_binding,
1622        );
1623    }
1624}
1625
1626struct BuilderForBoxFragment<'a> {
1627    fragment: &'a BoxFragmentWithStyle<'a>,
1628    containing_block_origin: PhysicalPoint<Au>,
1629    border_rect: units::LayoutRect,
1630    margin_rect: OnceCell<units::LayoutRect>,
1631    padding_rect: OnceCell<units::LayoutRect>,
1632    content_rect: OnceCell<units::LayoutRect>,
1633    border_radius: OnceCell<wr::BorderRadius>,
1634    border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1635    padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1636    content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1637}
1638
1639impl<'a> BuilderForBoxFragment<'a> {
1640    fn new(
1641        fragment: &'a BoxFragmentWithStyle<'a>,
1642        containing_block_origin: PhysicalPoint<Au>,
1643    ) -> Self {
1644        let border_rect = fragment
1645            .border_rect()
1646            .translate(containing_block_origin.to_vector());
1647        Self {
1648            fragment,
1649            containing_block_origin,
1650            border_rect: border_rect.to_webrender(),
1651            border_radius: OnceCell::new(),
1652            margin_rect: OnceCell::new(),
1653            padding_rect: OnceCell::new(),
1654            content_rect: OnceCell::new(),
1655            border_edge_clip_chain_id: RefCell::new(None),
1656            padding_edge_clip_chain_id: RefCell::new(None),
1657            content_edge_clip_chain_id: RefCell::new(None),
1658        }
1659    }
1660
1661    fn border_radius(&self) -> BorderRadius {
1662        *self
1663            .border_radius
1664            .get_or_init(|| self.fragment.border_radius())
1665    }
1666
1667    fn content_rect(&self) -> &units::LayoutRect {
1668        self.content_rect.get_or_init(|| {
1669            self.fragment
1670                .content_rect()
1671                .translate(self.containing_block_origin.to_vector())
1672                .to_webrender()
1673        })
1674    }
1675
1676    fn padding_rect(&self) -> &units::LayoutRect {
1677        self.padding_rect.get_or_init(|| {
1678            self.fragment
1679                .padding_rect()
1680                .translate(self.containing_block_origin.to_vector())
1681                .to_webrender()
1682        })
1683    }
1684
1685    fn margin_rect(&self) -> &units::LayoutRect {
1686        self.margin_rect.get_or_init(|| {
1687            self.fragment
1688                .margin_rect()
1689                .translate(self.containing_block_origin.to_vector())
1690                .to_webrender()
1691        })
1692    }
1693
1694    fn border_edge_clip(
1695        &self,
1696        builder: &mut DisplayListBuilder,
1697        state: &TraversalState,
1698        force_clip_creation: bool,
1699    ) -> Option<ClipChainId> {
1700        if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1701            return Some(clip);
1702        }
1703
1704        let maybe_clip = builder.maybe_create_clip(
1705            state,
1706            self.border_radius(),
1707            self.border_rect,
1708            force_clip_creation,
1709        );
1710        *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1711        maybe_clip
1712    }
1713
1714    fn padding_edge_clip(
1715        &self,
1716        builder: &mut DisplayListBuilder,
1717        state: &TraversalState,
1718        force_clip_creation: bool,
1719    ) -> Option<ClipChainId> {
1720        if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1721            return Some(clip);
1722        }
1723
1724        let radii = offset_radii(self.border_radius(), -self.fragment.border.to_webrender());
1725        let maybe_clip =
1726            builder.maybe_create_clip(state, radii, *self.padding_rect(), force_clip_creation);
1727        *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1728        maybe_clip
1729    }
1730
1731    fn content_edge_clip(
1732        &self,
1733        builder: &mut DisplayListBuilder,
1734        state: &TraversalState,
1735        force_clip_creation: bool,
1736    ) -> Option<ClipChainId> {
1737        if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1738            return Some(clip);
1739        }
1740
1741        let radii = offset_radii(
1742            self.border_radius(),
1743            -(self.fragment.border + self.fragment.padding).to_webrender(),
1744        );
1745        let maybe_clip =
1746            builder.maybe_create_clip(state, radii, *self.content_rect(), force_clip_creation);
1747        *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1748        maybe_clip
1749    }
1750
1751    fn build(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1752        if self
1753            .fragment
1754            .base
1755            .flags
1756            .contains(FragmentFlags::DO_NOT_PAINT)
1757        {
1758            return;
1759        }
1760
1761        self.build_backdrop_filter(builder, state);
1762        self.build_background(builder, state);
1763        self.build_box_shadow(builder, state);
1764        if !self.fragment.is_table_grid_with_collapsed_borders() {
1765            self.build_border(builder, state);
1766        }
1767
1768        let overflow = self
1769            .fragment
1770            .style()
1771            .effective_overflow(self.fragment.base.flags);
1772        let scrolls_via_user_input =
1773            |overflow| matches!(overflow, ComputedOverflow::Scroll | ComputedOverflow::Auto);
1774        if (scrolls_via_user_input(overflow.x) || scrolls_via_user_input(overflow.y)) &&
1775            self.fragment.style().get_inherited_ui().pointer_events !=
1776                style::computed_values::pointer_events::T::None
1777        {
1778            let mut inner_state = state.clone();
1779            inner_state.spatial_id = self
1780                .fragment
1781                .generated_scroll_tree_node_id()
1782                .unwrap_or(state.spatial_id);
1783            inner_state.clip_id = self.fragment.generated_clip_id().unwrap_or(state.clip_id);
1784
1785            self.build_hit_test(
1786                builder,
1787                &inner_state,
1788                self.fragment
1789                    .scrollable_overflow()
1790                    .translate(self.containing_block_origin.to_vector())
1791                    .to_webrender(),
1792            );
1793        }
1794    }
1795
1796    fn build_hit_test(
1797        &self,
1798        builder: &mut DisplayListBuilder,
1799        state: &TraversalState,
1800        rect: LayoutRect,
1801    ) {
1802        let external_scroll_node_id = builder
1803            .paint_info
1804            .external_scroll_id_for_scroll_tree_node(state.spatial_id);
1805
1806        let mut common = builder.common_properties(state, rect, self.fragment.style());
1807        if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1808            common.clip_chain_id = clip_chain_id;
1809        }
1810        builder.wr().push_hit_test(
1811            common.clip_rect,
1812            common.clip_chain_id,
1813            common.spatial_id,
1814            common.flags,
1815            (external_scroll_node_id.0, 0), /* tag */
1816        );
1817    }
1818
1819    fn build_background_for_painter(
1820        &mut self,
1821        builder: &mut DisplayListBuilder,
1822        state: &TraversalState,
1823        painter: &BackgroundPainter,
1824    ) {
1825        let b = painter.style.get_background();
1826        let background_color = painter.style.resolve_color(&b.background_color);
1827        if background_color.alpha > 0.0 {
1828            // https://drafts.csswg.org/css-backgrounds/#background-color
1829            // “The background color is clipped according to the background-clip
1830            //  value associated with the bottom-most background image layer.”
1831            let layer_index = b.background_image.0.len() - 1;
1832            let bounds = painter.painting_area(self, builder, layer_index);
1833            let common = painter.common_properties(self, builder, state, layer_index, bounds);
1834            builder
1835                .wr()
1836                .push_rect(&common, bounds, rgba(background_color));
1837
1838            // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
1839            // First paint ... includes non-default background paint and the enclosing box of an iframe.
1840            // The spec is vague. See also: https://github.com/w3c/paint-timing/issues/122
1841            if background_color != builder.shell_background_color {
1842                builder.mark_is_paintable();
1843            }
1844        }
1845
1846        self.build_background_image(builder, state, painter);
1847    }
1848
1849    fn build_backdrop_filter(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1850        // https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty
1851        // > ... unless the element it applies to is a document root element in
1852        // > the current browsing context.
1853        if self
1854            .fragment
1855            .base
1856            .flags
1857            .contains(FragmentFlags::IS_ROOT_ELEMENT)
1858        {
1859            return;
1860        }
1861
1862        let style = self.fragment.style();
1863        let effects = style.get_effects();
1864        if effects.backdrop_filter.0.is_empty() {
1865            return;
1866        }
1867
1868        let current_color = style.get_color();
1869        let filters: Vec<_> = effects
1870            .backdrop_filter
1871            .0
1872            .iter()
1873            .map(|filter| FilterToWebRender::to_webrender(filter, current_color))
1874            .collect();
1875
1876        let mut common = builder.common_properties(state, self.border_rect, style);
1877        if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1878            common.clip_chain_id = clip_chain_id;
1879        }
1880        builder.wr().push_backdrop_filter(&common, &filters, &[]);
1881    }
1882
1883    fn build_background(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1884        let flags = self.fragment.base.flags;
1885
1886        // The root element's background is painted separately as it might inherit the `<body>`'s
1887        // background.
1888        if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1889            return;
1890        }
1891        // If the `<body>` background was inherited by the root element, don't paint it again here.
1892        if !builder.paint_body_background &&
1893            flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1894        {
1895            return;
1896        }
1897
1898        // If this BoxFragment does not paint a background, do nothing.
1899        if let BackgroundMode::None = self.fragment.background_mode {
1900            return;
1901        }
1902
1903        // Paint all extra backgrounds for this BoxFragment. These are painted first, as that's
1904        // the order that they are expected to be painted for table cells (where this feature
1905        // is used).
1906        if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1907            for extra_background in extra_backgrounds {
1908                let positioning_area = extra_background.rect;
1909                let painter = BackgroundPainter {
1910                    style: &extra_background.style.borrow_mut(),
1911                    painting_area_override: None,
1912                    positioning_area_override: Some(
1913                        positioning_area
1914                            .translate(self.containing_block_origin.to_vector())
1915                            .to_webrender(),
1916                    ),
1917                };
1918                self.build_background_for_painter(builder, state, &painter);
1919            }
1920        }
1921
1922        let painter = BackgroundPainter {
1923            style: self.fragment.style(),
1924            painting_area_override: None,
1925            positioning_area_override: None,
1926        };
1927        self.build_background_for_painter(builder, state, &painter);
1928    }
1929
1930    fn build_background_image(
1931        &self,
1932        builder: &mut DisplayListBuilder,
1933        state: &TraversalState,
1934        painter: &BackgroundPainter,
1935    ) {
1936        let style = painter.style;
1937        let b = style.get_background();
1938        let need_blend_container = b
1939            .background_blend_mode
1940            .0
1941            .iter()
1942            .take(b.background_image.0.len())
1943            .any(|background_blend_mode| background_blend_mode != &BackgroundBlendMode::Normal);
1944
1945        let push_stacking_context = |builder: &mut DisplayListBuilder,
1946                                     blend_mode: BackgroundBlendMode,
1947                                     flags: StackingContextFlags|
1948         -> bool {
1949            let spatial_id = builder.spatial_id(state.spatial_id);
1950            builder.wr().push_stacking_context(
1951                spatial_id,
1952                PrimitiveFlags::default(),
1953                None,
1954                TransformStyle::Flat,
1955                blend_mode.to_webrender(),
1956                &[],
1957                &[],
1958                RasterSpace::Screen,
1959                flags,
1960                None,
1961            );
1962            true
1963        };
1964
1965        if need_blend_container {
1966            push_stacking_context(
1967                builder,
1968                BackgroundBlendMode::Normal,
1969                StackingContextFlags::IS_BLEND_CONTAINER,
1970            );
1971        }
1972
1973        let node = self.fragment.base.tag.map(|tag| tag.node);
1974        // Reverse because the property is top layer first, we want to paint bottom layer first.
1975        for (index, image) in b.background_image.0.iter().enumerate().rev() {
1976            let Ok(resolved_image) = builder.image_resolver.resolve_image(node, image) else {
1977                continue;
1978            };
1979            match resolved_image {
1980                ResolvedImage::Gradient(_) | ResolvedImage::Color(_) => {
1981                    let intrinsic = NaturalSizes::empty();
1982                    let Some(layer) =
1983                        &background::layout_layer(self, painter, builder, state, index, intrinsic)
1984                    else {
1985                        continue;
1986                    };
1987
1988                    let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1989                    if needs_blending {
1990                        push_stacking_context(builder, layer.blend_mode, Default::default());
1991                    }
1992
1993                    match resolved_image {
1994                        ResolvedImage::Gradient(gradient) => {
1995                            match gradient::build(style, gradient, layer.tile_size, builder) {
1996                                WebRenderGradient::Linear(linear_gradient) => {
1997                                    builder.wr().push_gradient(
1998                                        &layer.common,
1999                                        layer.bounds,
2000                                        linear_gradient,
2001                                        layer.tile_size,
2002                                        layer.tile_spacing,
2003                                    )
2004                                },
2005                                WebRenderGradient::Radial(radial_gradient) => {
2006                                    builder.wr().push_radial_gradient(
2007                                        &layer.common,
2008                                        layer.bounds,
2009                                        radial_gradient,
2010                                        layer.tile_size,
2011                                        layer.tile_spacing,
2012                                    )
2013                                },
2014                                WebRenderGradient::Conic(conic_gradient) => {
2015                                    builder.wr().push_conic_gradient(
2016                                        &layer.common,
2017                                        layer.bounds,
2018                                        conic_gradient,
2019                                        layer.tile_size,
2020                                        layer.tile_spacing,
2021                                    )
2022                                },
2023                            }
2024                        },
2025                        ResolvedImage::Color(color) => {
2026                            let color = rgba(style.resolve_color(color));
2027                            builder.wr().push_rect(&layer.common, layer.bounds, color);
2028                        },
2029                        _ => {},
2030                    }
2031
2032                    if needs_blending {
2033                        builder.wr().pop_stacking_context();
2034                    }
2035
2036                    builder
2037                        .paint_timing_handler
2038                        .check_if_paintable(layer.bounds, state.accumulated_opacity);
2039                },
2040                ResolvedImage::Image { image, size, url } => {
2041                    // FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
2042                    let dppx = 1.0;
2043                    let intrinsic =
2044                        NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
2045                    let layer =
2046                        background::layout_layer(self, painter, builder, state, index, intrinsic);
2047
2048                    let scale = builder.device_pixel_ratio.get();
2049                    let default_size: DeviceIntSize =
2050                        Size2D::new(size.width * scale, size.height * scale).to_i32();
2051                    let preferred_size = layer.as_ref().map(|layer| {
2052                        Size2D::new(
2053                            layer.tile_size.width * scale,
2054                            layer.tile_size.height * scale,
2055                        )
2056                        .to_i32()
2057                    });
2058
2059                    let Some(image_key) = builder.image_resolver.image_key_from_cached_image(
2060                        &image,
2061                        preferred_size.unwrap_or(default_size),
2062                        node,
2063                    ) else {
2064                        continue;
2065                    };
2066
2067                    if let Some(layer) = layer {
2068                        let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
2069                        if needs_blending {
2070                            push_stacking_context(builder, layer.blend_mode, Default::default());
2071                        }
2072
2073                        if layer.repeat {
2074                            builder.wr().push_repeating_image(
2075                                &layer.common,
2076                                layer.bounds,
2077                                layer.tile_size,
2078                                layer.tile_spacing,
2079                                style.get_image_rendering().to_webrender(),
2080                                wr::AlphaType::PremultipliedAlpha,
2081                                image_key,
2082                                wr::ColorF::WHITE,
2083                            )
2084                        } else {
2085                            builder.wr().push_image(
2086                                &layer.common,
2087                                layer.bounds,
2088                                style.get_image_rendering().to_webrender(),
2089                                wr::AlphaType::PremultipliedAlpha,
2090                                image_key,
2091                                wr::ColorF::WHITE,
2092                            )
2093                        }
2094
2095                        if needs_blending {
2096                            builder.wr().pop_stacking_context();
2097                        }
2098
2099                        if builder
2100                            .paint_timing_handler
2101                            .check_if_paintable(layer.bounds, state.accumulated_opacity)
2102                        {
2103                            // From <https://www.w3.org/TR/paint-timing/#sec-terminology>:
2104                            // An element target is contentful when one or more of the following apply:
2105                            // > target has a background-image which is a contentful image, and its used
2106                            // > background-size has non-zero width and height values.
2107                            builder.mark_is_contentful();
2108
2109                            let natural_width = Some(Au::from_f32_px(size.width / dppx));
2110                            let natural_height = Some(Au::from_f32_px(size.height / dppx));
2111                            builder.collect_image_record(
2112                                state,
2113                                layer.bounds,
2114                                layer.common.clip_rect,
2115                                self.fragment.base.tag,
2116                                Some(url),
2117                                natural_width,
2118                                natural_height,
2119                                image.load_time(),
2120                            );
2121
2122                            builder.collect_container_timing_record(
2123                                state,
2124                                &state.container_timing_roots_including(self.fragment),
2125                                self.fragment.base.tag.or(state.containing_element_tag),
2126                                layer.bounds,
2127                                layer.common.clip_rect,
2128                                natural_width,
2129                                natural_height,
2130                            );
2131                        }
2132                    }
2133                },
2134            }
2135        }
2136
2137        if need_blend_container {
2138            builder.wr().pop_stacking_context();
2139        }
2140    }
2141
2142    fn build_border_side(&self, style_color: BorderStyleColor) -> wr::BorderSide {
2143        wr::BorderSide {
2144            color: rgba(style_color.color),
2145            style: match style_color.style {
2146                BorderStyle::None => wr::BorderStyle::None,
2147                BorderStyle::Solid => wr::BorderStyle::Solid,
2148                BorderStyle::Double => wr::BorderStyle::Double,
2149                BorderStyle::Dotted => wr::BorderStyle::Dotted,
2150                BorderStyle::Dashed => wr::BorderStyle::Dashed,
2151                BorderStyle::Hidden => wr::BorderStyle::Hidden,
2152                BorderStyle::Groove => wr::BorderStyle::Groove,
2153                BorderStyle::Ridge => wr::BorderStyle::Ridge,
2154                BorderStyle::Inset => wr::BorderStyle::Inset,
2155                BorderStyle::Outset => wr::BorderStyle::Outset,
2156            },
2157        }
2158    }
2159
2160    fn build_collapsed_table_borders(
2161        &self,
2162        builder: &mut DisplayListBuilder,
2163        state: &TraversalState,
2164    ) {
2165        if self
2166            .fragment
2167            .base
2168            .flags
2169            .contains(FragmentFlags::DO_NOT_PAINT)
2170        {
2171            return;
2172        }
2173
2174        let layout_info = self.fragment.specific_layout_info();
2175        let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
2176            layout_info.as_deref()
2177        else {
2178            return;
2179        };
2180        let mut common =
2181            builder.common_properties(state, units::LayoutRect::default(), self.fragment.style());
2182        let radius = wr::BorderRadius::default();
2183        let mut column_sum = Au::zero();
2184        for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
2185            let mut row_sum = Au::zero();
2186            for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
2187                let left_border = &table_info.collapsed_borders.x[x][y];
2188                let right_border = &table_info.collapsed_borders.x[x + 1][y];
2189                let top_border = &table_info.collapsed_borders.y[y][x];
2190                let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
2191                let details = wr::BorderDetails::Normal(wr::NormalBorder {
2192                    left: self.build_border_side(left_border.style_color.clone()),
2193                    right: self.build_border_side(right_border.style_color.clone()),
2194                    top: self.build_border_side(top_border.style_color.clone()),
2195                    bottom: self.build_border_side(bottom_border.style_color.clone()),
2196                    radius,
2197                    do_aa: true,
2198                });
2199                let mut border_widths = PhysicalSides::new(
2200                    top_border.width,
2201                    right_border.width,
2202                    bottom_border.width,
2203                    left_border.width,
2204                );
2205                let left_adjustment = if x == 0 {
2206                    -border_widths.left / 2
2207                } else {
2208                    std::mem::take(&mut border_widths.left) / 2
2209                };
2210                let top_adjustment = if y == 0 {
2211                    -border_widths.top / 2
2212                } else {
2213                    std::mem::take(&mut border_widths.top) / 2
2214                };
2215                let origin =
2216                    PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
2217                let size = PhysicalSize::new(
2218                    *column_size - left_adjustment + border_widths.right / 2,
2219                    *row_size - top_adjustment + border_widths.bottom / 2,
2220                );
2221                let border_rect = PhysicalRect::new(origin, size)
2222                    .translate(self.fragment.content_rect().origin.to_vector())
2223                    .translate(self.containing_block_origin.to_vector())
2224                    .to_webrender();
2225                common.clip_rect = border_rect;
2226                builder.wr().push_border(
2227                    &common,
2228                    border_rect,
2229                    border_widths.to_webrender(),
2230                    details,
2231                );
2232                row_sum += *row_size;
2233            }
2234            column_sum += *column_size;
2235        }
2236    }
2237
2238    fn build_border(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2239        if self.fragment.has_collapsed_borders() {
2240            // Avoid painting borders for tables and table parts in collapsed-borders mode,
2241            // since the resulting collapsed borders are painted on their own in a special way.
2242            return;
2243        }
2244
2245        let style = self.fragment.style();
2246        let border = style.get_border();
2247        let border_widths = self.fragment.border.to_webrender();
2248
2249        if border_widths == SideOffsets2D::zero() {
2250            return;
2251        }
2252
2253        // `border-image` replaces an element's border entirely.
2254        if self.build_border_image(builder, state, border, border_widths) {
2255            return;
2256        }
2257
2258        let current_color = style.get_inherited_text().get_color();
2259        let style_color = BorderStyleColor::from_border(border, current_color);
2260        let details = wr::BorderDetails::Normal(wr::NormalBorder {
2261            top: self.build_border_side(style_color.top),
2262            right: self.build_border_side(style_color.right),
2263            bottom: self.build_border_side(style_color.bottom),
2264            left: self.build_border_side(style_color.left),
2265            radius: self.border_radius(),
2266            do_aa: true,
2267        });
2268        let common = builder.common_properties(state, self.border_rect, style);
2269        builder
2270            .wr()
2271            .push_border(&common, self.border_rect, border_widths, details)
2272    }
2273
2274    /// Add a display item for image borders if necessary.
2275    fn build_border_image(
2276        &self,
2277        builder: &mut DisplayListBuilder,
2278        state: &TraversalState,
2279        border: &Border,
2280        border_widths: SideOffsets2D<f32, LayoutPixel>,
2281    ) -> bool {
2282        let style = self.fragment.style();
2283        let border_style_struct = style.get_border();
2284        let border_image_outset =
2285            resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
2286        let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
2287        let border_image_size = border_image_area.size;
2288        let border_image_widths = resolve_border_image_width(
2289            &border_style_struct.border_image_width,
2290            border_widths,
2291            border_image_size,
2292        );
2293        let border_image_repeat = &border_style_struct.border_image_repeat;
2294        let border_image_fill = border_style_struct.border_image_slice.fill;
2295        let border_image_slice = &border_style_struct.border_image_slice.offsets;
2296        let common = builder.common_properties(state, border_image_area.to_box2d(), style);
2297
2298        let stops = Vec::new();
2299        let mut width = border_image_size.width;
2300        let mut height = border_image_size.height;
2301        let node = self.fragment.base.tag.map(|tag| tag.node);
2302        let source = match builder
2303            .image_resolver
2304            .resolve_image(node, &border.border_image_source)
2305        {
2306            Err(_) => return false,
2307            Ok(ResolvedImage::Image { image, size, .. }) => {
2308                let scale = builder.device_pixel_ratio.get();
2309                let raster_size = Size2D::new(size.width * scale, size.height * scale).to_i32();
2310                let Some(key) =
2311                    builder
2312                        .image_resolver
2313                        .image_key_from_cached_image(&image, raster_size, node)
2314                else {
2315                    return false;
2316                };
2317
2318                if builder.paint_timing_handler.check_if_paintable(
2319                    Box2D::from_size(size.cast_unit()),
2320                    state.accumulated_opacity,
2321                ) {
2322                    // From <https://www.w3.org/TR/paint-timing/#contentful>:
2323                    // An element target is contentful when one or more of the following apply:
2324                    // > target has a background-image which is a contentful image,
2325                    // > and its used background-size has non-zero width and height values.
2326                    builder.mark_is_contentful();
2327                }
2328
2329                width = size.width;
2330                height = size.height;
2331                let image_rendering = style.get_image_rendering().to_webrender();
2332                NinePatchBorderSource::Image(key, image_rendering)
2333            },
2334            Ok(ResolvedImage::Gradient(gradient)) => {
2335                match gradient::build(style, gradient, border_image_size, builder) {
2336                    WebRenderGradient::Linear(gradient) => {
2337                        NinePatchBorderSource::Gradient(gradient)
2338                    },
2339                    WebRenderGradient::Radial(gradient) => {
2340                        NinePatchBorderSource::RadialGradient(gradient)
2341                    },
2342                    WebRenderGradient::Conic(gradient) => {
2343                        NinePatchBorderSource::ConicGradient(gradient)
2344                    },
2345                }
2346            },
2347            Ok(ResolvedImage::Color(color)) => {
2348                // NinePatchBorderSource doesn't support a lone color, so pretend that
2349                // its a linear gradient.
2350                let color = rgba(style.resolve_color(color));
2351                let gradient = builder.wr().create_gradient(
2352                    Point2D::zero(),
2353                    Point2D::zero(),
2354                    vec![
2355                        wr::GradientStop { offset: 0.0, color },
2356                        wr::GradientStop { offset: 1.0, color },
2357                    ],
2358                    wr::ExtendMode::Clamp,
2359                );
2360                NinePatchBorderSource::Gradient(gradient)
2361            },
2362        };
2363
2364        let size = Size2D::new(width as i32, height as i32);
2365
2366        // If the size of the border is zero or the size of the border image is zero, just
2367        // don't render anything. Zero-sized gradients cause problems in WebRender.
2368        if size.is_empty() || border_image_size.is_empty() {
2369            return true;
2370        }
2371
2372        let details = BorderDetails::NinePatch(NinePatchBorder {
2373            source,
2374            width: size.width,
2375            height: size.height,
2376            slice: resolve_border_image_slice(border_image_slice, size),
2377            fill: border_image_fill,
2378            repeat_horizontal: border_image_repeat.0.to_webrender(),
2379            repeat_vertical: border_image_repeat.1.to_webrender(),
2380        });
2381        builder.wr().push_border(
2382            &common,
2383            border_image_area.to_box2d(),
2384            border_image_widths,
2385            details,
2386        );
2387        builder.wr().push_stops(&stops);
2388        true
2389    }
2390
2391    fn build_outline(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2392        let style = self.fragment.style();
2393        let outline = style.get_outline();
2394        if outline.outline_style.none_or_hidden() {
2395            return;
2396        }
2397        let width = outline.outline_width.0.to_f32_px();
2398        if width == 0.0 {
2399            return;
2400        }
2401        // <https://drafts.csswg.org/css-ui-3/#outline-offset>
2402        // > Negative values must cause the outline to shrink into the border box. Both
2403        // > the height and the width of outside of the shape drawn by the outline should
2404        // > not become smaller than twice the computed value of the outline-width
2405        // > property, to make sure that an outline can be rendered even with large
2406        // > negative values. User agents should apply this constraint independently in
2407        // > each dimension. If the outline is drawn as multiple disconnected shapes, this
2408        // > constraint applies to each shape separately.
2409        let offset = outline.outline_offset.to_f32_px() + width;
2410        let outline_rect = self.border_rect.inflate(
2411            offset.max(-self.border_rect.width() / 2.0 + width),
2412            offset.max(-self.border_rect.height() / 2.0 + width),
2413        );
2414        let common = builder.common_properties(state, outline_rect, style);
2415        let widths = SideOffsets2D::new_all_same(width);
2416        let border_style = match outline.outline_style {
2417            // TODO: treating 'auto' as 'solid' is allowed by the spec,
2418            // but we should do something better.
2419            OutlineStyle::Auto => BorderStyle::Solid,
2420            OutlineStyle::BorderStyle(s) => s,
2421        };
2422        let side = self.build_border_side(BorderStyleColor {
2423            style: border_style,
2424            color: style.resolve_color(&outline.outline_color),
2425        });
2426        let details = wr::BorderDetails::Normal(wr::NormalBorder {
2427            top: side,
2428            right: side,
2429            bottom: side,
2430            left: side,
2431            radius: offset_radii(self.border_radius(), SideOffsets2D::new_all_same(offset)),
2432            do_aa: true,
2433        });
2434        builder
2435            .wr()
2436            .push_border(&common, outline_rect, widths, details)
2437    }
2438
2439    fn build_box_shadow(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2440        let style = self.fragment.style();
2441        let box_shadows = &style.get_effects().box_shadow.0;
2442        if box_shadows.is_empty() {
2443            return;
2444        }
2445
2446        // Note: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
2447        for box_shadow in box_shadows.iter().rev() {
2448            let (rect, clip_mode) = if box_shadow.inset {
2449                (*self.padding_rect(), BoxShadowClipMode::Inset)
2450            } else {
2451                (self.border_rect, BoxShadowClipMode::Outset)
2452            };
2453
2454            let offset = LayoutVector2D::new(
2455                box_shadow.base.horizontal.px(),
2456                box_shadow.base.vertical.px(),
2457            );
2458            let spread = box_shadow.spread.px();
2459            let blur = box_shadow.base.blur.px();
2460            let clip_rect = match clip_mode {
2461                // Inset shadows are always inside the rect.
2462                BoxShadowClipMode::Inset => rect,
2463                // Match webrender's box_shadow.rs Gaussian blur inflation.
2464                // (BLUR_SAMPLE_SCALE * blur).ceil(). BLUR_SAMPLE_SCALE is 3.0.
2465                BoxShadowClipMode::Outset => {
2466                    let extra_size_from_blur = (blur * 3.0).ceil();
2467                    rect.translate(offset)
2468                        .inflate(spread, spread)
2469                        .inflate(extra_size_from_blur, extra_size_from_blur)
2470                },
2471            };
2472            let border_radius = match clip_mode {
2473                BoxShadowClipMode::Inset => {
2474                    // The `border-radius` value applies to the border box, but inset shadows
2475                    // use the padding box instead. So we need to shrink the `border-radius`
2476                    // by the border widths.
2477                    offset_radii(self.border_radius(), -self.fragment.border.to_webrender())
2478                },
2479                BoxShadowClipMode::Outset => self.border_radius(),
2480            };
2481            let shadow_radius = offset_radii(
2482                border_radius,
2483                SideOffsets2D::new_all_same(match clip_mode {
2484                    BoxShadowClipMode::Inset => -spread,
2485                    BoxShadowClipMode::Outset => spread,
2486                }),
2487            );
2488            let common = builder.common_properties(state, clip_rect, style);
2489            builder.wr().push_box_shadow(
2490                &common,
2491                rect,
2492                offset,
2493                rgba(style.resolve_color(&box_shadow.base.color)),
2494                blur,
2495                spread,
2496                border_radius,
2497                shadow_radius,
2498                clip_mode,
2499            );
2500        }
2501    }
2502}
2503
2504fn rgba(color: AbsoluteColor) -> wr::ColorF {
2505    let rgba = color.to_color_space(ColorSpace::Srgb);
2506    wr::ColorF::new(
2507        rgba.components.0.clamp(0.0, 1.0),
2508        rgba.components.1.clamp(0.0, 1.0),
2509        rgba.components.2.clamp(0.0, 1.0),
2510        rgba.alpha.clamp(0.0, 1.0),
2511    )
2512}
2513
2514/// Clear `glyphs` and fill it with a [`GlyphInstance`] for every glyph in the given
2515/// `shaped_text_slices`. Return a tuple that contains:
2516///
2517/// - The measure of the largest advance
2518/// - A boolean which is true if the slices only contained white space.
2519fn collect_glyphs(
2520    glyphs: &mut Vec<GlyphInstance>,
2521    shaped_text_slices: &[Arc<ShapedTextSlice>],
2522    mut baseline_origin: PhysicalPoint<Au>,
2523    justification_adjustment: Au,
2524) -> (Au, bool) {
2525    glyphs.clear();
2526    let mut largest_advance = Au::zero();
2527    let mut entirely_white_space = true;
2528
2529    for shaped_text_slice in shaped_text_slices {
2530        entirely_white_space &= shaped_text_slice.all_white_space();
2531        for glyph in shaped_text_slice.glyphs() {
2532            let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2533            let point = LayoutPoint::new(
2534                baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
2535                baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
2536            );
2537            let glyph_instance = GlyphInstance {
2538                index: glyph.id(),
2539                point,
2540            };
2541            glyphs.push(glyph_instance);
2542
2543            if glyph.char_is_word_separator() {
2544                baseline_origin.x += justification_adjustment;
2545            }
2546
2547            let advance = glyph.advance();
2548            baseline_origin.x += advance;
2549            largest_advance.max_assign(advance);
2550        }
2551    }
2552    (largest_advance, entirely_white_space)
2553}
2554
2555/// Given a set of corner radii for a rectangle, this function returns the corresponding radii
2556/// for the [outer rectangle][`Rect::outer_rect`] resulting from expanding the original
2557/// rectangle by the given offsets.
2558fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
2559    let expand = |radius: &mut f32, offset: f32| {
2560        // For negative offsets, just shrink the radius by that amount.
2561        if offset < 0.0 {
2562            *radius = (*radius + offset).max(0.0);
2563            return;
2564        }
2565
2566        // For positive offsets, expand the radius by that amount. But only if the
2567        // radius is positive, in order to preserve sharp corners.
2568        // TODO: this behavior is not continuous, we should use this algorithm instead:
2569        // https://github.com/w3c/csswg-drafts/issues/7103#issuecomment-3357331922
2570        if *radius > 0.0 {
2571            *radius += offset;
2572        }
2573    };
2574    if offsets.left != 0.0 {
2575        expand(&mut radii.top_left.width, offsets.left);
2576        expand(&mut radii.bottom_left.width, offsets.left);
2577    }
2578    if offsets.right != 0.0 {
2579        expand(&mut radii.top_right.width, offsets.right);
2580        expand(&mut radii.bottom_right.width, offsets.right);
2581    }
2582    if offsets.top != 0.0 {
2583        expand(&mut radii.top_left.height, offsets.top);
2584        expand(&mut radii.top_right.height, offsets.top);
2585    }
2586    if offsets.bottom != 0.0 {
2587        expand(&mut radii.bottom_right.height, offsets.bottom);
2588        expand(&mut radii.bottom_left.height, offsets.bottom);
2589    }
2590    radii
2591}
2592
2593/// Resolve the WebRender border-image outset area from the style values.
2594fn resolve_border_image_outset(
2595    outset: BorderImageOutset,
2596    border: SideOffsets2D<f32, LayoutPixel>,
2597) -> SideOffsets2D<f32, LayoutPixel> {
2598    fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2599        match outset {
2600            NonNegativeLengthOrNumber::Length(length) => length.px(),
2601            NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2602        }
2603    }
2604
2605    SideOffsets2D::new(
2606        image_outset_for_side(outset.0, border.top),
2607        image_outset_for_side(outset.1, border.right),
2608        image_outset_for_side(outset.2, border.bottom),
2609        image_outset_for_side(outset.3, border.left),
2610    )
2611}
2612
2613/// Resolve the WebRender border-image width from the style values.
2614fn resolve_border_image_width(
2615    width: &BorderImageWidth,
2616    border: SideOffsets2D<f32, LayoutPixel>,
2617    border_area: Size2D<f32, LayoutPixel>,
2618) -> SideOffsets2D<f32, LayoutPixel> {
2619    fn image_width_for_side(
2620        border_image_width: &BorderImageSideWidth,
2621        border_width: f32,
2622        total_length: f32,
2623    ) -> f32 {
2624        match border_image_width {
2625            BorderImageSideWidth::LengthPercentage(v) => {
2626                v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2627            },
2628            BorderImageSideWidth::Number(x) => border_width * x.0,
2629            BorderImageSideWidth::Auto => border_width,
2630        }
2631    }
2632
2633    SideOffsets2D::new(
2634        image_width_for_side(&width.0, border.top, border_area.height),
2635        image_width_for_side(&width.1, border.right, border_area.width),
2636        image_width_for_side(&width.2, border.bottom, border_area.height),
2637        image_width_for_side(&width.3, border.left, border_area.width),
2638    )
2639}
2640
2641/// Resolve the WebRender border-image slice from the style values.
2642fn resolve_border_image_slice(
2643    border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2644    size: Size2D<i32, UnknownUnit>,
2645) -> SideOffsets2D<i32, DevicePixel> {
2646    fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2647        match value.0 {
2648            NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2649            NumberOrPercentage::Number(n) => n.round() as i32,
2650        }
2651    }
2652
2653    SideOffsets2D::new(
2654        resolve_percentage(border_image_slice.0, size.height),
2655        resolve_percentage(border_image_slice.1, size.width),
2656        resolve_percentage(border_image_slice.2, size.height),
2657        resolve_percentage(border_image_slice.3, size.width),
2658    )
2659}
2660
2661pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2662    // Normalize radii that add up to > 100%.
2663    // https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
2664    // > Let f = min(L_i/S_i), where i ∈ {top, right, bottom, left},
2665    // > S_i is the sum of the two corresponding radii of the corners on side i,
2666    // > and L_top = L_bottom = the width of the box,
2667    // > and L_left = L_right = the height of the box.
2668    // > If f < 1, then all corner radii are reduced by multiplying them by f.
2669    let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2670        .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2671        .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2672        .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2673    if f < 1.0 {
2674        radius.top_left *= f;
2675        radius.top_right *= f;
2676        radius.bottom_right *= f;
2677        radius.bottom_left *= f;
2678    }
2679}
2680
2681/// <https://drafts.csswg.org/css-shapes-1/#valdef-shape-box-margin-box>
2682/// > The corner radii of this shape are determined by the corresponding
2683/// > border-radius and margin values. If the ratio of border-radius/margin is 1 or more,
2684/// > or margin is negative or zero, then the margin box corner radius is
2685/// > max(border-radius + margin, 0). If the ratio of border-radius/margin is less than 1,
2686/// > and margin is positive, then the margin box corner radius is
2687/// > border-radius + margin * (1 + (ratio-1)^3).
2688pub(super) fn compute_margin_box_radius(
2689    radius: wr::BorderRadius,
2690    layout_rect: LayoutSize,
2691    fragment: &BoxFragment,
2692) -> wr::BorderRadius {
2693    let style = fragment.style();
2694    let margin = style.physical_margin();
2695    let adjust_radius = |radius: f32, margin: f32| -> f32 {
2696        if margin <= 0. || (radius / margin) >= 1. {
2697            (radius + margin).max(0.)
2698        } else {
2699            radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2700        }
2701    };
2702    let compute_margin_radius = |radius: LayoutSize,
2703                                 layout_rect: LayoutSize,
2704                                 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2705     -> LayoutSize {
2706        let zero = LengthPercentage::zero();
2707        let width = margin
2708            .width
2709            .auto_is(|| &zero)
2710            .to_used_value(Au::from_f32_px(layout_rect.width));
2711        let height = margin
2712            .height
2713            .auto_is(|| &zero)
2714            .to_used_value(Au::from_f32_px(layout_rect.height));
2715        LayoutSize::new(
2716            adjust_radius(radius.width, width.to_f32_px()),
2717            adjust_radius(radius.height, height.to_f32_px()),
2718        )
2719    };
2720    wr::BorderRadius {
2721        top_left: compute_margin_radius(
2722            radius.top_left,
2723            layout_rect,
2724            Size2D::new(margin.left, margin.top),
2725        ),
2726        top_right: compute_margin_radius(
2727            radius.top_right,
2728            layout_rect,
2729            Size2D::new(margin.right, margin.top),
2730        ),
2731        bottom_left: compute_margin_radius(
2732            radius.bottom_left,
2733            layout_rect,
2734            Size2D::new(margin.left, margin.bottom),
2735        ),
2736        bottom_right: compute_margin_radius(
2737            radius.bottom_right,
2738            layout_rect,
2739            Size2D::new(margin.right, margin.bottom),
2740        ),
2741    }
2742}
2743
2744impl BoxFragment {
2745    fn border_radius(&self) -> BorderRadius {
2746        let style = self.style();
2747        let border = style.get_border();
2748        if border.border_top_left_radius.0.is_zero() &&
2749            border.border_top_right_radius.0.is_zero() &&
2750            border.border_bottom_right_radius.0.is_zero() &&
2751            border.border_bottom_left_radius.0.is_zero()
2752        {
2753            return BorderRadius::zero();
2754        }
2755
2756        let border_rect = self.border_rect();
2757        let resolve =
2758            |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2759        let corner = |corner: &style::values::computed::BorderCornerRadius| {
2760            Size2D::new(
2761                resolve(&corner.0.width.0, border_rect.size.width),
2762                resolve(&corner.0.height.0, border_rect.size.height),
2763            )
2764        };
2765
2766        let mut radius = wr::BorderRadius {
2767            top_left: corner(&border.border_top_left_radius),
2768            top_right: corner(&border.border_top_right_radius),
2769            bottom_right: corner(&border.border_bottom_right_radius),
2770            bottom_left: corner(&border.border_bottom_left_radius),
2771        };
2772
2773        normalize_radii(&border_rect.to_webrender(), &mut radius);
2774        radius
2775    }
2776}
2777
2778impl BaseFragment {
2779    fn visit_fragment(&self, builder: &mut DisplayListBuilder) {
2780        match self.status() {
2781            FragmentStatus::New => {
2782                builder.reflow_statistics.rebuilt_fragment_count += 1;
2783                self.set_status(FragmentStatus::Clean)
2784            },
2785            FragmentStatus::StyleChanged => {
2786                builder.reflow_statistics.restyle_fragment_count += 1;
2787                self.set_status(FragmentStatus::Clean)
2788            },
2789            FragmentStatus::OnlyDescendantsChanged => {
2790                builder.reflow_statistics.only_descendants_changed_count += 1;
2791                self.set_status(FragmentStatus::Clean)
2792            },
2793            FragmentStatus::Clean => {},
2794        }
2795    }
2796}