1use std::cell::{Cell, Ref, RefCell, RefMut};
6use std::collections::HashMap;
7use std::env;
8use std::fs::create_dir_all;
9use std::rc::Rc;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use base::generic_channel::RoutedReceiver;
13use base::id::{PainterId, WebViewId};
14use bitflags::bitflags;
15use canvas_traits::webgl::{WebGLContextId, WebGLThreads};
16use compositing_traits::rendering_context::RenderingContext;
17use compositing_traits::{
18 CompositorMsg, CompositorProxy, PainterSurfmanDetails, PainterSurfmanDetailsMap,
19 WebRenderExternalImageIdManager, WebViewTrait,
20};
21use constellation_traits::EmbedderToConstellationMessage;
22use crossbeam_channel::Sender;
23use dpi::PhysicalSize;
24use embedder_traits::{
25 EventLoopWaker, InputEventAndId, InputEventId, InputEventResult, ScreenshotCaptureError,
26 Scroll, ShutdownState, ViewportDetails, WebViewPoint, WebViewRect,
27};
28use euclid::{Scale, Size2D};
29use image::RgbaImage;
30use ipc_channel::ipc::{self};
31use log::{debug, warn};
32use profile_traits::mem::{
33 ProcessReports, ProfilerRegistration, Report, ReportKind, perform_memory_report,
34};
35use profile_traits::path;
36use profile_traits::time::{self as profile_time};
37use servo_geometry::DeviceIndependentPixel;
38use style_traits::CSSPixel;
39use surfman::Device;
40use surfman::chains::SwapChains;
41use webgl::WebGLComm;
42use webgl::webgl_thread::WebGLContextBusyMap;
43#[cfg(feature = "webgpu")]
44use webgpu::canvas_context::WebGpuExternalImageMap;
45use webrender::{CaptureBits, MemoryReport};
46use webrender_api::units::{DevicePixel, DevicePoint};
47
48use crate::InitialCompositorState;
49use crate::painter::Painter;
50use crate::webview_renderer::UnknownWebView;
51
52#[derive(Copy, Clone)]
54pub enum WebRenderDebugOption {
55 Profiler,
56 TextureCacheDebug,
57 RenderTargetDebug,
58}
59
60pub struct IOCompositor {
62 painters: Vec<Rc<RefCell<Painter>>>,
65
66 pub(crate) compositor_proxy: CompositorProxy,
69
70 pub(crate) event_loop_waker: Box<dyn EventLoopWaker>,
73
74 shutdown_state: Rc<Cell<ShutdownState>>,
77
78 compositor_receiver: RoutedReceiver<CompositorMsg>,
80
81 pub(crate) embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
83
84 webrender_external_image_id_manager: WebRenderExternalImageIdManager,
86
87 pub(crate) painter_surfman_details_map: PainterSurfmanDetailsMap,
90
91 pub(crate) busy_webgl_contexts_map: WebGLContextBusyMap,
95
96 webgl_threads: WebGLThreads,
98
99 pub(crate) swap_chains: SwapChains<WebGLContextId, Device>,
101
102 time_profiler_chan: profile_time::ProfilerChan,
104
105 _mem_profiler_registration: ProfilerRegistration,
108
109 #[cfg(feature = "webxr")]
111 webxr_main_thread: RefCell<webxr::MainThreadRegistry>,
112
113 #[cfg(feature = "webgpu")]
115 webgpu_image_map: std::cell::OnceCell<WebGpuExternalImageMap>,
116}
117
118#[derive(Clone, Copy, Default, PartialEq)]
120pub(crate) struct RepaintReason(u8);
121
122bitflags! {
123 impl RepaintReason: u8 {
124 const ReadyForScreenshot = 1 << 0;
126 const ChangedAnimationState = 1 << 1;
128 const NewWebRenderFrame = 1 << 2;
130 const Resize = 1 << 3;
132 const StartedFlinging = 1 << 4;
134 }
135}
136
137impl IOCompositor {
138 pub fn new(state: InitialCompositorState) -> Rc<RefCell<Self>> {
139 let registration = state.mem_profiler_chan.prepare_memory_reporting(
140 "compositor".into(),
141 state.compositor_proxy.clone(),
142 CompositorMsg::CollectMemoryReport,
143 );
144
145 let webrender_external_image_id_manager = WebRenderExternalImageIdManager::default();
146 let painter_surfman_details_map = PainterSurfmanDetailsMap::default();
147 let WebGLComm {
148 webgl_threads,
149 swap_chains,
150 busy_webgl_context_map,
151 #[cfg(feature = "webxr")]
152 webxr_layer_grand_manager,
153 } = WebGLComm::new(
154 state.compositor_proxy.cross_process_compositor_api.clone(),
155 webrender_external_image_id_manager.clone(),
156 painter_surfman_details_map.clone(),
157 );
158
159 #[cfg(feature = "webxr")]
161 let webxr_main_thread = {
162 use servo_config::pref;
163
164 let mut webxr_main_thread = webxr::MainThreadRegistry::new(
165 state.event_loop_waker.clone(),
166 webxr_layer_grand_manager,
167 )
168 .expect("Failed to create WebXR device registry");
169 if pref!(dom_webxr_enabled) {
170 state.webxr_registry.register(&mut webxr_main_thread);
171 }
172 webxr_main_thread
173 };
174
175 Rc::new(RefCell::new(IOCompositor {
176 painters: Default::default(),
177 compositor_proxy: state.compositor_proxy,
178 event_loop_waker: state.event_loop_waker,
179 shutdown_state: state.shutdown_state,
180 compositor_receiver: state.receiver,
181 embedder_to_constellation_sender: state.embedder_to_constellation_sender.clone(),
182 webrender_external_image_id_manager,
183 webgl_threads,
184 swap_chains,
185 time_profiler_chan: state.time_profiler_chan,
186 _mem_profiler_registration: registration,
187 painter_surfman_details_map,
188 busy_webgl_contexts_map: busy_webgl_context_map,
189 #[cfg(feature = "webxr")]
190 webxr_main_thread: RefCell::new(webxr_main_thread),
191 #[cfg(feature = "webgpu")]
192 webgpu_image_map: Default::default(),
193 }))
194 }
195
196 pub fn register_rendering_context(
197 &mut self,
198 rendering_context: Rc<dyn RenderingContext>,
199 ) -> PainterId {
200 if let Some(painter_id) = self.painters.iter().find_map(|painter| {
201 let painter = painter.borrow();
202 if Rc::ptr_eq(&painter.rendering_context, &rendering_context) {
203 Some(painter.painter_id)
204 } else {
205 None
206 }
207 }) {
208 return painter_id;
209 }
210
211 let painter = Painter::new(rendering_context.clone(), self);
212 let connection = rendering_context
213 .connection()
214 .expect("Failed to get connection");
215 let adapter = connection
216 .create_adapter()
217 .expect("Failed to create adapter");
218
219 let painter_surfman_details = PainterSurfmanDetails {
220 connection,
221 adapter,
222 };
223 self.painter_surfman_details_map
224 .insert(painter.painter_id, painter_surfman_details);
225
226 let painter_id = painter.painter_id;
227 self.painters.push(Rc::new(RefCell::new(painter)));
228 painter_id
229 }
230
231 pub(crate) fn painter<'a>(&'a self, painter_id: PainterId) -> Ref<'a, Painter> {
232 self.painters
233 .iter()
234 .map(|painter| painter.borrow())
235 .find(|painter| painter.painter_id == painter_id)
236 .expect("painter_id not found")
237 }
238
239 pub(crate) fn painter_mut<'a>(&'a self, painter_id: PainterId) -> RefMut<'a, Painter> {
240 self.painters
241 .iter()
242 .map(|painter| painter.borrow_mut())
243 .find(|painter| painter.painter_id == painter_id)
244 .expect("painter_id not found")
245 }
246
247 pub fn painter_id(&self) -> PainterId {
248 self.painters[0].borrow().painter_id
249 }
250
251 pub fn deinit(&mut self) {
252 for painter in &self.painters {
253 painter.borrow_mut().deinit();
254 }
255 }
256
257 pub fn rendering_context_size(&self, painter_id: PainterId) -> Size2D<u32, DevicePixel> {
258 self.painter(painter_id).rendering_context.size2d()
259 }
260
261 pub fn webgl_threads(&self) -> WebGLThreads {
262 self.webgl_threads.clone()
263 }
264
265 pub fn webrender_external_image_id_manager(&self) -> WebRenderExternalImageIdManager {
266 self.webrender_external_image_id_manager.clone()
267 }
268
269 pub fn webxr_running(&self) -> bool {
270 #[cfg(feature = "webxr")]
271 {
272 self.webxr_main_thread.borrow().running()
273 }
274 #[cfg(not(feature = "webxr"))]
275 {
276 false
277 }
278 }
279
280 #[cfg(feature = "webxr")]
281 pub fn webxr_main_thread_registry(&self) -> webxr_api::Registry {
282 self.webxr_main_thread.borrow().registry()
283 }
284
285 #[cfg(feature = "webgpu")]
286 pub fn webgpu_image_map(&self) -> WebGpuExternalImageMap {
287 self.webgpu_image_map.get_or_init(Default::default).clone()
288 }
289
290 pub fn webviews_needing_repaint(&self) -> Vec<WebViewId> {
291 self.painters
292 .iter()
293 .flat_map(|painter| painter.borrow().webviews_needing_repaint())
294 .collect()
295 }
296
297 pub fn finish_shutting_down(&self) {
298 while self.compositor_receiver.try_recv().is_ok() {}
301
302 let (webgl_exit_sender, webgl_exit_receiver) =
303 ipc::channel().expect("Failed to create IPC channel!");
304 if !self
305 .webgl_threads
306 .exit(webgl_exit_sender)
307 .is_ok_and(|_| webgl_exit_receiver.recv().is_ok())
308 {
309 warn!("Could not exit WebGLThread.");
310 }
311
312 if let Ok((sender, receiver)) = ipc::channel() {
314 self.time_profiler_chan
315 .send(profile_time::ProfilerMsg::Exit(sender));
316 let _ = receiver.recv();
317 }
318 }
319
320 fn handle_browser_message(&self, msg: CompositorMsg) {
321 trace_msg_from_constellation!(msg, "{msg:?}");
322
323 match self.shutdown_state() {
324 ShutdownState::NotShuttingDown => {},
325 ShutdownState::ShuttingDown => {
326 self.handle_browser_message_while_shutting_down(msg);
327 return;
328 },
329 ShutdownState::FinishedShuttingDown => {
330 return;
332 },
333 }
334
335 match msg {
336 CompositorMsg::CollectMemoryReport(sender) => {
337 self.collect_memory_report(sender);
338 },
339 CompositorMsg::ChangeRunningAnimationsState(
340 webview_id,
341 pipeline_id,
342 animation_state,
343 ) => {
344 self.painter_mut(webview_id.into())
345 .change_running_animations_state(webview_id, pipeline_id, animation_state);
346 },
347 CompositorMsg::SetFrameTreeForWebView(webview_id, frame_tree) => {
348 self.painter_mut(webview_id.into())
349 .set_frame_tree_for_webview(&frame_tree);
350 },
351 CompositorMsg::RemoveWebView(webview_id) => {
352 self.painter_mut(webview_id.into())
353 .remove_webview(webview_id);
354 },
355 CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
356 self.painter_mut(webview_id.into()).set_throttled(
357 webview_id,
358 pipeline_id,
359 throttled,
360 );
361 },
362 CompositorMsg::PipelineExited(webview_id, pipeline_id, pipeline_exit_source) => {
363 self.painter_mut(webview_id.into()).notify_pipeline_exited(
364 webview_id,
365 pipeline_id,
366 pipeline_exit_source,
367 );
368 },
369 CompositorMsg::NewWebRenderFrameReady(..) => {
370 unreachable!("New WebRender frames should be handled in the caller.");
371 },
372 CompositorMsg::SendInitialTransaction(webview_id, pipeline_id) => {
373 self.painter_mut(webview_id.into())
374 .send_initial_pipeline_transaction(webview_id, pipeline_id);
375 },
376 CompositorMsg::ScrollNodeByDelta(
377 webview_id,
378 pipeline_id,
379 offset,
380 external_scroll_id,
381 ) => {
382 self.painter_mut(webview_id.into()).scroll_node_by_delta(
383 webview_id,
384 pipeline_id,
385 offset,
386 external_scroll_id,
387 );
388 },
389 CompositorMsg::ScrollViewportByDelta(webview_id, delta) => {
390 self.painter_mut(webview_id.into())
391 .scroll_viewport_by_delta(webview_id, delta);
392 },
393 CompositorMsg::UpdateEpoch {
394 webview_id,
395 pipeline_id,
396 epoch,
397 } => {
398 self.painter_mut(webview_id.into())
399 .update_epoch(webview_id, pipeline_id, epoch);
400 },
401 CompositorMsg::SendDisplayList {
402 webview_id,
403 display_list_descriptor,
404 display_list_receiver,
405 } => {
406 self.painter_mut(webview_id.into()).handle_new_display_list(
407 webview_id,
408 display_list_descriptor,
409 display_list_receiver,
410 );
411 },
412 CompositorMsg::GenerateFrame(painter_ids) => {
413 for painter_id in painter_ids {
414 self.painter_mut(painter_id).generate_frame_for_script();
415 }
416 },
417 CompositorMsg::GenerateImageKey(webview_id, sender) => {
418 let _ = sender.send(self.painter(webview_id.into()).generate_image_key());
419 },
420 CompositorMsg::GenerateImageKeysForPipeline(webview_id, pipeline_id) => {
421 let _ = self.embedder_to_constellation_sender.send(
422 EmbedderToConstellationMessage::SendImageKeysForPipeline(
423 pipeline_id,
424 self.painter(webview_id.into()).generate_image_keys(),
425 ),
426 );
427 },
428 CompositorMsg::UpdateImages(painter_id, updates) => {
429 self.painter_mut(painter_id).update_images(updates);
430 },
431 CompositorMsg::DelayNewFrameForCanvas(
432 webview_id,
433 pipeline_id,
434 canvas_epoch,
435 image_keys,
436 ) => {
437 self.painter_mut(webview_id.into())
438 .delay_new_frames_for_canvas(pipeline_id, canvas_epoch, image_keys);
439 },
440 CompositorMsg::AddFont(painter_id, font_key, data, index) => {
441 debug_assert!(painter_id == font_key.into());
442 self.painter_mut(font_key.into())
443 .add_font(font_key, data, index);
444 },
445 CompositorMsg::AddSystemFont(painter_id, font_key, native_handle) => {
446 debug_assert!(painter_id == font_key.into());
447 self.painter_mut(font_key.into())
448 .add_system_font(font_key, native_handle);
449 },
450 CompositorMsg::AddFontInstance(
451 painter_id,
452 font_instance_key,
453 font_key,
454 size,
455 flags,
456 variations,
457 ) => {
458 debug_assert!(painter_id == font_key.into());
459 debug_assert!(painter_id == font_instance_key.into());
460 self.painter_mut(font_key.into()).add_font_instance(
461 font_instance_key,
462 font_key,
463 size,
464 flags,
465 variations,
466 );
467 },
468 CompositorMsg::RemoveFonts(painter_id, keys, instance_keys) => {
469 self.painter_mut(painter_id)
470 .remove_fonts(keys, instance_keys);
471 },
472 CompositorMsg::GenerateFontKeys(
473 number_of_font_keys,
474 number_of_font_instance_keys,
475 result_sender,
476 painter_id,
477 ) => {
478 let _ = result_sender.send(
479 self.painter_mut(painter_id)
480 .generate_font_keys(number_of_font_keys, number_of_font_instance_keys),
481 );
482 },
483 CompositorMsg::Viewport(webview_id, viewport_description) => {
484 self.painter_mut(webview_id.into())
485 .set_viewport_description(webview_id, viewport_description);
486 },
487 CompositorMsg::ScreenshotReadinessReponse(webview_id, pipelines_and_epochs) => {
488 self.painter(webview_id.into())
489 .handle_screenshot_readiness_reply(webview_id, pipelines_and_epochs);
490 },
491 CompositorMsg::SendLCPCandidate(lcp_candidate, webview_id, pipeline_id, epoch) => {
492 self.painter_mut(webview_id.into()).append_lcp_candidate(
493 lcp_candidate,
494 webview_id,
495 pipeline_id,
496 epoch,
497 );
498 },
499 }
500 }
501
502 fn collect_memory_report(&self, sender: profile_traits::mem::ReportsChan) {
503 let mut memory_report = MemoryReport::default();
504 for painter in &self.painters {
505 memory_report += painter.borrow().report_memory();
506 }
507
508 let mut reports = vec![
509 Report {
510 path: path!["webrender", "fonts"],
511 kind: ReportKind::ExplicitJemallocHeapSize,
512 size: memory_report.fonts,
513 },
514 Report {
515 path: path!["webrender", "images"],
516 kind: ReportKind::ExplicitJemallocHeapSize,
517 size: memory_report.images,
518 },
519 Report {
520 path: path!["webrender", "display-list"],
521 kind: ReportKind::ExplicitJemallocHeapSize,
522 size: memory_report.display_list,
523 },
524 ];
525
526 perform_memory_report(|ops| {
527 let scroll_trees_memory_usage = self
528 .painters
529 .iter()
530 .map(|painter| painter.borrow().scroll_trees_memory_usage(ops))
531 .sum();
532 reports.push(Report {
533 path: path!["compositor", "scroll-tree"],
534 kind: ReportKind::ExplicitJemallocHeapSize,
535 size: scroll_trees_memory_usage,
536 });
537 });
538
539 sender.send(ProcessReports::new(reports));
540 }
541
542 fn handle_browser_message_while_shutting_down(&self, msg: CompositorMsg) {
552 match msg {
553 CompositorMsg::PipelineExited(webview_id, pipeline_id, pipeline_exit_source) => {
554 self.painter_mut(webview_id.into()).notify_pipeline_exited(
555 webview_id,
556 pipeline_id,
557 pipeline_exit_source,
558 );
559 },
560 CompositorMsg::GenerateImageKey(webview_id, sender) => {
561 let _ = sender.send(
562 self.painter(webview_id.into())
563 .webrender_api
564 .generate_image_key(),
565 );
566 },
567 CompositorMsg::GenerateFontKeys(
568 number_of_font_keys,
569 number_of_font_instance_keys,
570 result_sender,
571 painter_id,
572 ) => {
573 let _ = result_sender.send(
574 self.painter_mut(painter_id)
575 .generate_font_keys(number_of_font_keys, number_of_font_instance_keys),
576 );
577 },
578 _ => {
579 debug!("Ignoring message ({:?} while shutting down", msg);
580 },
581 }
582 }
583
584 pub fn add_webview(&self, webview: Box<dyn WebViewTrait>, viewport_details: ViewportDetails) {
585 self.painter_mut(webview.id().into())
586 .add_webview(webview, viewport_details);
587 }
588
589 pub fn show_webview(&self, webview_id: WebViewId) -> Result<(), UnknownWebView> {
590 self.painter_mut(webview_id.into())
591 .set_webview_hidden(webview_id, false)
592 }
593
594 pub fn hide_webview(&self, webview_id: WebViewId) -> Result<(), UnknownWebView> {
595 self.painter_mut(webview_id.into())
596 .set_webview_hidden(webview_id, true)
597 }
598
599 pub fn set_hidpi_scale_factor(
600 &self,
601 webview_id: WebViewId,
602 new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
603 ) {
604 if self.shutdown_state() != ShutdownState::NotShuttingDown {
605 return;
606 }
607 self.painter_mut(webview_id.into())
608 .set_hidpi_scale_factor(webview_id, new_scale_factor);
609 }
610
611 pub fn resize_rendering_context(&self, webview_id: WebViewId, new_size: PhysicalSize<u32>) {
612 if self.shutdown_state() != ShutdownState::NotShuttingDown {
613 return;
614 }
615 self.painter_mut(webview_id.into())
616 .resize_rendering_context(new_size);
617 }
618
619 pub fn set_page_zoom(&self, webview_id: WebViewId, new_zoom: f32) {
620 if self.shutdown_state() != ShutdownState::NotShuttingDown {
621 return;
622 }
623 self.painter_mut(webview_id.into())
624 .set_page_zoom(webview_id, new_zoom);
625 }
626
627 pub fn page_zoom(&self, webview_id: WebViewId) -> f32 {
628 self.painter(webview_id.into()).page_zoom(webview_id)
629 }
630
631 pub fn render(&self, webview_id: WebViewId) {
633 self.painter_mut(webview_id.into())
634 .render(&self.time_profiler_chan);
635 }
636
637 pub fn receiver(&self) -> &RoutedReceiver<CompositorMsg> {
639 &self.compositor_receiver
640 }
641
642 #[servo_tracing::instrument(skip_all)]
643 pub fn handle_messages(&self, mut messages: Vec<CompositorMsg>) {
644 let mut saw_webrender_frame_ready_for_painter = HashMap::new();
649 messages.retain(|message| match message {
650 CompositorMsg::NewWebRenderFrameReady(painter_id, _document_id, need_repaint) => {
651 self.painter(*painter_id).decrement_pending_frames();
652 *saw_webrender_frame_ready_for_painter
653 .entry(*painter_id)
654 .or_insert(*need_repaint) |= *need_repaint;
655
656 false
657 },
658 _ => true,
659 });
660
661 for message in messages {
662 self.handle_browser_message(message);
663 if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
664 return;
665 }
666 }
667
668 for (painter_id, repaint_needed) in saw_webrender_frame_ready_for_painter.iter() {
669 self.painter(*painter_id)
670 .handle_new_webrender_frame_ready(*repaint_needed);
671 }
672 }
673
674 #[servo_tracing::instrument(skip_all)]
675 pub fn perform_updates(&self) -> bool {
676 if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
677 return false;
678 }
679
680 #[cfg(feature = "webxr")]
682 self.webxr_main_thread.borrow_mut().run_one_frame();
683
684 for painter in &self.painters {
685 painter.borrow_mut().perform_updates();
686 }
687
688 self.shutdown_state() != ShutdownState::FinishedShuttingDown
689 }
690
691 pub fn toggle_webrender_debug(&self, option: WebRenderDebugOption) {
692 for painter in &self.painters {
693 painter.borrow_mut().toggle_webrender_debug(option);
694 }
695 }
696
697 pub fn capture_webrender(&self, webview_id: WebViewId) {
698 let capture_id = SystemTime::now()
699 .duration_since(UNIX_EPOCH)
700 .unwrap_or_default()
701 .as_secs()
702 .to_string();
703 let available_path = [env::current_dir(), Ok(env::temp_dir())]
704 .iter()
705 .filter_map(|val| {
706 val.as_ref()
707 .map(|dir| dir.join("webrender-captures").join(&capture_id))
708 .ok()
709 })
710 .find(|val| create_dir_all(val).is_ok());
711
712 let Some(capture_path) = available_path else {
713 log::error!("Couldn't create a path for WebRender captures.");
714 return;
715 };
716
717 log::info!("Saving WebRender capture to {capture_path:?}");
718 self.painter(webview_id.into())
719 .webrender_api
720 .save_capture(capture_path.clone(), CaptureBits::all());
721 }
722
723 pub fn notify_input_event(&self, webview_id: WebViewId, event: InputEventAndId) {
724 if self.shutdown_state() != ShutdownState::NotShuttingDown {
725 return;
726 }
727 self.painter_mut(webview_id.into())
728 .notify_input_event(webview_id, event);
729 }
730
731 pub fn notify_scroll_event(&self, webview_id: WebViewId, scroll: Scroll, point: WebViewPoint) {
732 if self.shutdown_state() != ShutdownState::NotShuttingDown {
733 return;
734 }
735 self.painter_mut(webview_id.into())
736 .notify_scroll_event(webview_id, scroll, point);
737 }
738
739 pub fn pinch_zoom(&self, webview_id: WebViewId, pinch_zoom_delta: f32, center: DevicePoint) {
740 if self.shutdown_state() != ShutdownState::NotShuttingDown {
741 return;
742 }
743 self.painter_mut(webview_id.into())
744 .pinch_zoom(webview_id, pinch_zoom_delta, center);
745 }
746
747 pub fn device_pixels_per_page_pixel(
748 &self,
749 webview_id: WebViewId,
750 ) -> Scale<f32, CSSPixel, DevicePixel> {
751 self.painter_mut(webview_id.into())
752 .device_pixels_per_page_pixel(webview_id)
753 }
754
755 pub(crate) fn shutdown_state(&self) -> ShutdownState {
756 self.shutdown_state.get()
757 }
758
759 pub fn request_screenshot(
760 &self,
761 webview_id: WebViewId,
762 rect: Option<WebViewRect>,
763 callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
764 ) {
765 self.painter(webview_id.into())
766 .request_screenshot(webview_id, rect, callback);
767 }
768
769 pub fn notify_input_event_handled(
770 &self,
771 webview_id: WebViewId,
772 input_event_id: InputEventId,
773 result: InputEventResult,
774 ) {
775 self.painter_mut(webview_id.into())
776 .notify_input_event_handled(webview_id, input_event_id, result);
777 }
778}