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