005 - Communication in Webviews

Dec 8, 2024 • 5min

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.

Application Initialization
Application Initialization

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)

Window Creation
Window Creation

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

Execution
Execution

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.

Navigation Listeners:

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.

Listeners Bubbling
Listeners Bubbling

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

Chapters

001 - Wails - Learn Desktop Application Development

Nov 23, 2024

Guide to build a desktop application using Wails and Svelte
002 - Svelte, Vite & Wails Project Setup

Nov 23, 2024

Setup Wails project to support Frontend Development using Sveltekit and Vire
003 - Launch WebviewWindow

Nov 30, 2024

In this chapter, we will explore the Wails Webview Window and design a minimal frontend layout to display the calculator buttons.
004 - Calculator Service in Go

Dec 8, 2024

Creating calculator service that processes our inputs and gives us result on finish