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