Web views

Embed a help site, a document reader, or a browser pane with day-piece-webview. The piece uses the selected toolkit’s web engine and exposes navigation controls, JavaScript evaluation, browser profiles, and application-provided resources through one Rust interface.

For account sign-in, use authentication, which opens the platform’s sign-in surface. An embedded browser is not a replacement for that flow.

On this page: setup, platforms, navigation, bundled sites, application resources, JavaScript, and browser profiles.

Add to your app

Add day-piece-webview to your app’s Cargo dependencies:

[dependencies]
day-piece-webview = "0.4.4"

Crate · API reference · Source. The Day CLI enables the matching toolkit feature and packages native contributions. No dependency entry is needed in Day.toml.

Platforms and web engines

Platform / toolkitEngine and prerequisites
AppKit / UIKitWKWebView, provided by the OS.
macOS / GTKWKWebView hosted within the GTK layout. Native-child clipping and overlap need attention in nested scrolling or overlays.
Linux / GTKWebKitGTK, using the WebKitGTK 6 libraries alongside GTK 4.
macOS / Qt, Linux / QtQt WebEngine. A build without WebEngine uses a URL-label fallback.
Windows / WinUIWebView2. The installed WebView2 Runtime is separate from its build-time SDK.
AndroidAndroid WebView; Internet permission is contributed by the crate.
HarmonyOS / ArkUIArkWeb. A compatible installed runtime is required; custom-resource handling needs API 12+.
WebAn iframe in the host browser. Remote sites can refuse framing, and same-origin restrictions limit scripting and navigation observation.

Most capability functions describe compiled integration, not successful engine startup. A native support result does not prove a Qt WebEngine installation or a usable runtime.

ContentEntry point
Remote page or a URL selected at runtimeweb_view() with a string binding.
Site shipped with the appweb_view_inline() with a generated asset-directory constant.
Documents or binary data supplied by the appweb_view_resources() with a resource provider.

This component belongs in an app with a generated catalog containing browser_back and browser_reload. The URL is provided by the caller:

use day::prelude::*;
use day_piece_webview::{support, web_view};

pub fn browser_panel(url: Signal<String>) -> impl Piece {
    let back = Trigger::new();
    let reload = Trigger::new();
    column((
        row((
            button(res::str::browser_back())
                .enabled(support() == Support::Native)
                .action(move || back.notify()),
            button(res::str::browser_reload()).action(move || reload.notify()),
        )),
        web_view(url).back(back).reload(reload).grow(),
    ))
}
browser_back = Back
browser_reload = Reload

The initial URL loads when the view is built. Editing the URL binding alone does not navigate; attach a go() trigger and fire it after the edit. Native navigation writes the observed URL back to the binding. The browser iframe cannot report navigation from arbitrary remote origins.

The WebView builder also supplies forward, stop, reload, and force-reload triggers. Check support() when enabling history controls and force_reload_support() before promising a cache-bypassing reload.

Removing an ordinary view releases its browser. Use a WebSession when the app needs a retained browser across reconstruction. Session retention and browser storage are different concerns: browser profiles control cookies and website data, while a session controls the retained view.

Use the external-link callback to apply your app’s navigation policy. Treat remote content, redirect destinations, and scripts as untrusted unless your app explicitly controls them.

Bundle a site

Put index.html and its related files under resource/assets/site/. Open the generated directory constant:

use day::prelude::*;
use day_piece_webview::web_view_inline;

pub fn bundled_help() -> impl Piece {
    web_view_inline(res::assets::site).grow()
}

Use document-relative links inside the site, such as images/diagram.svg or guide.html. The toolkit chooses its local-content URL; applications should not construct a file:, qrc:, or engine-specific asset URL. App-authored text in a bundled interface must use the app’s generated translations. See resources.

Application resources

Use a resource provider when an embedded page comes from a database, archive, generated report, or another application-owned source. The web engine requests resources by path and the provider returns bytes with their MIME type. Ordinary relative links in the document continue to resolve within that provider.

Provide a document

This helper accepts document content from its caller. It serves one HTML document and reports other paths as missing. App-generated UI text must be translated before it reaches the helper; user-authored documents can retain their own text.

use std::sync::Arc;
use day_piece_webview::{ResourceProvider, ResourceResponse};

pub fn document_provider(html: Vec<u8>) -> ResourceProvider {
    let body: Arc<[u8]> = html.into();
    ResourceProvider::new(move |request| {
        if request.path == "index.html" {
            ResourceResponse::new("text/html; charset=utf-8", body.clone())
        } else {
            ResourceResponse::not_found()
        }
    })
}

Pass the provider and the document-relative entry path to web_view_resources(). The engine chooses the actual origin; treat provider URLs as opaque.

ResourceProvider::new() requires a thread-safe callback. Native engines may call it on workers; the browser integration calls it on the app thread. Do not read Day signals or touch toolkit objects inside the handler. Prepare immutable data or use a thread-safe store, and keep browser callbacks short.

Paths, responses, and lifetime

ResourceRequest contains the path, query, and method. Use a bounded namespace of allowed resources. Do not join an untrusted request path directly to an unrestricted filesystem root. ResourceResponse contains a status, MIME type, shared byte body, and optional headers. Each response is buffered; this is not a streaming server. Native adapters handle byte ranges and HEAD requests, with engine-specific restrictions.

Views retain their provider. Releasing a view cancels delivery to that view; keep a provider handle if other views will continue using its namespace. Check callback ownership before capturing large document caches.

For a static bundled site, prefer web_view_inline() with the generated asset-directory constant. It needs no custom handler. If your HTML references bundled images or styles, preserve document-relative paths and use the provider’s bundled-site mounting API when combining app resources with dynamic content.

Platform limits

Platform / toolkitConstraint
AppKit / UIKit / macOS GTKWebKit custom-scheme handling is installed before the browser is created.
Linux GTKUses a WebKit URI-scheme handler; test media/range behavior for the document types you serve.
Qt WebEngineRequires a registered custom scheme. Arbitrary HTTP status behavior differs from an HTTP server; additional response headers require Qt 6.6+.
Android / WinUIUses reserved origins and request interception. Return the correct MIME type for binary resources.
HarmonyOSRequires ArkWeb’s custom-resource handler support, API 12+.
WebRequires HTTPS or localhost and service workers. The owning app tab must stay alive; disabling service workers prevents provider views from loading.

The browser worker is scoped to this crate’s asset namespace. It is distinct from ordinary inline content and does not persist document bodies. Use browser profiles for website storage and web view scripting for interaction with a loaded document.

JavaScript interaction

Run JavaScript in a loaded page and receive its JSON result in Rust.

Bind a handle to the web view, then evaluate after its page loads. This is an integration fragment; add the generated site asset directory to your app:

use day_piece_webview::{JsHandle, web_view_inline};

let js = JsHandle::new();
let view = web_view_inline(res::assets::site).js(js);
// In an event handler:
day::task(async move {
    match js.eval("document.title").await {
        Ok(json) => { /* JSON string, including its quotes */ }
        Err(error) => { /* unavailable view, script error, or engine failure */ }
    }
});

JsHandle::eval() returns a future. The caller supplies trusted code. Do not concatenate untrusted strings into scripts; serialize values with a JSON encoder. Returned values are serialized as JSON. undefined becomes null; exceptions and values that cannot be serialized become errors. This is synchronous JavaScript evaluation, not an API for awaiting JavaScript promises.

Disposing the Day node invalidates its handle; callers must handle errors when navigating away. Use browser profiles to manage persistent storage.

Evaluation support

eval_support() reports whether an evaluation implementation exists. Browser same-origin rules still apply: a bundled site works; an inaccessible cross-origin frame returns an error. Remote sites can also refuse iframe embedding. This API does not bypass those restrictions.

Browser profiles

Use a browser profile to separate website accounts or retain a login across app launches. A profile controls cookies and engine-managed website data. It is independent of a retained browser session, which controls the lifetime of the view itself.

Choose a profile before building a view

use day::prelude::*;
use day_piece_webview::{WebProfile, web_view};

pub fn account_browser(url: Signal<String>, account_key: String) -> impl Piece {
    web_view(url).profile(WebProfile::persistent(account_key))
}

WebProfile::persistent() shares storage between views using the same identity and retains it across launches where the engine supports named profiles. The key is an app-defined identifier; do not put credentials or other secrets in it. Changing a profile requires rebuilding the view.

WebProfile::private() requests an in-memory store. It does not erase existing persistent storage or make app-managed downloads and databases private. Unsupported private views are blocked rather than silently opening with persistent storage.

Check capabilities

EngineProfile behavior
WKWebViewNamed persistent stores require macOS 14 / iOS 17+. Older systems isolate explicit named profiles in memory. Private views use a nonpersistent store.
Qt WebEngine / WebKitGTKNamed disk profiles and ephemeral private profiles; app-selected storage roots are supported.
WebView2Named profiles and InPrivate mode depend on the installed runtime’s capabilities.
Android WebViewNamed profiles require AndroidX MULTI_PROFILE; complete deletion requires DELETE_BROWSING_DATA. Stock WebView has no private mode through this API.
ArkWebNamed profiles are emulated over app-wide, origin-separated storage. Private mode is available; complete profile clearing is unsupported.
Web iframeThe host browser owns storage. Named profiles, private mode, and complete deletion are unsupported.

Check profile_support(), private_browsing_support(), and clear_data_support() before offering the corresponding controls.

Cookies and clearing data

Attach a JsHandle to address a realized view. Its cookie_header() operation returns eligible native cookies, including HttpOnly cookies, for a URL. Treat the result as a credential. For a redirected native HTTP request, obtain cookies for the new destination rather than forwarding the old header to another origin.

set_cookie() imports a response cookie into the same jar. clear_data() clears the entire profile, including cookies, cache, and persistent website storage. Await its result before making new requests. Give each site a dedicated profile when a Forget Site action must not erase unrelated accounts. Qt resets affected pages while clearing the profile.

Use authentication for provider sign-in and keychain storage for app-owned tokens. Browser profile storage is not an app token vault.