<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { addClass, addDisposableListener } from 'vs/base/browser/dom';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
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 * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/parts/webview/electron-browser/webviewProtocols';
import { areWebviewInputOptionsEqual } from './webviewEditorService';
import { WebviewFindWidget } from './webviewFindWidget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { endsWith } from 'vs/base/common/strings';
import { isMacintosh } from 'vs/base/common/platform';

export interface WebviewOptions {
	readonly allowScripts?: boolean;
	readonly allowSvgs?: boolean;
	readonly svgWhiteList?: string[];
	readonly enableWrappedPostMessage?: boolean;
	readonly useSameOriginForRoot?: boolean;
	readonly localResourceRoots?: ReadonlyArray<URI>;
	readonly extensionLocation?: URI;
}

interface IKeydownEvent {
	key: string;
	keyCode: number;
	code: string;
	shiftKey: boolean;
	altKey: boolean;
	ctrlKey: boolean;
	metaKey: boolean;
	repeat: boolean;
}

class WebviewProtocolProvider extends Disposable {
	constructor(
		webview: Electron.WebviewTag,
		private readonly _extensionLocation: URI,
		private readonly _getLocalResourceRoots: () => ReadonlyArray<URI>,
		private readonly _environmentService: IEnvironmentService,
		private readonly _fileService: IFileService,
	) {
		super();

		let loaded = false;
		this._register(addDisposableListener(webview, 'did-start-loading', () => {
			if (loaded) {
				return;
			}
			loaded = true;

			const contents = webview.getWebContents();
			if (contents) {
				this.registerFileProtocols(contents);
			}
		}));
	}

	private registerFileProtocols(contents: Electron.WebContents) {
		if (contents.isDestroyed()) {
			return;
		}

		const appRootUri = URI.file(this._environmentService.appRoot);

		registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, null, () => [
			appRootUri
		]);

		registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
			this._getLocalResourceRoots()
		);
	}
}

class SvgBlocker extends Disposable {

	private readonly _onDidBlockSvg = this._register(new Emitter<void>());
	public readonly onDidBlockSvg = this._onDidBlockSvg.event;

	constructor(
		webview: Electron.WebviewTag,
		private readonly _options: WebviewOptions,
	) {
		super();

		if (this._options.allowSvgs) {
			return;
		}

		let loaded = false;
		this._register(addDisposableListener(webview, 'did-start-loading', () => {
			if (loaded) {
				return;
			}
			loaded = true;

			const contents = webview.getWebContents();
			if (!contents) {
				return;
			}

			contents.session.webRequest.onBeforeRequest((details, callback) => {
				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 callback({ cancel: true });
					}
				}
				return callback({});
			});

			contents.session.webRequest.onHeadersReceived((details, callback) => {
				const contentType: string[] = details.responseHeaders['content-type'] || details.responseHeaders['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 callback({ cancel: true });
					}
				}
				return callback({ cancel: false, responseHeaders: details.responseHeaders });
			});
		}));
	}

	private isAllowedSvg(uri: URI): boolean {
		if (this._options.allowSvgs) {
			return true;
		}
		if (this._options.svgWhiteList) {
			return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0;
		}
		return false;
	}
}

class WebviewKeyboardHandler extends Disposable {
	constructor(
		private readonly _webview: Electron.WebviewTag,
		private readonly _keybindingService: IKeybindingService
	) {
		super();

		if (this.shouldToggleMenuShortcutsEnablement) {
			this._register(addDisposableListener(this._webview, 'did-start-loading', () => {
				const contents = this.getWebContents();
				if (contents) {
					contents.on('before-input-event', (_event, input) => {
						contents.setIgnoreMenuShortcuts(input.control || input.meta);
					});
				}
			}));
		}

		this._register(addDisposableListener(this._webview, 'ipc-message', (event) => {
			switch (event.channel) {
				case 'did-keydown':
					// Electron: workaround for https://github.com/electron/electron/issues/14258
					// We have to detect keyboard events in the <webview> and dispatch them to our
					// keybinding service because these events do not bubble to the parent window anymore.
					this.handleKeydown(event.args[0]);
					return;

				case 'did-blur':
					if (this.shouldToggleMenuShortcutsEnablement) {
						const contents = this.getWebContents();
						if (contents) {
							contents.setIgnoreMenuShortcuts(false);
						}
					}
					return;
			}
		}));
	}

	private get shouldToggleMenuShortcutsEnablement() {
		return isMacintosh;
	}

	private getWebContents(): Electron.WebContents | undefined {<commit_msg>Extract setIgnoreMenuShortcuts<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { addClass, addDisposableListener } from 'vs/base/browser/dom';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
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 * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/parts/webview/electron-browser/webviewProtocols';
import { areWebviewInputOptionsEqual } from './webviewEditorService';
import { WebviewFindWidget } from './webviewFindWidget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { endsWith } from 'vs/base/common/strings';
import { isMacintosh } from 'vs/base/common/platform';

export interface WebviewOptions {
	readonly allowScripts?: boolean;
	readonly allowSvgs?: boolean;
	readonly svgWhiteList?: string[];
	readonly enableWrappedPostMessage?: boolean;
	readonly useSameOriginForRoot?: boolean;
	readonly localResourceRoots?: ReadonlyArray<URI>;
	readonly extensionLocation?: URI;
}

interface IKeydownEvent {
	key: string;
	keyCode: number;
	code: string;
	shiftKey: boolean;
	altKey: boolean;
	ctrlKey: boolean;
	metaKey: boolean;
	repeat: boolean;
}

class WebviewProtocolProvider extends Disposable {
	constructor(
		webview: Electron.WebviewTag,
		private readonly _extensionLocation: URI,
		private readonly _getLocalResourceRoots: () => ReadonlyArray<URI>,
		private readonly _environmentService: IEnvironmentService,
		private readonly _fileService: IFileService,
	) {
		super();

		let loaded = false;
		this._register(addDisposableListener(webview, 'did-start-loading', () => {
			if (loaded) {
				return;
			}
			loaded = true;

			const contents = webview.getWebContents();
			if (contents) {
				this.registerFileProtocols(contents);
			}
		}));
	}

	private registerFileProtocols(contents: Electron.WebContents) {
		if (contents.isDestroyed()) {
			return;
		}

		const appRootUri = URI.file(this._environmentService.appRoot);

		registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, null, () => [
			appRootUri
		]);

		registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
			this._getLocalResourceRoots()
		);
	}
}

class SvgBlocker extends Disposable {

	private readonly _onDidBlockSvg = this._register(new Emitter<void>());
	public readonly onDidBlockSvg = this._onDidBlockSvg.event;

	constructor(
		webview: Electron.WebviewTag,
		private readonly _options: WebviewOptions,
	) {
		super();

		if (this._options.allowSvgs) {
			return;
		}

		let loaded = false;
		this._register(addDisposableListener(webview, 'did-start-loading', () => {
			if (loaded) {
				return;
			}
			loaded = true;

			const contents = webview.getWebContents();
			if (!contents) {
				return;
			}

			contents.session.webRequest.onBeforeRequest((details, callback) => {
				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 callback({ cancel: true });
					}
				}
				return callback({});
			});

			contents.session.webRequest.onHeadersReceived((details, callback) => {
				const contentType: string[] = details.responseHeaders['content-type'] || details.responseHeaders['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 callback({ cancel: true });
					}
				}
				return callback({ cancel: false, responseHeaders: details.responseHeaders });
			});
		}));
	}

	private isAllowedSvg(uri: URI): boolean {
		if (this._options.allowSvgs) {
			return true;
		}
		if (this._options.svgWhiteList) {
			return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0;
		}
		return false;
	}
}

class WebviewKeyboardHandler extends Disposable {
	constructor(
		private readonly _webview: Electron.WebviewTag,
		private readonly _keybindingService: IKeybindingService
	) {
		super();

		if (this.shouldToggleMenuShortcutsEnablement) {
			this._register(addDisposableListener(this._webview, 'did-start-loading', () => {
				const contents = this.getWebContents();
				if (contents) {
					contents.on('before-input-event', (_event, input) => {
						this.setIgnoreMenuShortcuts(input.control || input.meta);
					});
				}
			}));
		}

		this._register(addDisposableListener(this._webview, 'ipc-message', (event) => {
			switch (event.channel) {
				case 'did-keydown':
					// Electron: workaround for https://github.com/electron/electron/issues/14258
					// We have to detect keyboard events in the <webview> and dispatch them to our
					// keybinding service because these events do not bubble to the parent window anymore.
					this.handleKeydown(event.args[0]);
					return;

				case 'did-blur':
					this.setIgnoreMenuShortcuts(false);
					return;
			}
		}));
	}

	private get shouldToggleMenuShortcutsEnablement() {
		return isMacintosh;
	}

	private setIgnoreMenuShortcuts(value: boolean) {
		if (!this.shouldToggleMenuShortcutsEnablement) {
			return;
		}
		const contents = this.getWebContents();
		if (contents) {
			contents.setIgnoreMenuShortcuts(value);
		}
	}

	private getWebContents(): Electron.WebContents | undefined {
{'fraction_non_alphanumeric': 0.15114785679884413, 'fraction_numerical': 0.0014448547118317548, 'mean_word_length': 3.7318647930117734, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 39, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6439974', 'n_tokens_mistral': 4195, 'n_tokens_neox': 3619, 'n_words': 782}