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