Preface
Webview opens a new door:
The webview API allows extensions to create fully customizable views within Visual Studio Code. Webviews can also be used to build complex user interfaces beyond what VS Code's native APIs support.
It allows VS Code extensions to create complex UIs by rendering HTML, not limited to its API support. This flexibility gives extensions more possibilities:
This freedom makes webviews incredibly powerful, and opens up a whole new range of extension possibilities.
I. vscode.previewHtml Command
Early on, the vscode.previewHtml command was used to render HTML content:
// Render the html of the resource in an editor view.
vscode.commands.executeCommand(
'vscode.previewHtml',
uri,
viewColumn,
title
);
Essentially an iframe (see html preview part and command for details), used to support built-in Markdown preview and other features
Later, security and compatibility issues were encountered:
However the vscode.previewHtml command suffered from some important security and compatibility issues that we determined could not be fixed without breaking existing users of the command.
So the Webview API was adopted as a replacement:
The webview API is significantly easier to work with, correctly supports different filesystem setups, and webviews also offer many security benefits over htmlPreviews.
II. Webview API
Compared to previewHtml, Webview is safer, but also more resource-intensive:
Webviews are resource heavy and run in a separate context from normal extensions.
Its runtime environment is Electron's native Webview tag. Compared to iframe, the biggest difference is that Webview runs in a separate process with stronger security isolation:
Unlike an iframe, the webview runs in a separate process than your app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app safe from the embedded content.
On the other hand, since using Webview has performance overhead, the official documentation repeatedly emphasizes "don't use it unless necessary":
Webviews are pretty amazing, but they should also be used sparingly and only when VS Code's native API is inadequate.
And suggests considering 3 points before using Webview:
-
Does this feature really need to be in VS Code? Would it be more appropriate as a standalone application or website?
-
Is Webview the only way to achieve the target functionality? Can it be replaced with regular extension APIs?
-
Is the user value created worth the resources consumed by Webview?
III. Specific Usage
Specifically, create a Webview through vscode.window.createWebviewPanel:
// 1. Create and display Webview
const panel = vscode.window.createWebviewPanel(
// Identifier for this webview, any string
'catCoding',
// Title of the webview panel, shown to the user
'Cat Coding',
// Column where the webview panel is located
vscode.ViewColumn.One,
// Other webview options
{}
);
P.S. After creating the Webview panel, you can still modify the Tab title through webview.title
Then set the HTML content to be rendered in the Webview through webview.html:
// 2. Set the HTML content to be rendered in the webview
panel.webview.html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cat Coding</title>
</head>
<body>
<img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" />
</body>
</html>`;
Similar to vscode.previewHtml, the specified HTML content is ultimately loaded through an iframe, except this iframe is rendered by Webview. So, compared to the previous method, there's just an additional Webview layer to solve security problems
Lifecycle
After creating a Webview panel, there are 2 important lifecycle events:
-
Hide/Restore:
onDidChangeViewState, triggered when visibility (webview.visible) changes, and when the Webview is dragged to a different column (panel.viewColumn), usually used to save/restore state -
Dispose:
onDidDispose, triggered when the panel is closed, used to complete cleanup tasks like stopping timers
Specially, Webview content is destroyed when entering the background and recreated when visible again:
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
For example, after the user switches tabs, the content being displayed by the Webview is destroyed, and runtime state is also cleared. When the user switches back, or when the extension makes the Webview visible again through panel.reveal(), the Webview content reloads. When closed by the user, or closed by the extension through panel.dispose(), the Webview and its content are all destroyed
State Save and Restore
Therefore, Webview provides a mechanism to preserve state:
// webview
vscode.getState({ ... })
vscode.setState({ ... })
Can be used to restore Webview content, for example:
// webview
const vscode = acquireVsCodeApi();
const counter = document.getElementById('lines-of-code-counter');
// Get the previously saved state value
const previousState = vscode.getState();
let count = previousState ? previousState.count : 0;
counter.textContent = count;
setInterval(() => {
counter.textContent = count++;
// Write back when state value updates
vscode.setState({ count });
}, 100);
P.S. Among them, acquireVsCodeApi is a global function injected into the Webview environment, used to access VS Code's provided APIs like getState
Note that state saved through setState() is destroyed when the Webview panel is closed (not persisted):
The state is destroyed when the webview panel is destroyed.
If you want to persist it, you also need to implement the WebviewPanelSerializer interface:
// package.json
// 1. Declare the extension activation method onWebviewPanel:viewType in package.json
"activationEvents": [
...,
"onWebviewPanel:catCoding"
]
// extension.ts
// 2. Implement the WebviewPanelSerializer interface
vscode.window.registerWebviewPanelSerializer('catCoding',
new class implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
// Restore Webview content, state is the state saved through setState in the webview
webviewPanel.webview.html = restoreMyWebview(state);
}
}
);
This way, VS Code can automatically restore Webview content after restart
Besides manual save and restore, another simple method is to set the retainContextWhenHidden option (passed as a parameter when calling createWebviewPanel), requiring the Webview to retain content when invisible (equivalent to suspending), but this brings significant performance overhead. Use this option with caution
Communication
Although Webview content runs in an isolated environment, VS Code provides a messaging mechanism between extensions and Webview, enabling two-way communication:
// Extension sends
webview.postMessage({ ... })
// Webview receives
window.addEventListener('message', event => { ... })
// Webview sends
const vscode = acquireVsCodeApi()
vscode.postMessage({ ... })
// Extension receives
webview.onDidReceiveMessage(
message => { ... },
undefined,
context.subscriptions
);
Therefore, Webview state save and restore can be completely implemented manually if setState() and other APIs cannot meet the requirements
Theme Adaptation
Besides injecting JS to provide additional APIs, VS Code also presets some classes and CSS variables to support style adaptation
For example, body has 3 preset class values:
-
vscode-light: Light theme -
vscode-dark: Dark theme -
vscode-high-contrast: High contrast theme
You can use these three states to complete theme adaptation, for example:
body.vscode-light {
color: black;
}
body.vscode-dark {
color: white;
}
body.vscode-high-contrast {
color: red;
}
And user-configured specific color values are also exposed through CSS variables:
--vscode-editor-foreground corresponds to editor.foreground
--vscode-editor-font-size corresponds to editor.fontSize
IV. Debugging
Webview runs in an independent environment and cannot be directly debugged through DevTools. For this, VS Code provides 2 commands:
-
Developer: Open Webview Developer Tools: Open DevTools for the currently visible Webview -
Developer: Reload Webview: Reload all Webviews, reset their internal state, and reload local resources
DevTools for Webview can debug Webview content, just like opening DevTools through the Toggle Developer Tools command to debug VS Code's own UI
If Webview content loads local resources, you can reload them through the Reload Webview command without restarting the extension or reopening the Webview
V. Security Restrictions
Whether the previous vscode.previewHtml command or the current Webview API, there exist numerous security restrictions:
-
Navigation is not supported in Webview. Clicking
atags has no effect. It's recommended to implement navigation through extensions by modifying Webview content -
Still limited by the
iframeenvironment (just that theiframeis placed inside Webview). For example, pages with response headers containingX-Frame-Options: SAMEORIGINsettings cannot be loaded (see #76384, #70339 for details) -
Some security options of Electron's
webviewtag are not enabled. Such asallow-modals, which preventsalert(see #67109 for details) -
Loading local resources is restricted. By default, only the extension directory and opened workspace directory are allowed to be accessed, and must be converted through a specific API (
webview.asWebviewUri), or set the local resource root path through the<base href="${mediaPath}">tag (see #47631 for details)
For example, same-origin policy prevents loading some resources through iframe:
Refused to display 'https://code.visualstudio.com/api/extension-guides/webview' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
Such errors cannot be directly captured (see Catch error if iframe src fails to load), but you can try to access the resource before loading it through iframe, and only load it after confirming it's accessible:
fetch(url).then(() => {
// Can be loaded through iframe
frames[0].src = url;
}, () => {
// Cannot be loaded through iframe, prompt the user
});
VI. Summary
Seemingly flexible and open but actually has extremely many restrictions. Currently (2019/12/14), VS Code's positioning of Webview capability is just an HTML renderer, as a supplement to UI extension capabilities:
You should think of the webview more as an html view (one that does not have any server or origin) rather than a webpage.
(Excerpted from #72900, personally stated by the Webview API author)
No comments yet. Be the first to share your thoughts.