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-dragmove 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, withcolor-schemeanddata-cuarzo-theme,data-cuarzo-platform, anddata-cuarzo-maximizedon the root element, which follow the system; cuarzo.toolkit.shortcut("mod+s", run)runsrunon the keys, Command on macOS and Ctrl elsewhere, andshortcutLabel("mod+s")shows them as the system does.
Any web framework works on top.