src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts/BaseWebview/constructor
class BaseWebview:	constructor(
		public readonly id: string,
		private readonly options: WebviewOptions,
		contentOptions: WebviewContentOptions,
		public extension: WebviewExtensionDescription | undefined,
		protected readonly webviewThemeDataProvider: WebviewThemeDataProvider,
		services: {
			contextMenuService: IContextMenuService,
			environmentService: IWorkbenchEnvironmentService,
			fileService: IFileService,
			logService: ILogService,
			menuService: IMenuService,
			notificationService: INotificationService,
			remoteAuthorityResolverService: IRemoteAuthorityResolverService,
			telemetryService: ITelemetryService,
			tunnelService: ITunnelService,
		}
	) {
		super();

		this._environmentService = services.environmentService;
		this._fileService = services.fileService;
		this._logService = services.logService;
		this._remoteAuthorityResolverService = services.remoteAuthorityResolverService;
		this._telemetryService = services.telemetryService;
		this._tunnelService = services.tunnelService;

		this.content = {
			html: '',
			options: contentOptions,
			state: undefined
		};

		this._portMappingManager = this._register(new WebviewPortMappingManager(
			() => this.extension?.location,
			() => this.content.options.portMapping || [],
			this._tunnelService
		));

		this._element = this.createElement(options, contentOptions);

		const subscription = this._register(this.on(WebviewMessageChannels.webviewReady, () => {
			this._logService.debug(`Webview(${this.id}): webview ready`);

			this.element?.classList.add('ready');

			if (this._state.type === WebviewState.Type.Initializing) {
				this._state.pendingMessages.forEach(({ channel, data }) => this.doPostMessage(channel, data));
			}
			this._state = WebviewState.Ready;

			subscription.dispose();
		}));

		this._register(this.on('no-csp-found', () => {
			this.handleNoCspFound();
		}));

		this._register(this.on(WebviewMessageChannels.didClickLink, (uri: string) => {
			this._onDidClickLink.fire(uri);
		}));

		this._register(this.on(WebviewMessageChannels.onmessage, (data: { message: any, transfer?: ArrayBuffer[] }) => {
			this._onMessage.fire({
				message: data.message,
				transfer: data.transfer,
			});
		}));

		this._register(this.on(WebviewMessageChannels.didScroll, (scrollYPercentage: number) => {
			this._onDidScroll.fire({ scrollYPercentage: scrollYPercentage });
		}));

		this._register(this.on(WebviewMessageChannels.doReload, () => {
			this.reload();
		}));

		this._register(this.on(WebviewMessageChannels.doUpdateState, (state: any) => {
			this.state = state;
			this._onDidUpdateState.fire(state);
		}));

		this._register(this.on(WebviewMessageChannels.didFocus, () => {
			this.handleFocusChange(true);
		}));

		this._register(this.on(WebviewMessageChannels.wheel, (event: IMouseWheelEvent) => {
			this._onDidWheel.fire(event);
		}));

		this._register(this.on(WebviewMessageChannels.didBlur, () => {
			this.handleFocusChange(false);
		}));

		this._register(this.on<{ message: string }>(WebviewMessageChannels.fatalError, (e) => {
			services.notificationService.error(localize('fatalErrorMessage', "Error loading webview: {0}", e.message));
		}));

		this._register(this.on('did-keydown', (data: KeyboardEvent) => {
			// 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.handleKeyEvent('keydown', data);
		}));

		this._register(this.on('did-keyup', (data: KeyboardEvent) => {
			this.handleKeyEvent('keyup', data);
		}));

		this._register(this.on('did-context-menu', (data: { clientX: number, clientY: number }) => {
			if (!this.element) {
				return;
			}
			if (!this._contextKeyService) {
				return;
			}
			const elementBox = this.element.getBoundingClientRect();
			services.contextMenuService.showContextMenu({
				getActions: () => {
					const result: IAction[] = [];
					const menu = services.menuService.createMenu(MenuId.WebviewContext, this._contextKeyService!);
					createAndFillInContextMenuActions(menu, undefined, result);
					menu.dispose();
					return result;
				},
				getAnchor: () => ({
					x: elementBox.x + data.clientX,
					y: elementBox.y + data.clientY
				})
			});
		}));

		this._register(this.on(WebviewMessageChannels.loadResource, (entry: { id: number, path: string, query: string, scheme: string, authority: string, ifNoneMatch?: string }) => {
			try {
				const uri = URI.from({
					scheme: entry.scheme,
					authority: entry.authority,
					path: decodeURIComponent(entry.path), // This gets re-encoded
					query: entry.query ? decodeURIComponent(entry.query) : entry.query,
				});
				this.loadResource(entry.id, uri, entry.ifNoneMatch);
			} catch (e) {
				this._send('did-load-resource', {
					id: entry.id,
					status: 404,
					path: entry.path,
				});
			}
		}));

		this._register(this.on(WebviewMessageChannels.loadLocalhost, (entry: any) => {
			this.localLocalhost(entry.id, entry.origin);
		}));

		this.style();
		this._register(webviewThemeDataProvider.onThemeDataChanged(this.style, this));
	}