Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions v3/examples/raw-message/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package main
import (
"embed"
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
"fmt"
"log"

"github.com/wailsapp/wails/v3/pkg/application"
)

//go:embed assets
var assets embed.FS

// main creates and runs the Wails application "Raw Message Demo" with embedded assets,
// a single webview window named "Window 1", and a RawMessageHandler that logs incoming
// raw messages along with their origin information. It logs and exits if the app fails to run.
func main() {

app := application.New(application.Options{
Expand All @@ -21,8 +26,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))
},
})

Expand All @@ -41,4 +46,4 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
}
}
19 changes: 15 additions & 4 deletions v3/pkg/application/application_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,19 +335,30 @@ func processApplicationEvent(eventID C.uint, data unsafe.Pointer) {
applicationEvents <- event
}

//export processWindowEvent
// processWindowEvent enqueues a window event for processing by the application's event loop.
// It is called from C and sends a windowEvent containing the provided window and event IDs to the internal windowEvents channel.
func processWindowEvent(windowID C.uint, eventID C.uint) {
windowEvents <- &windowEvent{
WindowID: uint(windowID),
EventID: uint(eventID),
}
}

//export processMessage
func processMessage(windowID C.uint, message *C.char) {
// processMessage enqueues a message from a webview into the internal window message buffer,
// including the message origin and whether it came from the main frame.
// If `origin` is nil, Origin in the queued OriginInfo is set to the empty string.
Copy link
Member

Choose a reason for hiding this comment

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

@CodeRabbit you can't remove //export processMessage or any //export line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@leaanthony You're absolutely right, and I apologize for that error. The //export directive is a critical CGo compiler directive that makes Go functions callable from C code. Without it, the C bridge would break.

The //export directives must be preserved. Documentation comments should be added before the //export line, like this:

// processMessage enqueues a message from a webview into the internal window message buffer,
// including the message origin and whether it came from the main frame.
// If `origin` is nil, Origin in the queued OriginInfo is set to the empty string.
//export processMessage
func processMessage(windowID C.uint, message *C.char, origin *C.char, isMainFrame bool) {
    // ... function body
}

I'll remember this for all CGo code reviews going forward.


✏️ Learnings added
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4717
File: v3/pkg/application/application_darwin.go:349-349
Timestamp: 2025-11-21T07:21:52.534Z
Learning: In Go CGo code, the `//export FunctionName` directive must never be removed from functions that are called from C code. This directive is a compiler instruction that makes the Go function callable from C. Documentation comments should be placed before the `//export` line, not replace it.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).

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,
},
}
}

Expand Down Expand Up @@ -465,4 +476,4 @@ func HandleCustomProtocol(urlCString *C.char) {
Id: uint(events.Common.ApplicationLaunchedWithUrl),
ctx: eventContext,
}
}
}
42 changes: 40 additions & 2 deletions v3/pkg/application/linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -1102,13 +1112,19 @@ func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy Webvi
return
}

// windowNewWebview creates a WebKit WebView configured with a user content manager and the requested GPU policy.
// It associates the WebView and its content manager with the provided parent window ID, registers the "external"
// script message handler and the custom "wails" URI scheme (once), sets the application user agent, and returns a
// pointer to the created WebView.
func windowNewWebview(parentId uint, gpuPolicy WebviewGpuPolicy) pointer {
c := NewCalloc()
defer c.Free()
manager := C.webkit_user_content_manager_new()
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))
Expand Down Expand Up @@ -1639,13 +1655,32 @@ func onProcessRequest(request *C.WebKitURISchemeRequest, data C.uintptr_t) {
}
}

//export sendMessageToBackend
// sendMessageToBackend forwards a JavaScript message from a WebView to the application's
// window message channel, attaching the originating window ID and page origin.
//
// contentManager is the WebKitUserContentManager associated with the sender WebView.
// result is the WebKitJavascriptResult containing the JavaScript value to forward.
// data is callback user data (unused).
//
// The function extracts the message string and publishes a windowMessage onto
// windowMessageBuffer with the windowId and an OriginInfo containing the page URI.
func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.WebKitJavascriptResult,
data unsafe.Pointer) {

// 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)
Expand All @@ -1654,6 +1689,9 @@ func sendMessageToBackend(contentManager *C.WebKitUserContentManager, result *C.
windowMessageBuffer <- &windowMessage{
windowId: thisWindowID,
message: msg,
originInfo: &OriginInfo{
Origin: origin,
},
}
}

Expand Down Expand Up @@ -1926,4 +1964,4 @@ func (w *linuxWebviewWindow) redo() {
}

func (w *linuxWebviewWindow) delete() {
}
}
Loading