Logo

dev-resources.site

for different kinds of informations.

VsCode extension using webview and message posting

Published at
12/12/2020
Categories
vscode
typescript
extension
webview
Author
coderallan
Author
10 person written this
coderallan
open
VsCode extension using webview and message posting

In this article I will show how to create a simple VsCode extension that uses a webview with a html canvas to export graphics from the canvas to a PNG file.

VsCode extension example

When we have a webview panel in our extension then we cannot save data directly to the users disk from the html page or have download links inside the webview because it is contained in a sandbox for security reasons.

To show how we can get around this barrier this example extension will create a html canvas and display a rectangle. The image data from the canvas will then be saved to the root folder of the current workspace when the user clicks a button. This will illustrate how the message posting can be used to send data from a html page in a webview back to the VsCode context.

I have created a simple extension with a single command. When activated the command will create a webview panel using the createWebView panel command. Then I created an eventlistener for catching the onDidReceiveMessage events that I will use to send data from my html canvas

Below you can see the code for creating the webview panel and setup the eventlistner.



export function activate(context: vscode.ExtensionContext) {
  const vscodeTestDisposable = vscode.commands.registerCommand('vscodetest.webview', () => {
    const webviewPanel = vscode.window.createWebviewPanel(
      'vscodeTest',
      'VsCode test webview',
      vscode.ViewColumn.One,
      {
        enableScripts: true
      }
    );    
    webviewPanel.webview.onDidReceiveMessage(
      message => {
        switch (message.command) {
          case 'saveAsPng':
            saveAsPng(message.text);
            return;
        }
      },
      undefined,
      context.subscriptions
    );
    setHtmlContent(webviewPanel.webview, context);
  });
  context.subscriptions.push(vscodeTestDisposable);
}


Enter fullscreen mode Exit fullscreen mode

In the eventlistener I call the function saveToPng(...) that will decode the received data from the html page and then save the data to a PNG file. The data is base64 encoded because the message posting can only transmit text data. So the Javascript inside the webview has to convert all binary data to a text format before posting the message. This example extension will use base64 encoded data for the posted messages.

Below you can see the method that decodes the received messages.



function saveAsPng(messageText: string) {
  const dataUrl = messageText.split(',');
  if (dataUrl.length > 0) {
    const u8arr = Base64.toUint8Array(dataUrl[1]);
    const workspaceDirectory = getWorkspaceFolder();
    const newFilePath = path.join(workspaceDirectory, 'VsCodeExtensionTest.png');
    writeFile(newFilePath, u8arr, () => {
      vscode.window.showInformationMessage(`The file ${newFilePath} has been created in the root of the workspace.`);      
    });
  }
}


Enter fullscreen mode Exit fullscreen mode

The html the webview is using for showing the html canvas is shown below. There are a few details I would like to highlight here. Because of security reasons you should have Content-Security-Policy metatag in the html that restricts the code executing in the extension to avoid code injection. Below you can see I use a script nonce. This is a random string that is generated each time the page is reloaded and it prevent external injection of foreign code into you html page.
Also for security reasons all Javascript code and CSS styles should be contained in files. Inline code should be avoided. To allow the html to load files from the local drive you need to encode the uri's to the files using the asWebviewUri(...) method.




function getNonce() {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

function setHtmlContent(webview: vscode.Webview, extensionContext: vscode.ExtensionContext) {
  let htmlContent = `<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src cspSource; script-src 'nonce-nonce';">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="vscodeTest.css" rel="stylesheet">
  </head>
  <body>
    <div id="buttons">
      <input type="button" id="saveAsPngButton" value="Save as png">
    </div>
    <div id="canvasSection"><canvas id="vscodeTestCanvas" /></div>
    <script type="text/javascript" src="vscodeTest.js"></script>
  </body>
</html>`;
  const jsFilePath = vscode.Uri.joinPath(extensionContext.extensionUri, 'javascript', 'vscodeTest.js');
  const visUri = webview.asWebviewUri(jsFilePath);
  htmlContent = htmlContent.replace('vscodeTest.js', visUri.toString());

  const cssPath = vscode.Uri.joinPath(extensionContext.extensionUri, 'stylesheet', 'vscodeTest.css');
  const cssUri = webview.asWebviewUri(cssPath);
  htmlContent = htmlContent.replace('vscodeTest.css', cssUri.toString());

  const nonce = getNonce();
  htmlContent = htmlContent.replace('nonce-nonce', `nonce-${nonce}`);
  htmlContent = htmlContent.replace(/<script /g, `<script nonce="${nonce}" `);
  htmlContent = htmlContent.replace('cspSource', webview.cspSource);

  webview.html = htmlContent;
}


Enter fullscreen mode Exit fullscreen mode

In the example webview html page I draw a simple rectangle on the canvas and then the user must click the 'Save as Png' button to get the image data posted back to the extension context to be saved.
As described in a previous section the data has to be base64 encoded, but this is fortunately built into the canvas, so when you call the toBaseURL() function it returns the image data as a PNG base64 encoded. All we need to do is create a message and then post it using the postMessage(...) function that is part of the VsCode JS API.




function saveAsPng() {
  // Call back to the extension context to save the image to the workspace folder.
  const vscode = acquireVsCodeApi();
  vscode.postMessage({
    command: 'saveAsPng',
    text: canvas.toDataURL()
  });
}

const saveAsPngButton = document.getElementById('saveAsPngButton');
saveAsPngButton.addEventListener('click', saveAsPng);


Enter fullscreen mode Exit fullscreen mode

I hope you have found the article interesting and can use some of the techniques I have illustrated.

If you have not yet tried to create a Visual Studio Code extension, there are a lot of good articles on the VsCode website. To get started have a look at this article.

The entire code for this example extension can be found on GitHub: vscode-extension-webview

The techniques used in this article can be seen in a real-world extension in the AngularTools extension. I made a short introduction to the extension in a previous article here on Dev.io

webview Article's
30 articles in total
Favicon
A New Era of Cross-Platform Desktop Application Development in Go Language: A Comprehensive Analysis of LCL, CEF, and Webview
Favicon
Building Document PDF Scanner for Windows, Android and iOS with .NET MAUI Blazor
Favicon
Webview em QML (Qt Modeling Language)
Favicon
Switching from .NET MAUI Blazor to WebView Control for Document Scanning
Favicon
Developing a Desktop Document Scanner Application with .NET MAUI Blazor
Favicon
Is there any fix in react native which I can apply for upi payment using react-native-webview?
Favicon
Notification List - inbox list Webview
Favicon
Android WebView: Handle special Intent URI requests
Favicon
Chrome extensions in WebView2, CefSharp, and DotNetBrowser
Favicon
It is possible to build a full-featured browser using react-native.
Favicon
React Native WebView: Make Android pullToRefresh work without any glitches
Favicon
8 Tips for Creating a Native Look and Feel in Tauri Applications
Favicon
How to Add CanvasJS Charts to your Android App?
Favicon
WebView Callback response in Flutter
Favicon
What is a WebView And How To Test It?
Favicon
Aplikasi WebView Pro (Convert Web ke Apk)
Favicon
Creating a GO GUI with Alpine.js and Webview
Favicon
WKWebView persistent cookies open source solution and other useful things for WKWebView
Favicon
Download PDF files to device storage with React Native Webview
Favicon
Fix: physical keyboard can't type in Flutter's Webview
Favicon
ERR_CLEARTEXT_NOT_PERMITTED from Android WebView
Favicon
Append a new User-Agent to WebView in Android
Favicon
Which Embed Browser engine to use?
Favicon
Interaction between Vue JS Webview and native Apps
Favicon
Flutter둜 μ›Ήμ•± λ§Œλ“€λ©° μ§„ν–‰ν–ˆλ˜ 정리 (1)
Favicon
Generate Right Big Arrow 👲 with CSS for Local Webview
Favicon
VsCode extension using webview and message posting
Favicon
React Native WebView debugging
Favicon
Proton Native
Favicon
WebView Apps on your OS

Featured ones: