From 030f8c962dae662a499dcfc406c2a66cb3118a35 Mon Sep 17 00:00:00 2001 From: Joshua Richardson Date: Mon, 23 Feb 2026 15:43:45 +0000 Subject: [PATCH 1/4] feat: add webview screenshot capture API --- Cargo.toml | 4 + examples/screenshot_smoke.rs | 139 +++++++++++++++++++++++++++++++++++ src/android/mod.rs | 8 ++ src/error.rs | 6 ++ src/lib.rs | 15 ++++ src/webkitgtk/mod.rs | 47 +++++++++++- src/webview2/mod.rs | 53 +++++++++++++ src/wkwebview/mod.rs | 57 +++++++++++++- 8 files changed, 321 insertions(+), 8 deletions(-) create mode 100644 examples/screenshot_smoke.rs diff --git a/Cargo.toml b/Cargo.toml index a63623dc2..bd3cd07f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ objc2-web-kit = { version = "0.3.0", default-features = false, features = [ "WKWebpagePreferences", "WKNavigationResponse", "WKUserScript", + "WKSnapshotConfiguration", "WKHTTPCookieStore", "WKWindowFeatures", ] } @@ -190,6 +191,9 @@ objc2-app-kit = { version = "0.3.0", default-features = false, features = [ "NSSavePanel", "NSMenu", "NSGraphics", + "NSImage", + "NSImageRep", + "NSBitmapImageRep", "NSScreen", ] } diff --git a/examples/screenshot_smoke.rs b/examples/screenshot_smoke.rs new file mode 100644 index 000000000..8b3ce816f --- /dev/null +++ b/examples/screenshot_smoke.rs @@ -0,0 +1,139 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use std::time::Duration; + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoopBuilder}, + window::WindowBuilder, +}; +use wry::{PageLoadEvent, WebViewBuilder}; + +#[derive(Debug, Clone, Copy)] +enum UserEvent { + Capture, + Exit, +} + +fn main() -> wry::Result<()> { + let event_loop = EventLoopBuilder::::with_user_event().build(); + let proxy = event_loop.create_proxy(); + let window = WindowBuilder::new() + .with_title("wry screenshot smoke") + .build(&event_loop) + .unwrap(); + + let already_requested = Arc::new(AtomicBool::new(false)); + let already_requested_ = already_requested.clone(); + let proxy_for_load = proxy.clone(); + + let builder = WebViewBuilder::new() + .with_html( + r#" + + + + + WRY Screenshot Smoke + + + +
+

Screenshot Smoke Test

+

If you can read this in screenshot.png, capture worked.

+
+ +"#, + ) + .with_on_page_load_handler(move |event, _url| { + if matches!(event, PageLoadEvent::Finished) + && !already_requested_.swap(true, Ordering::SeqCst) + { + let proxy = proxy_for_load.clone(); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_millis(1000)); + let _ = proxy.send_event(UserEvent::Capture); + }); + } + }); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::UserEvent(UserEvent::Capture) => { + let proxy = proxy.clone(); + webview + .screenshot(move |result| { + match result { + Ok(bytes) => { + if let Err(err) = std::fs::write("screenshot.png", bytes) { + eprintln!("failed to write screenshot.png: {err}"); + } else { + println!("wrote screenshot.png"); + } + } + Err(err) => eprintln!("screenshot failed: {err}"), + } + let _ = proxy.send_event(UserEvent::Exit); + }) + .expect("failed to request screenshot"); + } + Event::UserEvent(UserEvent::Exit) => *control_flow = ControlFlow::Exit, + _ => {} + } + }); +} diff --git a/src/android/mod.rs b/src/android/mod.rs index 5c7905da4..213bdc9b5 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -346,6 +346,14 @@ impl InnerWebView { Ok(()) } + pub fn screenshot(&self, _handler: F) -> Result<()> + where + F: Fn(Result>) + 'static + Send, + { + // Unsupported + Ok(()) + } + pub fn id(&self) -> crate::WebViewId<'_> { &self.id } diff --git a/src/error.rs b/src/error.rs index aed324ee4..f7e07540c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,9 @@ pub enum Error { #[cfg(gtk)] #[error("Couldn't find X11 Display")] X11DisplayNotFound, + #[cfg(gtk)] + #[error(transparent)] + CairoError(#[from] gtk::cairo::Error), #[cfg(all(gtk, feature = "x11"))] #[error(transparent)] XlibError(#[from] x11_dl::error::OpenError), @@ -74,4 +77,7 @@ pub enum Error { #[cfg(any(target_os = "macos", target_os = "ios"))] #[error("data store is currently opened")] DataStoreInUse, + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[error("Could not obtain screenshot from webview")] + NilScreenshot, } diff --git a/src/lib.rs b/src/lib.rs index 78d07f05d..ec406993c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2033,6 +2033,21 @@ impl WebView { self.webview.print() } + /// Capture a PNG screenshot of the currently visible webview contents. + /// + /// The screenshot is returned asynchronously via `handler`. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS / Windows**: Implemented (visible region only). + /// - **Android / iOS**: Unsupported. + pub fn screenshot(&self, handler: F) -> Result<()> + where + F: Fn(Result>) + 'static + Send, + { + self.webview.screenshot(handler) + } + /// Get a list of cookies for specific url. pub fn cookies_for_url(&self, url: &str) -> Result>> { self.webview.cookies_for_url(url) diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index 6be022caf..2a90f2e1f 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -29,6 +29,7 @@ use std::ffi::c_ulong; use std::sync::atomic::{AtomicBool, Ordering}; use std::{ collections::HashMap, + convert::TryFrom, rc::Rc, sync::{Arc, Mutex}, }; @@ -37,10 +38,10 @@ use webkit2gtk::WebInspectorExt; use webkit2gtk::{ AutoplayPolicy, CookieManagerExt, InputMethodContextExt, LoadEvent, NavigationPolicyDecision, NavigationPolicyDecisionExt, NetworkProxyMode, NetworkProxySettings, PolicyDecisionType, - PrintOperationExt, SettingsExt, URIRequest, URIRequestExt, UserContentInjectedFrames, - UserContentManager, UserContentManagerExt, UserScript, UserScriptInjectionTime, - WebContextExt as Webkit2gtkWeContextExt, WebView, WebViewExt, WebsiteDataManagerExt, - WebsiteDataManagerExtManual, WebsitePolicies, + PrintOperationExt, SettingsExt, SnapshotOptions, SnapshotRegion, URIRequest, URIRequestExt, + UserContentInjectedFrames, UserContentManager, UserContentManagerExt, UserScript, + UserScriptInjectionTime, WebContextExt as Webkit2gtkWeContextExt, WebView, WebViewExt, + WebsiteDataManagerExt, WebsiteDataManagerExtManual, WebsitePolicies, }; use webkit2gtk_sys::{ webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version, @@ -679,6 +680,44 @@ impl InnerWebView { Ok(()) } + pub fn screenshot(&self, handler: F) -> Result<()> + where + F: Fn(Result>) + 'static + Send, + { + let cancellable: Option<&Cancellable> = None; + let cb = move |result: std::result::Result| match result + { + Ok(surface) => match gtk::cairo::ImageSurface::try_from(surface) { + Ok(image) => { + let width = image.width(); + let height = image.height(); + match gdk::pixbuf_get_from_surface(&image, 0, 0, width, height) { + Some(pixbuf) => match pixbuf.save_to_bufferv("png", &[]) { + Ok(bytes) => handler(Ok(bytes)), + Err(err) => handler(Err(Error::GlibError(err))), + }, + None => handler(Err(Error::CairoError( + gtk::cairo::Error::SurfaceTypeMismatch, + ))), + } + } + Err(_) => handler(Err(Error::CairoError( + gtk::cairo::Error::SurfaceTypeMismatch, + ))), + }, + Err(err) => handler(Err(Error::GlibError(err))), + }; + + self.webview.snapshot( + SnapshotRegion::Visible, + SnapshotOptions::NONE, + cancellable, + cb, + ); + + Ok(()) + } + pub fn url(&self) -> Result { Ok(self.webview.uri().unwrap_or_default().to_string()) } diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 2b421a3b3..5adecb291 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -1701,6 +1701,59 @@ impl InnerWebView { ) } + pub fn screenshot(&self, handler: F) -> Result<()> + where + F: Fn(Result>) + 'static + Send, + { + unsafe { + let Some(stream) = SHCreateMemStream(None) else { + return Err(Error::from(windows::core::Error::from(E_POINTER))); + }; + + self.webview.CapturePreview( + COREWEBVIEW2_CAPTURE_PREVIEW_IMAGE_FORMAT_PNG, + &stream, + &CapturePreviewCompletedHandler::create(Box::new(move |res| { + let result = (|| -> windows::core::Result> { + res?; + + let mut bytes = Vec::new(); + stream.Seek(0, STREAM_SEEK_SET, None)?; + + let mut buffer = [0u8; 4096]; + loop { + let mut cb_read = 0; + stream + .Read( + buffer.as_mut_ptr() as *mut _, + buffer.len() as u32, + Some(&mut cb_read), + ) + .ok()?; + + if cb_read == 0 { + break; + } + + bytes.extend_from_slice(&buffer[..cb_read as usize]); + } + + Ok(bytes) + })(); + + match result { + Ok(bytes) => handler(Ok(bytes)), + Err(err) => handler(Err(err.into())), + } + + Ok(()) + })), + )?; + } + + Ok(()) + } + pub fn clear_all_browsing_data(&self) -> Result<()> { unsafe { self diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 77f7a4719..b55d8a138 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -38,12 +38,15 @@ use objc2::{ AllocAnyThread, DeclaredClass, MainThreadOnly, Message, }; #[cfg(target_os = "macos")] -use objc2_app_kit::{NSApplication, NSAutoresizingMaskOptions, NSTitlebarSeparatorStyle, NSView}; +use objc2_app_kit::{ + NSApplication, NSAutoresizingMaskOptions, NSBitmapImageFileType, NSBitmapImageRep, + NSBitmapImageRepPropertyKey, NSImage, NSTitlebarSeparatorStyle, NSView, +}; #[cfg(target_os = "macos")] use objc2_core_foundation::CGSize; use objc2_core_foundation::{CGPoint, CGRect}; use objc2_foundation::{ - ns_string, MainThreadMarker, NSArray, NSBundle, NSDate, NSError, NSHTTPCookie, + ns_string, MainThreadMarker, NSArray, NSBundle, NSDate, NSDictionary, NSError, NSHTTPCookie, NSHTTPCookieDomain, NSHTTPCookieExpires, NSHTTPCookieMaximumAge, NSHTTPCookieName, NSHTTPCookiePath, NSHTTPCookiePropertyKey, NSHTTPCookieSecure, NSHTTPCookieValue, NSHTTPCookieVersion, NSJSONSerialization, NSMutableDictionary, NSMutableURLRequest, NSNumber, @@ -69,8 +72,9 @@ use crate::wkwebview::util::operating_system_version; use objc2_web_kit::WKWebView; use objc2_web_kit::{ - WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKURLSchemeHandler, WKUserContentController, - WKUserScript, WKUserScriptInjectionTime, WKWebViewConfiguration, WKWebsiteDataStore, + WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKSnapshotConfiguration, WKURLSchemeHandler, + WKUserContentController, WKUserScript, WKUserScriptInjectionTime, WKWebViewConfiguration, + WKWebsiteDataStore, }; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; @@ -836,6 +840,51 @@ r#"Object.defineProperty(window, 'ipc', { self.print_with_options(&PrintOptions::default()) } + pub fn screenshot(&self, handler: F) -> crate::Result<()> + where + F: Fn(Result>) + 'static + Send, + { + #[cfg(target_os = "macos")] + unsafe { + let config = WKSnapshotConfiguration::new(self.mtm); + config.setAfterScreenUpdates(true); + + let callback = block2::RcBlock::new(move |image: *mut NSImage, _err: *mut NSError| { + let Some(image) = image.as_ref() else { + handler(Err(Error::NilScreenshot)); + return; + }; + + let Some(tiff) = image.TIFFRepresentation() else { + handler(Err(Error::NilScreenshot)); + return; + }; + + let Some(bitmap) = NSBitmapImageRep::imageRepWithData(&tiff) else { + handler(Err(Error::NilScreenshot)); + return; + }; + + let props: Retained> = + NSDictionary::new(); + let png = bitmap.representationUsingType_properties(NSBitmapImageFileType::PNG, &props); + match png { + Some(data) => handler(Ok(data.to_vec())), + None => handler(Err(Error::NilScreenshot)), + } + }); + + self + .webview + .takeSnapshotWithConfiguration_completionHandler(Some(&config), &callback); + } + + #[cfg(target_os = "ios")] + let _ = handler; + + Ok(()) + } + pub fn print_with_options(&self, _options: &PrintOptions) -> crate::Result<()> { // Safety: objc runtime calls are unsafe #[cfg(target_os = "macos")] From feaa8dbeba31e27b0cc5a2d974eed86d6268e4cc Mon Sep 17 00:00:00 2001 From: Joshua Richardson Date: Mon, 23 Feb 2026 15:50:42 +0000 Subject: [PATCH 2/4] fix: windows borrow checker --- src/webview2/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 5adecb291..cd37fe9f9 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -1709,6 +1709,7 @@ impl InnerWebView { let Some(stream) = SHCreateMemStream(None) else { return Err(Error::from(windows::core::Error::from(E_POINTER))); }; + let stream_for_handler = stream.clone(); self.webview.CapturePreview( COREWEBVIEW2_CAPTURE_PREVIEW_IMAGE_FORMAT_PNG, @@ -1718,12 +1719,12 @@ impl InnerWebView { res?; let mut bytes = Vec::new(); - stream.Seek(0, STREAM_SEEK_SET, None)?; + stream_for_handler.Seek(0, STREAM_SEEK_SET, None)?; let mut buffer = [0u8; 4096]; loop { let mut cb_read = 0; - stream + stream_for_handler .Read( buffer.as_mut_ptr() as *mut _, buffer.len() as u32, From dd5525072ccaea4f8992ccd97f144a519585c17f Mon Sep 17 00:00:00 2001 From: Joshua Richardson Date: Mon, 23 Feb 2026 16:14:04 +0000 Subject: [PATCH 3/4] fix: more in keeping with general wry conventions vs features and error handling --- examples/screenshot_smoke.rs | 39 +++++++++++++++++++++++------------- src/error.rs | 5 ++++- src/lib.rs | 2 +- src/webkitgtk/mod.rs | 5 +---- src/wkwebview/mod.rs | 6 +++++- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/examples/screenshot_smoke.rs b/examples/screenshot_smoke.rs index 8b3ce816f..ae6118fde 100644 --- a/examples/screenshot_smoke.rs +++ b/examples/screenshot_smoke.rs @@ -115,22 +115,33 @@ fn main() -> wry::Result<()> { .. } => *control_flow = ControlFlow::Exit, Event::UserEvent(UserEvent::Capture) => { - let proxy = proxy.clone(); - webview - .screenshot(move |result| { - match result { - Ok(bytes) => { - if let Err(err) = std::fs::write("screenshot.png", bytes) { - eprintln!("failed to write screenshot.png: {err}"); - } else { - println!("wrote screenshot.png"); + // screenshot is not supported on Android or iOS; the handler would + // never be called, so we exit immediately on those platforms. + #[cfg(any(target_os = "android", target_os = "ios"))] + { + let _ = proxy.send_event(UserEvent::Exit); + return; + } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + let proxy = proxy.clone(); + webview + .screenshot(move |result| { + match result { + Ok(bytes) => { + if let Err(err) = std::fs::write("screenshot.png", bytes) { + eprintln!("failed to write screenshot.png: {err}"); + } else { + println!("wrote screenshot.png"); + } } + Err(err) => eprintln!("screenshot failed: {err}"), } - Err(err) => eprintln!("screenshot failed: {err}"), - } - let _ = proxy.send_event(UserEvent::Exit); - }) - .expect("failed to request screenshot"); + let _ = proxy.send_event(UserEvent::Exit); + }) + .expect("failed to request screenshot"); + } } Event::UserEvent(UserEvent::Exit) => *control_flow = ControlFlow::Exit, _ => {} diff --git a/src/error.rs b/src/error.rs index f7e07540c..e9d195efd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,9 @@ pub enum Error { #[cfg(gtk)] #[error(transparent)] CairoError(#[from] gtk::cairo::Error), + #[cfg(gtk)] + #[error("Failed to convert WebView snapshot to a Pixbuf")] + PixbufConversionFailed, #[cfg(all(gtk, feature = "x11"))] #[error(transparent)] XlibError(#[from] x11_dl::error::OpenError), @@ -77,7 +80,7 @@ pub enum Error { #[cfg(any(target_os = "macos", target_os = "ios"))] #[error("data store is currently opened")] DataStoreInUse, - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(target_os = "macos")] #[error("Could not obtain screenshot from webview")] NilScreenshot, } diff --git a/src/lib.rs b/src/lib.rs index ec406993c..502a2d2bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2040,7 +2040,7 @@ impl WebView { /// ## Platform-specific /// /// - **Linux / macOS / Windows**: Implemented (visible region only). - /// - **Android / iOS**: Unsupported. + /// - **Android / iOS**: Not supported; `handler` will never be called. pub fn screenshot(&self, handler: F) -> Result<()> where F: Fn(Result>) + 'static + Send, diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index 2a90f2e1f..b983fe5d0 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -29,7 +29,6 @@ use std::ffi::c_ulong; use std::sync::atomic::{AtomicBool, Ordering}; use std::{ collections::HashMap, - convert::TryFrom, rc::Rc, sync::{Arc, Mutex}, }; @@ -696,9 +695,7 @@ impl InnerWebView { Ok(bytes) => handler(Ok(bytes)), Err(err) => handler(Err(Error::GlibError(err))), }, - None => handler(Err(Error::CairoError( - gtk::cairo::Error::SurfaceTypeMismatch, - ))), + None => handler(Err(Error::PixbufConversionFailed)), } } Err(_) => handler(Err(Error::CairoError( diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index b55d8a138..7b2f842be 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -844,6 +844,7 @@ r#"Object.defineProperty(window, 'ipc', { where F: Fn(Result>) + 'static + Send, { + // Safety: objc runtime calls are unsafe #[cfg(target_os = "macos")] unsafe { let config = WKSnapshotConfiguration::new(self.mtm); @@ -880,7 +881,10 @@ r#"Object.defineProperty(window, 'ipc', { } #[cfg(target_os = "ios")] - let _ = handler; + { + // Unsupported + let _ = handler; + } Ok(()) } From bb37d12874f31d35b508b785a1e5968cef4eb51f Mon Sep 17 00:00:00 2001 From: Joshua Richardson Date: Thu, 5 Mar 2026 14:46:08 +0000 Subject: [PATCH 4/4] Address PR comments --- Cargo.lock | 24 ++++++++++++++++++++++++ Cargo.toml | 2 ++ src/error.rs | 7 +++++++ src/wkwebview/mod.rs | 28 ++++++++++++++++++---------- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b169d85b..e7f899737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1803,6 +1803,7 @@ dependencies = [ "bitflags 2.8.0", "objc2 0.6.0", "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.0", ] @@ -1852,6 +1853,18 @@ dependencies = [ "objc2 0.6.0", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -1915,6 +1928,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.0", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index bd3cd07f8..c87eacd9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,7 +194,9 @@ objc2-app-kit = { version = "0.3.0", default-features = false, features = [ "NSImage", "NSImageRep", "NSBitmapImageRep", + "NSGraphicsContext", "NSScreen", + "objc2-core-graphics", ] } [target."cfg(target_os = \"android\")".dependencies] diff --git a/src/error.rs b/src/error.rs index e9d195efd..3ef96bc2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,4 +83,11 @@ pub enum Error { #[cfg(target_os = "macos")] #[error("Could not obtain screenshot from webview")] NilScreenshot, + #[cfg(target_os = "macos")] + #[error("Screenshot failed ({domain}:{code}): {description}")] + MacOsScreenshotError { + domain: String, + code: isize, + description: String, + }, } diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 7b2f842be..254a2164c 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -72,7 +72,7 @@ use crate::wkwebview::util::operating_system_version; use objc2_web_kit::WKWebView; use objc2_web_kit::{ - WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKSnapshotConfiguration, WKURLSchemeHandler, + WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKURLSchemeHandler, WKUserContentController, WKUserScript, WKUserScriptInjectionTime, WKWebViewConfiguration, WKWebsiteDataStore, }; @@ -847,24 +847,32 @@ r#"Object.defineProperty(window, 'ipc', { // Safety: objc runtime calls are unsafe #[cfg(target_os = "macos")] unsafe { - let config = WKSnapshotConfiguration::new(self.mtm); - config.setAfterScreenUpdates(true); + let callback = block2::RcBlock::new(move |image: *mut NSImage, err: *mut NSError| { + if let Some(err) = err.as_ref() { + handler(Err(Error::MacOsScreenshotError { + domain: err.domain().to_string(), + code: err.code(), + description: err.localizedDescription().to_string(), + })); + return; + } - let callback = block2::RcBlock::new(move |image: *mut NSImage, _err: *mut NSError| { let Some(image) = image.as_ref() else { handler(Err(Error::NilScreenshot)); return; }; - let Some(tiff) = image.TIFFRepresentation() else { + let Some(cg_image) = image.CGImageForProposedRect_context_hints( + std::ptr::null_mut(), + None, + None, + ) else { handler(Err(Error::NilScreenshot)); return; }; - let Some(bitmap) = NSBitmapImageRep::imageRepWithData(&tiff) else { - handler(Err(Error::NilScreenshot)); - return; - }; + let bitmap = NSBitmapImageRep::initWithCGImage(NSBitmapImageRep::alloc(), &cg_image); + bitmap.setSize(image.size()); let props: Retained> = NSDictionary::new(); @@ -877,7 +885,7 @@ r#"Object.defineProperty(window, 'ipc', { self .webview - .takeSnapshotWithConfiguration_completionHandler(Some(&config), &callback); + .takeSnapshotWithConfiguration_completionHandler(None, &callback); } #[cfg(target_os = "ios")]