本文目录

[[toc]]

vscode-resource 协议

基本格式为: vscode-resource:/Users/matt/projects/catre/creosote-cat.gif

由于自定义 URI Scheme 需要操作系统支持(添加到注册表),为了规避这种复杂行为,可以通过 service worker 拦截 https 请求,并解析到正确的资源地址,并且 service worker 还可以从他们处于活动状态的页面发送和接受消息。

整体的工作流程如下:

  1. 重写 HTML 中的 vscode-resource:/vscode-resource
  2. 注册 service worker ,拦截 /vscode-resource 所有请求
  3. 为请求创建 Promise ,并创建缓存,在下次请求时优先使用缓存
  4. 在外部 WebView 环境中( iFrame 之外),通过 postMessage 将请求转发到 VSCode
  5. 在 VSCode 中处理资源请求,通过 postMessage 回传到 WebView 中
  6. 通过 postMessage 发送解析结果给 service worker
  7. service worker 对之前创建的 Promise resolve 并缓存请求结果

WebView

简单来说, WebView 提供了一个设置 HTML 以及通过 message 进行 host 与 view 交互的 API 。

主要有以下特点:

  • 与主 VSCode 进程隔离
  • 彼此孤立
  • 控制本地资源加载(资源黑白名单,通过 vscode-resource 协议实现)
  • 支持通过 postMessage 与 VSCode 通信
  • 监控 WebView 内部的事件
  • 禁用脚本
  • 主题化

WebView 的内容将通过 data: URI 加载,这样可以确保每个 WebView 都是独立的源

WebView 整体结构如下:

<webview>
  <html>
    <body>
      <!-- 注入事件监听劫持、主题样式、环境变量等等信息 -->
      <script src="main.js"></script>
      <script>
        // 用于管理 WebView 内容的 VSCode 脚本
        ;(function () {
          // 获取 uuid ,用于与 VSCode 进行通信的唯一标识
          const id = document.location.search.match(/\bid=([\w\-]+)/)[1]

          // 收集 iframe 中,对 VSCode 发送来的消息的监听函数
          const handlers = new Map()

          // 定义与 VSCode 通信的写函数
          const postMessageToVsCode = (channel, data) => {
            window.parent.postMessage({ target: id, channel, data }, '*')
          }

          window.addEventListener('message', (e) => {
            if (e.data && (e.data.command === 'onmessage' || e.data.command === 'do-update-state')) {
              // 处理内部 iframe 发送出来的消息,转发给 VSCode
              postMessageToVsCode(e.data.command, e.data.data)
              return
            }

            // 处理 VSCode 发来的请求
            const channel = e.data.channel
            const handler = handlers.get(channel)
            if (handler) {
              handler(e, e.data.args)
            } else {
              console.log('no handler for ', e)
            }
          })

          // 处理 WebView 内容与 VSCode 通信问题的脚本
          // 主要进行转发操作
          createWebviewManager({
            origin: document.location.href,
            postMessage: (channel, data) => {
              // 转发给 VSCode
              postMessageToVsCode(channel, data)
            },
            onMessage: (channel, handler) => {
              // 添加到监听函数收集
              handlers.set(channel, handler)
            },
          })
        })()
      </script>
      <iframe width="100%" height="100%" sandbox="allow-same-origin allow-scripts">
        <!-- 实际上从扩展中加载的 WebView 内容 -->

        <!-- 注入通信脚本,主要与 WebView 标签内的 Script 通信,由其转发到 VSCode -->
      </iframe>
    </body>
  </html>
</webview>