<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 { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron';
import { addDisposableListener } from 'vs/base/browser/dom';
import { ThrottledDelayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { once } from 'vs/base/common/functional';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
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 { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping';
import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing';
import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget';


class WebviewTagHandle extends Disposable {

	private _webContents: undefined | WebContents | 'destroyed';

	public constructor(
		public readonly webview: WebviewTag,
	) {
		super();

		this._register(addDisposableListener(this.webview, 'destroyed', () => {
			this._webContents = 'destroyed';
		}));

		this._register(addDisposableListener(this.webview, 'did-start-loading', once(() => {
			const contents = this.webContents;
			if (contents) {
				this._onFirstLoad.fire(contents);
				this._register(toDisposable(() => {
					contents.removeAllListeners();
				}));
			}
		})));
	}

	private readonly _onFirstLoad = this._register(new Emitter<WebContents>());
	public readonly onFirstLoad = this._onFirstLoad.event;

	public get webContents(): WebContents | undefined {
		if (this._webContents === 'destroyed') {
			return undefined;
		}
		if (this._webContents) {
			return this._webContents;
		}
		this._webContents = this.webview.getWebContents();
		return this._webContents;
	}
}

type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise<Response | undefined>;
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined;

class WebviewSession extends Disposable {

	private readonly _onBeforeRequestDelegates: Array<OnBeforeRequestDelegate> = [];
	private readonly _onHeadersReceivedDelegates: Array<OnHeadersReceivedDelegate> = [];

	public constructor(
		webviewHandle: WebviewTagHandle,
	) {
		super();

		this._register(webviewHandle.onFirstLoad(contents => {
			contents.session.webRequest.onBeforeRequest(async (details, callback) => {
				for (const delegate of this._onBeforeRequestDelegates) {
					const result = await delegate(details);
					if (typeof result !== 'undefined') {
						callback(result);
						return;
					}
				}
				callback({});
			});

			contents.session.webRequest.onHeadersReceived((details, callback) => {
				for (const delegate of this._onHeadersReceivedDelegates) {
					const result = delegate(details);
					if (typeof result !== 'undefined') {
						callback(result);
						return;
					}
				}
				callback({ cancel: false, responseHeaders: details.responseHeaders });
			});
		}));
	}

	public onBeforeRequest(delegate: OnBeforeRequestDelegate) {
		this._onBeforeRequestDelegates.push(delegate);
	}

	public onHeadersReceived(delegate: OnHeadersReceivedDelegate) {
		this._onHeadersReceivedDelegates.push(delegate);
	}
}

class WebviewProtocolProvider extends Disposable {

	private _resolve!: () => void;
	private _reject!: () => void;

	public readonly ready: Promise<void>;

	constructor(
		handle: WebviewTagHandle,
		getExtensionLocation: () => URI | undefined,
		getLocalResourceRoots: () => ReadonlyArray<URI>,
		fileService: IFileService,
	) {
		super();

		this.ready = new Promise((resolve, reject) => {
			this._resolve = resolve;
			this._reject = reject;
		});

		this._register(handle.onFirstLoad(contents => {
			try {
				registerFileProtocol(contents, WebviewResourceScheme, fileService, getExtensionLocation(), getLocalResourceRoots);
				this._resolve();
			} catch {
				this._reject();
			}
		}));
	}
}

class WebviewPortMappingProvider extends Disposable {

	constructor(
		session: WebviewSession,
		getExtensionLocation: () => URI | undefined,
		mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
		tunnelService: ITunnelService,
	) {
		super();
		const manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService));

		session.onBeforeRequest(async details => {
			const redirect = await manager.getRedirect(details.url);
			return redirect ? { redirectURL: redirect } : undefined;
		});
	}
}

class WebviewKeyboardHandler {

	private _ignoreMenuShortcut = false;

	private readonly _webviews = new Set<WebviewTagHandle>();
	private readonly _isUsingNativeTitleBars: boolean;

	constructor(configurationService: IConfigurationService) {
		this._isUsingNativeTitleBars = configurationService.getValue<string>('window.titleBarStyle') === 'native';
	}

	public add(
		webviewHandle: WebviewTagHandle,
	): IDisposable {
		this._webviews.add(webviewHandle);

		const disposables = new DisposableStore();
		if (this.shouldToggleMenuShortcutsEnablement) {
			disposables.add(webviewHandle.onFirstLoad(contents => {
				contents.on('before-input-event', (_event, input) => {
					if (input.type === 'keyDown' && document.activeElement === webviewHandle.webview) {
						this._ignoreMenuShortcut = input.control || input.meta;
						this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut);
					}
				});

				this.setIgnoreMenuShortcutsForWebview(webviewHandle, this._ignoreMenuShortcut);
			}));
		}

		disposables.add(addDisposableListener(webviewHandle.webview, 'ipc-message', (event) => {
			switch (event.channel) {
				case 'did-focus':
					this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut);
					break;

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

		return toDisposable(() => {
			disposables.dispose();
			this._webviews.delete(webviewHandle);
		});
	}

	private get shouldToggleMenuShortcutsEnablement() {
		return isMacintosh || this._isUsingNativeTitleBars;
	}

	private setIgnoreMenuShortcuts(value: boolean) {
		for (const webview of this._webviews) {
			this.setIgnoreMenuShortcutsForWebview(webview, value);
		}
	}

	private setIgnoreMenuShortcutsForWebview(webview: WebviewTagHandle, value: boolean) {
		if (this.shouldToggleMenuShortcutsEnablement) {
			const contents = webview.webContents;
			contents?.setIgnoreMenuShortcuts(value);
		}
	}<commit_msg>Always set `setIgnoreMenuShortcuts` while the webview is focused<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 { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron';
import { addDisposableListener } from 'vs/base/browser/dom';
import { ThrottledDelayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { once } from 'vs/base/common/functional';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
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 { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping';
import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing';
import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget';


class WebviewTagHandle extends Disposable {

	private _webContents: undefined | WebContents | 'destroyed';

	public constructor(
		public readonly webview: WebviewTag,
	) {
		super();

		this._register(addDisposableListener(this.webview, 'destroyed', () => {
			this._webContents = 'destroyed';
		}));

		this._register(addDisposableListener(this.webview, 'did-start-loading', once(() => {
			const contents = this.webContents;
			if (contents) {
				this._onFirstLoad.fire(contents);
				this._register(toDisposable(() => {
					contents.removeAllListeners();
				}));
			}
		})));
	}

	private readonly _onFirstLoad = this._register(new Emitter<WebContents>());
	public readonly onFirstLoad = this._onFirstLoad.event;

	public get webContents(): WebContents | undefined {
		if (this._webContents === 'destroyed') {
			return undefined;
		}
		if (this._webContents) {
			return this._webContents;
		}
		this._webContents = this.webview.getWebContents();
		return this._webContents;
	}
}

type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise<Response | undefined>;
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined;

class WebviewSession extends Disposable {

	private readonly _onBeforeRequestDelegates: Array<OnBeforeRequestDelegate> = [];
	private readonly _onHeadersReceivedDelegates: Array<OnHeadersReceivedDelegate> = [];

	public constructor(
		webviewHandle: WebviewTagHandle,
	) {
		super();

		this._register(webviewHandle.onFirstLoad(contents => {
			contents.session.webRequest.onBeforeRequest(async (details, callback) => {
				for (const delegate of this._onBeforeRequestDelegates) {
					const result = await delegate(details);
					if (typeof result !== 'undefined') {
						callback(result);
						return;
					}
				}
				callback({});
			});

			contents.session.webRequest.onHeadersReceived((details, callback) => {
				for (const delegate of this._onHeadersReceivedDelegates) {
					const result = delegate(details);
					if (typeof result !== 'undefined') {
						callback(result);
						return;
					}
				}
				callback({ cancel: false, responseHeaders: details.responseHeaders });
			});
		}));
	}

	public onBeforeRequest(delegate: OnBeforeRequestDelegate) {
		this._onBeforeRequestDelegates.push(delegate);
	}

	public onHeadersReceived(delegate: OnHeadersReceivedDelegate) {
		this._onHeadersReceivedDelegates.push(delegate);
	}
}

class WebviewProtocolProvider extends Disposable {

	private _resolve!: () => void;
	private _reject!: () => void;

	public readonly ready: Promise<void>;

	constructor(
		handle: WebviewTagHandle,
		getExtensionLocation: () => URI | undefined,
		getLocalResourceRoots: () => ReadonlyArray<URI>,
		fileService: IFileService,
	) {
		super();

		this.ready = new Promise((resolve, reject) => {
			this._resolve = resolve;
			this._reject = reject;
		});

		this._register(handle.onFirstLoad(contents => {
			try {
				registerFileProtocol(contents, WebviewResourceScheme, fileService, getExtensionLocation(), getLocalResourceRoots);
				this._resolve();
			} catch {
				this._reject();
			}
		}));
	}
}

class WebviewPortMappingProvider extends Disposable {

	constructor(
		session: WebviewSession,
		getExtensionLocation: () => URI | undefined,
		mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
		tunnelService: ITunnelService,
	) {
		super();
		const manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService));

		session.onBeforeRequest(async details => {
			const redirect = await manager.getRedirect(details.url);
			return redirect ? { redirectURL: redirect } : undefined;
		});
	}
}

class WebviewKeyboardHandler {

	private readonly _webviews = new Set<WebviewTagHandle>();
	private readonly _isUsingNativeTitleBars: boolean;

	constructor(configurationService: IConfigurationService) {
		this._isUsingNativeTitleBars = configurationService.getValue<string>('window.titleBarStyle') === 'native';
	}

	public add(
		webviewHandle: WebviewTagHandle,
	): IDisposable {
		this._webviews.add(webviewHandle);

		const disposables = new DisposableStore();
		if (this.shouldToggleMenuShortcutsEnablement) {
			disposables.add(webviewHandle.onFirstLoad(() => {
				this.setIgnoreMenuShortcutsForWebview(webviewHandle, true);
			}));
		}

		disposables.add(addDisposableListener(webviewHandle.webview, 'ipc-message', (event) => {
			switch (event.channel) {
				case 'did-focus':
					this.setIgnoreMenuShortcuts(true);
					break;

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

		return toDisposable(() => {
			disposables.dispose();
			this._webviews.delete(webviewHandle);
		});
	}

	private get shouldToggleMenuShortcutsEnablement() {
		return isMacintosh || this._isUsingNativeTitleBars;
	}

	private setIgnoreMenuShortcuts(value: boolean) {
		for (const webview of this._webviews) {
			this.setIgnoreMenuShortcutsForWebview(webview, value);
		}
	}

	private setIgnoreMenuShortcutsForWebview(webview: WebviewTagHandle, value: boolean) {
		if (this.shouldToggleMenuShortcutsEnablement) {
			const contents = webview.webContents;
			contents?.setIgnoreMenuShortcuts(value);
		}
	}
{'fraction_non_alphanumeric': 0.1376431760823141, 'fraction_numerical': 0.0, 'mean_word_length': 4.472379603399434, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 62, 'https://': 0, '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': '3436705', 'n_tokens_mistral': 4897, 'n_tokens_neox': 4318, 'n_words': 854}