compositing/
largest_contentful_paint_calculator.rs1use 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#[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#[derive(Default)]
71struct LargestContentfulPaintsContainer {
72 lcp_candidates: Vec<LCPCandidate>,
74 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}