diff --git a/v3/pkg/webcontentsview/snapshot.go b/v3/pkg/webcontentsview/snapshot.go new file mode 100644 index 000000000..fc9a2e8b2 --- /dev/null +++ b/v3/pkg/webcontentsview/snapshot.go @@ -0,0 +1,22 @@ +package webcontentsview + +import ( + "sync" + "sync/atomic" +) + +var snapshotCallbacks sync.Map +var snapshotCallbackID uintptr + +func registerSnapshotCallback(ch chan string) uintptr { + id := atomic.AddUintptr(&snapshotCallbackID, 1) + snapshotCallbacks.Store(id, ch) + return id +} + +func dispatchSnapshotResult(id uintptr, data string) { + if ch, ok := snapshotCallbacks.Load(id); ok { + ch.(chan string) <- data + snapshotCallbacks.Delete(id) + } +} diff --git a/v3/pkg/webcontentsview/webcontentsview.go b/v3/pkg/webcontentsview/webcontentsview.go index a0c2d4657..0f05d38cb 100644 --- a/v3/pkg/webcontentsview/webcontentsview.go +++ b/v3/pkg/webcontentsview/webcontentsview.go @@ -45,6 +45,10 @@ func (v *WebContentsView) SetURL(url string) { } // ExecJS executes the given javascript in the WebContentsView. +func (v *WebContentsView) ExecJS(js string) { + v.impl.execJS(js) +} + // GoBack navigates to the previous page in history. func (v *WebContentsView) GoBack() { v.impl.goBack() @@ -55,8 +59,9 @@ func (v *WebContentsView) GetURL() string { return v.impl.getURL() } -func (v *WebContentsView) ExecJS(js string) { - v.impl.execJS(js) +// TakeSnapshot returns a base64 encoded PNG of the current view. +func (v *WebContentsView) TakeSnapshot() string { + return v.impl.takeSnapshot() } // Attach binds the WebContentsView to a Wails Window. @@ -76,7 +81,7 @@ type webContentsViewImpl interface { execJS(js string) goBack() getURL() string - + takeSnapshot() string attach(window application.Window) detach() nativeView() unsafe.Pointer diff --git a/v3/pkg/webcontentsview/webcontentsview_android.go b/v3/pkg/webcontentsview/webcontentsview_android.go index b914b59eb..6c04a365c 100644 --- a/v3/pkg/webcontentsview/webcontentsview_android.go +++ b/v3/pkg/webcontentsview/webcontentsview_android.go @@ -20,6 +20,8 @@ func (w *androidWebContentsView) setURL(url string) {} func (w *androidWebContentsView) execJS(js string) {} func (w *androidWebContentsView) goBack() {} func (w *androidWebContentsView) getURL() string { return "" } +func (w *androidWebContentsView) takeSnapshot() string { return "" } + func (w *androidWebContentsView) attach(window application.Window) {} func (w *androidWebContentsView) detach() {} diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.go b/v3/pkg/webcontentsview/webcontentsview_darwin.go index 46f20161b..c6bbef5e9 100644 --- a/v3/pkg/webcontentsview/webcontentsview_darwin.go +++ b/v3/pkg/webcontentsview/webcontentsview_darwin.go @@ -78,6 +78,15 @@ func (w *macosWebContentsView) goBack() { C.webContentsViewGoBack(w.nsView) } +func (w *macosWebContentsView) takeSnapshot() string { + ch := make(chan string, 1) + id := registerSnapshotCallback(ch) + application.InvokeSync(func() { + C.webContentsViewTakeSnapshot(w.nsView, C.uintptr_t(id)) + }) + return <-ch +} + func (w *macosWebContentsView) getURL() string { cUrl := C.webContentsViewGetURL(w.nsView) if cUrl == nil { @@ -110,3 +119,12 @@ func (w *macosWebContentsView) detach() { func (w *macosWebContentsView) nativeView() unsafe.Pointer { return w.nsView } +//export browserViewSnapshotCallback +func browserViewSnapshotCallback(callbackID C.uintptr_t, base64 *C.char) { + id := uintptr(callbackID) + str := "" + if base64 != nil { + str = C.GoString(base64) + } + dispatchSnapshotResult(id, str) +} diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.h b/v3/pkg/webcontentsview/webcontentsview_darwin.h index d7cce568c..3517d1e98 100644 --- a/v3/pkg/webcontentsview/webcontentsview_darwin.h +++ b/v3/pkg/webcontentsview/webcontentsview_darwin.h @@ -23,10 +23,13 @@ extern void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPr extern void webContentsViewSetBounds(void* view, int x, int y, int w, int h); extern void webContentsViewSetURL(void* view, const char* url); extern void webContentsViewExecJS(void* view, const char* js); +extern void webContentsViewGoBack(void* view); +extern const char* webContentsViewGetURL(void* view); extern void windowAddWebContentsView(void* nsWindow, void* view); extern void windowRemoveWebContentsView(void* nsWindow, void* view); +// Async snapshot +extern void webContentsViewTakeSnapshot(void* view, uintptr_t callbackID); + #endif /* webcontentsview_darwin_h */ -extern void webContentsViewGoBack(void* view); -extern const char* webContentsViewGetURL(void* view); diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.m b/v3/pkg/webcontentsview/webcontentsview_darwin.m index 7feb30a70..9940946b5 100644 --- a/v3/pkg/webcontentsview/webcontentsview_darwin.m +++ b/v3/pkg/webcontentsview/webcontentsview_darwin.m @@ -30,11 +30,11 @@ void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPreferenc WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; [webView setValue:@(NO) forKey:@"drawsTransparentBackground"]; + if (prefs.userAgent != NULL) { NSString* customUA = [NSString stringWithUTF8String:prefs.userAgent]; [webView setCustomUserAgent:customUA]; } - return webView; } @@ -44,7 +44,6 @@ void webContentsViewSetBounds(void* view, int x, int y, int w, int h) { NSView* superview = [webView superview]; if (superview != nil) { - // macOS standard coordinates: 0,0 is bottom-left CGFloat superHeight = superview.bounds.size.height; CGFloat cocoaY = superHeight - y - h; [webView setFrame:NSMakeRect(x, cocoaY, w, h)]; @@ -73,11 +72,7 @@ void webContentsViewExecJS(void* view, const char* js) { void windowAddWebContentsView(void* nsWindow, void* view) { NSWindow* window = (NSWindow*)nsWindow; WKWebView* webView = (WKWebView*)view; - - // Add directly to the window's root view so we avoid clipping issues [window.contentView addSubview:webView]; - - // Force to front by giving it a high zPosition [webView setWantsLayer:YES]; webView.layer.zPosition = 9999.0; } @@ -113,3 +108,61 @@ const char* webContentsViewGetURL(void* view) { } return result; } + +extern void browserViewSnapshotCallback(uintptr_t callbackID, const char* base64); + +void webContentsViewTakeSnapshot(void* view, uintptr_t callbackID) { + WKWebView* webView = (WKWebView*)view; + dispatch_async(dispatch_get_main_queue(), ^{ + @try { + if (@available(macOS 10.13, *)) { + WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init]; + [webView takeSnapshotWithConfiguration:config completionHandler:^(NSImage *image, NSError *error) { + if (error != nil || image == nil) { + browserViewSnapshotCallback(callbackID, NULL); + return; + } + + @try { + CGImageRef cgRef = [image CGImageForProposedRect:NULL context:nil hints:nil]; + if (cgRef == NULL) { + browserViewSnapshotCallback(callbackID, NULL); + return; + } + + NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; + if (newRep == nil) { + browserViewSnapshotCallback(callbackID, NULL); + return; + } + + [newRep setSize:[image size]]; + NSData *pngData = [newRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + + if (pngData == nil) { + browserViewSnapshotCallback(callbackID, NULL); + return; + } + + NSString *base64String = [pngData base64EncodedStringWithOptions:0]; + if (base64String == nil) { + browserViewSnapshotCallback(callbackID, NULL); + return; + } + + NSString *fullBase64 = [NSString stringWithFormat:@"data:image/png;base64,%@", base64String]; + browserViewSnapshotCallback(callbackID, [fullBase64 UTF8String]); + } @catch (NSException *innerException) { + NSLog(@"Error processing snapshot image: %@", innerException.reason); + browserViewSnapshotCallback(callbackID, NULL); + } + }]; + } else { + browserViewSnapshotCallback(callbackID, NULL); + } + } @catch (NSException *e) { + NSLog(@"Exception in snapshot configuration: %@", e.reason); + browserViewSnapshotCallback(callbackID, NULL); + } + }); +} diff --git a/v3/pkg/webcontentsview/webcontentsview_ios.go b/v3/pkg/webcontentsview/webcontentsview_ios.go index 5b6b8fb54..435cb22f9 100644 --- a/v3/pkg/webcontentsview/webcontentsview_ios.go +++ b/v3/pkg/webcontentsview/webcontentsview_ios.go @@ -20,6 +20,8 @@ func (w *iosWebContentsView) setURL(url string) {} func (w *iosWebContentsView) execJS(js string) {} func (w *iosWebContentsView) goBack() {} func (w *iosWebContentsView) getURL() string { return "" } +func (w *iosWebContentsView) takeSnapshot() string { return "" } + func (w *iosWebContentsView) attach(window application.Window) {} func (w *iosWebContentsView) detach() {} diff --git a/v3/pkg/webcontentsview/webcontentsview_linux.go b/v3/pkg/webcontentsview/webcontentsview_linux.go index fa336a798..9f3f7e36e 100644 --- a/v3/pkg/webcontentsview/webcontentsview_linux.go +++ b/v3/pkg/webcontentsview/webcontentsview_linux.go @@ -115,6 +115,11 @@ func (w *linuxWebContentsView) goBack() { func (w *linuxWebContentsView) getURL() string { return "" } +func (w *linuxWebContentsView) takeSnapshot() string { + return "" + + return "" +} func (w *linuxWebContentsView) execJS(js string) { cJs := C.CString(js)