|
|
|
|
@@ -9,13 +9,14 @@ import { Emitter, Event } from 'vs/base/common/event';
|
|
|
|
|
import { once } from 'vs/base/common/functional';
|
|
|
|
|
import { Disposable } from 'vs/base/common/lifecycle';
|
|
|
|
|
import { isMacintosh } from 'vs/base/common/platform';
|
|
|
|
|
import { endsWith } from 'vs/base/common/strings';
|
|
|
|
|
import { URI } from 'vs/base/common/uri';
|
|
|
|
|
import * as modes from 'vs/editor/common/modes';
|
|
|
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
|
|
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
|
|
|
import { IFileService } from 'vs/platform/files/common/files';
|
|
|
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
|
|
|
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
|
|
|
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
|
|
|
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
|
|
|
|
import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping';
|
|
|
|
|
import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing';
|
|
|
|
|
@@ -135,51 +136,6 @@ class WebviewPortMappingProvider extends Disposable {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SvgBlocker extends Disposable {
|
|
|
|
|
|
|
|
|
|
private readonly _onDidBlockSvg = this._register(new Emitter<void>());
|
|
|
|
|
public readonly onDidBlockSvg = this._onDidBlockSvg.event;
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
session: WebviewSession,
|
|
|
|
|
private readonly _options: WebviewContentOptions,
|
|
|
|
|
) {
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
session.onBeforeRequest(async (details) => {
|
|
|
|
|
if (details.url.indexOf('.svg') > 0) {
|
|
|
|
|
const uri = URI.parse(details.url);
|
|
|
|
|
if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg') && !this.isAllowedSvg(uri)) {
|
|
|
|
|
this._onDidBlockSvg.fire();
|
|
|
|
|
return { cancel: true };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
session.onHeadersReceived((details) => {
|
|
|
|
|
const headers: any = details.responseHeaders;
|
|
|
|
|
const contentType: string[] = headers['content-type'] || headers['Content-Type'];
|
|
|
|
|
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
|
|
|
|
const uri = URI.parse(details.url);
|
|
|
|
|
if (uri && !this.isAllowedSvg(uri)) {
|
|
|
|
|
this._onDidBlockSvg.fire();
|
|
|
|
|
return { cancel: true };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isAllowedSvg(uri: URI): boolean {
|
|
|
|
|
if (this._options.svgWhiteList) {
|
|
|
|
|
return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class WebviewKeyboardHandler extends Disposable {
|
|
|
|
|
|
|
|
|
|
private _ignoreMenuShortcut = false;
|
|
|
|
|
@@ -284,6 +240,8 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview {
|
|
|
|
|
@IFileService fileService: IFileService,
|
|
|
|
|
@ITunnelService tunnelService: ITunnelService,
|
|
|
|
|
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
|
|
|
|
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
|
|
|
|
@IEnvironmentService private readonly _environementService: IEnvironmentService,
|
|
|
|
|
) {
|
|
|
|
|
super();
|
|
|
|
|
this.content = {
|
|
|
|
|
@@ -331,11 +289,6 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview {
|
|
|
|
|
tunnelService,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
if (!this._options.allowSvgs) {
|
|
|
|
|
const svgBlocker = this._register(new SvgBlocker(session, this.content.options));
|
|
|
|
|
svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._register(new WebviewKeyboardHandler(this._webview));
|
|
|
|
|
|
|
|
|
|
this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) {
|
|
|
|
|
@@ -412,6 +365,10 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview {
|
|
|
|
|
case 'did-blur':
|
|
|
|
|
this.handleFocusChange(false);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case 'no-csp-found':
|
|
|
|
|
this.handleNoCspFound();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
this._register(addDisposableListener(this._webview, 'devtools-opened', () => {
|
|
|
|
|
@@ -546,14 +503,34 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public sendMessage(data: any): void {
|
|
|
|
|
this._send('message', data);
|
|
|
|
|
private _hasAlertedAboutMissingCsp = false;
|
|
|
|
|
|
|
|
|
|
private handleNoCspFound(): void {
|
|
|
|
|
if (this._hasAlertedAboutMissingCsp) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._hasAlertedAboutMissingCsp = true;
|
|
|
|
|
|
|
|
|
|
if (this._options.extension && this._options.extension.id) {
|
|
|
|
|
if (this._environementService.isExtensionDevelopment) {
|
|
|
|
|
console.warn(`${this._options.extension.id.value} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TelemetryClassification = {
|
|
|
|
|
extension?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }
|
|
|
|
|
};
|
|
|
|
|
type TelemetryData = {
|
|
|
|
|
extension?: string,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this._telemetryService.publicLog2<TelemetryData, TelemetryClassification>('webviewMissingCsp', {
|
|
|
|
|
extension: this._options.extension.id.value
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onDidBlockSvg() {
|
|
|
|
|
this.sendMessage({
|
|
|
|
|
name: 'vscode-did-block-svg'
|
|
|
|
|
});
|
|
|
|
|
public sendMessage(data: any): void {
|
|
|
|
|
this._send('message', data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private style(theme: ITheme): void {
|
|
|
|
|
|