paint/
largest_contentful_paint_calculator.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 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/// Holds the [`LargestContentfulPaintsContainer`] for each pipeline.
12#[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/// Holds the LCP candidates and the latest LCP for a specific pipeline.
67#[derive(Default)]
68struct LargestContentfulPaintsContainer {
69    /// List of candidates for Largest Contentful Paint in this pipeline.
70    lcp_candidates: Vec<LCPCandidate>,
71    /// The most recent Largest Contentful Paint, if any.
72    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}