Communication in Webviews

· 5mins

We discussed briefly about Rendering Engines and JS runtimes in our previous chapter. Here we will discuss how Wails integrates Webviews and communicates with them to send/receive data.

NOTE: All research was conducted on a Linux (Ubuntu) system. Some details may vary on other platforms.

Webviews:

Pros:

  • The biggest pro I see in using Webview is to use HTML and CSS, which are well-known technologies, allowing for the creation of complex 2D UI components with ease.
  • Provides an easy starting point for developing local-first desktop applications, with the flexibility to progressively integrate remotely served webpages as separate views.
  • Remote-only code can be updated easily without requiring users to download large installers for every release
  • Cross-Platform Compatibility: While debatable, Webviews offer cross-compatibility, which is becoming a standard feature across many GUI frameworks.

Cons:

  • In rare cases where UI updates are required within milliseconds or less, performance can be an issue
  • If we allow loading remote content within our applications, security can be a concern if not handled properly.

Webviews in Wails:

Each Wails build is tailored to the Webviews supported by the target platform:

Windows, Wails uses go-webview2 to call Webview APIs.
Ref: pkg/application/webview_window_windows.go

Linux, Wails uses purego / CGo to run C APIs from the following dynamic libraries:

  • libgtk-*.so: To manage GTK windows
  • libwebkit2gtk-*: To manage Webkit Webview inside GTK Window.

Refs: pkg/application/linux_purego.go, pkg/application/linux_cgo.go

MacOS, Wails uses CGo to invoke C APIs in Go.
Ref: pkg/application/webview_window_darwin.go

Wails Lifecycle:

Application Initialization

We first create the Wails Application, which initializes various components internally, as can be seen in the below diagram.

Window Creation

Using the Application instance we then create a Window with Webview. (Wails internally handles which platform APIs to use to create platform-specific Window)

Application Execution

And at the end we start the application, which is responsible for handling events/messages from Webviews and forwarding them to the Go backend

Webview Communication in Wails

There are three main strategies for communication between Webviews and the Wails backend, though using user-defined services and a request handler (structs that extend ServeHTTP interface) is enough for most use-cases.

NOTE: Requests via wails:// protocol are done via in-memory data comunication, i.e. Inter Process Communication (IPC). It’s not the same as Network Requests done when making an API request using http[s]:// protocols.

User defined Events

Using the wails.Events API, you can emit events that are routed to the backend via /wails/runtime request. Payload might look like the following:

{
   object: 3,
   method: 0,
   args { name: "calculator:test", "data": "Foo is not Bar" }
}

Commit that demos the user defined events in our repository can be found here .
Details on how the above request is handled can be read in Navigation Listeners section.

Read more:

  • Example of Custom Events in Wails v3.
  • Predefined events: check the wails v3 repo to find list of all the internal defaults here pkg/events/defaults.go

Inter Process communication

Wails provides a system API called invoke(key: string), which internally triggers Browser postMessage API, handled by user defined RawMessageHandler (user can define this handler while creating Wails Application instance).

NOTE: Avoid using keys prefixed with wails:* when invoking postMessage API directly. These internal keys, such as wails:runtime:ready or wails:resize:*, are reserved for Wails and handled internally.

Example

// In main.go
func main() {
	// ...
	app := application.New(application.Options{
		// ...
		RawMessageHandler: func(window application.Window, message string) {
			// We can also send serialised JSON strings with payload to receive data from frontend.
			switch true {
			case message == "calculator:clear":
				Calculator.Reset()
			// ...
			}
		},
	})
	// ...
}

Invoke the RawMessageHandler from frontend

<script lang="ts">
	import { System } from "@wailsio/runtime";
	const result = await System.invoke("calculator:clear")
</script>

Refer to this example in the Wails repository for the implementation details..

Platform-Specific Details

Windows: Uses window.chrome.webview.postMessage() API in webview, while processMessage in pkg/application/webview_window_windows.go handles these messages in backend via go-webview2’s MessageCallback hook.

Linux/MacOS: Uses window.webkit.messageHandlers.external.postMessage() API in webview

  • On MacOS: Handled by processMessage function in pkg/application/application_darwin.go.
  • On Linux: Managed by sendMessageToBackend function in pkg/application/linux_cgo.go.

Each platform has a unique Webview-to-Go binding to manage navigation and asset requests. For e.g, in Linux, the onProcessRequest function in pkg/application/linux_cgo.go is invoked for every navigation or asset request made within the Webview.

Types of Navigation in Wails

  • Backend Requests (Above diagram represents this user flow)
    • Managed by Assetserver service in Wails.
    • Uses custom wails:// protocol
    • Available for default Webview Windows configured with a relative URL (e.g., /*).
  • External/Remote Requests
    • Webpages served remotely using http or https protocols
    • Available for Webview windows configured with a remote URL.

Note:
API requests using http or https:// cannot be made directly from the frontend (Webview window). We can make API requests only from our backend.

The preferred solution is to set up a custom Assetserver Middleware using frameworks like Chi and call these backend APIs from our Sveltekit page loaders.
Ref: wails-htmx-templ-template

Backend Requests:

Predefined Routes

/wails/runtime predefined route

  • One of the most critical (predefined) routes in Wails.
  • Invokes messageprocessor.ServeHTTP , responsible for handling:
    • User defined events
    • User defined Service API calls
    • and much more

I haven’t listed all the predefined routes. Additional predefined routes can be found in the Wails repository

Asset/Custom Routes (/, /index.html)

These requests are served by Assetserver.serveHTTP() method, which fetches content using a file service. Relative requests not managed by the Assetserver are forwarded to user defined Custom Services implementing the ServeHTTP interface.

Further Reading