Skip to main content

L4 remote WebView

L4 is the highest isolation level for plugin Rendering (Render): the plugin page runs in its own Tauri WebviewWindow (a separate renderer process), isolated from the main interface at the process level. Even if a plugin gets stuck in a busy loop, only its own window freezes; the main UI is unaffected (it can still respond, render, and close). This suits heavy-UI, unrestrained plugins (a complex editor, a game UI enhancement, a long-running background page). Inside an iframe (L2) such plugins still share the same WebView with the main interface, so one infinite loop freezes the whole Launcher.

Manifest declaration

L4 Rendering turns on when either condition holds:
  • "render": "webview", or
  • the layers array contains "l4"
Priority (plugin-loader.tsx useWebview): render === 'webview' || layers.includes('l4'). A plugin that declares l4 must still provide entry.frontend, or it never activates and renders.

How the cross-window Bridge (bridge)works

An l4 window is not an ordinary child window opened with window.open; it is a separate WebView window created through Tauri IPC WebviewWindow (opener is null), so you cannot rely on window.opener.postMessage. Cross-window communication uses the Tauri event system (emitTo / listen; core:event is allowed by default):
  • Activation (Activate): when the plugin is enabled, createRemoteWebview opens the window plugin-webview-{id} (1200×800 by default) at the plugin page URL with a pluginWebview=1 marker.
  • Lightweight startup: once App.tsx sees the marker, it skips the main Layout / routes / automatic plugin Activation and renders only PluginWebviewPage.
  • Rendering: PluginWebviewPage pulls the plugin frontend assets, injects theme variables and the Bridge script through buildPluginDoc, and renders the result into an iframe with sandbox="allow-scripts".
  • API forwarding: an __PLUGIN_API__ call inside the iframe emits an __plugin_api_call message → the page forwards it as the Tauri event emitTo('main', 'plugin-l4-api-call', {pluginId, id, method, args}).
  • Main window execution: initL4Bridge in the main window receives the event → it calls the same handleApiCall used for iframes (METHOD_PERMISSIONS Permission (permissions)check + executePluginMethod) → the response goes back through emitTo(l4label, 'plugin-l4-api-response') → PluginWebviewPage listens and postMessages it back to the iframe unchanged.
  • Stream abort: __plugin_api_abort → the plugin-l4-api-abort event → abortStream in the main window.
Message bodies reuse the postMessage protocol from sandbox.ts ({type,id,method,args} / {type,id,result,error}); only the transport changes to cross-window events, so plugins see no difference.

Non-Tauri fallback

l4 depends on the Tauri WebViewWindow, so it automatically falls back to an iframe (l2) outside Tauri:
  • Plain browser / Vite dev (no window.__TAURI_INTERNALS__): createRemoteWebview goes straight to createSandbox (iframe Sandbox).
  • Window creation failure (tauri://error): the log reports a fallback to an iframe, and it falls back to createSandbox.
Windows are deduplicated by the plugin-webview-{id} label (an existing one is focused, not rebuilt); when you close a window manually, the instance table is cleaned up so deduplication does not misfire.

Trade-offs vs. iframe (l2) / WASM (l3)

  • Regular UI plugins → keep the default iframe (l2); do not declare l4 casually.
  • Plugins with heavy computation or Rendering that may block the UI → move the heavy work into WASM (l3) first; use l4 only when “the entire page must be isolated in its own process”.
  • Need access to the main interface DOM → iframes cannot do it, so use "render": "inline" (same window as the main interface, weakest isolation; use with care).

Known boundaries (the code is authoritative)

  • Cross-plugin calls through registerMethod / callPlugin: l4 windows are relayed through plugin-registry.ts in the main window; directly using the registry from the l4 side is marked TODO (see ADR-054), so prefer the main-window registry for plugin-to-plugin calls.
  • Theme push: l4 windows have no full theme pipeline; the current theme variables (__qomicex_theme) are pushed once when the iframe fires onload, and new values after a theme switch are not synced automatically (the code is authoritative).