blob: 3006182b2f4c9ed356bdbb3c053936b8477a61a7 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as vscode from 'vscode';
import { Ffx, FfxLog, FuchsiaDevice } from '../ffx';
export const TOGGLE_LOG_VIEW_COMMAND = 'workbench.view.extension.fuchsia-logging';
export class LoggingViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'vscode-fuchsia.loggingView';
private view?: vscode.WebviewView;
private process?: FfxLog;
private currentDevice?: string;
constructor(
private readonly extensionUri: vscode.Uri,
private readonly ffx: Ffx,
) {
this.ffx.onSetTarget(device => {
if (this.currentDevice !== device?.nodeName) {
this.currentDevice = device?.nodeName;
if (this.view?.visible) {
this.resetView();
this.startListeningForLogs();
}
}
});
}
public get currentTargetDevice(): string | undefined {
return this.currentDevice;
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext<unknown>,
token: vscode.CancellationToken
): void | Thenable<void> {
this.view = webviewView;
webviewView.webview.options = {
// Allow scripts in the webview
enableScripts: true,
localResourceRoots: [
this.extensionUri
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(data => {
// No messages expected yet coming out of the webview.
});
this.view.onDidChangeVisibility(() => {
// Changing visibility triggers reconstruction of the webview, so we re-run ffx.
if (this.view?.visible) {
this.startListeningForLogs();
} else {
this.process?.stop();
}
});
}
public resetAndShowView(device: FuchsiaDevice | undefined) {
// If the device changed, update it.
if (this.currentDevice !== device?.nodeName) {
this.currentDevice = device?.nodeName;
// If the device changed and the view was already visible, clear it and start
// streaming logs from the new device.
if (this.view?.visible) {
this.resetView();
this.startListeningForLogs();
return;
}
}
// If we don't have a webview, make sure to create one.
if (this.view === undefined) {
vscode.commands.executeCommand(TOGGLE_LOG_VIEW_COMMAND);
}
// If the view wasn't visible, make it visible. This will result in streaming logs for the
// currently selected device.
if (!this.view?.visible) {
this.view?.show();
}
}
private addLog(log: Object) {
this.view?.webview.postMessage({ type: 'addLog', log });
}
private resetView() {
this.view?.webview.postMessage({ type: 'reset' });
}
private startListeningForLogs() {
this.process?.stop();
this.process = this.ffx.runLog(this.currentDevice, (data: Object) => {
this.addLog(data);
});
}
private _getHtmlForWebview(webview: vscode.Webview): string {
// Get the local path to main script run in the webview, then convert it to a uri we can use in
// the webview.
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview-logging.js'));
// Do the same for the stylesheet.
const styleResetUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'media', 'reset.css'));
const styleVSCodeUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'media', 'vscode.css'));
const styleMainUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview-logging.css'));
// Use a nonce to only allow a specific script to be run.
const nonce = this.getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our
extension directory, and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'none';
style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleResetUri}" rel="stylesheet">
<link href="${styleVSCodeUri}" rel="stylesheet">
<link href="${styleMainUri}" rel="stylesheet">
<title>Fuchsia Logs</title>
</head>
<body>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
private getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}