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