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:
- Rewrite
vscode-resource:in the HTML to/vscode-resource - Register a service worker to intercept all
/vscode-resourcerequests - Create a Promise for each request and create a cache, preferring the cache on subsequent requests
- In the outer WebView environment (outside the iFrame), forward the request to VSCode via postMessage
- Handle the resource request in VSCode and send it back to the WebView via postMessage
- Send the resolution result to the service worker via postMessage
- 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>