layout/display_list/
paint_timing_handler.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 euclid::Rect;
6use paint_api::largest_contentful_paint_candidate::{LCPCandidate, LCPCandidateID};
7use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
8use style_traits::CSSPixel;
9use webrender_api::units::{LayoutRect, LayoutSize};
10
11use crate::query::transform_au_rectangle;
12
13pub(crate) struct PaintTimingHandler {
14    /// The document’s largest contentful paint size
15    lcp_size: f32,
16    /// The LCP candidate, it may be a image or text.
17    lcp_candidate: Option<LCPCandidate>,
18    /// The rect of viewport.
19    viewport_rect: LayoutRect,
20    /// Flag to indicate if there is an update to LCP candidate.
21    /// This is used to avoid sending duplicate LCP candidates to `Paint`.
22    lcp_candidate_updated: bool,
23}
24
25impl PaintTimingHandler {
26    pub(crate) fn new(viewport_size: LayoutSize) -> Self {
27        Self {
28            lcp_size: 0.0,
29            lcp_candidate: None,
30            viewport_rect: LayoutRect::from_size(viewport_size),
31            lcp_candidate_updated: false,
32        }
33    }
34
35    // Returns true if has non-zero width and height values.
36    pub(crate) fn check_bounding_rect(&self, bounds: LayoutRect, clip_rect: LayoutRect) -> bool {
37        let clipped_rect = bounds
38            .intersection(&clip_rect)
39            .unwrap_or(LayoutRect::zero())
40            .to_rect();
41
42        let bounding_rect = clipped_rect
43            .intersection(&self.viewport_rect.to_rect().cast_unit())
44            .unwrap_or(Rect::zero());
45
46        !bounding_rect.is_empty()
47    }
48
49    fn calculate_intersection_rect(
50        &self,
51        bounds: LayoutRect,
52        clip_rect: LayoutRect,
53        transform: FastLayoutTransform,
54    ) -> Rect<f32, CSSPixel> {
55        let clipped_rect = bounds
56            .intersection(&clip_rect)
57            .unwrap_or(LayoutRect::zero());
58
59        let transformed_rect = transform_au_rectangle(
60            f32_rect_to_au_rect(clipped_rect.to_rect().cast_unit()),
61            transform,
62        )
63        .unwrap_or_default();
64
65        let transformed_rect = au_rect_to_f32_rect(transformed_rect);
66
67        let intersection_rect =
68            transformed_rect.intersection(&self.viewport_rect.to_rect().cast_unit());
69
70        intersection_rect.unwrap_or(Rect::zero())
71    }
72
73    pub(crate) fn update_lcp_candidate(
74        &mut self,
75        lcp_candidate_id: LCPCandidateID,
76        bounds: LayoutRect,
77        clip_rect: LayoutRect,
78        transform: FastLayoutTransform,
79    ) {
80        // From <https://www.w3.org/TR/largest-contentful-paint/#sec-report-largest-contentful-paint>:
81        //  Let intersectionRect be the value returned by the intersection rect algorithm using imageElement as the target and viewport as the root.
82        let intersection_rect = self.calculate_intersection_rect(bounds, clip_rect, transform);
83
84        // Let size be the effective visual size of candidate’s element given intersectionRect.
85        let size = intersection_rect.size.width * intersection_rect.size.height;
86
87        // If size is less than or equal to document’s largest contentful paint size, return.
88        if size <= self.lcp_size {
89            return;
90        }
91
92        // Set newCandidate to be a new largest contentful paint candidate
93        self.lcp_candidate = Some(LCPCandidate::new(lcp_candidate_id, size as usize));
94        self.lcp_size = size;
95        self.lcp_candidate_updated = true;
96    }
97
98    pub(crate) fn did_lcp_candidate_update(&self) -> bool {
99        self.lcp_candidate_updated
100    }
101
102    pub(crate) fn unset_lcp_candidate_updated(&mut self) {
103        self.lcp_candidate_updated = false;
104    }
105
106    pub(crate) fn largest_contentful_paint_candidate(&self) -> Option<LCPCandidate> {
107        self.lcp_candidate
108    }
109}