Skip to content
Back to blog

VSCode

An introduction to VSCode WebView isolation and message passing, and the implementation approach of forwarding local resource requests through a Service Worker.

Updated

The vscode-resource Protocol

The basic format is: vscode-resource:/Users/matt/projects/catre/creosote-cat.gif

Since a custom URI Scheme requires operating system support (adding to the registry), to avoid such complex behavior, a service worker can intercept https requests and resolve them to the correct resource addresses; a service worker can also send and receive messages from the pages where it is active.

The overall workflow is as follows:

  1. Rewrite vscode-resource: in the HTML to /vscode-resource
  2. Register a service worker to intercept all /vscode-resource requests
  3. Create a Promise for each request and create a cache, preferring the cache on subsequent requests
  4. In the outer WebView environment (outside the iFrame), forward the request to VSCode via postMessage
  5. Handle the resource request in VSCode and send it back to the WebView via postMessage
  6. Send the resolution result to the service worker via postMessage
  7. The service worker resolves the previously created Promise and caches the request result

WebView

Simply put, a WebView provides an API for setting HTML and for host–view interaction through messages.

Its main characteristics:

  • Isolated from the main VSCode process
  • Isolated from each other
  • Controls local resource loading (resource allowlists and blocklists, implemented through the vscode-resource protocol)
  • Supports communication with VSCode via postMessage
  • Monitors events inside the WebView
  • Script disabling
  • Theming

WebView content is loaded through a data: URI, which ensures that every WebView has its own origin

The overall structure of a WebView is as follows:

<webview>
  <html>
    <body>
      <!-- Injected event-listener hijacking, theme styles, environment variables, and so on -->
      <script src="main.js"></script>
      <script>
        // VSCode script for managing WebView content
        ;(function () {
          // Get the uuid, the unique identifier for communicating with VSCode
          const id = document.location.search.match(/\bid=([\w\-]+)/)[1]

          // Collect the listener functions in the iframe for messages sent from VSCode
          const handlers = new Map()

          // Define the write function for communicating with 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')) {
              // Handle messages sent from the inner iframe and forward them to VSCode
              postMessageToVsCode(e.data.command, e.data.data)
              return
            }

            // Handle requests from 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)
            }
          })

          // Script that handles communication between WebView content and VSCode
          // Mainly performs forwarding
          createWebviewManager({
            origin: document.location.href,
            postMessage: (channel, data) => {
              // Forward to VSCode
              postMessageToVsCode(channel, data)
            },
            onMessage: (channel, handler) => {
              // Add to the listener collection
              handlers.set(channel, handler)
            },
          })
        })()
      </script>
      <iframe width="100%" height="100%" sandbox="allow-same-origin allow-scripts">
        <!-- The WebView content actually loaded from the extension -->

        <!-- Communication script injected here mainly talks to the Script inside the WebView tag, which forwards to VSCode -->
      </iframe>
    </body>
  </html>
</webview>