Communication in Webviews
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 windowslibwebkit2gtk-*
: 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 usinghttp[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 invokingpostMessage
API directly. These internal keys, such aswails:runtime:ready
orwails: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 inpkg/application/application_darwin.go
. - On Linux: Managed by
sendMessageToBackend
function inpkg/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 inpkg/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
orhttps
protocols - Available for Webview windows configured with a remote URL.
- Webpages served remotely using
Note:
API requests usinghttp
orhttps://
cannot be made directly from thefrontend
(Webview window). We can make API requests only from our backend.
- Origin wails://wails is not allowed by Access-Control-Allow-Origin
- Workaround on Windows (not recommended)
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.