Skip to main content

layout/display_list/
mod.rs

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