diff --git a/packages/plugin-ext/src/main/browser/webview/pre/host.js b/packages/plugin-ext/src/main/browser/webview/pre/host.js index 399070fdce43f..7ef317406d393 100644 --- a/packages/plugin-ext/src/main/browser/webview/pre/host.js +++ b/packages/plugin-ext/src/main/browser/webview/pre/host.js @@ -36,7 +36,7 @@ break; } } - if (sourceIsChildFrame && e.data && (e.data.command === 'onmessage' || e.data.command === 'do-update-state')) { + if (sourceIsChildFrame && e.data && (e.data.command === 'onmessage' || e.data.command === 'do-update-state' || e.data.command === 'onconsole')) { this.postMessage(e.data.command, e.data.data); } else if (sourceIsChildFrame || sourceIsSelfOrParentFrame) { const channel = e.data.channel; diff --git a/packages/plugin-ext/src/main/browser/webview/pre/main.js b/packages/plugin-ext/src/main/browser/webview/pre/main.js index 402124a328720..59105add0f051 100644 --- a/packages/plugin-ext/src/main/browser/webview/pre/main.js +++ b/packages/plugin-ext/src/main/browser/webview/pre/main.js @@ -144,40 +144,65 @@ * @param {*} [state] * @return {string} */ - function getVsCodeApiScript(state) { + function getDefaultScript(state) { return ` - const acquireVsCodeApi = (function() { - const originalPostMessage = window.parent.postMessage.bind(window.parent); - const targetOrigin = '*'; - let acquired = false; +const acquireVsCodeApi = (function() { + const originalPostMessage = window.parent.postMessage.bind(window.parent); + const originalConsole = {...console}; + const targetOrigin = '*'; + let acquired = false; + + let state = ${state ? `JSON.parse(${JSON.stringify(state)})` : undefined}; + + const forwardConsoleLog = (level, msg, args) => { + let message, optionalParams; + try { + if (msg) { + message = JSON.stringify(msg) ?? null; + } + if (args) { + optionalParams = JSON.stringify(args) ?? null; + } + } catch (e) { + // Log non serializable objects inside of view + originalConsole[level](msg, args); + return; + } + originalPostMessage({ command: 'onconsole', data: { level, message, optionalParams } }, targetOrigin); + }; - let state = ${state ? `JSON.parse(${JSON.stringify(state)})` : undefined}; + console.log = (message, args) => forwardConsoleLog('log', message, args); + console.info = (message, args) => forwardConsoleLog('info', message, args); + console.warn = (message, args) => forwardConsoleLog('warn', message, args); + console.error = (message, args) => forwardConsoleLog('error', message, args); + console.debug = (message, args) => forwardConsoleLog('debug', message, args); + console.trace = (message, args) => forwardConsoleLog('trace', message, args); - return () => { - if (acquired) { - throw new Error('An instance of the VS Code API has already been acquired'); - } - acquired = true; - return Object.freeze({ - postMessage: function(msg) { - return originalPostMessage({ command: 'onmessage', data: msg }, targetOrigin); - }, - setState: function(newState) { - state = newState; - originalPostMessage({ command: 'do-update-state', data: JSON.stringify(newState) }, targetOrigin); - return newState; - }, - getState: function() { - return state; - } - }); - }; - })(); - const acquireTheiaApi = acquireVsCodeApi; - delete window.parent; - delete window.top; - delete window.frameElement; - `; + return () => { + if (acquired) { + throw new Error('An instance of the VS Code API has already been acquired'); + } + acquired = true; + return Object.freeze({ + postMessage: function (msg) { + return originalPostMessage({ command: 'onmessage', data: msg }, targetOrigin); + }, + setState: function (newState) { + state = newState; + originalPostMessage({ command: 'do-update-state', data: JSON.stringify(newState) }, targetOrigin); + return newState; + }, + getState: function () { + return state; + } + }); + }; +})(); +const acquireTheiaApi = acquireVsCodeApi; +delete window.parent; +delete window.top; +delete window.frameElement; +`; } /** @@ -369,7 +394,7 @@ // apply default script if (options.allowScripts) { const defaultScript = newDocument.createElement('script'); - defaultScript.textContent = getVsCodeApiScript(data.state); + defaultScript.textContent = getDefaultScript(data.state); newDocument.head.prepend(defaultScript); } diff --git a/packages/plugin-ext/src/main/browser/webview/webview.ts b/packages/plugin-ext/src/main/browser/webview/webview.ts index ac6f7310adee1..f7956a6ee1e71 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview.ts @@ -69,7 +69,8 @@ export const enum WebviewMessageChannels { webviewReady = 'webview-ready', didKeydown = 'did-keydown', didMouseDown = 'did-mousedown', - didMouseUp = 'did-mouseup' + didMouseUp = 'did-mouseup', + onconsole = 'onconsole' } export interface WebviewContentOptions { @@ -80,6 +81,12 @@ export interface WebviewContentOptions { readonly enableCommandUris?: boolean | readonly string[]; } +export interface WebviewConsoleLog { + level: Extract; + message?: string; + optionalParams?: string; +} + @injectable() export class WebviewWidgetIdentifier { id: string; @@ -318,6 +325,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget, Extract this.toHide.push(subscription); this.toHide.push(this.on(WebviewMessageChannels.onmessage, (data: any) => this.onMessageEmitter.fire(data))); + this.toHide.push(this.on(WebviewMessageChannels.onconsole, (data: WebviewConsoleLog) => this.forwardConsoleLog(data))); this.toHide.push(this.on(WebviewMessageChannels.didClickLink, (uri: string) => this.openLink(new URI(uri)))); this.toHide.push(this.on(WebviewMessageChannels.doUpdateState, (state: any) => { this._state = state; @@ -462,6 +470,15 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget, Extract this.doUpdateContent(); } + protected forwardConsoleLog(log: WebviewConsoleLog): void { + const message = `[webview: ${this.identifier.id}] ${log.message ? JSON.parse(log.message) : undefined}`; + if (log.optionalParams !== undefined) { + console[log.level](message, JSON.parse(log.optionalParams)); + } else { + console[log.level](message); + } + } + protected style(): void { const { styles, activeThemeType, activeThemeName } = this.themeDataProvider.getThemeData(); this.doSend('styles', { styles, activeThemeType, activeThemeName });