ext_webview
The ext_webview crate provides a simple API for creating and controlling WebView windows through the runtime:webview module. Built as a lightweight wrapper around ext_window, it offers a streamlined interface for common WebView operations.
Quick Start
import { webviewNew, webviewEval, webviewExit } from "runtime:webview";
// Create a WebView windowconst webview = await webviewNew({ title: "My App", url: "https://example.com", width: 800, height: 600, resizable: true, debug: false, frameless: false});
// Execute JavaScriptawait webviewEval(webview.id, "console.log('Hello!')");
// Close when doneawait webviewExit(webview.id);API Reference
Creating WebViews
webviewNew(options)
Creates a new WebView window with the specified configuration.
Parameters:
title(string) - Window title displayed in title barurl(string) - Initial URL to load (http://, https://, file://, app://)width(number) - Window width in pixelsheight(number) - Window height in pixelsresizable(boolean) - Allow user to resize windowdebug(boolean) - Enable DevTools for debuggingframeless(boolean) - Remove window decorations (title bar, borders)
Returns: WebViewHandle containing the window ID
Throws:
- Error [9000] if window creation fails
- Error [9001] if permission denied
const webview = await webviewNew({ title: "Web Browser", url: "about:blank", width: 1024, height: 768, resizable: true, debug: true, // Enable DevTools frameless: false});Alias: newWebView()
Controlling WebViews
webviewExit(id)
Closes the WebView window. The window ID becomes invalid after closing.
await webviewExit(webview.id);Alias: exitWebView()
webviewEval(id, javascript)
Executes JavaScript code in the WebView context. Return values are not captured - use for side effects only.
// Inject contentawait webviewEval(webview.id, ` document.body.innerHTML = '<h1>Hello, World!</h1>';`);
// Add stylesawait webviewEval(webview.id, ` document.body.style.backgroundColor = '#f0f0f0';`);Alias: evalInWebView()
webviewSetTitle(id, title)
Updates the window title. For frameless windows, the title may not be visible but is still used by the OS.
await webviewSetTitle(webview.id, "Updated Title");Alias: setWebViewTitle()
webviewSetColor(id, r, g, b, a)
Sets the WebView background color using RGBA values (0-255 for each channel).
// Light blue backgroundawait webviewSetColor(webview.id, 240, 240, 255, 255);
// Semi-transparent whiteawait webviewSetColor(webview.id, 255, 255, 255, 128);Alias: setWebViewColor()
webviewSetFullscreen(id, fullscreen)
Toggles fullscreen mode. In fullscreen, the window occupies the entire screen with all decorations hidden.
// Enter fullscreenawait webviewSetFullscreen(webview.id, true);
// Exit fullscreenawait webviewSetFullscreen(webview.id, false);Alias: setWebViewFullscreen()
Event Loop Compatibility
webviewLoop(id, blocking)
Event loop shim (no-op in Forge). Exists for API compatibility with reference WebView plugins. Always returns { code: 0 }.
const result = await webviewLoop(webview.id, 0);// Always returns { code: 0 }Alias: webViewLoop()
webviewRun(id)
Run loop shim (no-op in Forge). Exists for API compatibility. Returns immediately.
await webviewRun(webview.id);Alias: runWebView()
Common Patterns
Browser-Like Window
const browser = await webviewNew({ title: "Web Browser", url: "about:blank", width: 1024, height: 768, resizable: true, debug: true, // Enable DevTools frameless: false});
// Users can navigate using browser controlsFrameless Application Window
const app = await webviewNew({ title: "My App", url: "app://index.html", width: 600, height: 400, resizable: false, debug: false, frameless: true // No title bar or borders});
// Custom UI for close/minimize buttons via JavaScriptDynamic Content Injection
const view = await webviewNew({ title: "Dynamic Content", url: "about:blank", width: 800, height: 600, resizable: true, debug: false, frameless: false});
// Inject HTMLawait webviewEval(view.id, ` document.body.innerHTML = '<h1>Hello, World!</h1>';`);
// Style the pageawait webviewSetColor(view.id, 240, 240, 255, 255);Title Updates
const view = await webviewNew({ title: "Initial Title", url: "https://example.com", width: 800, height: 600, resizable: true, debug: false, frameless: false});
// Update based on contentawait webviewSetTitle(view.id, `Viewing: ${currentUrl}`);Architecture
ext_webview is a lightweight wrapper around ext_window:
TypeScript Application ↓runtime:webview ↓ (translates to WindowCmd messages)runtime:window ↓ (wry/tao)Native Window SystemAll WebView operations are translated to window commands:
| WebView Operation | Window Command | Description |
|---|---|---|
webviewNew() | WindowCmd::Create | Create window |
webviewExit() | WindowCmd::Close | Close window |
webviewEval() | WindowCmd::EvalJs | Execute JavaScript |
webviewSetColor() | WindowCmd::InjectCss | Inject CSS for background color |
webviewSetTitle() | WindowCmd::SetTitle | Update title |
webviewSetFullscreen() | WindowCmd::SetFullscreen | Toggle fullscreen |
Error Handling
All operations use structured error codes:
| Code | Error | Description |
|---|---|---|
| 9000 | Generic | General WebView operation failure |
| 9001 | PermissionDenied | Window creation permission denied |
try { const webview = await webviewNew({ title: "Example", url: "https://example.com", width: 800, height: 600, resizable: true, debug: false, frameless: false });} catch (error) { // Error 9000: Creation failed // Error 9001: Permission denied console.error("Failed to create WebView:", error);}Permissions
WebView operations require window creation permissions in manifest.app.toml:
[permissions.ui]windows = true # Required for WebView operationsOperations fail with error 9001 if permissions are not granted.
Platform Support
| Platform | WebView Backend | Status |
|---|---|---|
| macOS | WebKit (WKWebView) | ✅ Full support |
| Windows | WebView2 (Edge) | ✅ Full support |
| Linux | WebKitGTK | ✅ Full support |
Platform-specific behavior is handled by the underlying wry crate.
Common Pitfalls
Using Invalid Window IDs
// ❌ ERROR: Using ID after window closedawait webviewExit(webview.id);await webviewEval(webview.id, "..."); // ID is now invalid
// ✅ CORRECT: Don't use ID after closingawait webviewEval(webview.id, "...");await webviewExit(webview.id); // Close lastExpecting JavaScript Return Values
// ❌ INCORRECT: webviewEval doesn't return resultsconst result = await webviewEval(webview.id, "2 + 2"); // undefined
// ✅ CORRECT: Use for side effects onlyawait webviewEval(webview.id, ` console.log(2 + 2); // Log the result`);Manual Event Loop Management
// ❌ UNNECESSARY: These are no-ops in Forgeawait webviewLoop(webview.id, 0);await webviewRun(webview.id);
// ✅ CORRECT: Forge handles event loop automaticallyconst webview = await webviewNew({ ... });Implementation Details
Window Creation
webviewNew() converts parameters to WindowOpts and sends WindowCmd::Create:
- Check permissions via
WindowCapabilities - Convert parameters (frameless -> !decorations, debug -> devtools)
- Send
WindowCmd::Createto ext_window command channel - Await response with window ID
- Return handle containing window ID
JavaScript Evaluation
webviewEval() sends JavaScript through the window command channel:
- Check permissions
- Send
WindowCmd::EvalJswith window ID and script - Await completion (no return value captured)
JavaScript runs asynchronously in the WebView context. Use for side effects only.
Background Color
webviewSetColor() uses CSS injection:
- Convert RGBA (0-255) to CSS rgba() format
- Generate:
body { background-color: rgba(r,g,b,a); } - Send
WindowCmd::InjectCssto inject the rule
Event Loop
webviewLoop() and webviewRun() are no-ops for API compatibility. Forge uses a centralized event loop that handles all window events automatically.
Testing
# Run testscargo test -p ext_webview
# With debug loggingRUST_LOG=ext_webview=debug cargo test -p ext_webview -- --nocaptureRelated
- ext_window - Window management extension
- ext_ipc - IPC communication
- ext_devtools - Developer tools
- Architecture - System architecture