paint/
largest_contentful_paint_calculator.rs1use base::cross_process_instant::CrossProcessInstant;
6use base::id::WebViewId;
7use paint_api::largest_contentful_paint_candidate::{LCPCandidate, LargestContentfulPaint};
8use rustc_hash::{FxHashMap, FxHashSet};
9use webrender_api::PipelineId;
10
11#[derive(Default)]
13pub(crate) struct LargestContentfulPaintCalculator {
14 lcp_containers: FxHashMap<PipelineId, LargestContentfulPaintsContainer>,
15 disabled_lcp_for_webviews: FxHashSet<WebViewId>,
16}
17
18impl LargestContentfulPaintCalculator {
19 pub(crate) fn new() -> Self {
20 Self {
21 lcp_containers: Default::default(),
22 disabled_lcp_for_webviews: Default::default(),
23 }
24 }
25
26 pub(crate) fn append_lcp_candidate(
27 &mut self,
28 webview_id: WebViewId,
29 pipeline_id: PipelineId,
30 candidate: LCPCandidate,
31 ) -> bool {
32 if self.disabled_lcp_for_webviews.contains(&webview_id) {
33 return false;
34 }
35 self.lcp_containers
36 .entry(pipeline_id)
37 .or_default()
38 .lcp_candidates
39 .push(candidate);
40 true
41 }
42
43 pub(crate) fn remove_lcp_candidates_for_pipeline(&mut self, pipeline_id: PipelineId) {
44 self.lcp_containers.remove(&pipeline_id);
45 }
46
47 pub(crate) fn calculate_largest_contentful_paint(
48 &mut self,
49 paint_time: CrossProcessInstant,
50 pipeline_id: PipelineId,
51 ) -> Option<LargestContentfulPaint> {
52 self.lcp_containers
53 .get_mut(&pipeline_id)
54 .and_then(|container| container.calculate_largest_contentful_paint(paint_time))
55 }
56
57 pub(crate) fn disable_for_webview(&mut self, webview_id: WebViewId) {
58 self.disabled_lcp_for_webviews.insert(webview_id);
59 }
60
61 pub(crate) fn note_webview_removed(&mut self, webview_id: WebViewId) {
62 self.disabled_lcp_for_webviews.remove(&webview_id);
63 }
64}
65
66#[derive(Default)]
68struct LargestContentfulPaintsContainer {
69 lcp_candidates: Vec<LCPCandidate>,
71 latest_lcp: Option<LargestContentfulPaint>,
73}
74
75impl LargestContentfulPaintsContainer {
76 fn calculate_largest_contentful_paint(
77 &mut self,
78 paint_time: CrossProcessInstant,
79 ) -> Option<LargestContentfulPaint> {
80 if self.lcp_candidates.is_empty() {
81 return self.latest_lcp;
82 }
83
84 let candidates = std::mem::take(&mut self.lcp_candidates);
85 if let Some(max_candidate) = candidates.into_iter().max_by_key(|c| c.area) {
86 match self.latest_lcp {
87 None => {
88 self.latest_lcp = Some(LargestContentfulPaint::from(max_candidate, paint_time));
89 },
90 Some(ref latest_lcp) => {
91 if max_candidate.area > latest_lcp.area {
92 self.latest_lcp =
93 Some(LargestContentfulPaint::from(max_candidate, paint_time));
94 }
95 },
96 }
97 }
98
99 self.latest_lcp
100 }
101}