1use std::cell::RefCell;
6use std::rc::Rc;
7
8use base::Epoch;
9use base::id::{PipelineId, WebViewId};
10use embedder_traits::ScreenshotCaptureError;
11use euclid::{Point2D, Size2D};
12use image::RgbaImage;
13use log::error;
14use rustc_hash::FxHashMap;
15use webrender_api::units::{DeviceIntRect, DeviceRect};
16
17use crate::paint::RepaintReason;
18use crate::painter::Painter;
19
20pub(crate) struct ScreenshotRequest {
21 webview_id: WebViewId,
22 rect: Option<DeviceRect>,
23 callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
24 phase: ScreenshotRequestPhase,
25}
26
27#[derive(PartialEq)]
29pub(crate) enum ScreenshotRequestPhase {
30 ConstellationRequest,
45 WaitingOnPipelineDisplayLists(Rc<FxHashMap<PipelineId, Epoch>>),
49 WaitingOnFrame,
56}
57
58#[derive(Default)]
59pub(crate) struct ScreenshotTaker {
60 requests: RefCell<Vec<ScreenshotRequest>>,
64}
65
66impl ScreenshotTaker {
67 pub(crate) fn request_screenshot(
68 &self,
69 webview_id: WebViewId,
70 rect: Option<DeviceRect>,
71 callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
72 ) {
73 self.requests.borrow_mut().push(ScreenshotRequest {
74 webview_id,
75 rect,
76 callback,
77 phase: ScreenshotRequestPhase::ConstellationRequest,
78 });
79 }
80
81 pub(crate) fn handle_screenshot_readiness_reply(
82 &self,
83 webview_id: WebViewId,
84 expected_epochs: FxHashMap<PipelineId, Epoch>,
85 renderer: &Painter,
86 ) {
87 let expected_epochs = Rc::new(expected_epochs);
88
89 for screenshot_request in self.requests.borrow_mut().iter_mut() {
90 if screenshot_request.webview_id != webview_id ||
91 screenshot_request.phase != ScreenshotRequestPhase::ConstellationRequest
92 {
93 continue;
94 }
95 screenshot_request.phase =
96 ScreenshotRequestPhase::WaitingOnPipelineDisplayLists(expected_epochs.clone());
97 }
98
99 self.prepare_screenshot_requests_for_render(renderer);
103 }
104
105 pub(crate) fn prepare_screenshot_requests_for_render(&self, renderer: &Painter) {
106 let mut any_became_ready = false;
107
108 for screenshot_request in self.requests.borrow_mut().iter_mut() {
109 let ScreenshotRequestPhase::WaitingOnPipelineDisplayLists(pipelines) =
110 &screenshot_request.phase
111 else {
112 continue;
113 };
114
115 let Some(webview) = renderer.webview_renderer(screenshot_request.webview_id) else {
116 continue;
117 };
118
119 if pipelines.iter().all(|(pipeline_id, expected_epoch)| {
120 webview
121 .pipelines
122 .get(pipeline_id)
123 .and_then(|pipeline| pipeline.display_list_epoch)
124 .is_some_and(|epoch| epoch >= *expected_epoch)
125 }) {
126 screenshot_request.phase = ScreenshotRequestPhase::WaitingOnFrame;
127 any_became_ready = true;
128 }
129 }
130
131 if any_became_ready {
135 self.maybe_trigger_paint_for_screenshot(renderer);
136 }
137 }
138
139 pub(crate) fn maybe_trigger_paint_for_screenshot(&self, renderer: &Painter) {
140 if renderer.has_pending_frames() {
141 return;
142 }
143
144 if self.requests.borrow().iter().any(|screenshot_request| {
145 matches!(
146 screenshot_request.phase,
147 ScreenshotRequestPhase::WaitingOnFrame
148 )
149 }) {
150 renderer.set_needs_repaint(RepaintReason::ReadyForScreenshot);
151 }
152 }
153
154 pub(crate) fn maybe_take_screenshots(&self, renderer: &Painter) {
155 if renderer.has_pending_frames() {
156 return;
157 }
158
159 let mut requests = self.requests.borrow_mut();
160 if requests.is_empty() {
161 return;
162 }
163
164 let screenshots = requests.drain(..);
167 *requests = screenshots
168 .filter_map(|screenshot_request| {
169 if !matches!(
170 screenshot_request.phase,
171 ScreenshotRequestPhase::WaitingOnFrame
172 ) {
173 return Some(screenshot_request);
174 }
175
176 let callback = screenshot_request.callback;
177 let Some(webview_renderer) =
178 renderer.webview_renderer(screenshot_request.webview_id)
179 else {
180 callback(Err(ScreenshotCaptureError::WebViewDoesNotExist));
181 return None;
182 };
183
184 let viewport_rect = webview_renderer.rect.to_i32();
185 let viewport_size = viewport_rect.size();
186 let rect = screenshot_request.rect.map_or(viewport_rect, |rect| {
187 let x = rect.min.x as i32;
192 let y = 0.max(
193 (viewport_size.height as f32 - rect.min.y - rect.size().height) as i32,
194 );
195 let w = rect.size().width as i32;
196 let h = rect.size().height as i32;
197
198 DeviceIntRect::from_origin_and_size(Point2D::new(x, y), Size2D::new(w, h))
199 });
200 if let Err(error) = renderer.rendering_context.make_current() {
201 error!("Failed to make the rendering context current: {error:?}");
202 }
203 let result = renderer
204 .rendering_context
205 .read_to_image(rect)
206 .ok_or(ScreenshotCaptureError::CouldNotReadImage);
207 callback(result);
208 None
209 })
210 .collect();
211 }
212}