Workflows
Creating and Interacting with a Webview Panel
How an extension creates a native webview panel via Cocoon and Mountain, sets HTML content, and exchanges messages with the host.
Webview Panel Lifecycle
Webview panels let extensions embed arbitrary HTML inside the editor. The panel itself is a native Tauri webview managed by Mountain; Cocoon holds a lightweight shim that proxies property assignments and message events across gRPC. Every panel.webview.html assignment and every postMessage call crosses the full Cocoon → Mountain → Sky path.
stateDiagram-v2
[*] --> Creating: Extension calls<br/>createWebviewPanel()
state Creating {
[*] --> SendRequest
SendRequest --> CreateDTO
CreateDTO --> GRPCRequest
note right of GRPCRequest: $createWebviewPanel<br/>to Mountain
}
Creating --> Initialized: Mountain generates<br/>handle & emits event
state Initialized {
[*] --> ReceiveHandle
ReceiveHandle --> CreateWebviewShim
CreateWebviewShim --> ReturnToExtension
note right of ReturnToExtension: Shim stores handle
}
Initialized --> ContentSet: Extension sets<br/>webview.html
Initialized --> Active: Webview component<br/>created & visible
state ContentSet {
[*] --> SetHTML
SetHTML --> GRPCSetHtml
note right of GRPCSetHtml: $setWebviewHtml<br/>to Mountain
}
ContentSet --> Active: Webview renders HTML
state Active {
[*] --> Ready
Ready --> ReceivingMessages
Ready --> FocusGain: User focuses
FocusGain --> Ready
Ready --> FocusLoss: User blurs
FocusLoss --> Ready
}
state ReceivingMessages {
[*] --> UserClicks
UserClicks --> PostMessage
PostMessage --> TauriCommand
note right of TauriCommand: mountain://webview/on-message
TauriCommand --> GRPCNotify
note right of GRPCNotify: $onDidReceiveMessage<br/>to Cocoon
GRPCNotify --> FireEvent
FireEvent --> ExtensionHandler
note right of ExtensionHandler: Extension receives<br/>onDidReceiveMessage
}
ReceivingMessages --> Active
Active --> Disposed: Extension disposes<br/>or user closes
state Disposed {
[*] --> Cleanup
Cleanup --> RemoveState
RemoveState --> NotifyClosed
note right of NotifyClosed: gRPC notification
}
Disposed --> [*]Phase 1 - Extension creates the panel (Cocoon → Mountain)
Extension Activation (
Cocoon) — An extension is activated. Itsactivate()function runs.vscode.window.createWebviewPanel()(Cocoon’ssrc/Service/WebviewPanel.ts) — The extension callswindow.createWebviewPanel(...), providingviewType,title,viewColumn, andoptions(which include enabling scripts).The call is received by the
WebviewPanelProviderservice in Cocoon. ItsCreateWebviewPaneleffect is executed.IpcProvider(Cocoon’ssrc/Service/Ipc.ts) — TheCreateWebviewPaneleffect constructs a detailed DTO containing all panel options, the extension’s ID, and its location on disk (for resolvinglocalResourceRoots).Cocoon sends a
$createWebviewPanelgRPC request to Mountain, passing this DTO, and awaits a unique handle in the response.
Phase 2 - Mountain allocates the native handle (Mountain → Sky)
Mountain’s Vine gRPC server dispatches the request to
WebviewProvider.CreateWebviewPanel()on theMountainEnvironment.The method generates a UUID handle, creates a
WebviewStateDtowith the received options, and stores it inAppState.ActiveWebviewskeyed on the handle.Mountain emits a Tauri event to the Sky frontend:
AppHandle.emit("sky://webview/create", { Handle, Title, ... }). This tells the UI layer to physically create a new webview component.Mountain returns the handle to Cocoon as the gRPC response.
Phase 3 - Sky renders the empty panel (Sky)
- A listener in Wind’s
WebviewManagementServicereceives thesky://webview/createevent, creates a newTauriWebviewWindowor an<iframe>element inside the main window’s DOM, and associates it with the handle. The webview is initially empty.
Phase 4 - Extension sets content (Cocoon → Mountain → Sky)
Cocoon receives the handle from the gRPC response and constructs a
WebviewPanelShimand aWebviewShiminstance that store the handle internally. It returns theWebviewPanelShimto the extension.The extension sets the panel’s HTML:
panel.webview.html = "<h1>Hello World</h1>";The
set htmlaccessor onWebviewShim(Cocoon’ssrc/Service/Webview.ts) is triggered. It sends a$setWebviewHtmlgRPC request to Mountain carrying the handle and the HTML string.Mountain’s
WebviewProviderreceives$setWebviewHtml, looks up the handle inAppState.ActiveWebviews, then emits:AppHandle.emit("sky://webview/set-html", { Handle, Html });Wind’s webview manager receives the
sky://webview/set-htmlevent, finds the element associated with the handle, and sets its inner HTML. “Hello World” is now visible in the panel.
Phase 5 - Bidirectional message passing (Sky → Mountain → Cocoon)
The user clicks a button inside the webview. The webview’s HTML has an
onclickhandler that callsvscode.postMessage({ command: "doSomething" })using thevscodeobject injected by the preload script for that webview.The message is sent from the webview to its host (Wind). Wind intercepts the message and sends a Tauri command to the backend:
TauriInvoke("mountain://webview/on-message", { Handle, Message });Mountain receives the command. The handler looks up the webview’s owner sidecar (
"cocoon-main") inAppState.ActiveWebviews.Mountain sends a
$onDidReceiveMessagegRPC notification to Cocoon, including thehandleand the message payload.Cocoon receives the notification. The
WebviewPanelProviderfinds theWebviewShimfor thathandleand fires itsonDidReceiveMessageevent.The extension’s listener for
panel.webview.onDidReceiveMessageis executed with the{ command: "doSomething" }payload. The communication loop is complete. The extension can now react to the user’s action in the UI.
Phase 6 - Disposal
The extension calls
panel.dispose()or the user closes the webview tab. Cocoon sends a$disposeWebviewPanelgRPC request to Mountain.Mountain removes the entry from
AppState.ActiveWebviews, emits a Tauri close event to Sky, and Wind tears down the webview DOM element.During cleanup, Mountain also removes the webview state and notifies Cocoon via gRPC that the panel has been closed.
Cleanup patterns
Extension-initiated disposal: Cocoon sends
$disposeWebviewPanelgRPC request. Mountain removes the state fromAppState.ActiveWebviewsand emits a close event to Sky. Wind removes the DOM element.User-initiated closure: The user closes the webview tab in the UI. Wind notifies Mountain via a Tauri command. Mountain removes the state, emits a gRPC notification back to Cocoon, and Cocoon fires the panel’s
onDidDisposeevent.Extension deactivation: When the extension deactivates, Cocoon automatically disposes all webview panels owned by that extension. Each panel follows the extension-initiated disposal path above.
Warning
The webview messaging bridge ($onDidReceiveMessage gRPC) requires the Environment/WebviewProvider/Messaging.rs handler to be present in the Mountain build. If setup_webview_message_listener_impl is absent, messages posted from the webview content are silently dropped and the extension never receives them. This affects all panel-based extensions including Roo, Continue, and Continue.