This project is in alpha — APIs may change without notice.
@nativewindow/webview

NativeWindow

API reference for the native window addon

NativeWindow Class

The NativeWindow class creates a native OS window with an embedded webview. It wraps the Rust napi-rs addon (powered by wry + tao) and manages the lifecycle automatically:

  • Auto-init — the native subsystem initializes on first window creation
  • Auto-pump — events are pumped at ~60fps (16ms interval) automatically
  • Auto-stop — the event pump stops when all windows are closed
import { NativeWindow } from "@nativewindow/webview";

const win = new NativeWindow({
  title: "My App",
  width: 1024,
  height: 768,
  devtools: true,
});

Properties

id (readonly)

Unique numeric identifier for the window, assigned at construction time.

const win = new NativeWindow({ title: "My App" });
console.log(win.id); // e.g. 1

WindowOptions

All options are optional. Pass them to the NativeWindow constructor:

OptionTypeDefaultDescription
titlestring""Window title
widthnumber800Inner width in logical pixels
heightnumber600Inner height in logical pixels
xnumberX position in screen coordinates
ynumberY position in screen coordinates
minWidthnumberMinimum inner width
minHeightnumberMinimum inner height
maxWidthnumberMaximum inner width
maxHeightnumberMaximum inner height
resizablebooleantrueAllow window resizing
decorationsbooleantrueShow title bar and borders
transparentbooleanfalseTransparent window background
alwaysOnTopbooleanfalseFloat above other windows
visiblebooleantrueShow window immediately on creation
devtoolsbooleanfalseEnable browser devtools
cspstringContent Security Policy injected at document start via a <meta> tag
trustedOriginsstring[]Native-layer IPC origin filter; messages from non-matching origins are silently dropped (defense-in-depth — see also trustedOrigins in Typed IPC)
allowedHostsstring[]Restrict all navigations to matching hosts. Supports wildcard prefixes ("*.example.com" matches subdomains and the base domain). Internal URLs (about:blank, loadHtml() content) are always permitted. See Security guide
allowCamerabooleanfalseAllow the webview to access the camera. All camera requests are denied by default. See Security guide
allowMicrophonebooleanfalseAllow the webview to access the microphone. All microphone requests are denied by default. See Security guide
allowFileSystembooleanfalseAllow the webview to use the File System Access API (showOpenFilePicker, showSaveFilePicker, showDirectoryPicker). Windows only — WebKit does not support this API on macOS or Linux. See Security guide

Content Loading

loadUrl(url: string): void

Navigate the webview to a URL.

win.loadUrl("https://example.com");

Security: javascript:, file:, data:, and blob: URL schemes are blocked and will throw an error. Only http: and https: URLs are allowed. See the Security guide for details.

loadHtml(html: string): void

Load an HTML string directly into the webview.

win.loadHtml("<h1>Hello</h1>");

Security: Never interpolate unsanitized user input into HTML strings. Use a sanitization library such as DOMPurify or sanitize-html. See the Security guide for details.

unsafe.evaluateJs(script: string): void

Execute JavaScript in the webview context. This is fire-and-forget — there is no return value. Use postMessage/onMessage to send results back.

Grouped under the unsafe namespace to signal injection risk.

win.unsafe.evaluateJs('document.title = "Updated"');

Security: Never pass unsanitized user input directly. Use sanitizeForJs() to escape strings. See the Security guide for details.

postMessage(message: string): void

Send a string message to the webview. The message is delivered via the window.__native_message__ callback on the webview side.

win.postMessage("data from host");

Window Control

MethodDescription
setTitle(title: string)Set the window title
setSize(width: number, height: number)Set the window size in logical pixels
setMinSize(width: number, height: number)Set minimum window size
setMaxSize(width: number, height: number)Set maximum window size
setPosition(x: number, y: number)Set window position in screen coordinates
setResizable(resizable: boolean)Enable or disable window resizing
setDecorations(decorations: boolean)Show or hide title bar and borders
setAlwaysOnTop(alwaysOnTop: boolean)Toggle always-on-top mode

Window State

MethodDescription
show()Show the window
hide()Hide the window
close()Close and destroy the window
focus()Bring the window to focus
maximize()Maximize the window
minimize()Minimize the window
unmaximize()Restore the window from maximized state
reload()Reload the current page in the webview

Note: All public methods throw Error("Window is closed") if called after close(). The NativeWindow tracks its closed state internally and rejects further operations.

Events

MethodCallback Signature
onMessage(cb)(message: string, sourceUrl: string) => void
onClose(cb)() => void
onResize(cb)(width: number, height: number) => void
onMove(cb)(x: number, y: number) => void
onFocus(cb)() => void
onBlur(cb)() => void
onPageLoad(cb)(event: "started" | "finished", url: string) => void
onTitleChanged(cb)(title: string) => void
onReload(cb)() => void
onNavigationBlocked(cb)(url: string) => void

Example:

win.onPageLoad((event, url) => {
  if (event === "finished") {
    console.log("Page loaded:", url);
  }
});

win.onResize((width, height) => {
  console.log(`Window resized to ${width}x${height}`);
});

Security: The raw onMessage callback does not filter by origin — all messages from the webview are delivered regardless of the source page URL. Use the sourceUrl parameter to validate the origin yourself, or use Typed IPC with trustedOrigins for automatic origin filtering.

Note: Calling onClose() more than once replaces the previous handler and emits a console.warn. Use a single handler with all your cleanup logic.

onNavigationBlocked

Fired when a navigation is blocked by the allowedHosts restriction. Use it to log blocked attempts or notify the user:

const win = new NativeWindow({
  title: "My App",
  allowedHosts: ["myapp.com", "*.cdn.myapp.com"],
});

win.onNavigationBlocked((url) => {
  console.warn("Blocked navigation to:", url);
});

getCookies(url?: string): Promise<CookieInfo[]>

Query cookies from the native cookie store. Returns a Promise that resolves with validated CookieInfo objects, including HttpOnly cookies that are invisible to document.cookie.

const cookies = await win.getCookies("https://example.com");
const session = cookies.find((c) => c.name === "session_id");
if (session) {
  console.log("Session:", session.value, "HttpOnly:", session.httpOnly);
}

If url is provided, only cookies matching that URL are returned. If omitted, all cookies in the webview's cookie store are returned.

Platform behavior:

  • Cookie access uses wry's cross-platform cookie API under the hood
  • If url is provided, only cookies matching that URL are returned on all platforms

Security: getCookies() returns HttpOnly cookies that are inaccessible to document.cookie in the webview. Treat the returned data as sensitive — do not expose cookie values to untrusted contexts.

CookieInfo

Return type for each cookie from getCookies():

interface CookieInfo {
  name: string; // Cookie name
  value: string; // Cookie value
  domain: string; // Domain the cookie belongs to
  path: string; // Path the cookie is restricted to
  httpOnly: boolean; // Whether the cookie is HttpOnly (inaccessible to JS)
  secure: boolean; // Whether the cookie requires HTTPS
  sameSite: string; // SameSite policy: "none", "lax", or "strict"
  expires: number; // Expiry as Unix timestamp (seconds); -1 for session cookies
}

Utility Functions

sanitizeForJs

Escape a string for safe embedding inside a JavaScript string literal. Handles backslashes, quotes, backticks, template expressions (${}), newlines, null bytes, closing </script> tags, and Unicode line/paragraph separators.

import { NativeWindow, sanitizeForJs } from "@nativewindow/webview";

const userInput = 'He said "hello"\n<script>alert(1)</script>';
win.unsafe.evaluateJs(`display("${sanitizeForJs(userInput)}")`);

checkRuntime(): RuntimeInfo

Check if the native webview runtime is available. Returns { available: boolean, version?: string, platform: "macos" | "windows" | "linux" | "unsupported" }.

ensureRuntime(): RuntimeInfo

Check for the runtime and install it if missing (Windows only). Downloads the WebView2 Evergreen Bootstrapper (~2MB) from Microsoft and runs it silently. The downloaded executable is verified using Authenticode signature validation before execution — if verification fails, the bootstrapper is not executed and the function throws. Throws on failure.

Security: Do not call ensureRuntime() in an elevated (Administrator) context without explicit user consent — the silent installer applies system-wide. Prefer calling checkRuntime() first to avoid unnecessary network requests when the runtime is already present.

RuntimeInfo

Return type for checkRuntime() and ensureRuntime():

interface RuntimeInfo {
  available: boolean;
  version?: string;
  platform: "macos" | "windows" | "linux" | "unsupported";
}

Legacy API

The following functions exist for backward compatibility but are not needed when using the NativeWindow class, which manages initialization and event pumping automatically:

FunctionDescription
init()Initialize the native window system manually
pumpEvents()Process pending native UI events manually

Known Limitations

  • ~16ms event latency from the pumpEvents() polling interval
  • HTML origin differences — content loaded via loadHtml() has a https://nativewindow.localhost origin on Windows and a nativewindow://localhost origin on macOS and Linux (both are secure contexts). When configuring trustedOrigins, use the platform-appropriate value.
  • No return values from unsafe.evaluateJs() — use postMessage/onMessage to send results back
  • 2 MB HTML limit on Windows when using loadHtml()
  • 10 MB message size limit — messages larger than 10 MB are silently dropped at the native layer
  • javascript:, file:, data:, and blob: URLs are blockedloadUrl() and in-page navigations reject these schemes
  • Use bun --watch (or equivalent) instead of bun --hot for development (native addon reloading requires a process restart)

On this page