compositing/
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 compositing_traits::largest_contentful_paint_candidate::{
8    LCPCandidate, LargestContentfulPaint,
9};
10use rustc_hash::{FxHashMap, FxHashSet};
11use webrender_api::PipelineId;
12
13/// Holds the [`LargestContentfulPaintsContainer`] for each pipeline.
14#[derive(Default)]
15pub(crate) struct LargestContentfulPaintCalculator {
16    lcp_containers: FxHashMap<PipelineId, LargestContentfulPaintsContainer>,
17    disabled_lcp_for_webviews: FxHashSet<WebViewId>,
18}
19
20impl LargestContentfulPaintCalculator {
21    pub(crate) fn new() -> Self {
22        Self {
23            lcp_containers: Default::default(),
24            disabled_lcp_for_webviews: Default::default(),
25        }
26    }
27
28    pub(crate) fn append_lcp_candidate(
29        &mut self,
30        webview_id: WebViewId,
31        pipeline_id: PipelineId,
32        candidate: LCPCandidate,
33    ) -> bool {
34        if self.disabled_lcp_for_webviews.contains(&webview_id) {
35            return false;
36        }
37        self.lcp_containers
38            .entry(pipeline_id)
39            .or_default()
40            .lcp_candidates
41            .push(candidate);
42        true
43    }
44
45    pub(crate) fn remove_lcp_candidates_for_pipeline(&mut self, pipeline_id: PipelineId) {
46        self.lcp_containers.remove(&pipeline_id);
47    }
48
49    pub(crate) fn calculate_largest_contentful_paint(
50        &mut self,
51        paint_time: CrossProcessInstant,
52        pipeline_id: PipelineId,
53    ) -> Option<LargestContentfulPaint> {
54        self.lcp_containers
55            .get_mut(&pipeline_id)
56            .and_then(|container| container.calculate_largest_contentful_paint(paint_time))
57    }
58
59    pub(crate) fn add_to_disabled_lcp_webviews(&mut self, webview_id: WebViewId) {
60        self.disabled_lcp_for_webviews.insert(webview_id);
61    }
62
63    pub(crate) fn clear(&mut self) {
64        self.lcp_containers.clear();
65        self.disabled_lcp_for_webviews.clear();
66    }
67}
68
69/// Holds the LCP candidates and the latest LCP for a specific pipeline.
70#[derive(Default)]
71struct LargestContentfulPaintsContainer {
72    /// List of candidates for Largest Contentful Paint in this pipeline.
73    lcp_candidates: Vec<LCPCandidate>,
74    /// The most recent Largest Contentful Paint, if any.
75    latest_lcp: Option<LargestContentfulPaint>,
76}
77
78impl LargestContentfulPaintsContainer {
79    fn calculate_largest_contentful_paint(
80        &mut self,
81        paint_time: CrossProcessInstant,
82    ) -> Option<LargestContentfulPaint> {
83        if self.lcp_candidates.is_empty() {
84            return self.latest_lcp;
85        }
86
87        let candidates = std::mem::take(&mut self.lcp_candidates);
88        if let Some(max_candidate) = candidates.into_iter().max_by_key(|c| c.area) {
89            match self.latest_lcp {
90                None => {
91                    self.latest_lcp = Some(LargestContentfulPaint::from(max_candidate, paint_time));
92                },
93                Some(ref latest_lcp) => {
94                    if max_candidate.area > latest_lcp.area {
95                        self.latest_lcp =
96                            Some(LargestContentfulPaint::from(max_candidate, paint_time));
97                    }
98                },
99            }
100        }
101
102        self.latest_lcp
103    }
104}