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_webviews: FxHashSet<WebViewId>,
16}
17
18impl LargestContentfulPaintCalculator {
19 pub(crate) fn new() -> Self {
20 Self {
21 lcp_containers: Default::default(),
22 disabled_webviews: Default::default(),
23 }
24 }
25
26 pub(crate) fn append_lcp_candidate(
27 &mut self,
28 candidate: LCPCandidate,
29 pipeline_id: PipelineId,
30 webview_id: &WebViewId,
31 ) {
32 assert!(self.enabled_for_webview(webview_id));
33 self.lcp_containers
34 .entry(pipeline_id)
35 .or_default()
36 .lcp_candidates
37 .push(candidate);
38 }
39
40 pub(crate) fn enabled_for_webview(&self, webview_id: &WebViewId) -> bool {
41 !self.disabled_webviews.contains(webview_id)
42 }
43
44 pub(crate) fn remove_lcp_candidates_for_pipeline(&mut self, pipeline_id: &PipelineId) {
45 self.lcp_containers.remove(pipeline_id);
46 }
47
48 pub(crate) fn calculate_largest_contentful_paint(
49 &mut self,
50 paint_time: CrossProcessInstant,
51 pipeline_id: PipelineId,
52 ) -> Option<LargestContentfulPaint> {
53 self.lcp_containers
54 .get_mut(&pipeline_id)
55 .and_then(|container| container.calculate_largest_contentful_paint(paint_time))
56 }
57
58 pub(crate) fn disable_for_webview(&mut self, webview_id: WebViewId) {
60 self.disabled_webviews.insert(webview_id);
61 }
62
63 pub(crate) fn enable_for_webview(&mut self, webview_id: &WebViewId) {
64 self.disabled_webviews.remove(webview_id);
65 }
66}
67
68#[derive(Default)]
70struct LargestContentfulPaintsContainer {
71 lcp_candidates: Vec<LCPCandidate>,
73 latest_lcp: Option<LargestContentfulPaint>,
75}
76
77impl LargestContentfulPaintsContainer {
78 fn calculate_largest_contentful_paint(
79 &mut self,
80 paint_time: CrossProcessInstant,
81 ) -> Option<LargestContentfulPaint> {
82 if self.lcp_candidates.is_empty() {
83 return self.latest_lcp;
84 }
85
86 let candidates = std::mem::take(&mut self.lcp_candidates);
87 if let Some(max_candidate) = candidates.into_iter().max_by_key(|c| c.area) {
88 match self.latest_lcp {
89 None => {
90 self.latest_lcp = Some(LargestContentfulPaint::from(max_candidate, paint_time));
91 },
92 Some(ref latest_lcp) => {
93 if max_candidate.area > latest_lcp.area {
94 self.latest_lcp =
95 Some(LargestContentfulPaint::from(max_candidate, paint_time));
96 }
97 },
98 }
99 }
100
101 self.latest_lcp
102 }
103}