Skip to content

JavaScript interoperability

An embedded page may report its title or completion event to Compose, while the host may read document state or install theme and telemetry code before each page starts. controller.bridge is the low-level, platform-native JavaScript channel; it does not impose a message protocol or object model.

For typed values and request/response helpers, see JavaScript enhancements. Full signatures are in the JavaScriptBridge Dokka API.

Capability Use Lifecycle Dokka
evaluateScript(script) Evaluate or trigger an action in the current page suspend; returns the engine’s string representation evaluateScript
registerWebMessageHandler(handler) Receive page-to-host messages suspend; returns a closable handle registerWebMessageHandler
registerDocumentStartHook(script) Inject into later page loads at document start suspend; returns a closable handle registerDocumentStartHook

All three operate on the controller’s single native WebView. Observe loadingState before DOM work; Ready does not mean the first document has finished loading.

LaunchedEffect(controller.loadingState) {
if (controller.loadingState is LoadingState.LoadingEnd && controller.loadingState.success) {
onPageTitle(controller.bridge.evaluateScript("document.title"))
}
}

Use fixed scripts and explicit serialization. Concatenating untrusted input into JavaScript creates injection risk.

LaunchedEffect(controller) {
val handle = controller.bridge.registerWebMessageHandler { payload -> onWebEvent(payload) }
try { awaitCancellation() } finally { handle.close() }
}

The page-side entry point depends on the backend: WebView2 uses window.chrome.webview.postMessage(...), WebKit uses window.webkit.messageHandlers.wvbridge.postMessage(...), and Android uses window._wvbridge.postMessage(...). Treat payloads as untrusted strings and validate schema, size, and permissions.

LaunchedEffect(controller) {
val hook = controller.bridge.registerDocumentStartHook("window.__hostTheme = 'dark';")
try { awaitCancellation() } finally { hook.close() }
}

Hooks affect later page loads. Use evaluateScript() for the already-open page, then navigate or refresh for document-start behavior.

UI enters composition
├─ registerWebMessageHandler ──> CloseHandle
└─ registerDocumentStartHook ──> CloseHandle
UI leaves / feature closes ───────────────┘ close()

Close every handle in finally or DisposableEffect. A bridge is a trust boundary, not authentication: enable privileged protocols only for controlled origins, validate every event, and never map messages directly to file, account, payment, or arbitrary script operations. Android registrations may be unsupported; feature-check and provide a fallback.

For URL-level source restrictions, combine this page with Interceptors.