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