layout/display_list/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use base::id::ScrollTreeNodeId;
10use clip::{Clip, ClipId};
11use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo};
12use compositing_traits::largest_contentful_paint_candidate::{
13    LCPCandidateID, LargestContentfulPaintType,
14};
15use euclid::{Point2D, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
16use fonts::GlyphStore;
17use gradient::WebRenderGradient;
18use net_traits::image_cache::Image as CachedImage;
19use range::Range as ServoRange;
20use servo_arc::Arc as ServoArc;
21use servo_config::opts::DebugOptions;
22use servo_geometry::MaxRect;
23use style::Zero;
24use style::color::{AbsoluteColor, ColorSpace};
25use style::computed_values::border_image_outset::T as BorderImageOutset;
26use style::computed_values::text_decoration_style::{
27    T as ComputedTextDecorationStyle, T as TextDecorationStyle,
28};
29use style::dom::OpaqueNode;
30use style::properties::ComputedValues;
31use style::properties::longhands::visibility::computed_value::T as Visibility;
32use style::properties::style_structs::Border;
33use style::values::computed::{
34    BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
35    NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
36};
37use style::values::generics::NonNegative;
38use style::values::generics::rect::Rect;
39use style::values::specified::text::TextDecorationLine;
40use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
41use webrender_api::units::{
42    DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSideOffsets, LayoutSize,
43};
44use webrender_api::{
45    self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
46    ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, NinePatchBorder,
47    NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding, SpatialId,
48    SpatialTreeItemKey, units,
49};
50use wr::units::LayoutVector2D;
51
52use crate::cell::ArcRefCell;
53use crate::context::{ImageResolver, ResolvedImage};
54pub(crate) use crate::display_list::conversions::ToWebRender;
55use crate::display_list::stacking_context::StackingContextSection;
56use crate::fragment_tree::{
57    BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
58    TextFragment,
59};
60use crate::geom::{
61    LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
62};
63use crate::replaced::NaturalSizes;
64use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
65
66mod background;
67mod clip;
68mod conversions;
69mod gradient;
70mod hit_test;
71mod largest_contenful_paint_candidate_collector;
72mod stacking_context;
73
74use background::BackgroundPainter;
75pub(crate) use hit_test::HitTest;
76pub(crate) use largest_contenful_paint_candidate_collector::LargestContentfulPaintCandidateCollector;
77pub(crate) use stacking_context::*;
78
79const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
80
81pub(crate) struct DisplayListBuilder<'a> {
82    /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This
83    /// allows only passing the builder instead passing the containing
84    /// [stacking_context::StackingContextContent::Fragment] as an argument to display
85    /// list building functions.
86    current_scroll_node_id: ScrollTreeNodeId,
87
88    /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This is necessary in addition
89    /// to the [Self::current_scroll_node_id], because some pieces of fragments as backgrounds with
90    /// `background-attachment: fixed` need to not scroll while the rest of the fragment does.
91    current_reference_frame_scroll_node_id: ScrollTreeNodeId,
92
93    /// The current [`ClipId`] for this [DisplayListBuilder]. This allows
94    /// only passing the builder instead passing the containing
95    /// [stacking_context::StackingContextContent::Fragment] as an argument to display
96    /// list building functions.
97    current_clip_id: ClipId,
98
99    /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`].
100    pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
101
102    /// The [`CompositorDisplayListInfo`] used to collect display list items and metadata.
103    pub compositor_info: &'a mut CompositorDisplayListInfo,
104
105    /// Data about the fragments that are highlighted by the inspector, if any.
106    ///
107    /// This data is collected during the traversal of the fragment tree and used
108    /// to paint the highlight at the very end.
109    inspector_highlight: Option<InspectorHighlight>,
110
111    /// Whether or not the `<body>` element should be painted. This is false if the root `<html>`
112    /// element inherits the `<body>`'s background to paint the page canvas background.
113    /// See <https://drafts.csswg.org/css-backgrounds/#body-background>.
114    paint_body_background: bool,
115
116    /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender
117    /// display list.
118    clip_map: Vec<ClipChainId>,
119
120    /// An [`ImageResolver`] to use during display list construction.
121    image_resolver: Arc<ImageResolver>,
122
123    /// The device pixel ratio used for this `Document`'s display list.
124    device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
125
126    /// The collector for calculating Largest Contentful Paint
127    lcp_candidate_collector: Option<&'a mut LargestContentfulPaintCandidateCollector>,
128}
129
130struct InspectorHighlight {
131    /// The node that should be highlighted
132    tag: Tag,
133
134    /// Accumulates information about the fragments that belong to the highlighted node.
135    ///
136    /// This information is collected as the fragment tree is traversed to build the
137    /// display list.
138    state: Option<HighlightTraversalState>,
139}
140
141struct HighlightTraversalState {
142    /// The smallest rectangle that fully encloses all fragments created by the highlighted
143    /// dom node, if any.
144    content_box: euclid::Rect<Au, StyloCSSPixel>,
145
146    spatial_id: SpatialId,
147
148    clip_chain_id: ClipChainId,
149
150    /// When the highlighted fragment is a box fragment we remember the information
151    /// needed to paint padding, border and margin areas.
152    maybe_box_fragment: Option<ArcRefCell<BoxFragment>>,
153}
154
155impl InspectorHighlight {
156    fn for_node(node: OpaqueNode) -> Self {
157        Self {
158            tag: Tag {
159                node,
160                // TODO: Support highlighting pseudo-elements.
161                pseudo_element_chain: Default::default(),
162            },
163            state: None,
164        }
165    }
166}
167
168impl DisplayListBuilder<'_> {
169    pub(crate) fn build(
170        stacking_context_tree: &mut StackingContextTree,
171        fragment_tree: &FragmentTree,
172        image_resolver: Arc<ImageResolver>,
173        device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
174        highlighted_dom_node: Option<OpaqueNode>,
175        debug: &DebugOptions,
176        lcp_candidate_collector: Option<&mut LargestContentfulPaintCandidateCollector>,
177    ) -> BuiltDisplayList {
178        // Build the rest of the display list which inclues all of the WebRender primitives.
179        let compositor_info = &mut stacking_context_tree.compositor_info;
180        let pipeline_id = compositor_info.pipeline_id;
181        let mut webrender_display_list_builder =
182            webrender_api::DisplayListBuilder::new(pipeline_id);
183        webrender_display_list_builder.begin();
184
185        // `dump_serialized_display_list` doesn't actually print anything. It sets up
186        // the display list for printing the serialized version when `finalize()` is called.
187        // We need to call this before adding any display items so that they are printed
188        // during `finalize()`.
189        if debug.dump_display_list {
190            webrender_display_list_builder.dump_serialized_display_list();
191        }
192
193        #[cfg(feature = "tracing")]
194        let _span =
195            tracing::trace_span!("DisplayListBuilder::build", servo_profiling = true).entered();
196        let mut builder = DisplayListBuilder {
197            current_scroll_node_id: compositor_info.root_reference_frame_id,
198            current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id,
199            current_clip_id: ClipId::INVALID,
200            webrender_display_list_builder: &mut webrender_display_list_builder,
201            compositor_info,
202            inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
203            paint_body_background: true,
204            clip_map: Default::default(),
205            image_resolver,
206            device_pixel_ratio,
207            lcp_candidate_collector,
208        };
209
210        builder.add_all_spatial_nodes();
211
212        for clip in stacking_context_tree.clip_store.0.iter() {
213            builder.add_clip_to_display_list(clip);
214        }
215
216        // Add a single hit test that covers the entire viewport, so that WebRender knows
217        // which pipeline it hits when doing hit testing.
218        let pipeline_id = builder.compositor_info.pipeline_id;
219        let viewport_size = builder.compositor_info.viewport_details.size;
220        let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
221        builder.wr().push_hit_test(
222            viewport_rect,
223            ClipChainId::INVALID,
224            SpatialId::root_reference_frame(pipeline_id),
225            PrimitiveFlags::default(),
226            (0, 0), /* tag */
227        );
228
229        // Paint the canvas’ background (if any) before/under everything else
230        stacking_context_tree
231            .root_stacking_context
232            .build_canvas_background_display_list(&mut builder, fragment_tree);
233        stacking_context_tree
234            .root_stacking_context
235            .build_display_list(&mut builder);
236        builder.paint_dom_inspector_highlight();
237
238        webrender_display_list_builder.end().1
239    }
240
241    fn wr(&mut self) -> &mut wr::DisplayListBuilder {
242        self.webrender_display_list_builder
243    }
244
245    fn pipeline_id(&mut self) -> wr::PipelineId {
246        self.compositor_info.pipeline_id
247    }
248
249    fn mark_is_contentful(&mut self) {
250        self.compositor_info.is_contentful = true;
251    }
252
253    fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
254        self.compositor_info.scroll_tree.webrender_id(id)
255    }
256
257    fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
258        match id {
259            ClipId::INVALID => ClipChainId::INVALID,
260            _ => *self
261                .clip_map
262                .get(id.0)
263                .expect("Should never try to get clip before adding it to WebRender display list"),
264        }
265    }
266
267    pub(crate) fn add_all_spatial_nodes(&mut self) {
268        // A count of the number of SpatialTree nodes pushed to the WebRender display
269        // list. This is merely to ensure that the currently-unused SpatialTreeItemKey
270        // produced for every SpatialTree node is unique.
271        let mut spatial_tree_count = 0;
272        let mut scroll_tree = std::mem::take(&mut self.compositor_info.scroll_tree);
273        let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
274
275        mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
276        mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
277
278        let pipeline_id = self.pipeline_id();
279        let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
280
281        for node in scroll_tree.nodes.iter().skip(2) {
282            let parent_scroll_node_id = node
283                .parent
284                .expect("Should have already added root reference frame");
285            let parent_spatial_node_id = mapping
286                .get(parent_scroll_node_id.index)
287                .expect("Should add spatial nodes to display list in order");
288
289            // Produce a new SpatialTreeItemKey. This is currently unused by WebRender,
290            // but has to be unique to the entire scene.
291            spatial_tree_count += 1;
292            let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
293
294            mapping.push(match &node.info {
295                SpatialTreeNodeInfo::ReferenceFrame(info) => {
296                    let spatial_id = self.wr().push_reference_frame(
297                        info.origin,
298                        *parent_spatial_node_id,
299                        info.transform_style,
300                        PropertyBinding::Value(*info.transform.to_transform()),
301                        info.kind,
302                        spatial_tree_item_key,
303                    );
304                    self.wr().pop_reference_frame();
305                    spatial_id
306                },
307                SpatialTreeNodeInfo::Scroll(info) => {
308                    self.wr().define_scroll_frame(
309                        *parent_spatial_node_id,
310                        info.external_id,
311                        info.content_rect,
312                        info.clip_rect,
313                        LayoutVector2D::zero(), /* external_scroll_offset */
314                        0,                      /* scroll_offset_generation */
315                        wr::HasScrollLinkedEffect::No,
316                        spatial_tree_item_key,
317                    )
318                },
319                SpatialTreeNodeInfo::Sticky(info) => {
320                    self.wr().define_sticky_frame(
321                        *parent_spatial_node_id,
322                        info.frame_rect,
323                        info.margins,
324                        info.vertical_offset_bounds,
325                        info.horizontal_offset_bounds,
326                        LayoutVector2D::zero(), /* previously_applied_offset */
327                        spatial_tree_item_key,
328                        None, /* transform */
329                    )
330                },
331            });
332        }
333
334        scroll_tree.update_mapping(mapping);
335        self.compositor_info.scroll_tree = scroll_tree;
336    }
337
338    /// Add the given [`Clip`] to the WebRender display list and create a mapping from
339    /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens:
340    ///  - When WebRender display list construction starts: All clips created during the
341    ///    `StackingContextTree` construction are added in one batch. These clips are used
342    ///    for things such as `overflow: scroll` elements.
343    ///  - When a clip is added during WebRender display list construction for individual
344    ///    items. In that case, this is called by [`Self::maybe_create_clip`].
345    pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
346        assert_eq!(
347            clip.id.0,
348            self.clip_map.len(),
349            "Clips should be added in order"
350        );
351
352        let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
353        let new_clip_id = if clip.radii.is_zero() {
354            self.wr().define_clip_rect(spatial_id, clip.rect)
355        } else {
356            self.wr().define_clip_rounded_rect(
357                spatial_id,
358                ComplexClipRegion {
359                    rect: clip.rect,
360                    radii: clip.radii,
361                    mode: ClipMode::Clip,
362                },
363            )
364        };
365
366        // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
367        // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
368        // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
369        // leads to a crash.
370        let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
371            ClipChainId::INVALID => None,
372            parent => Some(parent),
373        };
374        let clip_chain_id = self
375            .wr()
376            .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
377        self.clip_map.push(clip_chain_id);
378        clip_chain_id
379    }
380
381    /// Add a new clip to the WebRender display list being built. This only happens during
382    /// WebRender display list building and these clips should be added after all clips
383    /// from the `StackingContextTree` have already been processed.
384    fn maybe_create_clip(
385        &mut self,
386        radii: wr::BorderRadius,
387        rect: units::LayoutRect,
388        force_clip_creation: bool,
389    ) -> Option<ClipChainId> {
390        if radii.is_zero() && !force_clip_creation {
391            return None;
392        }
393
394        Some(self.add_clip_to_display_list(&Clip {
395            id: ClipId(self.clip_map.len()),
396            radii,
397            rect,
398            parent_scroll_node_id: self.current_scroll_node_id,
399            parent_clip_id: self.current_clip_id,
400        }))
401    }
402
403    fn common_properties(
404        &self,
405        clip_rect: units::LayoutRect,
406        style: &ComputedValues,
407    ) -> wr::CommonItemProperties {
408        // TODO(mrobinson): We should take advantage of this field to pass hit testing
409        // information. This will allow us to avoid creating hit testing display items
410        // for fragments that paint their entire border rectangle.
411        wr::CommonItemProperties {
412            clip_rect,
413            spatial_id: self.spatial_id(self.current_scroll_node_id),
414            clip_chain_id: self.clip_chain_id(self.current_clip_id),
415            flags: style.get_webrender_primitive_flags(),
416        }
417    }
418
419    /// Draw highlights around the node that is currently hovered in the devtools.
420    fn paint_dom_inspector_highlight(&mut self) {
421        let Some(highlight) = self
422            .inspector_highlight
423            .take()
424            .and_then(|highlight| highlight.state)
425        else {
426            return;
427        };
428
429        const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
430            r: 0.23,
431            g: 0.7,
432            b: 0.87,
433            a: 0.5,
434        };
435
436        const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
437            r: 0.49,
438            g: 0.3,
439            b: 0.7,
440            a: 0.5,
441        };
442
443        const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
444            r: 0.2,
445            g: 0.2,
446            b: 0.2,
447            a: 0.5,
448        };
449
450        const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
451            r: 1.,
452            g: 0.93,
453            b: 0.,
454            a: 0.5,
455        };
456
457        // Highlight content box
458        let content_box = highlight.content_box.to_webrender();
459        let properties = wr::CommonItemProperties {
460            clip_rect: content_box,
461            spatial_id: highlight.spatial_id,
462            clip_chain_id: highlight.clip_chain_id,
463            flags: wr::PrimitiveFlags::default(),
464        };
465
466        self.wr()
467            .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
468
469        // Highlight margin, border and padding
470        if let Some(box_fragment) = highlight.maybe_box_fragment {
471            let mut paint_highlight =
472                |color: webrender_api::ColorF,
473                 fragment_relative_bounds: PhysicalRect<Au>,
474                 widths: webrender_api::units::LayoutSideOffsets| {
475                    if widths.is_zero() {
476                        return;
477                    }
478
479                    let bounds = box_fragment
480                        .borrow()
481                        .offset_by_containing_block(&fragment_relative_bounds)
482                        .to_webrender();
483
484                    // We paint each highlighted area as if it was a border for simplicity
485                    let border_style = wr::BorderSide {
486                        color,
487                        style: wr::BorderStyle::Solid,
488                    };
489
490                    let details = wr::BorderDetails::Normal(wr::NormalBorder {
491                        top: border_style,
492                        right: border_style,
493                        bottom: border_style,
494                        left: border_style,
495                        radius: webrender_api::BorderRadius::default(),
496                        do_aa: true,
497                    });
498
499                    let common = wr::CommonItemProperties {
500                        clip_rect: bounds,
501                        spatial_id: highlight.spatial_id,
502                        clip_chain_id: highlight.clip_chain_id,
503                        flags: wr::PrimitiveFlags::default(),
504                    };
505                    self.wr().push_border(&common, bounds, widths, details)
506                };
507
508            let box_fragment = box_fragment.borrow();
509            paint_highlight(
510                PADDING_BOX_HIGHLIGHT_COLOR,
511                box_fragment.padding_rect(),
512                box_fragment.padding.to_webrender(),
513            );
514            paint_highlight(
515                BORDER_BOX_HIGHLIGHT_COLOR,
516                box_fragment.border_rect(),
517                box_fragment.border.to_webrender(),
518            );
519            paint_highlight(
520                MARGIN_BOX_HIGHLIGHT_COLOR,
521                box_fragment.margin_rect(),
522                box_fragment.margin.to_webrender(),
523            );
524        }
525    }
526
527    #[inline]
528    fn collect_lcp_candidate(
529        &mut self,
530        lcp_type: LargestContentfulPaintType,
531        lcp_candidate_id: LCPCandidateID,
532        clip_rect: LayoutRect,
533        bounds: LayoutRect,
534    ) {
535        if let Some(lcp_collector) = &mut self.lcp_candidate_collector {
536            let transform = self
537                .compositor_info
538                .scroll_tree
539                .cumulative_node_to_root_transform(self.current_scroll_node_id);
540            lcp_collector.add_or_update_candidate(
541                lcp_type,
542                lcp_candidate_id,
543                clip_rect,
544                bounds,
545                transform,
546            );
547        }
548    }
549}
550
551impl InspectorHighlight {
552    fn register_fragment_of_highlighted_dom_node(
553        &mut self,
554        fragment: &Fragment,
555        spatial_id: SpatialId,
556        clip_chain_id: ClipChainId,
557        containing_block: &PhysicalRect<Au>,
558    ) {
559        let state = self.state.get_or_insert(HighlightTraversalState {
560            content_box: euclid::Rect::zero(),
561            spatial_id,
562            clip_chain_id,
563            maybe_box_fragment: None,
564        });
565
566        // We expect all fragments generated by one node to be in the same scroll tree node and clip node
567        debug_assert_eq!(spatial_id, state.spatial_id);
568        if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
569            debug_assert_eq!(
570                clip_chain_id, state.clip_chain_id,
571                "Fragments of the same node must either have no clip chain or the same one"
572            );
573        }
574
575        let fragment_relative_rect = match fragment {
576            Fragment::Box(fragment) | Fragment::Float(fragment) => {
577                state.maybe_box_fragment = Some(fragment.clone());
578
579                fragment.borrow().content_rect
580            },
581            Fragment::Positioning(fragment) => fragment.borrow().rect,
582            Fragment::Text(fragment) => fragment.borrow().rect,
583            Fragment::Image(image_fragment) => image_fragment.borrow().rect,
584            Fragment::AbsoluteOrFixedPositioned(_) => return,
585            Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow().rect,
586        };
587
588        state.content_box = state
589            .content_box
590            .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
591    }
592}
593
594impl Fragment {
595    pub(crate) fn build_display_list(
596        &self,
597        builder: &mut DisplayListBuilder,
598        containing_block: &PhysicalRect<Au>,
599        section: StackingContextSection,
600        is_hit_test_for_scrollable_overflow: bool,
601        is_collapsed_table_borders: bool,
602        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
603    ) {
604        let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
605        let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
606        if let Some(inspector_highlight) = &mut builder.inspector_highlight {
607            if self.tag() == Some(inspector_highlight.tag) {
608                inspector_highlight.register_fragment_of_highlighted_dom_node(
609                    self,
610                    spatial_id,
611                    clip_chain_id,
612                    containing_block,
613                );
614            }
615        }
616
617        match self {
618            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
619                let box_fragment = &*box_fragment.borrow();
620                match box_fragment.style.get_inherited_box().visibility {
621                    Visibility::Visible => BuilderForBoxFragment::new(
622                        box_fragment,
623                        containing_block,
624                        is_hit_test_for_scrollable_overflow,
625                        is_collapsed_table_borders,
626                    )
627                    .build(builder, section),
628                    Visibility::Hidden => (),
629                    Visibility::Collapse => (),
630                }
631            },
632            Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
633            Fragment::Image(image) => {
634                let image = image.borrow();
635                match image.style.get_inherited_box().visibility {
636                    Visibility::Visible => {
637                        builder.mark_is_contentful();
638
639                        let image_rendering = image
640                            .style
641                            .get_inherited_box()
642                            .image_rendering
643                            .to_webrender();
644                        let rect = image
645                            .rect
646                            .translate(containing_block.origin.to_vector())
647                            .to_webrender();
648                        let clip = image
649                            .clip
650                            .translate(containing_block.origin.to_vector())
651                            .to_webrender();
652                        let common = builder.common_properties(clip, &image.style);
653
654                        if let Some(image_key) = image.image_key {
655                            builder.wr().push_image(
656                                &common,
657                                rect,
658                                image_rendering,
659                                wr::AlphaType::PremultipliedAlpha,
660                                image_key,
661                                wr::ColorF::WHITE,
662                            );
663                        }
664
665                        if image.showing_broken_image_icon {
666                            self.build_display_list_for_broken_image_border(
667                                builder,
668                                containing_block,
669                                &common,
670                            );
671                        }
672
673                        let lcp_candidate_id = image
674                            .base
675                            .tag
676                            .map(|tag| LCPCandidateID(tag.node.id()))
677                            .unwrap_or(LCPCandidateID(0));
678                        builder.collect_lcp_candidate(
679                            LargestContentfulPaintType::Image,
680                            lcp_candidate_id,
681                            common.clip_rect,
682                            rect,
683                        );
684                    },
685                    Visibility::Hidden => (),
686                    Visibility::Collapse => (),
687                }
688            },
689            Fragment::IFrame(iframe) => {
690                let iframe = iframe.borrow();
691                match iframe.style.get_inherited_box().visibility {
692                    Visibility::Visible => {
693                        builder.mark_is_contentful();
694                        let rect = iframe.rect.translate(containing_block.origin.to_vector());
695
696                        let common = builder.common_properties(rect.to_webrender(), &iframe.style);
697                        builder.wr().push_iframe(
698                            rect.to_webrender(),
699                            common.clip_rect,
700                            &wr::SpaceAndClipInfo {
701                                spatial_id: common.spatial_id,
702                                clip_chain_id: common.clip_chain_id,
703                            },
704                            iframe.pipeline_id.into(),
705                            true,
706                        );
707                    },
708                    Visibility::Hidden => (),
709                    Visibility::Collapse => (),
710                }
711            },
712            Fragment::Text(text) => {
713                let text = &*text.borrow();
714                match text
715                    .inline_styles
716                    .style
717                    .borrow()
718                    .get_inherited_box()
719                    .visibility
720                {
721                    Visibility::Visible => self.build_display_list_for_text_fragment(
722                        text,
723                        builder,
724                        containing_block,
725                        text_decorations,
726                    ),
727                    Visibility::Hidden => (),
728                    Visibility::Collapse => (),
729                }
730            },
731        }
732    }
733
734    fn build_display_list_for_text_fragment(
735        &self,
736        fragment: &TextFragment,
737        builder: &mut DisplayListBuilder,
738        containing_block: &PhysicalRect<Au>,
739        text_decorations: &Arc<Vec<FragmentTextDecoration>>,
740    ) {
741        // NB: The order of painting text components (CSS Text Decoration Module Level 3) is:
742        // shadows, underline, overline, text, text-emphasis, and then line-through.
743
744        builder.mark_is_contentful();
745
746        let rect = fragment.rect.translate(containing_block.origin.to_vector());
747        let mut baseline_origin = rect.origin;
748        baseline_origin.y += fragment.font_metrics.ascent;
749        let include_whitespace =
750            fragment.has_selection() || text_decorations.iter().any(|item| !item.line.is_empty());
751
752        let glyphs = glyphs(
753            &fragment.glyphs,
754            baseline_origin,
755            fragment.justification_adjustment,
756            include_whitespace,
757        );
758        if glyphs.is_empty() {
759            return;
760        }
761
762        let parent_style = fragment.inline_styles.style.borrow();
763        let color = parent_style.clone_color();
764        let font_metrics = &fragment.font_metrics;
765        let dppx = builder.device_pixel_ratio.get();
766        let common = builder.common_properties(rect.to_webrender(), &parent_style);
767
768        // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to
769        // back).
770        let shadows = &parent_style.get_inherited_text().text_shadow;
771        for shadow in shadows.0.iter().rev() {
772            builder.wr().push_shadow(
773                &wr::SpaceAndClipInfo {
774                    spatial_id: common.spatial_id,
775                    clip_chain_id: common.clip_chain_id,
776                },
777                wr::Shadow {
778                    offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
779                    color: rgba(shadow.color.resolve_to_absolute(&color)),
780                    blur_radius: shadow.blur.px(),
781                },
782                true, /* should_inflate */
783            );
784        }
785
786        for text_decoration in text_decorations.iter() {
787            if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
788                let mut rect = rect;
789                rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
790                rect.size.height =
791                    Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
792
793                self.build_display_list_for_text_decoration(
794                    &parent_style,
795                    builder,
796                    &rect,
797                    text_decoration,
798                    TextDecorationLine::UNDERLINE,
799                );
800            }
801        }
802
803        for text_decoration in text_decorations.iter() {
804            if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
805                let mut rect = rect;
806                rect.size.height =
807                    Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
808                self.build_display_list_for_text_decoration(
809                    &parent_style,
810                    builder,
811                    &rect,
812                    text_decoration,
813                    TextDecorationLine::OVERLINE,
814                );
815            }
816        }
817
818        // TODO: This caret/text selection implementation currently does not account for vertical text
819        // and RTL text properly.
820        if let Some(range) = fragment.selection_range {
821            let baseline_origin = rect.origin;
822            if !range.is_empty() {
823                let start = glyphs_advance_by_index(
824                    &fragment.glyphs,
825                    range.begin(),
826                    baseline_origin,
827                    fragment.justification_adjustment,
828                );
829
830                let end = glyphs_advance_by_index(
831                    &fragment.glyphs,
832                    range.end(),
833                    baseline_origin,
834                    fragment.justification_adjustment,
835                );
836
837                let selection_rect = LayoutRect::new(
838                    Point2D::new(start.x.to_f32_px(), containing_block.min_y().to_f32_px()),
839                    Point2D::new(end.x.to_f32_px(), containing_block.max_y().to_f32_px()),
840                );
841                if let Some(selection_color) = fragment
842                    .inline_styles
843                    .selected
844                    .borrow()
845                    .clone_background_color()
846                    .as_absolute()
847                {
848                    let selection_common = builder.common_properties(selection_rect, &parent_style);
849                    builder.wr().push_rect(
850                        &selection_common,
851                        selection_rect,
852                        rgba(*selection_color),
853                    );
854                }
855            } else {
856                let insertion_point = glyphs_advance_by_index(
857                    &fragment.glyphs,
858                    range.begin(),
859                    baseline_origin,
860                    fragment.justification_adjustment,
861                );
862
863                let insertion_point_rect = LayoutRect::new(
864                    Point2D::new(
865                        insertion_point.x.to_f32_px(),
866                        containing_block.min_y().to_f32_px(),
867                    ),
868                    Point2D::new(
869                        insertion_point.x.to_f32_px() + INSERTION_POINT_LOGICAL_WIDTH.to_f32_px(),
870                        containing_block.max_y().to_f32_px(),
871                    ),
872                );
873                let insertion_point_common =
874                    builder.common_properties(insertion_point_rect, &parent_style);
875                // TODO: The color of the caret is currently hardcoded to the text color.
876                // We should be retrieving the caret color from the style properly.
877                builder
878                    .wr()
879                    .push_rect(&insertion_point_common, insertion_point_rect, rgba(color));
880            }
881        }
882
883        builder.wr().push_text(
884            &common,
885            rect.to_webrender(),
886            &glyphs,
887            fragment.font_key,
888            rgba(color),
889            None,
890        );
891
892        for text_decoration in text_decorations.iter() {
893            if text_decoration
894                .line
895                .contains(TextDecorationLine::LINE_THROUGH)
896            {
897                let mut rect = rect;
898                rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
899                rect.size.height =
900                    Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
901                self.build_display_list_for_text_decoration(
902                    &parent_style,
903                    builder,
904                    &rect,
905                    text_decoration,
906                    TextDecorationLine::LINE_THROUGH,
907                );
908            }
909        }
910
911        if !shadows.0.is_empty() {
912            builder.wr().pop_all_shadows();
913        }
914    }
915
916    fn build_display_list_for_text_decoration(
917        &self,
918        parent_style: &ServoArc<ComputedValues>,
919        builder: &mut DisplayListBuilder,
920        rect: &PhysicalRect<Au>,
921        text_decoration: &FragmentTextDecoration,
922        line: TextDecorationLine,
923    ) {
924        if text_decoration.style == ComputedTextDecorationStyle::MozNone {
925            return;
926        }
927
928        let mut rect = rect.to_webrender();
929        let line_thickness = rect.height().ceil();
930
931        if text_decoration.style == ComputedTextDecorationStyle::Wavy {
932            rect = rect.inflate(0.0, line_thickness * 1.0);
933        }
934
935        let common_properties = builder.common_properties(rect, parent_style);
936        builder.wr().push_line(
937            &common_properties,
938            &rect,
939            line_thickness,
940            wr::LineOrientation::Horizontal,
941            &rgba(text_decoration.color),
942            text_decoration.style.to_webrender(),
943        );
944
945        if text_decoration.style == TextDecorationStyle::Double {
946            let half_height = (rect.height() / 2.0).floor().max(1.0);
947            let y_offset = match line {
948                TextDecorationLine::OVERLINE => -rect.height() - half_height,
949                _ => rect.height() + half_height,
950            };
951            let rect = rect.translate(Vector2D::new(0.0, y_offset));
952            let common_properties = builder.common_properties(rect, parent_style);
953            builder.wr().push_line(
954                &common_properties,
955                &rect,
956                line_thickness,
957                wr::LineOrientation::Horizontal,
958                &rgba(text_decoration.color),
959                text_decoration.style.to_webrender(),
960            );
961        }
962    }
963
964    fn build_display_list_for_broken_image_border(
965        &self,
966        builder: &mut DisplayListBuilder,
967        containing_block: &PhysicalRect<Au>,
968        common: &CommonItemProperties,
969    ) {
970        let border_side = BorderSide {
971            color: ColorF::BLACK,
972            style: wr::BorderStyle::Inset,
973        };
974        builder.wr().push_border(
975            common,
976            containing_block.to_webrender(),
977            LayoutSideOffsets::new_all_same(1.0),
978            BorderDetails::Normal(NormalBorder {
979                left: border_side,
980                right: border_side,
981                top: border_side,
982                bottom: border_side,
983                radius: BorderRadius::zero(),
984                do_aa: true,
985            }),
986        );
987    }
988}
989
990struct BuilderForBoxFragment<'a> {
991    fragment: &'a BoxFragment,
992    containing_block: &'a PhysicalRect<Au>,
993    border_rect: units::LayoutRect,
994    margin_rect: OnceCell<units::LayoutRect>,
995    padding_rect: OnceCell<units::LayoutRect>,
996    content_rect: OnceCell<units::LayoutRect>,
997    border_radius: wr::BorderRadius,
998    border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
999    padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1000    content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1001    is_hit_test_for_scrollable_overflow: bool,
1002    is_collapsed_table_borders: bool,
1003}
1004
1005impl<'a> BuilderForBoxFragment<'a> {
1006    fn new(
1007        fragment: &'a BoxFragment,
1008        containing_block: &'a PhysicalRect<Au>,
1009        is_hit_test_for_scrollable_overflow: bool,
1010        is_collapsed_table_borders: bool,
1011    ) -> Self {
1012        let border_rect = fragment
1013            .border_rect()
1014            .translate(containing_block.origin.to_vector());
1015        Self {
1016            fragment,
1017            containing_block,
1018            border_rect: border_rect.to_webrender(),
1019            border_radius: fragment.border_radius(),
1020            margin_rect: OnceCell::new(),
1021            padding_rect: OnceCell::new(),
1022            content_rect: OnceCell::new(),
1023            border_edge_clip_chain_id: RefCell::new(None),
1024            padding_edge_clip_chain_id: RefCell::new(None),
1025            content_edge_clip_chain_id: RefCell::new(None),
1026            is_hit_test_for_scrollable_overflow,
1027            is_collapsed_table_borders,
1028        }
1029    }
1030
1031    fn content_rect(&self) -> &units::LayoutRect {
1032        self.content_rect.get_or_init(|| {
1033            self.fragment
1034                .content_rect
1035                .translate(self.containing_block.origin.to_vector())
1036                .to_webrender()
1037        })
1038    }
1039
1040    fn padding_rect(&self) -> &units::LayoutRect {
1041        self.padding_rect.get_or_init(|| {
1042            self.fragment
1043                .padding_rect()
1044                .translate(self.containing_block.origin.to_vector())
1045                .to_webrender()
1046        })
1047    }
1048
1049    fn margin_rect(&self) -> &units::LayoutRect {
1050        self.margin_rect.get_or_init(|| {
1051            self.fragment
1052                .margin_rect()
1053                .translate(self.containing_block.origin.to_vector())
1054                .to_webrender()
1055        })
1056    }
1057
1058    fn border_edge_clip(
1059        &self,
1060        builder: &mut DisplayListBuilder,
1061        force_clip_creation: bool,
1062    ) -> Option<ClipChainId> {
1063        if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1064            return Some(clip);
1065        }
1066
1067        let maybe_clip =
1068            builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
1069        *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1070        maybe_clip
1071    }
1072
1073    fn padding_edge_clip(
1074        &self,
1075        builder: &mut DisplayListBuilder,
1076        force_clip_creation: bool,
1077    ) -> Option<ClipChainId> {
1078        if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1079            return Some(clip);
1080        }
1081
1082        let radii = inner_radii(self.border_radius, self.fragment.border.to_webrender());
1083        let maybe_clip =
1084            builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
1085        *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1086        maybe_clip
1087    }
1088
1089    fn content_edge_clip(
1090        &self,
1091        builder: &mut DisplayListBuilder,
1092        force_clip_creation: bool,
1093    ) -> Option<ClipChainId> {
1094        if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1095            return Some(clip);
1096        }
1097
1098        let radii = inner_radii(
1099            self.border_radius,
1100            (self.fragment.border + self.fragment.padding).to_webrender(),
1101        );
1102        let maybe_clip =
1103            builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
1104        *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1105        maybe_clip
1106    }
1107
1108    fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
1109        if self.is_hit_test_for_scrollable_overflow &&
1110            self.fragment.style.get_inherited_ui().pointer_events !=
1111                style::computed_values::pointer_events::T::None
1112        {
1113            self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender());
1114            return;
1115        }
1116        if self.is_collapsed_table_borders {
1117            self.build_collapsed_table_borders(builder);
1118            return;
1119        }
1120
1121        if section == StackingContextSection::Outline {
1122            self.build_outline(builder);
1123            return;
1124        }
1125
1126        if self
1127            .fragment
1128            .base
1129            .flags
1130            .contains(FragmentFlags::DO_NOT_PAINT)
1131        {
1132            return;
1133        }
1134
1135        self.build_background(builder);
1136        self.build_box_shadow(builder);
1137        self.build_border(builder);
1138    }
1139
1140    fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
1141        let external_scroll_node_id = builder
1142            .compositor_info
1143            .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
1144
1145        let mut common = builder.common_properties(rect, &self.fragment.style);
1146        if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
1147            common.clip_chain_id = clip_chain_id;
1148        }
1149        builder.wr().push_hit_test(
1150            common.clip_rect,
1151            common.clip_chain_id,
1152            common.spatial_id,
1153            common.flags,
1154            (external_scroll_node_id.0, 0), /* tag */
1155        );
1156    }
1157
1158    fn build_background_for_painter(
1159        &mut self,
1160        builder: &mut DisplayListBuilder,
1161        painter: &BackgroundPainter,
1162    ) {
1163        let b = painter.style.get_background();
1164        let background_color = painter.style.resolve_color(&b.background_color);
1165        if background_color.alpha > 0.0 {
1166            // https://drafts.csswg.org/css-backgrounds/#background-color
1167            // “The background color is clipped according to the background-clip
1168            //  value associated with the bottom-most background image layer.”
1169            let layer_index = b.background_image.0.len() - 1;
1170            let bounds = painter.painting_area(self, builder, layer_index);
1171            let common = painter.common_properties(self, builder, layer_index, bounds);
1172            builder
1173                .wr()
1174                .push_rect(&common, bounds, rgba(background_color))
1175        }
1176
1177        self.build_background_image(builder, painter);
1178    }
1179
1180    fn build_background(&mut self, builder: &mut DisplayListBuilder) {
1181        let flags = self.fragment.base.flags;
1182
1183        // The root element's background is painted separately as it might inherit the `<body>`'s
1184        // background.
1185        if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1186            return;
1187        }
1188        // If the `<body>` background was inherited by the root element, don't paint it again here.
1189        if !builder.paint_body_background &&
1190            flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1191        {
1192            return;
1193        }
1194
1195        // If this BoxFragment does not paint a background, do nothing.
1196        if let BackgroundMode::None = self.fragment.background_mode {
1197            return;
1198        }
1199
1200        // Paint all extra backgrounds for this BoxFragment. These are painted first, as that's
1201        // the order that they are expected to be painted for table cells (where this feature
1202        // is used).
1203        if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1204            for extra_background in extra_backgrounds {
1205                let positioning_area = extra_background.rect;
1206                let painter = BackgroundPainter {
1207                    style: &extra_background.style.borrow_mut(),
1208                    painting_area_override: None,
1209                    positioning_area_override: Some(
1210                        positioning_area
1211                            .translate(self.containing_block.origin.to_vector())
1212                            .to_webrender(),
1213                    ),
1214                };
1215                self.build_background_for_painter(builder, &painter);
1216            }
1217        }
1218
1219        let painter = BackgroundPainter {
1220            style: &self.fragment.style,
1221            painting_area_override: None,
1222            positioning_area_override: None,
1223        };
1224        self.build_background_for_painter(builder, &painter);
1225    }
1226
1227    fn build_background_image(
1228        &mut self,
1229        builder: &mut DisplayListBuilder,
1230        painter: &BackgroundPainter,
1231    ) {
1232        let style = painter.style;
1233        let b = style.get_background();
1234        let node = self.fragment.base.tag.map(|tag| tag.node);
1235        // Reverse because the property is top layer first, we want to paint bottom layer first.
1236        for (index, image) in b.background_image.0.iter().enumerate().rev() {
1237            match builder.image_resolver.resolve_image(node, image) {
1238                Err(_) => {},
1239                Ok(ResolvedImage::Gradient(gradient)) => {
1240                    let intrinsic = NaturalSizes::empty();
1241                    let Some(layer) =
1242                        &background::layout_layer(self, painter, builder, index, intrinsic)
1243                    else {
1244                        continue;
1245                    };
1246
1247                    match gradient::build(style, gradient, layer.tile_size, builder) {
1248                        WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
1249                            &layer.common,
1250                            layer.bounds,
1251                            linear_gradient,
1252                            layer.tile_size,
1253                            layer.tile_spacing,
1254                        ),
1255                        WebRenderGradient::Radial(radial_gradient) => {
1256                            builder.wr().push_radial_gradient(
1257                                &layer.common,
1258                                layer.bounds,
1259                                radial_gradient,
1260                                layer.tile_size,
1261                                layer.tile_spacing,
1262                            )
1263                        },
1264                        WebRenderGradient::Conic(conic_gradient) => {
1265                            builder.wr().push_conic_gradient(
1266                                &layer.common,
1267                                layer.bounds,
1268                                conic_gradient,
1269                                layer.tile_size,
1270                                layer.tile_spacing,
1271                            )
1272                        },
1273                    }
1274                },
1275                Ok(ResolvedImage::Image { image, size }) => {
1276                    // FIXME: https://drafts.csswg.org/css-images-4/#the-image-resolution
1277                    let dppx = 1.0;
1278                    let intrinsic =
1279                        NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1280                    let layer = background::layout_layer(self, painter, builder, index, intrinsic);
1281                    let image_wr_key = match image {
1282                        CachedImage::Raster(raster_image) => raster_image.id,
1283                        CachedImage::Vector(vector_image) => {
1284                            let scale = builder.device_pixel_ratio.get();
1285                            let default_size: DeviceIntSize =
1286                                Size2D::new(size.width * scale, size.height * scale).to_i32();
1287                            let layer_size = layer.as_ref().map(|layer| {
1288                                Size2D::new(
1289                                    layer.tile_size.width * scale,
1290                                    layer.tile_size.height * scale,
1291                                )
1292                                .to_i32()
1293                            });
1294
1295                            node.and_then(|node| {
1296                                let size = layer_size.unwrap_or(default_size);
1297                                builder.image_resolver.rasterize_vector_image(
1298                                    vector_image.id,
1299                                    size,
1300                                    node,
1301                                )
1302                            })
1303                            .and_then(|rasterized_image| rasterized_image.id)
1304                        },
1305                    };
1306
1307                    let Some(image_key) = image_wr_key else {
1308                        continue;
1309                    };
1310
1311                    if let Some(layer) = layer {
1312                        if layer.repeat {
1313                            builder.wr().push_repeating_image(
1314                                &layer.common,
1315                                layer.bounds,
1316                                layer.tile_size,
1317                                layer.tile_spacing,
1318                                style.clone_image_rendering().to_webrender(),
1319                                wr::AlphaType::PremultipliedAlpha,
1320                                image_key,
1321                                wr::ColorF::WHITE,
1322                            )
1323                        } else {
1324                            builder.wr().push_image(
1325                                &layer.common,
1326                                layer.bounds,
1327                                style.clone_image_rendering().to_webrender(),
1328                                wr::AlphaType::PremultipliedAlpha,
1329                                image_key,
1330                                wr::ColorF::WHITE,
1331                            )
1332                        }
1333
1334                        let lcp_candidate_id = self
1335                            .fragment
1336                            .base
1337                            .tag
1338                            .map(|tag| LCPCandidateID(tag.node.id()))
1339                            .unwrap_or(LCPCandidateID(0));
1340                        builder.collect_lcp_candidate(
1341                            LargestContentfulPaintType::BackgroundImage,
1342                            lcp_candidate_id,
1343                            layer.common.clip_rect,
1344                            layer.bounds,
1345                        );
1346                    }
1347                },
1348            }
1349        }
1350    }
1351
1352    fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1353        wr::BorderSide {
1354            color: rgba(style_color.color),
1355            style: match style_color.style {
1356                BorderStyle::None => wr::BorderStyle::None,
1357                BorderStyle::Solid => wr::BorderStyle::Solid,
1358                BorderStyle::Double => wr::BorderStyle::Double,
1359                BorderStyle::Dotted => wr::BorderStyle::Dotted,
1360                BorderStyle::Dashed => wr::BorderStyle::Dashed,
1361                BorderStyle::Hidden => wr::BorderStyle::Hidden,
1362                BorderStyle::Groove => wr::BorderStyle::Groove,
1363                BorderStyle::Ridge => wr::BorderStyle::Ridge,
1364                BorderStyle::Inset => wr::BorderStyle::Inset,
1365                BorderStyle::Outset => wr::BorderStyle::Outset,
1366            },
1367        }
1368    }
1369
1370    fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
1371        let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1372            self.fragment.specific_layout_info()
1373        else {
1374            return;
1375        };
1376        let mut common =
1377            builder.common_properties(units::LayoutRect::default(), &self.fragment.style);
1378        let radius = wr::BorderRadius::default();
1379        let mut column_sum = Au::zero();
1380        for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1381            let mut row_sum = Au::zero();
1382            for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1383                let left_border = &table_info.collapsed_borders.x[x][y];
1384                let right_border = &table_info.collapsed_borders.x[x + 1][y];
1385                let top_border = &table_info.collapsed_borders.y[y][x];
1386                let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1387                let details = wr::BorderDetails::Normal(wr::NormalBorder {
1388                    left: self.build_border_side(left_border.style_color.clone()),
1389                    right: self.build_border_side(right_border.style_color.clone()),
1390                    top: self.build_border_side(top_border.style_color.clone()),
1391                    bottom: self.build_border_side(bottom_border.style_color.clone()),
1392                    radius,
1393                    do_aa: true,
1394                });
1395                let mut border_widths = PhysicalSides::new(
1396                    top_border.width,
1397                    right_border.width,
1398                    bottom_border.width,
1399                    left_border.width,
1400                );
1401                let left_adjustment = if x == 0 {
1402                    -border_widths.left / 2
1403                } else {
1404                    std::mem::take(&mut border_widths.left) / 2
1405                };
1406                let top_adjustment = if y == 0 {
1407                    -border_widths.top / 2
1408                } else {
1409                    std::mem::take(&mut border_widths.top) / 2
1410                };
1411                let origin =
1412                    PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1413                let size = PhysicalSize::new(
1414                    *column_size - left_adjustment + border_widths.right / 2,
1415                    *row_size - top_adjustment + border_widths.bottom / 2,
1416                );
1417                let border_rect = PhysicalRect::new(origin, size)
1418                    .translate(self.fragment.content_rect.origin.to_vector())
1419                    .translate(self.containing_block.origin.to_vector())
1420                    .to_webrender();
1421                common.clip_rect = border_rect;
1422                builder.wr().push_border(
1423                    &common,
1424                    border_rect,
1425                    border_widths.to_webrender(),
1426                    details,
1427                );
1428                row_sum += *row_size;
1429            }
1430            column_sum += *column_size;
1431        }
1432    }
1433
1434    fn build_border(&mut self, builder: &mut DisplayListBuilder) {
1435        if self.fragment.has_collapsed_borders() {
1436            // Avoid painting borders for tables and table parts in collapsed-borders mode,
1437            // since the resulting collapsed borders are painted on their own in a special way.
1438            return;
1439        }
1440
1441        let border = self.fragment.style.get_border();
1442        let border_widths = self.fragment.border.to_webrender();
1443
1444        if border_widths == SideOffsets2D::zero() {
1445            return;
1446        }
1447
1448        // `border-image` replaces an element's border entirely.
1449        let common = builder.common_properties(self.border_rect, &self.fragment.style);
1450        if self.build_border_image(builder, &common, border, border_widths) {
1451            return;
1452        }
1453
1454        let current_color = self.fragment.style.get_inherited_text().clone_color();
1455        let style_color = BorderStyleColor::from_border(border, &current_color);
1456        let details = wr::BorderDetails::Normal(wr::NormalBorder {
1457            top: self.build_border_side(style_color.top),
1458            right: self.build_border_side(style_color.right),
1459            bottom: self.build_border_side(style_color.bottom),
1460            left: self.build_border_side(style_color.left),
1461            radius: self.border_radius,
1462            do_aa: true,
1463        });
1464        builder
1465            .wr()
1466            .push_border(&common, self.border_rect, border_widths, details)
1467    }
1468
1469    /// Add a display item for image borders if necessary.
1470    fn build_border_image(
1471        &self,
1472        builder: &mut DisplayListBuilder,
1473        common: &CommonItemProperties,
1474        border: &Border,
1475        border_widths: SideOffsets2D<f32, LayoutPixel>,
1476    ) -> bool {
1477        let border_style_struct = self.fragment.style.get_border();
1478        let border_image_outset =
1479            resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1480        let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1481        let border_image_size = border_image_area.size;
1482        let border_image_widths = resolve_border_image_width(
1483            &border_style_struct.border_image_width,
1484            border_widths,
1485            border_image_size,
1486        );
1487        let border_image_repeat = &border_style_struct.border_image_repeat;
1488        let border_image_fill = border_style_struct.border_image_slice.fill;
1489        let border_image_slice = &border_style_struct.border_image_slice.offsets;
1490
1491        let stops = Vec::new();
1492        let mut width = border_image_size.width;
1493        let mut height = border_image_size.height;
1494        let node = self.fragment.base.tag.map(|tag| tag.node);
1495        let source = match builder
1496            .image_resolver
1497            .resolve_image(node, &border.border_image_source)
1498        {
1499            Err(_) => return false,
1500            Ok(ResolvedImage::Image { image, size }) => {
1501                let Some(image) = image.as_raster_image() else {
1502                    return false;
1503                };
1504
1505                let Some(key) = image.id else {
1506                    return false;
1507                };
1508
1509                width = size.width;
1510                height = size.height;
1511                let image_rendering = self.fragment.style.clone_image_rendering().to_webrender();
1512                NinePatchBorderSource::Image(key, image_rendering)
1513            },
1514            Ok(ResolvedImage::Gradient(gradient)) => {
1515                match gradient::build(&self.fragment.style, gradient, border_image_size, builder) {
1516                    WebRenderGradient::Linear(gradient) => {
1517                        NinePatchBorderSource::Gradient(gradient)
1518                    },
1519                    WebRenderGradient::Radial(gradient) => {
1520                        NinePatchBorderSource::RadialGradient(gradient)
1521                    },
1522                    WebRenderGradient::Conic(gradient) => {
1523                        NinePatchBorderSource::ConicGradient(gradient)
1524                    },
1525                }
1526            },
1527        };
1528
1529        let size = euclid::Size2D::new(width as i32, height as i32);
1530
1531        // If the size of the border is zero or the size of the border image is zero, just
1532        // don't render anything. Zero-sized gradients cause problems in WebRender.
1533        if size.is_empty() || border_image_size.is_empty() {
1534            return true;
1535        }
1536
1537        let details = BorderDetails::NinePatch(NinePatchBorder {
1538            source,
1539            width: size.width,
1540            height: size.height,
1541            slice: resolve_border_image_slice(border_image_slice, size),
1542            fill: border_image_fill,
1543            repeat_horizontal: border_image_repeat.0.to_webrender(),
1544            repeat_vertical: border_image_repeat.1.to_webrender(),
1545        });
1546        builder.wr().push_border(
1547            common,
1548            border_image_area.to_box2d(),
1549            border_image_widths,
1550            details,
1551        );
1552        builder.wr().push_stops(&stops);
1553        true
1554    }
1555
1556    fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
1557        let style = &self.fragment.style;
1558        let outline = style.get_outline();
1559        let width = outline.outline_width.to_f32_px();
1560        if width == 0.0 {
1561            return;
1562        }
1563        // <https://drafts.csswg.org/css-ui-3/#outline-offset>
1564        // > Negative values must cause the outline to shrink into the border box. Both
1565        // > the height and the width of outside of the shape drawn by the outline should
1566        // > not become smaller than twice the computed value of the outline-width
1567        // > property, to make sure that an outline can be rendered even with large
1568        // > negative values. User agents should apply this constraint independently in
1569        // > each dimension. If the outline is drawn as multiple disconnected shapes, this
1570        // > constraint applies to each shape separately.
1571        let offset = outline.outline_offset.to_f32_px() + width;
1572        let outline_rect = self.border_rect.inflate(
1573            offset.max(-self.border_rect.width() / 2.0 + width),
1574            offset.max(-self.border_rect.height() / 2.0 + width),
1575        );
1576        let common = builder.common_properties(outline_rect, &self.fragment.style);
1577        let widths = SideOffsets2D::new_all_same(width);
1578        let border_style = match outline.outline_style {
1579            // TODO: treating 'auto' as 'solid' is allowed by the spec,
1580            // but we should do something better.
1581            OutlineStyle::Auto => BorderStyle::Solid,
1582            OutlineStyle::BorderStyle(s) => s,
1583        };
1584        let side = self.build_border_side(BorderStyleColor {
1585            style: border_style,
1586            color: style.resolve_color(&outline.outline_color),
1587        });
1588        let details = wr::BorderDetails::Normal(wr::NormalBorder {
1589            top: side,
1590            right: side,
1591            bottom: side,
1592            left: side,
1593            radius: offset_radii(self.border_radius, offset),
1594            do_aa: true,
1595        });
1596        builder
1597            .wr()
1598            .push_border(&common, outline_rect, widths, details)
1599    }
1600
1601    fn build_box_shadow(&self, builder: &mut DisplayListBuilder<'_>) {
1602        let box_shadows = &self.fragment.style.get_effects().box_shadow.0;
1603        if box_shadows.is_empty() {
1604            return;
1605        }
1606
1607        // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
1608        let common = builder.common_properties(MaxRect::max_rect(), &self.fragment.style);
1609        for box_shadow in box_shadows.iter().rev() {
1610            let (rect, clip_mode) = if box_shadow.inset {
1611                (*self.padding_rect(), BoxShadowClipMode::Inset)
1612            } else {
1613                (self.border_rect, BoxShadowClipMode::Outset)
1614            };
1615
1616            builder.wr().push_box_shadow(
1617                &common,
1618                rect,
1619                LayoutVector2D::new(
1620                    box_shadow.base.horizontal.px(),
1621                    box_shadow.base.vertical.px(),
1622                ),
1623                rgba(self.fragment.style.resolve_color(&box_shadow.base.color)),
1624                box_shadow.base.blur.px(),
1625                box_shadow.spread.px(),
1626                self.border_radius,
1627                clip_mode,
1628            );
1629        }
1630    }
1631}
1632
1633fn rgba(color: AbsoluteColor) -> wr::ColorF {
1634    let rgba = color.to_color_space(ColorSpace::Srgb);
1635    wr::ColorF::new(
1636        rgba.components.0.clamp(0.0, 1.0),
1637        rgba.components.1.clamp(0.0, 1.0),
1638        rgba.components.2.clamp(0.0, 1.0),
1639        rgba.alpha,
1640    )
1641}
1642
1643fn glyphs(
1644    glyph_runs: &[Arc<GlyphStore>],
1645    mut baseline_origin: PhysicalPoint<Au>,
1646    justification_adjustment: Au,
1647    include_whitespace: bool,
1648) -> Vec<wr::GlyphInstance> {
1649    use fonts_traits::ByteIndex;
1650    use range::Range;
1651
1652    let mut glyphs = vec![];
1653    for run in glyph_runs {
1654        for glyph in run.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), run.len())) {
1655            if !run.is_whitespace() || include_whitespace {
1656                let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1657                let point = units::LayoutPoint::new(
1658                    baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1659                    baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1660                );
1661                let glyph = wr::GlyphInstance {
1662                    index: glyph.id(),
1663                    point,
1664                };
1665                glyphs.push(glyph);
1666            }
1667
1668            if glyph.char_is_word_separator() {
1669                baseline_origin.x += justification_adjustment;
1670            }
1671            baseline_origin.x += glyph.advance();
1672        }
1673    }
1674    glyphs
1675}
1676
1677// TODO: The implementation here does not account for multiple glyph runs properly.
1678fn glyphs_advance_by_index(
1679    glyph_runs: &[Arc<GlyphStore>],
1680    index: fonts_traits::ByteIndex,
1681    baseline_origin: PhysicalPoint<Au>,
1682    justification_adjustment: Au,
1683) -> PhysicalPoint<Au> {
1684    let mut point = baseline_origin;
1685    let mut index = index;
1686    for run in glyph_runs {
1687        let range = ServoRange::new(fonts::ByteIndex(0), index.min(run.len()));
1688        index = index - range.length();
1689        let total_advance = run.advance_for_byte_range(&range, justification_adjustment);
1690        point.x += total_advance;
1691    }
1692    point
1693}
1694
1695/// Radii for the padding edge or content edge
1696fn inner_radii(mut radii: wr::BorderRadius, insets: units::LayoutSideOffsets) -> wr::BorderRadius {
1697    assert!(insets.left >= 0.0, "left inset must not be negative");
1698    radii.top_left.width = (radii.top_left.width - insets.left).max(0.0);
1699    radii.bottom_left.width = (radii.bottom_left.width - insets.left).max(0.0);
1700
1701    assert!(insets.right >= 0.0, "left inset must not be negative");
1702    radii.top_right.width = (radii.top_right.width - insets.right).max(0.0);
1703    radii.bottom_right.width = (radii.bottom_right.width - insets.right).max(0.0);
1704
1705    assert!(insets.top >= 0.0, "top inset must not be negative");
1706    radii.top_left.height = (radii.top_left.height - insets.top).max(0.0);
1707    radii.top_right.height = (radii.top_right.height - insets.top).max(0.0);
1708
1709    assert!(insets.bottom >= 0.0, "bottom inset must not be negative");
1710    radii.bottom_left.height = (radii.bottom_left.height - insets.bottom).max(0.0);
1711    radii.bottom_right.height = (radii.bottom_right.height - insets.bottom).max(0.0);
1712    radii
1713}
1714
1715fn offset_radii(mut radii: wr::BorderRadius, offset: f32) -> wr::BorderRadius {
1716    if offset == 0.0 {
1717        return radii;
1718    }
1719    if offset < 0.0 {
1720        return inner_radii(radii, units::LayoutSideOffsets::new_all_same(-offset));
1721    }
1722    let expand = |radius: &mut f32| {
1723        // Expand the radius by the specified amount, but keeping sharp corners.
1724        // TODO: this behavior is not continuous, it's being discussed in the CSSWG:
1725        // https://github.com/w3c/csswg-drafts/issues/7103
1726        if *radius > 0.0 {
1727            *radius += offset;
1728        }
1729    };
1730    expand(&mut radii.top_left.width);
1731    expand(&mut radii.top_left.height);
1732    expand(&mut radii.top_right.width);
1733    expand(&mut radii.top_right.height);
1734    expand(&mut radii.bottom_right.width);
1735    expand(&mut radii.bottom_right.height);
1736    expand(&mut radii.bottom_left.width);
1737    expand(&mut radii.bottom_left.height);
1738    radii
1739}
1740
1741/// Resolve the WebRender border-image outset area from the style values.
1742fn resolve_border_image_outset(
1743    outset: BorderImageOutset,
1744    border: SideOffsets2D<f32, LayoutPixel>,
1745) -> SideOffsets2D<f32, LayoutPixel> {
1746    fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
1747        match outset {
1748            NonNegativeLengthOrNumber::Length(length) => length.px(),
1749            NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
1750        }
1751    }
1752
1753    SideOffsets2D::new(
1754        image_outset_for_side(outset.0, border.top),
1755        image_outset_for_side(outset.1, border.right),
1756        image_outset_for_side(outset.2, border.bottom),
1757        image_outset_for_side(outset.3, border.left),
1758    )
1759}
1760
1761/// Resolve the WebRender border-image width from the style values.
1762fn resolve_border_image_width(
1763    width: &BorderImageWidth,
1764    border: SideOffsets2D<f32, LayoutPixel>,
1765    border_area: Size2D<f32, LayoutPixel>,
1766) -> SideOffsets2D<f32, LayoutPixel> {
1767    fn image_width_for_side(
1768        border_image_width: &BorderImageSideWidth,
1769        border_width: f32,
1770        total_length: f32,
1771    ) -> f32 {
1772        match border_image_width {
1773            BorderImageSideWidth::LengthPercentage(v) => {
1774                v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
1775            },
1776            BorderImageSideWidth::Number(x) => border_width * x.0,
1777            BorderImageSideWidth::Auto => border_width,
1778        }
1779    }
1780
1781    SideOffsets2D::new(
1782        image_width_for_side(&width.0, border.top, border_area.height),
1783        image_width_for_side(&width.1, border.right, border_area.width),
1784        image_width_for_side(&width.2, border.bottom, border_area.height),
1785        image_width_for_side(&width.3, border.left, border_area.width),
1786    )
1787}
1788
1789/// Resolve the WebRender border-image slice from the style values.
1790fn resolve_border_image_slice(
1791    border_image_slice: &Rect<NonNegative<NumberOrPercentage>>,
1792    size: Size2D<i32, UnknownUnit>,
1793) -> SideOffsets2D<i32, DevicePixel> {
1794    fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
1795        match value.0 {
1796            NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
1797            NumberOrPercentage::Number(n) => n.round() as i32,
1798        }
1799    }
1800
1801    SideOffsets2D::new(
1802        resolve_percentage(border_image_slice.0, size.height),
1803        resolve_percentage(border_image_slice.1, size.width),
1804        resolve_percentage(border_image_slice.2, size.height),
1805        resolve_percentage(border_image_slice.3, size.width),
1806    )
1807}
1808
1809pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
1810    // Normalize radii that add up to > 100%.
1811    // https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
1812    // > Let f = min(L_i/S_i), where i ∈ {top, right, bottom, left},
1813    // > S_i is the sum of the two corresponding radii of the corners on side i,
1814    // > and L_top = L_bottom = the width of the box,
1815    // > and L_left = L_right = the height of the box.
1816    // > If f < 1, then all corner radii are reduced by multiplying them by f.
1817    let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
1818        .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
1819        .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
1820        .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
1821    if f < 1.0 {
1822        radius.top_left *= f;
1823        radius.top_right *= f;
1824        radius.bottom_right *= f;
1825        radius.bottom_left *= f;
1826    }
1827}
1828
1829/// <https://drafts.csswg.org/css-shapes-1/#valdef-shape-box-margin-box>
1830/// > The corner radii of this shape are determined by the corresponding
1831/// > border-radius and margin values. If the ratio of border-radius/margin is 1 or more,
1832/// > or margin is negative or zero, then the margin box corner radius is
1833/// > max(border-radius + margin, 0). If the ratio of border-radius/margin is less than 1,
1834/// > and margin is positive, then the margin box corner radius is
1835/// > border-radius + margin * (1 + (ratio-1)^3).
1836pub(super) fn compute_margin_box_radius(
1837    radius: wr::BorderRadius,
1838    layout_rect: LayoutSize,
1839    fragment: &BoxFragment,
1840) -> wr::BorderRadius {
1841    let margin = fragment.style.physical_margin();
1842    let adjust_radius = |radius: f32, margin: f32| -> f32 {
1843        if margin <= 0. || (radius / margin) >= 1. {
1844            (radius + margin).max(0.)
1845        } else {
1846            radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
1847        }
1848    };
1849    let compute_margin_radius = |radius: LayoutSize,
1850                                 layout_rect: LayoutSize,
1851                                 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
1852     -> LayoutSize {
1853        let zero = LengthPercentage::zero();
1854        let width = margin
1855            .width
1856            .auto_is(|| &zero)
1857            .to_used_value(Au::from_f32_px(layout_rect.width));
1858        let height = margin
1859            .height
1860            .auto_is(|| &zero)
1861            .to_used_value(Au::from_f32_px(layout_rect.height));
1862        LayoutSize::new(
1863            adjust_radius(radius.width, width.to_f32_px()),
1864            adjust_radius(radius.height, height.to_f32_px()),
1865        )
1866    };
1867    wr::BorderRadius {
1868        top_left: compute_margin_radius(
1869            radius.top_left,
1870            layout_rect,
1871            Size2D::new(margin.left, margin.top),
1872        ),
1873        top_right: compute_margin_radius(
1874            radius.top_right,
1875            layout_rect,
1876            Size2D::new(margin.right, margin.top),
1877        ),
1878        bottom_left: compute_margin_radius(
1879            radius.bottom_left,
1880            layout_rect,
1881            Size2D::new(margin.left, margin.bottom),
1882        ),
1883        bottom_right: compute_margin_radius(
1884            radius.bottom_right,
1885            layout_rect,
1886            Size2D::new(margin.right, margin.bottom),
1887        ),
1888    }
1889}
1890
1891impl BoxFragment {
1892    fn border_radius(&self) -> BorderRadius {
1893        let border = self.style.get_border();
1894        if border.border_top_left_radius.0.is_zero() &&
1895            border.border_top_right_radius.0.is_zero() &&
1896            border.border_bottom_right_radius.0.is_zero() &&
1897            border.border_bottom_left_radius.0.is_zero()
1898        {
1899            return BorderRadius::zero();
1900        }
1901
1902        let border_rect = self.border_rect();
1903        let resolve =
1904            |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
1905        let corner = |corner: &style::values::computed::BorderCornerRadius| {
1906            Size2D::new(
1907                resolve(&corner.0.width.0, border_rect.size.width),
1908                resolve(&corner.0.height.0, border_rect.size.height),
1909            )
1910        };
1911
1912        let mut radius = wr::BorderRadius {
1913            top_left: corner(&border.border_top_left_radius),
1914            top_right: corner(&border.border_top_right_radius),
1915            bottom_right: corner(&border.border_bottom_right_radius),
1916            bottom_left: corner(&border.border_bottom_left_radius),
1917        };
1918
1919        normalize_radii(&border_rect.to_webrender(), &mut radius);
1920        radius
1921    }
1922}