| // 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; |
| } |
| } |