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