Skip to content

feat: add Rust-side webview screenshot API#1674

Open
josh-richardson wants to merge 4 commits into
tauri-apps:devfrom
vibefi:feat/screenshots
Open

feat: add Rust-side webview screenshot API#1674
josh-richardson wants to merge 4 commits into
tauri-apps:devfrom
vibefi:feat/screenshots

Conversation

@josh-richardson
Copy link
Copy Markdown

@josh-richardson josh-richardson commented Feb 23, 2026

Related to #1358. Prior art: #266, #1258. I'd really like to be able to take screenshots of my webviews in my Wry app.

Adds a Rust-side screenshot API for WebView: WebView::screenshot(handler) which returns visible-region screenshots as PNG bytes. Implemented for and tested on:

  • Windows 11 Pro 25H2 26200.7840
  • Arch Linux (6.6.119-1-MANJARO, Gnome 49.2, Wayland (had to run with WEBKIT_DISABLE_DMABUF_RENDERER=1))
  • macOS Sequoia 15.6
  • Unsupported and stubbed on iOS and Android.

I've also added examples/screenshot_smoke.rs (inline HTML, waits for page load + 1s, writes screenshot.png). I suspect this should probably be removed before this is merged, I just wanted an easy way of testing cross platform.

Scope is intentionally limited to visible-region screenshots only (no full-document capture, afaict APIs to do this cross platform are not mature / well standardized yet).

On Linux I'm using webkit2gtk snapshot API and encodes PNGs via GTK/GDK Pixbuf, so it does not introduce a cairo dependency. On macOS, it uses WKWebView’s native snapshot API rather than OS/screen capture APIs, which avoids the screen-recording permission requirement that affected the older approach.

@josh-richardson josh-richardson marked this pull request as ready for review February 23, 2026 16:43
@josh-richardson josh-richardson requested a review from a team as a code owner February 23, 2026 16:43
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 28, 2026

Package Changes Through bb37d12

There are 1 changes which include wry with patch

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
wry 0.54.2 0.54.3

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

Copy link
Copy Markdown
Member

@FabianLars FabianLars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just some comments on macos, windows and linux look good at first glance and seem to match the other PRs and https://github.com/Fractal-Tess/tauri-plugin-snapshot (which i helped with back then so take that with a grain of salt)

Comment thread src/wkwebview/mod.rs Outdated
#[cfg(target_os = "macos")]
unsafe {
let config = WKSnapshotConfiguration::new(self.mtm);
config.setAfterScreenUpdates(true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the default https://developer.apple.com/documentation/webkit/wksnapshotconfiguration/afterscreenupdates?language=objc so i think we can remove this and give takeSnapshotWithConfiguration None for the config?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ought to be resolved

Comment thread src/wkwebview/mod.rs Outdated
let config = WKSnapshotConfiguration::new(self.mtm);
config.setAfterScreenUpdates(true);

let callback = block2::RcBlock::new(move |image: *mut NSImage, _err: *mut NSError| {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we not check for an error first?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented

Comment thread src/wkwebview/mod.rs
Comment on lines +854 to +876
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<NSBitmapImageRepPropertyKey, AnyObject>> =
NSDictionary::new();
let png = bitmap.representationUsingType_properties(NSBitmapImageFileType::PNG, &props);
match png {
Some(data) => handler(Ok(data.to_vec())),
None => handler(Err(Error::NilScreenshot)),
}
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very different from all other implementations i saw on the internet (see the other 2 prs for how it usually looks like). Do you have some source for this perhaps? Also, did you actually test this on macOS?
Looks like people usually have issues with scaling with retina somethingsomething

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did test this on macOS Sequoia 15.6, but it's entirely possible that I missed bugs if there are issues with scaling at high DPIs. Implementation is now closer to the other open PRs.

Comment thread src/wkwebview/mod.rs
Comment on lines +883 to +887
#[cfg(target_os = "ios")]
{
// Unsupported
let _ = handler;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add this before merging but i think i can take this on if you can't do mobile :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably try my hand but I can't promise anything good - will update if I have issues.

Comment thread src/lib.rs
///
/// - **Linux / macOS / Windows**: Implemented (visible region only).
/// - **Android / iOS**: Not supported; `handler` will never be called.
pub fn screenshot<F>(&self, handler: F) -> Result<()>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be so awesome if we could abstract away the handler but i guess that's more something for crate consumers like tauri

@josh-richardson
Copy link
Copy Markdown
Author

Thanks for the review. I'll address these tomorrow when I wake up.

@GavinPacini
Copy link
Copy Markdown

Colleague of @josh-richardson's here. Want to add that I've tested the latest commit on both:

  • a Macbook Pro running macOS 26.3 and
  • a hackintosh running macOS 15.6.1 (connected to via DP to 1440p LG monitor)

Screenshot is 1:1 with the window I see on my screen, so can confirm working on both Retina and non-Retina displays.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants