Skip to content

Webview

Custom HTML panels for rich UI within VS Code.

WebviewPanel

WebviewPanel(client: VSCodeClient, panel_id: str, view_type: str, title: str)

Represents a VS Code webview panel.

A webview panel displays custom HTML content in a VS Code editor tab.

Initialize a WebviewPanel.

Parameters:

Name Type Description Default
client VSCodeClient

The VS Code client instance

required
panel_id str

Unique identifier for this panel

required
view_type str

Identifier for the webview type

required
title str

Title displayed in the editor tab

required
Source code in vscode_sockpuppet/webview.py
def __init__(self, client: "VSCodeClient", panel_id: str, view_type: str, title: str):
    """
    Initialize a WebviewPanel.

    Args:
        client: The VS Code client instance
        panel_id: Unique identifier for this panel
        view_type: Identifier for the webview type
        title: Title displayed in the editor tab
    """
    self._client = client
    self._id = panel_id
    self._view_type = view_type
    self._title = title
    self._disposed = False
    # Panel-level message handlers receive the 'message' payload (Any)
    self._message_handlers = []
    # Dispose handlers receive no arguments
    self._dispose_handlers = []
    # View state handlers receive the small state dict
    self._view_state_handlers = []
    self._subscription_active = False
    self._dispose_subscription_active = False
    self._view_state_subscription_active = False
    # Current view state
    self._visible = False
    self._active = False
    # Whether the webview JS has signaled it's ready to receive messages
    self._ready = False
    # Buffer messages posted before the webview is ready
    self._message_buffer = []

visible property

visible: bool

Whether the panel is currently visible in the editor.

active property

active: bool

Whether the panel is currently the active (focused) panel.

id property

id: str

Get the unique identifier for this panel.

view_type property

view_type: str

Get the view type identifier.

title property

title: str

Get the panel title.

disposed property

disposed: bool

Check if the panel has been disposed.

update_html

update_html(html: str) -> None

Update the HTML content of the webview.

Parameters:

Name Type Description Default
html str

The new HTML content to display

required

Raises:

Type Description
RuntimeError

If the panel has been disposed

Source code in vscode_sockpuppet/webview.py
def update_html(self, html: str) -> None:
    """
    Update the HTML content of the webview.

    Args:
        html: The new HTML content to display

    Raises:
        RuntimeError: If the panel has been disposed
    """
    if self._disposed:
        raise RuntimeError("Cannot update disposed webview panel")

    self._client._send_request("window.updateWebviewPanel", {"id": self._id, "html": html})

update_title

update_title(title: str) -> None

Update the title of the webview panel.

Parameters:

Name Type Description Default
title str

The new title to display

required

Raises:

Type Description
RuntimeError

If the panel has been disposed

Source code in vscode_sockpuppet/webview.py
def update_title(self, title: str) -> None:
    """
    Update the title of the webview panel.

    Args:
        title: The new title to display

    Raises:
        RuntimeError: If the panel has been disposed
    """
    if self._disposed:
        raise RuntimeError("Cannot update disposed webview panel")

    self._title = title
    self._client._send_request("window.updateWebviewPanel", {"id": self._id, "title": title})

update_icon

update_icon(icon_path: str) -> None

Update the icon of the webview panel.

Parameters:

Name Type Description Default
icon_path str

Absolute path to the icon file

required

Raises:

Type Description
RuntimeError

If the panel has been disposed

Source code in vscode_sockpuppet/webview.py
def update_icon(self, icon_path: str) -> None:
    """
    Update the icon of the webview panel.

    Args:
        icon_path: Absolute path to the icon file

    Raises:
        RuntimeError: If the panel has been disposed
    """
    if self._disposed:
        raise RuntimeError("Cannot update disposed webview panel")

    self._client._send_request(
        "window.updateWebviewPanel",
        {"id": self._id, "iconPath": icon_path},
    )

post_message

post_message(message: Any) -> None

Post a message to the webview's JavaScript context.

The message can be received in the webview's JavaScript using: window.addEventListener('message', event => { const message = event.data; // Handle message });

Parameters:

Name Type Description Default
message Any

The message to send (must be JSON-serializable)

required

Raises:

Type Description
RuntimeError

If the panel has been disposed

Source code in vscode_sockpuppet/webview.py
def post_message(self, message: Any) -> None:
    """
    Post a message to the webview's JavaScript context.

    The message can be received in the webview's JavaScript using:
    window.addEventListener('message', event => {
        const message = event.data;
        // Handle message
    });

    Args:
        message: The message to send (must be JSON-serializable)

    Raises:
        RuntimeError: If the panel has been disposed
    """
    if self._disposed:
        raise RuntimeError("Cannot post message to disposed webview panel")

    # If the webview hasn't signaled readiness, buffer the message to avoid
    # losing messages posted before the JS listener is installed.
    if not self._ready:
        self._message_buffer.append(message)
        return

    self._client._send_request(
        "window.postMessageToWebview", {"id": self._id, "message": message}
    )

as_webview_uri

as_webview_uri(local_uri: str) -> str

Convert a local file URI to a webview URI.

Webviews cannot directly load resources from the workspace or local file system using file: URIs. This method converts a local file: URI into a URI that can be used inside the webview to load the same resource.

The local resource must be in a directory listed in localResourceRoots when creating the webview, otherwise it cannot be loaded.

Parameters:

Name Type Description Default
local_uri str

Local file URI (e.g., 'file:///path/to/file.png') or absolute file path

required

Returns:

Type Description
str

Webview URI that can be used in webview HTML

Raises:

Type Description
RuntimeError

If the panel has been disposed

Example

Convert a local file to webview URI

img_uri = panel.as_webview_uri('file:///path/to/image.png')

Use in HTML

html = f'' panel.update_html(html)

Source code in vscode_sockpuppet/webview.py
def as_webview_uri(self, local_uri: str) -> str:
    """
    Convert a local file URI to a webview URI.

    Webviews cannot directly load resources from the workspace or local
    file system using file: URIs. This method converts a local file:
    URI into a URI that can be used inside the webview to load the same
    resource.

    The local resource must be in a directory listed in
    localResourceRoots when creating the webview, otherwise it cannot
    be loaded.

    Args:
        local_uri: Local file URI (e.g., 'file:///path/to/file.png')
            or absolute file path

    Returns:
        Webview URI that can be used in webview HTML

    Raises:
        RuntimeError: If the panel has been disposed

    Example:
        # Convert a local file to webview URI
        img_uri = panel.as_webview_uri('file:///path/to/image.png')

        # Use in HTML
        html = f'<img src="{img_uri}">'
        panel.update_html(html)
    """
    if self._disposed:
        raise RuntimeError("Cannot convert URI for disposed webview panel")

    # Ensure URI is in proper format
    if not local_uri.startswith("file://"):
        # Convert absolute path to file URI
        normalized_path = local_uri.replace("\\", "/")
        local_uri = f"file://{normalized_path}"

    result = self._client._send_request(
        "window.asWebviewUri", {"id": self._id, "uri": local_uri}
    )
    return result["webviewUri"]

on_did_receive_message

on_did_receive_message(handler: Callable[[Any], None]) -> Callable[[], None]

Subscribe to messages posted from the webview's JavaScript.

The handler will be called whenever the webview posts a message using: const vscode = acquireVsCodeApi(); vscode.postMessage({ your: 'data' });

Parameters:

Name Type Description Default
handler Callable[[Any], None]

Callback function that receives the message data

required

Returns:

Type Description
Callable[[], None]

A function that can be called to unsubscribe the handler

Example

def handle_message(message): print(f"Received: {message}")

Subscribe

unsubscribe = panel.on_did_receive_message(handle_message)

Later, to unsubscribe

unsubscribe()

Source code in vscode_sockpuppet/webview.py
def on_did_receive_message(self, handler: Callable[[Any], None]) -> Callable[[], None]:
    """
    Subscribe to messages posted from the webview's JavaScript.

    The handler will be called whenever the webview posts a message using:
    const vscode = acquireVsCodeApi();
    vscode.postMessage({ your: 'data' });

    Args:
        handler: Callback function that receives the message data

    Returns:
        A function that can be called to unsubscribe the handler

    Example:
        def handle_message(message):
            print(f"Received: {message}")

        # Subscribe
        unsubscribe = panel.on_did_receive_message(handle_message)

        # Later, to unsubscribe
        unsubscribe()
    """
    if self._disposed:
        raise RuntimeError("Cannot subscribe to disposed webview panel")

    # Add handler to our list
    self._message_handlers.append(handler)

    # Set up (or register with) global subscription for this client
    if not self._subscription_active:
        self._setup_message_subscription()
        self._subscription_active = True

    # Return unsubscribe function
    def unsubscribe():
        if handler in self._message_handlers:
            self._message_handlers.remove(handler)

        # If no message handlers left for this panel, remove panel from
        # registry so it no longer receives dispatched messages.
        if not self._message_handlers:
            _unregister_panel_from_global(self._client, self._id)

    return unsubscribe

on_did_dispose

on_did_dispose(handler: Callable[[], None]) -> Callable[[], None]

Subscribe to the panel disposal event.

The handler will be called when the webview panel is disposed, either by calling dispose() or when the user closes the panel.

Parameters:

Name Type Description Default
handler Callable[[], None]

Callback function called when the panel is disposed

required

Returns:

Type Description
Callable[[], None]

A function that can be called to unsubscribe the handler

Example::

def on_dispose():
    print("Webview was closed")

# Subscribe
unsubscribe = panel.on_did_dispose(on_dispose)

# Later, to unsubscribe
unsubscribe()
Source code in vscode_sockpuppet/webview.py
def on_did_dispose(self, handler: Callable[[], None]) -> Callable[[], None]:
    """
    Subscribe to the panel disposal event.

    The handler will be called when the webview panel is disposed,
    either by calling dispose() or when the user closes the panel.

    Args:
        handler: Callback function called when the panel is disposed

    Returns:
        A function that can be called to unsubscribe the handler

    Example::

        def on_dispose():
            print("Webview was closed")

        # Subscribe
        unsubscribe = panel.on_did_dispose(on_dispose)

        # Later, to unsubscribe
        unsubscribe()
    """
    if self._disposed:
        # Already disposed, call handler immediately
        handler()
        return lambda: None

    # Add handler to our list
    self._dispose_handlers.append(handler)

    # Register with global dispose dispatcher for this client
    if not self._dispose_subscription_active:
        self._setup_dispose_subscription()
        self._dispose_subscription_active = True

    # Return unsubscribe function
    def unsubscribe():
        if handler in self._dispose_handlers:
            self._dispose_handlers.remove(handler)

        # If no dispose handlers remain for this panel, remove it from
        # the global dispose registry.
        if not self._dispose_handlers:
            _unregister_dispose_panel_from_global(self._client, self._id)

    return unsubscribe

on_did_change_view_state

on_did_change_view_state(handler: Callable[[dict], None]) -> Callable[[], None]

Subscribe to view state change events.

The handler will be called when the panel's visibility or active state changes (e.g., when the user switches tabs or focuses the panel).

Parameters:

Name Type Description Default
handler Callable[[dict], None]

Callback function that receives a dict with 'visible' and 'active' keys

required

Returns:

Type Description
Callable[[], None]

A function that can be called to unsubscribe the handler

Example::

def on_view_state_change(state):
    print(f"Visible: {state['visible']}, Active: {state['active']}")
    if state['active']:
        print("Panel is now in focus!")

# Subscribe
unsubscribe = panel.on_did_change_view_state(on_view_state_change)

# Later, to unsubscribe
unsubscribe()
Source code in vscode_sockpuppet/webview.py
def on_did_change_view_state(self, handler: Callable[[dict], None]) -> Callable[[], None]:
    """
    Subscribe to view state change events.

    The handler will be called when the panel's visibility or active
    state changes (e.g., when the user switches tabs or focuses the panel).

    Args:
        handler: Callback function that receives a dict with 'visible' and 'active' keys

    Returns:
        A function that can be called to unsubscribe the handler

    Example::

        def on_view_state_change(state):
            print(f"Visible: {state['visible']}, Active: {state['active']}")
            if state['active']:
                print("Panel is now in focus!")

        # Subscribe
        unsubscribe = panel.on_did_change_view_state(on_view_state_change)

        # Later, to unsubscribe
        unsubscribe()
    """
    if self._disposed:
        return lambda: None

    # Add handler to our list
    self._view_state_handlers.append(handler)

    # Register with global view-state dispatcher for this client
    if not self._view_state_subscription_active:
        self._setup_view_state_subscription()
        self._view_state_subscription_active = True

    # Return unsubscribe function
    def unsubscribe():
        if handler in self._view_state_handlers:
            self._view_state_handlers.remove(handler)

        if not self._view_state_handlers:
            _unregister_viewstate_panel_from_global(self._client, self._id)

    return unsubscribe

dispose

dispose() -> None

Dispose of the webview panel, closing it in VS Code.

After disposal, the panel cannot be used anymore. This will trigger any registered on_did_dispose handlers.

Source code in vscode_sockpuppet/webview.py
def dispose(self) -> None:
    """
    Dispose of the webview panel, closing it in VS Code.

    After disposal, the panel cannot be used anymore.
    This will trigger any registered on_did_dispose handlers.
    """
    if self._disposed:
        return

    self._client._send_request("window.disposeWebviewPanel", {"id": self._id})
    self._disposed = True

    # Call dispose handlers
    for handler in self._dispose_handlers[:]:
        try:
            handler()
        except Exception as e:
            print(f"Error in webview dispose handler: {e}")
    # Clear handlers after calling them
    self._dispose_handlers.clear()

    # Remove from global message registry so the dispatcher no longer
    # attempts to route messages to this panel.
    _unregister_panel_from_global(self._client, self._id)
    # Also remove from dispose and viewstate registries so client-level
    # handlers can be unsubscribed when no panels remain.
    _unregister_dispose_panel_from_global(self._client, self._id)
    _unregister_viewstate_panel_from_global(self._client, self._id)

__enter__

__enter__()

Context manager entry.

Source code in vscode_sockpuppet/webview.py
def __enter__(self):
    """Context manager entry."""
    return self

__exit__

__exit__(exc_type, exc_val, exc_tb)

Context manager exit - automatically dispose the panel.

Source code in vscode_sockpuppet/webview.py
def __exit__(self, exc_type, exc_val, exc_tb):
    """Context manager exit - automatically dispose the panel."""
    self.dispose()
    return False

WebviewOptions

WebviewOptions

WebviewOptions(enable_scripts: bool = True, retain_context_when_hidden: bool = False, local_resource_roots: Optional[list[str]] = None)

Options for configuring a webview panel.

Initialize webview options.

Parameters:

Name Type Description Default
enable_scripts bool

Whether to enable JavaScript in the webview

True
retain_context_when_hidden bool

Whether to keep the webview's context when hidden

False
local_resource_roots Optional[list[str]]

List of URI paths that the webview can load local resources from

None
Source code in vscode_sockpuppet/webview.py
def __init__(
    self,
    enable_scripts: bool = True,
    retain_context_when_hidden: bool = False,
    local_resource_roots: Optional[list[str]] = None,
):
    """
    Initialize webview options.

    Args:
        enable_scripts: Whether to enable JavaScript in the webview
        retain_context_when_hidden: Whether to keep the webview's
            context when hidden
        local_resource_roots: List of URI paths that the webview can
            load local resources from
    """
    self.enable_scripts = enable_scripts
    self.retain_context_when_hidden = retain_context_when_hidden
    self.local_resource_roots = local_resource_roots or []

to_dict

to_dict() -> Dict[str, Any]

Convert options to a dictionary for JSON serialization.

Source code in vscode_sockpuppet/webview.py
def to_dict(self) -> Dict[str, Any]:
    """Convert options to a dictionary for JSON serialization."""
    return {
        "enableScripts": self.enable_scripts,
        "retainContextWhenHidden": self.retain_context_when_hidden,
        "localResourceRoots": self.local_resource_roots,
    }