layout/display_list/
paint_timing_handler.rs1use 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 lcp_size: f32,
18 lcp_candidate: Option<LCPCandidate>,
20 viewport_rect: LayoutRect,
22 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 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 let intersection_rect = self.calculate_intersection_rect(bounds, clip_rect, transform);
86
87 let size = intersection_rect.size.width * intersection_rect.size.height;
89
90 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 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}