[V3] Add origin to raw message handler (#4710)

* Add support for origin tracking in raw message handling

- Implemented origin and top origin tracking for web messages from JavaScript.
- Updated `RawMessageHandler` to include `originInfo`.
- Added cross-platform support for retrieving the origin of messages in macOS, Windows, and Linux.

* fix build

* fix build

* fix build

* fix build

* fix build

* Fix nil checks and string handling for message origins across platforms

- Ensure proper fallback to empty strings for `origin` and `topOrigin` when errors or nil values are encountered.
- Normalize handling of `message.body` to account for non-NSString values in macOS.

* add docs

* Remove unused doc

* update changelog

* fix build

---------

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
Andrey Pshenkin 2025-12-08 09:29:09 +00:00 committed by GitHub
commit 7d0016bbbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 122 additions and 49 deletions

View file

@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file
## Added
<!-- New features, capabilities, or enhancements -->
Add origin to raw message handler by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4710)
Add universal link support for macOS by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4712)
Refactor binding transport layer by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4702)

View file

@ -3,8 +3,10 @@ package main
import (
"embed"
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
"fmt"
"log"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed assets
@ -21,8 +23,8 @@ func main() {
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
RawMessageHandler: func(window application.Window, message string) {
println("Raw message received from Window '" + window.Name() + "' with message: " + message)
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
println(fmt.Sprintf("Raw message received from Window %s with message: %s, origin %s, topOrigin %s, isMainFrame %t", window.Name(), message, originInfo.Origin, originInfo.TopOrigin, originInfo.IsMainFrame))
},
})

View file

@ -237,8 +237,15 @@ type (
// Messages sent from javascript get routed here
type windowMessage struct {
windowId uint
message string
windowId uint
message string
originInfo *OriginInfo
}
type OriginInfo struct {
Origin string
TopOrigin string
IsMainFrame bool
}
var windowMessageBuffer = make(chan *windowMessage, 5)
@ -734,7 +741,7 @@ func (a *App) handleWindowMessage(event *windowMessage) {
window.HandleMessage(event.message)
} else {
if a.options.RawMessageHandler != nil {
a.options.RawMessageHandler(window, event.message)
a.options.RawMessageHandler(window, event.message, event.originInfo)
}
}
}

View file

@ -344,10 +344,18 @@ func processWindowEvent(windowID C.uint, eventID C.uint) {
}
//export processMessage
func processMessage(windowID C.uint, message *C.char) {
func processMessage(windowID C.uint, message *C.char, origin *C.char, isMainFrame bool) {
o := ""
if origin != nil {
o = C.GoString(origin)
}
windowMessageBuffer <- &windowMessage{
windowId: uint(windowID),
message: C.GoString(message),
originInfo: &OriginInfo{
Origin: o,
IsMainFrame: isMainFrame,
},
}
}

View file

@ -88,7 +88,7 @@ type Options struct {
// RawMessageHandler is called when the frontend sends a raw message.
// This is useful for implementing custom frontend-to-backend communication.
RawMessageHandler func(window Window, message string)
RawMessageHandler func(window Window, message string, originInfo *OriginInfo)
// WarningHandler is called when a warning occurs
WarningHandler func(string)

View file

@ -62,6 +62,16 @@ static void save_window_id(void *object, uint value)
g_object_set_data((GObject *)object, "windowid", GUINT_TO_POINTER((guint)value));
}
static void save_webview_to_content_manager(void *contentManager, void *webview)
{
g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview);
}
static WebKitWebView* get_webview_from_content_manager(void *contentManager)
{
return WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview"));
}
static guint get_window_id(void *object)
{
return GPOINTER_TO_UINT(g_object_get_data((GObject *)object, "windowid"));
@ -1109,6 +1119,8 @@ func windowNewWebview(parentId uint, gpuPolicy WebviewGpuPolicy) pointer {
C.webkit_user_content_manager_register_script_message_handler(manager, c.String("external"))
webView := C.webkit_web_view_new_with_user_content_manager(manager)
C.save_webview_to_content_manager(unsafe.Pointer(manager), unsafe.Pointer(webView))
// attach window id to both the webview and contentmanager
C.save_window_id(unsafe.Pointer(webView), C.uint(parentId))
C.save_window_id(unsafe.Pointer(manager), C.uint(parentId))
@ -1645,6 +1657,17 @@ func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.
// Get the windowID from the contentManager
thisWindowID := uint(C.get_window_id(unsafe.Pointer(contentManager)))
webView := C.get_webview_from_content_manager(unsafe.Pointer(contentManager))
var origin string
if webView != nil {
currentUri := C.webkit_web_view_get_uri(webView)
if currentUri != nil {
uri := C.g_strdup(currentUri)
defer C.g_free(C.gpointer(uri))
origin = C.GoString(uri)
}
}
var msg string
value := C.webkit_javascript_result_get_js_value(result)
message := C.jsc_value_to_string(value)
@ -1653,6 +1676,9 @@ func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.
windowMessageBuffer <- &windowMessage{
windowId: thisWindowID,
message: msg,
originInfo: &OriginInfo{
Origin: origin,
},
}
}

View file

@ -4,7 +4,7 @@
#import <QuartzCore/QuartzCore.h>
#import "webview_window_darwin.h"
#import "../events/events_darwin.h"
extern void processMessage(unsigned int, const char*);
extern void processMessage(unsigned int, const char*, const char *, bool);
extern void processURLRequest(unsigned int, void *);
extern void processDragItems(unsigned int windowId, char** arr, int length, int x, int y);
extern void processWindowKeyDownEvent(unsigned int, const char*);
@ -272,7 +272,7 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSLog(@"WebviewWindowDelegate: performDragOperation called. WindowID: %u", self.windowId);
NSPasteboard *pasteboard = [sender draggingPasteboard];
if (hasListeners(EventWindowFileDraggingPerformed)) {
processWindowEvent(self.windowId, EventWindowFileDraggingPerformed);
}
@ -295,13 +295,13 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
NSLog(@"WebviewWindowDelegate: performDragOperation - File %lu: %@", (unsigned long)i, str);
cArray[i] = (char*)[str UTF8String];
}
// Get the WebviewWindow instance, which is the dragging destination
WebviewWindow *window = (WebviewWindow *)[sender draggingDestinationWindow];
WKWebView *webView = window.webView; // Get the webView from the window
NSPoint dropPointInWindow = [sender draggingLocation];
NSPoint dropPointInView = [webView convertPoint:dropPointInWindow fromView:nil]; // Convert to webView's coordinate system
CGFloat viewHeight = webView.frame.size.height;
int x = (int)dropPointInView.x;
int y = (int)(viewHeight - dropPointInView.y); // Flip Y for web coordinate system
@ -336,9 +336,22 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
}
// Handle script messages from the external bridge
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
NSString *m = message.body;
// Get the origin from the message's frame
NSString *origin = nil;
if (message.frameInfo && message.frameInfo.request && message.frameInfo.request.URL) {
NSURL *url = message.frameInfo.request.URL;
if (url.scheme && url.host) {
origin = [url absoluteString];
}
}
id body = message.body;
NSString *m = [body isKindOfClass:[NSString class]] ? (NSString *)body : [body description];
const char *_m = [m UTF8String];
processMessage(self.windowId, _m);
const char *_origin = origin ? [origin UTF8String] : "";
processMessage(self.windowId, _m, _origin, message.frameInfo.isMainFrame);
}
- (void)handleLeftMouseDown:(NSEvent *)event {
self.leftMouseEvent = event;
@ -782,25 +795,25 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
void windowSetScreen(void* window, void* screen, int yOffset) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
NSScreen* nsScreen = (NSScreen*)screen;
// Get current frame
NSRect frame = [nsWindow frame];
// Convert frame to screen coordinates
NSRect screenFrame = [nsScreen frame];
NSRect currentScreenFrame = [[nsWindow screen] frame];
// Calculate the menubar height for the target screen
NSRect visibleFrame = [nsScreen visibleFrame];
CGFloat menubarHeight = screenFrame.size.height - visibleFrame.size.height;
// Calculate the distance from the top of the current screen
CGFloat topOffset = currentScreenFrame.origin.y + currentScreenFrame.size.height - frame.origin.y;
// Position relative to new screen's top, accounting for menubar
frame.origin.x = screenFrame.origin.x + (frame.origin.x - currentScreenFrame.origin.x);
frame.origin.y = screenFrame.origin.y + screenFrame.size.height - topOffset - menubarHeight - yOffset;
// Set the frame which moves the window to the new screen
[nsWindow setFrame:frame display:YES];
}
@ -816,13 +829,13 @@ bool isLiquidGlassSupported() {
void windowRemoveVisualEffects(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSView* contentView = [window contentView];
// Get NSGlassEffectView class if available (avoid hard reference)
Class glassEffectViewClass = nil;
if (@available(macOS 26.0, *)) {
glassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
}
// Remove all NSVisualEffectView and NSGlassEffectView subviews
NSArray* subviews = [contentView subviews];
for (NSView* subview in subviews) {
@ -836,13 +849,13 @@ void windowRemoveVisualEffects(void* nsWindow) {
void configureWebViewForLiquidGlass(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
WKWebView* webView = window.webView;
// Make WebView background transparent
[webView setValue:@NO forKey:@"drawsBackground"];
if (@available(macOS 10.12, *)) {
[webView setValue:[NSColor clearColor] forKey:@"backgroundColor"];
}
// Ensure WebView is above glass layer
if (webView.layer) {
webView.layer.zPosition = 1.0;
@ -852,10 +865,10 @@ void configureWebViewForLiquidGlass(void* nsWindow) {
}
// Apply Liquid Glass effect to window
void windowSetLiquidGlass(void* nsWindow, int style, int material, double cornerRadius,
int r, int g, int b, int a,
int r, int g, int b, int a,
const char* groupID, double groupSpacing) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Ensure we're on the main thread for UI operations
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
@ -863,38 +876,38 @@ void windowSetLiquidGlass(void* nsWindow, int style, int material, double corner
});
return;
}
// Remove any existing visual effects
windowRemoveVisualEffects(nsWindow);
// Try to use NSGlassEffectView if available
NSView* glassView = nil;
if (@available(macOS 26.0, *)) {
Class NSGlassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
if (NSGlassEffectViewClass) {
// Create NSGlassEffectView (autoreleased)
glassView = [[[NSGlassEffectViewClass alloc] init] autorelease];
// Set corner radius if the property exists
if (cornerRadius > 0 && [glassView respondsToSelector:@selector(setCornerRadius:)]) {
[glassView setValue:@(cornerRadius) forKey:@"cornerRadius"];
}
// Set tint color if the property exists and color is specified
if (a > 0 && [glassView respondsToSelector:@selector(setTintColor:)]) {
NSColor* tintColor = [NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a/255.0];
// Use performSelector to safely set tintColor if the setter exists
[glassView performSelector:@selector(setTintColor:) withObject:tintColor];
}
// Set style if the property exists
if ([glassView respondsToSelector:@selector(setStyle:)]) {
// For vibrant style, try to use Light style for a lighter effect
int lightStyle = (style == LiquidGlassStyleVibrant) ? LiquidGlassStyleLight : style;
[glassView setValue:@(lightStyle) forKey:@"style"];
}
// Set group identifier if the property exists and groupID is specified
if (groupID && strlen(groupID) > 0) {
if ([glassView respondsToSelector:@selector(setGroupIdentifier:)]) {
@ -905,19 +918,19 @@ void windowSetLiquidGlass(void* nsWindow, int style, int material, double corner
[glassView performSelector:@selector(setGroupName:) withObject:groupIDString];
}
}
// Set group spacing if the property exists and spacing is specified
if (groupSpacing > 0 && [glassView respondsToSelector:@selector(setGroupSpacing:)]) {
[glassView setValue:@(groupSpacing) forKey:@"groupSpacing"];
}
}
}
// Fallback to NSVisualEffectView if NSGlassEffectView is not available
if (!glassView) {
NSVisualEffectView* effectView = [[[NSVisualEffectView alloc] init] autorelease];
glassView = effectView; // Use effectView as glassView for the rest of the function
// If a custom material is specified, use it directly
if (material >= 0) {
[effectView setMaterial:(NSVisualEffectMaterial)material];
@ -968,15 +981,15 @@ void windowSetLiquidGlass(void* nsWindow, int style, int material, double corner
break;
}
}
// Use followsWindowActiveState for automatic adjustment
[effectView setState:NSVisualEffectStateFollowsWindowActiveState];
// Don't emphasize - it makes the effect too dark
if (@available(macOS 10.12, *)) {
[effectView setEmphasized:NO];
}
// Apply corner radius if specified
if (cornerRadius > 0) {
[effectView setWantsLayer:YES];
@ -984,30 +997,30 @@ void windowSetLiquidGlass(void* nsWindow, int style, int material, double corner
effectView.layer.masksToBounds = YES;
}
}
// Get the content view
NSView* contentView = [window contentView];
// Set up the glass view
[glassView setFrame:contentView.bounds];
[glassView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
// Check if this is a real NSGlassEffectView with contentView property
BOOL hasContentView = [glassView respondsToSelector:@selector(contentView)];
if (hasContentView) {
// NSGlassEffectView: Add it to window and webView goes in its contentView
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
// Safely reparent the webView to the glass view's contentView
WKWebView* webView = window.webView;
NSView* glassContentView = [glassView valueForKey:@"contentView"];
// Only proceed if both webView and glassContentView are non-nil
if (webView && glassContentView) {
// Always remove from current superview to avoid exceptions
[webView removeFromSuperview];
// Add to the glass view's contentView
[glassContentView addSubview:webView];
[webView setFrame:glassContentView.bounds];
@ -1016,17 +1029,17 @@ void windowSetLiquidGlass(void* nsWindow, int style, int material, double corner
} else {
// NSVisualEffectView: Add glass as bottom layer, webView on top
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
WKWebView* webView = window.webView;
if (webView) {
[webView removeFromSuperview];
[contentView addSubview:webView positioned:NSWindowAbove relativeTo:glassView];
}
}
// Configure WebView for liquid glass
configureWebViewForLiquidGlass(nsWindow);
// Make window transparent
[window setOpaque:NO];
[window setBackgroundColor:[NSColor clearColor]];

View file

@ -1802,10 +1802,26 @@ func (w *windowsWebviewWindow) isAlwaysOnTop() bool {
// processMessage is given a message sent from JS via the postMessage API
// We put it on the global window message buffer to be processed centrally
func (w *windowsWebviewWindow) processMessage(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) {
topSource, err := sender.GetSource()
if err != nil {
globalApplication.error("Unable to get source from sender: %s", err.Error())
topSource = ""
}
senderSource, err := args.GetSource()
if err != nil {
globalApplication.error("Unable to get source from args: %s", err.Error())
senderSource = ""
}
// We send all messages to the centralised window message buffer
windowMessageBuffer <- &windowMessage{
windowId: w.parent.id,
message: message,
originInfo: &OriginInfo{
Origin: senderSource,
TopOrigin: topSource,
},
}
}