Skip to main content

layout/display_list/
mod.rs

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