std/webview

An application's interface in HTML, CSS, and JavaScript, in the system's web view, with a bridge between the page and the program.

import "std/desktop"
import "std/webview"
import "std/json"
import "std/async"

#[derive(Json)]
struct OpenFile do
    str path
end

#[derive(Json)]
struct FileContent do
    str text
end

app_run(AppOptions("Notes", "dev.example.notes"), func(App app) do
    Window w = app.window(WindowOptions(title: "Notes"))
    WebView view = webview(w, WebViewOptions(files: embed_dir("ui")))?
    register_command(view, "open_file", func(OpenFile f) Task[FileContent] => ready(FileContent(read_text(f.path))))
    view.navigate(app_url("index.html"))
end)?
const file = await cuarzo.invoke("open_file", {path: "notes.txt"});

Windows has WebView2: the runtime that Windows has, or a fixed version beside the program, found without WebView2's SDK or its loader. Other systems' engines follow (WKWebView and WebKitGTK); without one, webview gives an error of kind "unsupported".

The web view

Function or method Result
webview(window, options) a WebView in the window, all of it unless bounds say otherwise
app_url(path), app_origin() the address of one of the application's files, https://app.localhost/path on Windows, and their origin
webview_runtime_version() the version of the engine's runtime, or "" when there is none
install_webview_runtime() Windows: downloads Microsoft's bootstrapper of the WebView2 runtime and runs it, which installs the runtime for the machine
webview_development() whether this is a development build, one built without --build-version
view.navigate(url), view.load_html(html) loads a page, or shows text as one of its own origin
view.load_app(path) loads one of the application's pages: from dev_url in a development build when the options have one, with the server's own live reload, and from the application's files otherwise
view.reload(), view.back(), view.forward(), view.stop(), view.url(), view.title(), view.can_go_back()
view.add_init_script(script) runs script in each page before the page's own scripts
view.eval(script) a Task[Json] of the value of its last expression, awaited when a promise, or the error it threw
view.set_bounds(x, y, width, height), view.fill(), view.show(), view.hide(), view.focus() several web views can share a window
view.set_zoom(zoom), view.zoom(), view.open_devtools(), view.print(), view.find(text) find is a Task[bool]
view.set_user_agent(agent), view.user_agent(), view.allow_drops(on), view.clear_storage() clear_storage clears cookies, storage, and the cache, as a Task[bool]
view.on(handler) calls handler with each WebViewEvent
view.close()

WebViewOptions has bounds, devtools (on in development builds), dev_url, context_menu (false leaves the menu to the page, or to the program's popup_menu through a command), user_agent, background ("transparent" or a color), files (the application's files by path, as embed_dir gives them), folder (served instead, as during development) with live_reload, allowed_origins (other origins the page may navigate to; links to any other open in the system's browser), command_origins, content_security_policy (strict by default), data_dir (the engine's storage, the application's data directory without it), and runtime_dir (a fixed version of the runtime, beside the program).

The engines differ where the web does: WebView2 is Chromium, with its storage APIs and media codecs, H.264 and AAC included; WKWebView and WebKitGTK are WebKit, whose storage quotas, IndexedDB limits, and codecs are the system's. The application's origin is https://app.localhost on Windows and app://localhost with WebKit, which app_url and app_origin hide.

The application's files are served from app_url(""), a host that never reaches the network, with their media types, byte ranges, an ETag, and the Content-Security-Policy; a path that is not there is a 404.

A WebViewEvent has a kind: NAVIGATING (url, user; cancel keeps it from loading), LOADED (ok, url), TITLE_CHANGED (text), NEW_WINDOW (url, which opens in the system's browser unless cancelled), DOWNLOAD_STARTED (number, url, path; cancellable), DOWNLOAD_PROGRESS (received, total), DOWNLOAD_DONE, DOWNLOAD_FAILED, STATE_CHANGED (name, value), and PAGE_EVENT (name, value, from cuarzo.emit).

The bridge

Cuarzo JavaScript
register_command(view, name, func(A) Task[R] handler) await cuarzo.invoke(name, args) gives the result, or throws an error with message and kind
view.command_json(name, func(Json) Task[Json] handler) the same, with JSON as it is
emit_event(view, name, value), view.emit_json(name, json) cuarzo.listen(name, listener), which returns a function that stops listening
view.channel(name) gives a WebViewChannel with send(json), send_bytes(data), on_message(listener), and close() cuarzo.channel(name) with send(data), onMessage(listener), and close()
view.share(name, json), and STATE_CHANGED events cuarzo.state(name) with value, subscribe(listener), and set(value)
PAGE_EVENT events cuarzo.emit(name, value)

A command's arguments become an A, and its result is written from an R, through their derived JSON code; arguments that are not an A are an error of kind "invalid" before the handler runs, a command that is not there one of kind "not_found", and a page whose origin is not the application's, or one of command_origins, gets "forbidden". A channel sends while the other side has room for 64 messages it has not taken yet: send's task settles when there is room, so a fast sender waits for a slow page, and the page's send does the same. Bytes go as base64, which the page's channel gives as a Uint8Array. The state "window" has the window's width, height, maximized, fullscreen, focused, and frameless, and "theme" has dark, accent, highContrast, font, fontSize, and platform, each kept current.

Typed clients

cuarzo bridge-types app.cz -o bridge.d.ts writes TypeScript for the program's bridge: each command, event, and channel it registers with its name written as text, with the types its derived JSON code has, and window.cuarzo typed by them, so cuarzo.invoke("open_file", {path}) is checked against the program. A struct is an interface of its fields under their JSON names, those that may be missing optional; a plain enum a union of its members' names, and one whose members carry values a union of objects with kind; an Option is T | null, a map Record<string, V>, and Json unknown.

The toolkit

Every application serves /__cuarzo/toolkit.js, which a page loads with <script src="/__cuarzo/toolkit.js"></script>:

  • elements with data-cuarzo-drag move the window when dragged, as a frameless window's title bar, and maximize it on a double click; data-cuarzo-window="minimize", "maximize", or "close" make the window's buttons; cuarzo.toolkit.minimize() and the rest do the same;
  • the theme is CSS variables, --cuarzo-accent, --cuarzo-font, and --cuarzo-font-size, with color-scheme and data-cuarzo-theme, data-cuarzo-platform, and data-cuarzo-maximized on the root element, which follow the system;
  • cuarzo.toolkit.shortcut("mod+s", run) runs run on the keys, Command on macOS and Ctrl elsewhere, and shortcutLabel("mod+s") shows them as the system does.

Any web framework works on top.