@Dale-Lin
2021-01-07T22:36:46.000000Z
字数 4817
阅读 1168
VSCode开发
Webview API 允许插件程序在 VSCode 中创建完全自定义的视图,例如内置的 Markdown 插件就是用 webview 生成的预览视图。
一个 webview 就像插件程序控制的 iframe
,能渲染几乎所有 HTML 内容,并通过 postMessage 的形式和插件程序通信。
使用 createWebviewPanel
方法可以创建一个 webview panel,此时 webview panel 会在 VSCode 中不同的编辑器中显示,可以方便地展示自定义 UI。
Webview 应该保守地、仅当 VSCode 原生 API 不能满足的时候才使用。Webview 和普通扩展程序运行在不同的上下文,且消耗大量性能。
用户价值是否匹配资源耗损?
原生 API 是否能满足?
是否在其他应用或网页中展示更合适?
Webview 几乎啥也能做,但不意味着啥都要用 webview 来做。
window.createWebviewPanel(viewType, panelTitle, viewColumn | { preserveFocus: bool, viewColumn }[, options]): WebviewPanel
window.registerWebviewPanelSerializer(viewType, serializer): Disposable
设置 webview 中的 html 元素,包括 !Doctype、script 等(注意 csp)。类似设置 iframe 中的内容。
webview.html 内是完整的 html 文档。
设置 webview 的 title,类似 html 文档中 title 元素的作用,且不会引起文档的重新加载。
Webview panels 由 createWebviewPanel
方法调用后返回的实例控制,因此在插件程序中需要对 panel 实例进行保存,一旦丢失则会失去控制。
插件可以对 panel 进行关闭,一旦 panel 关闭,对应的 webview 也会被关闭。(尝试操作一个关闭的 webview 会报错)。
当 panel 的 webview 被销毁时调用。
手动销毁 panel 的 webview 实例。
返回 panel 可视性的 boolean。
将 panel 的 webview 显示到 VSCode 指定的列,如果未指定,则显示到当前列(Active)。
const columnActive = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined
if (currentPanel) {
currentPanel.reveal(columnActive)
} else {
currentPanel = vscode.window.createWebviewPanel(
viewType,
title,
columnActive,
{}
)
}
常用的 vscode.ViewColumn 枚举值:
- Active: -1
- Beside: -2
- One: 1
- Two: 2
...- Eight: 8
当 webView 的可视性改变或移动到新的一列后触发,回调函数接收一个带有 webviewPanel
属性的事件,可以获取触发改事件的 panel 对象(及相关属性)。
panel.onDidChangeViewState(
e => {
const panel = e.webviewPanel
console.log(panel.viewColumn, panel.visible)
if (panel.viewColumn === vscode.ViewColumn.One) {
// 如果是第一列做点啥
}
}
)
在 command palette 中输入 Developer: Open Webview Developer Tools
打开 chromium 调试工具。
如果有多个 webview 在调试,在控制台 tab 下修改 active-frame 环境调试当前 webview。
Developer: Reload Webview
命令将所有 active webviews 进行重载。
由于安全性原因,webview 运行的上下文不能直接访问本地资源。因此要加载插件或工作区中的图片、样式表等资源,需要使用 panel.webview.asWebviewUri()
方法将本地 Uri 转化成能够在 webview 中使用的 Uri。
const diskPath = vscode.Uri.file(
'/Users/codey/workspace/cat.gif'
)
const catGifSrc = panel.webview.asWebviewUri(diskPath)
// 在 webview 中使用 catGifSrc
默认情况下 webview 只能访问以下两种本地资源:
- 在插件安装目录下的资源
- 在用户当前工作区下的资源
createWebviewPanel 方法的第4个参数 webviewOptions.localResourceRoots
是一个只读数组,包含了本地资源的 root 路径,默认是 插件安装根目录 和 工作区根目录。设置成空数组可以禁止访问任何本地资源。
const panel = vscode.window.createWebviewPanel(
'catCoding',
'cat coding',
vscode.ViewColumn.One,
{
localResourceRoots: [
vscode.Uri.file(path.join(context.extensionPath, 'media'))
]
}
)
context.extensionPath 是插件的安装根目录,上例限制 webview 只能访问工作区下的 /media 本地资源。
默认情况下 webview 禁用 script。在 window.createWebviewPanel 第4个参数 webviewOptions 中将 enableScripts 设置为 true 即可开启脚本执行权限。
const panel = vscode.createWebviewPanel(
'catCoding',
'cat coding',
vscode.ViewColumn.One,
{
enableScripts: true
}
)
脚本可以用 script 标签的方式内联,也可以用 scr 属性加载外部资源,同样需要注意 localResourceRoots 的设置和 CSP。
panel.webview.postMessage()
方法接收任何可以被 JSON 序列化的信息,在 webview 中 window.onMessage
事件中可以接收到 MessageEvent 对象。
在 webview 的 script 中调用 acquireVsCodeApi()
可以从 VSCode 环境中获取到 vscode 实例,从而调用 VSCode API 的 vscode.postMessage()
方法向插件通信。
<script>
(function {
const vscode = acquireVsCodeApi()
vscode.postMessage({
command: 'alert',
text: "bug's here!"
})
}())
</script>
注意 acquireVsCodeApi()
方法在每个会话窗口内只能调用一次,每次使用 API 都需要通过该方法返回的实例,而且要保证该实例不会泄露到全局作用域中(安全性)。
略
在 webview 的 html 文档 head 中添加 meta 标签,设置 CSP:
<meta http-equiv="Content-Security-Policy" content="default-src: 'none'; style-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
上例中,样式表和脚本 src 设置为允许获取本地资源,但不支持行内写法,img 能从 https 协议获取资源。Webview.cspSource 是 webview 实例的一个可读属性,${} 作为占位符可以存储 js 变量。
webview 的生命周期是从 createWebviewPanel() 创建开始,到 panel 被关闭或手动调用了 panel.dispose() 方法结束,但是 webview 的数据会在其可视性变化时发生创建或销毁,即当 webview 不可见时,其所有的数据状态都会丢失。
可以用 acquireVsCodeApi().postMessage()
方法将数据或状态传递给插件,从而进行存储,当 panel.visible 变为 true 后再恢复。
webview 中运行的 script 可以使用 acquireVsCodeApi().getState()
和 acquireVsCodeApi().setState()
,这两个方法可以保存一个可以被 JSON 序列化的 state,state 即使 webview 变成不可视了也会保存(其他数据仍然会被销毁),只有在 panel 销毁的时候才会随之销毁。
使用 VSCode API 中的 window.registerWebviewPanelSerializer(viewType, serializer): Disposable
方法,可以将插件 webview 在 VSCode 重启时恢复存储的 state,有以下步骤:
onWebviewPanel:<viewType>
,保证当 VSCode 在恢复被注册的 webview 时,插件程序能被激活。
vscode.window.registerWebviewPanelSerializer(viewType, {
async deserializeWebviewPanel(webviewPanel, state) {
// 对恢复的 webviewPanle 和 state 做一些事
}
})
从上例可以看出,一个注册的 serializer: vscode.WebviewPanelSerializer 是一个包含异步方法 deserializeWebviewPanel(webviewPanel, state)
方法的对象。
Serialization 也是基于 getState/setState 实现的。
在 vscode.window.createWebviewPanel() 方法的第4个参数 webviewOptions 中设置 retainContextWhenHidden
为 true 即可开启。
不需要使用 state,webview 的上下文在可视性变化时也会一直保持(期间脚本执行会暂停)。
设置了 retainContextWhenHidden 的 webview 类似浏览器中后台 tab 页,脚本和其他动态内容会被挂起,直到变成可视时才恢复。
开启 retainContextWhenHidden 的内存开销很高,能使用其他手段实现持续性时不要使用。