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