Skip to content

Commit

Permalink
Forward webview console log messages to browser console (#13084)
Browse files Browse the repository at this point in the history
This ensures that webview logs are correctly propagated to the backend logger
Contributed on behalf of STMicroelectronics
  • Loading branch information
tortmayr authored Nov 27, 2023
1 parent 716aee5 commit 59613f9
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/main/browser/webview/pre/host.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
89 changes: 57 additions & 32 deletions packages/plugin-ext/src/main/browser/webview/pre/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`;
}

/**
Expand Down Expand Up @@ -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);
}

Expand Down
19 changes: 18 additions & 1 deletion packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -80,6 +81,12 @@ export interface WebviewContentOptions {
readonly enableCommandUris?: boolean | readonly string[];
}

export interface WebviewConsoleLog {
level: Extract<keyof typeof console, 'log' | 'info' | 'warn' | 'error' | 'trace' | 'debug'>;
message?: string;
optionalParams?: string;
}

@injectable()
export class WebviewWidgetIdentifier {
id: string;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
Expand Down

0 comments on commit 59613f9

Please sign in to comment.