Path: blob/main/src/vs/workbench/services/host/browser/browserHostService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../../base/common/event.js';6import { IHostService } from './host.js';7import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';8import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';9import { IEditorService } from '../../editor/common/editorService.js';10import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';11import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen } from '../../../../platform/window/common/window.js';12import { isResourceEditorInput, pathsToEditors } from '../../../common/editor.js';13import { whenEditorClosed } from '../../../browser/editor.js';14import { IWorkspace, IWorkspaceProvider } from '../../../browser/web.api.js';15import { IFileService } from '../../../../platform/files/common/files.js';16import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';17import { EventType, ModifierKeyEmitter, addDisposableListener, addDisposableThrottledListener, detectFullscreen, disposableWindowInterval, getActiveDocument, getWindowId, onDidRegisterWindow, trackFocus } from '../../../../base/browser/dom.js';18import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';19import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js';20import { memoize } from '../../../../base/common/decorators.js';21import { parseLineAndColumnAware } from '../../../../base/common/extpath.js';22import { IWorkspaceFolderCreationData } from '../../../../platform/workspaces/common/workspaces.js';23import { IWorkspaceEditingService } from '../../workspaces/common/workspaceEditing.js';24import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';25import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from '../../lifecycle/common/lifecycle.js';26import { BrowserLifecycleService } from '../../lifecycle/browser/lifecycleService.js';27import { ILogService } from '../../../../platform/log/common/log.js';28import { getWorkspaceIdentifier } from '../../workspaces/browser/workspaces.js';29import { localize } from '../../../../nls.js';30import Severity from '../../../../base/common/severity.js';31import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';32import { DomEmitter } from '../../../../base/browser/event.js';33import { isUndefined } from '../../../../base/common/types.js';34import { isTemporaryWorkspace, IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';35import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';36import { Schemas } from '../../../../base/common/network.js';37import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js';38import { coalesce } from '../../../../base/common/arrays.js';39import { mainWindow, isAuxiliaryWindow } from '../../../../base/browser/window.js';40import { isIOS, isMacintosh } from '../../../../base/common/platform.js';41import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';42import { URI } from '../../../../base/common/uri.js';43import { VSBuffer } from '../../../../base/common/buffer.js';44import { MarkdownString } from '../../../../base/common/htmlContent.js';4546enum HostShutdownReason {4748/**49* An unknown shutdown reason.50*/51Unknown = 1,5253/**54* A shutdown that was potentially triggered by keyboard use.55*/56Keyboard = 2,5758/**59* An explicit shutdown via code.60*/61Api = 362}6364export class BrowserHostService extends Disposable implements IHostService {6566declare readonly _serviceBrand: undefined;6768private workspaceProvider: IWorkspaceProvider;6970private shutdownReason = HostShutdownReason.Unknown;7172constructor(73@ILayoutService private readonly layoutService: ILayoutService,74@IConfigurationService private readonly configurationService: IConfigurationService,75@IFileService private readonly fileService: IFileService,76@ILabelService private readonly labelService: ILabelService,77@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,78@IInstantiationService private readonly instantiationService: IInstantiationService,79@ILifecycleService private readonly lifecycleService: BrowserLifecycleService,80@ILogService private readonly logService: ILogService,81@IDialogService private readonly dialogService: IDialogService,82@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,83@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService84) {85super();8687if (environmentService.options?.workspaceProvider) {88this.workspaceProvider = environmentService.options.workspaceProvider;89} else {90this.workspaceProvider = new class implements IWorkspaceProvider {91readonly workspace = undefined;92readonly trusted = undefined;93async open() { return true; }94};95}9697this.registerListeners();98}99100101private registerListeners(): void {102103// Veto shutdown depending on `window.confirmBeforeClose` setting104this._register(this.lifecycleService.onBeforeShutdown(e => this.onBeforeShutdown(e)));105106// Track modifier keys to detect keybinding usage107this._register(ModifierKeyEmitter.getInstance().event(() => this.updateShutdownReasonFromEvent()));108}109110private onBeforeShutdown(e: BeforeShutdownEvent): void {111112switch (this.shutdownReason) {113114// Unknown / Keyboard shows veto depending on setting115case HostShutdownReason.Unknown:116case HostShutdownReason.Keyboard: {117const confirmBeforeClose = this.configurationService.getValue('window.confirmBeforeClose');118if (confirmBeforeClose === 'always' || (confirmBeforeClose === 'keyboardOnly' && this.shutdownReason === HostShutdownReason.Keyboard)) {119e.veto(true, 'veto.confirmBeforeClose');120}121break;122}123// Api never shows veto124case HostShutdownReason.Api:125break;126}127128// Unset for next shutdown129this.shutdownReason = HostShutdownReason.Unknown;130}131132private updateShutdownReasonFromEvent(): void {133if (this.shutdownReason === HostShutdownReason.Api) {134return; // do not overwrite any explicitly set shutdown reason135}136137if (ModifierKeyEmitter.getInstance().isModifierPressed) {138this.shutdownReason = HostShutdownReason.Keyboard;139} else {140this.shutdownReason = HostShutdownReason.Unknown;141}142}143144//#region Focus145146@memoize147get onDidChangeFocus(): Event<boolean> {148const emitter = this._register(new Emitter<boolean>());149150this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => {151const focusTracker = disposables.add(trackFocus(window));152const visibilityTracker = disposables.add(new DomEmitter(window.document, 'visibilitychange'));153154Event.any(155Event.map(focusTracker.onDidFocus, () => this.hasFocus, disposables),156Event.map(focusTracker.onDidBlur, () => this.hasFocus, disposables),157Event.map(visibilityTracker.event, () => this.hasFocus, disposables),158Event.map(this.onDidChangeActiveWindow, () => this.hasFocus, disposables),159)(focus => emitter.fire(focus), undefined, disposables);160}, { window: mainWindow, disposables: this._store }));161162return Event.latch(emitter.event, undefined, this._store);163}164165get hasFocus(): boolean {166return getActiveDocument().hasFocus();167}168169async hadLastFocus(): Promise<boolean> {170return true;171}172173async focus(targetWindow: Window): Promise<void> {174targetWindow.focus();175}176177//#endregion178179180//#region Window181182@memoize183get onDidChangeActiveWindow(): Event<number> {184const emitter = this._register(new Emitter<number>());185186this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => {187const windowId = getWindowId(window);188189// Emit via focus tracking190const focusTracker = disposables.add(trackFocus(window));191disposables.add(focusTracker.onDidFocus(() => emitter.fire(windowId)));192193// Emit via interval: immediately when opening an auxiliary window,194// it is possible that document focus has not yet changed, so we195// poll for a while to ensure we catch the event.196if (isAuxiliaryWindow(window)) {197disposables.add(disposableWindowInterval(window, () => {198const hasFocus = window.document.hasFocus();199if (hasFocus) {200emitter.fire(windowId);201}202203return hasFocus;204}, 100, 20));205}206}, { window: mainWindow, disposables: this._store }));207208return Event.latch(emitter.event, undefined, this._store);209}210211@memoize212get onDidChangeFullScreen(): Event<{ windowId: number; fullscreen: boolean }> {213const emitter = this._register(new Emitter<{ windowId: number; fullscreen: boolean }>());214215this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => {216const windowId = getWindowId(window);217const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */;218219// Fullscreen (Browser)220for (const event of [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE]) {221disposables.add(addDisposableListener(window.document, event, () => emitter.fire({ windowId, fullscreen: !!detectFullscreen(window) })));222}223224// Fullscreen (Native)225disposables.add(addDisposableThrottledListener(viewport, EventType.RESIZE, () => emitter.fire({ windowId, fullscreen: !!detectFullscreen(window) }), undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */));226}, { window: mainWindow, disposables: this._store }));227228return emitter.event;229}230231openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;232openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;233openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {234if (Array.isArray(arg1)) {235return this.doOpenWindow(arg1, arg2);236}237238return this.doOpenEmptyWindow(arg1);239}240241private async doOpenWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void> {242const payload = this.preservePayload(false /* not an empty window */, options);243const fileOpenables: IFileToOpen[] = [];244245const foldersToAdd: IWorkspaceFolderCreationData[] = [];246const foldersToRemove: URI[] = [];247248for (const openable of toOpen) {249openable.label = openable.label || this.getRecentLabel(openable);250251// Folder252if (isFolderToOpen(openable)) {253if (options?.addMode) {254foldersToAdd.push({ uri: openable.folderUri });255} else if (options?.removeMode) {256foldersToRemove.push(openable.folderUri);257} else {258this.doOpen({ folderUri: openable.folderUri }, { reuse: this.shouldReuse(options, false /* no file */), payload });259}260}261262// Workspace263else if (isWorkspaceToOpen(openable)) {264this.doOpen({ workspaceUri: openable.workspaceUri }, { reuse: this.shouldReuse(options, false /* no file */), payload });265}266267// File (handled later in bulk)268else if (isFileToOpen(openable)) {269fileOpenables.push(openable);270}271}272273// Handle Folders to add or remove274if (foldersToAdd.length > 0 || foldersToRemove.length > 0) {275this.withServices(async accessor => {276const workspaceEditingService: IWorkspaceEditingService = accessor.get(IWorkspaceEditingService);277if (foldersToAdd.length > 0) {278await workspaceEditingService.addFolders(foldersToAdd);279}280281if (foldersToRemove.length > 0) {282await workspaceEditingService.removeFolders(foldersToRemove);283}284});285}286287// Handle Files288if (fileOpenables.length > 0) {289this.withServices(async accessor => {290const editorService = accessor.get(IEditorService);291292// Support mergeMode293if (options?.mergeMode && fileOpenables.length === 4) {294const editors = coalesce(await pathsToEditors(fileOpenables, this.fileService, this.logService));295if (editors.length !== 4 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1]) || !isResourceEditorInput(editors[2]) || !isResourceEditorInput(editors[3])) {296return; // invalid resources297}298299// Same Window: open via editor service in current window300if (this.shouldReuse(options, true /* file */)) {301editorService.openEditor({302input1: { resource: editors[0].resource },303input2: { resource: editors[1].resource },304base: { resource: editors[2].resource },305result: { resource: editors[3].resource },306options: { pinned: true }307});308}309310// New Window: open into empty window311else {312const environment = new Map<string, string>();313environment.set('mergeFile1', editors[0].resource.toString());314environment.set('mergeFile2', editors[1].resource.toString());315environment.set('mergeFileBase', editors[2].resource.toString());316environment.set('mergeFileResult', editors[3].resource.toString());317318this.doOpen(undefined, { payload: Array.from(environment.entries()) });319}320}321322// Support diffMode323else if (options?.diffMode && fileOpenables.length === 2) {324const editors = coalesce(await pathsToEditors(fileOpenables, this.fileService, this.logService));325if (editors.length !== 2 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1])) {326return; // invalid resources327}328329// Same Window: open via editor service in current window330if (this.shouldReuse(options, true /* file */)) {331editorService.openEditor({332original: { resource: editors[0].resource },333modified: { resource: editors[1].resource },334options: { pinned: true }335});336}337338// New Window: open into empty window339else {340const environment = new Map<string, string>();341environment.set('diffFileSecondary', editors[0].resource.toString());342environment.set('diffFilePrimary', editors[1].resource.toString());343344this.doOpen(undefined, { payload: Array.from(environment.entries()) });345}346}347348// Just open normally349else {350for (const openable of fileOpenables) {351352// Same Window: open via editor service in current window353if (this.shouldReuse(options, true /* file */)) {354let openables: IPathData<ITextEditorOptions>[] = [];355356// Support: --goto parameter to open on line/col357if (options?.gotoLineMode) {358const pathColumnAware = parseLineAndColumnAware(openable.fileUri.path);359openables = [{360fileUri: openable.fileUri.with({ path: pathColumnAware.path }),361options: {362selection: !isUndefined(pathColumnAware.line) ? { startLineNumber: pathColumnAware.line, startColumn: pathColumnAware.column || 1 } : undefined363}364}];365} else {366openables = [openable];367}368369editorService.openEditors(coalesce(await pathsToEditors(openables, this.fileService, this.logService)), undefined, { validateTrust: true });370}371372// New Window: open into empty window373else {374const environment = new Map<string, string>();375environment.set('openFile', openable.fileUri.toString());376377if (options?.gotoLineMode) {378environment.set('gotoLineMode', 'true');379}380381this.doOpen(undefined, { payload: Array.from(environment.entries()) });382}383}384}385386// Support wait mode387const waitMarkerFileURI = options?.waitMarkerFileURI;388if (waitMarkerFileURI) {389(async () => {390391// Wait for the resources to be closed in the text editor...392const filesToWaitFor: URI[] = [];393if (options.mergeMode) {394filesToWaitFor.push(fileOpenables[3].fileUri /* [3] is the resulting merge file */);395} else {396filesToWaitFor.push(...fileOpenables.map(fileOpenable => fileOpenable.fileUri));397}398await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, filesToWaitFor));399400// ...before deleting the wait marker file401await this.fileService.del(waitMarkerFileURI);402})();403}404});405}406}407408private withServices(fn: (accessor: ServicesAccessor) => unknown): void {409// Host service is used in a lot of contexts and some services410// need to be resolved dynamically to avoid cyclic dependencies411// (https://github.com/microsoft/vscode/issues/108522)412this.instantiationService.invokeFunction(accessor => fn(accessor));413}414415private preservePayload(isEmptyWindow: boolean, options?: IOpenWindowOptions): Array<unknown> | undefined {416417// Selectively copy payload: for now only extension debugging properties are considered418const newPayload: Array<unknown> = new Array();419if (!isEmptyWindow && this.environmentService.extensionDevelopmentLocationURI) {420newPayload.push(['extensionDevelopmentPath', this.environmentService.extensionDevelopmentLocationURI.toString()]);421422if (this.environmentService.debugExtensionHost.debugId) {423newPayload.push(['debugId', this.environmentService.debugExtensionHost.debugId]);424}425426if (this.environmentService.debugExtensionHost.port) {427newPayload.push(['inspect-brk-extensions', String(this.environmentService.debugExtensionHost.port)]);428}429}430431const newWindowProfile = options?.forceProfile432? this.userDataProfilesService.profiles.find(profile => profile.name === options?.forceProfile)433: undefined;434if (newWindowProfile && !newWindowProfile.isDefault) {435newPayload.push(['profile', newWindowProfile.name]);436}437438return newPayload.length ? newPayload : undefined;439}440441private getRecentLabel(openable: IWindowOpenable): string {442if (isFolderToOpen(openable)) {443return this.labelService.getWorkspaceLabel(openable.folderUri, { verbose: Verbosity.LONG });444}445446if (isWorkspaceToOpen(openable)) {447return this.labelService.getWorkspaceLabel(getWorkspaceIdentifier(openable.workspaceUri), { verbose: Verbosity.LONG });448}449450return this.labelService.getUriLabel(openable.fileUri, { appendWorkspaceSuffix: true });451}452453private shouldReuse(options: IOpenWindowOptions = Object.create(null), isFile: boolean): boolean {454if (options.waitMarkerFileURI) {455return true; // always handle --wait in same window456}457458const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');459const openInNewWindowConfig = isFile ? (windowConfig?.openFilesInNewWindow || 'off' /* default */) : (windowConfig?.openFoldersInNewWindow || 'default' /* default */);460461let openInNewWindow = (options.preferNewWindow || !!options.forceNewWindow) && !options.forceReuseWindow;462if (!options.forceNewWindow && !options.forceReuseWindow && (openInNewWindowConfig === 'on' || openInNewWindowConfig === 'off')) {463openInNewWindow = (openInNewWindowConfig === 'on');464}465466return !openInNewWindow;467}468469private async doOpenEmptyWindow(options?: IOpenEmptyWindowOptions): Promise<void> {470return this.doOpen(undefined, {471reuse: options?.forceReuseWindow,472payload: this.preservePayload(true /* empty window */, options)473});474}475476private async doOpen(workspace: IWorkspace, options?: { reuse?: boolean; payload?: object }): Promise<void> {477478// When we are in a temporary workspace and are asked to open a local folder479// we swap that folder into the workspace to avoid a window reload. Access480// to local resources is only possible without a window reload because it481// needs user activation.482if (workspace && isFolderToOpen(workspace) && workspace.folderUri.scheme === Schemas.file && isTemporaryWorkspace(this.contextService.getWorkspace())) {483this.withServices(async accessor => {484const workspaceEditingService: IWorkspaceEditingService = accessor.get(IWorkspaceEditingService);485486await workspaceEditingService.updateFolders(0, this.contextService.getWorkspace().folders.length, [{ uri: workspace.folderUri }]);487});488489return;490}491492// We know that `workspaceProvider.open` will trigger a shutdown493// with `options.reuse` so we handle this expected shutdown494if (options?.reuse) {495await this.handleExpectedShutdown(ShutdownReason.LOAD);496}497498const opened = await this.workspaceProvider.open(workspace, options);499if (!opened) {500await this.dialogService.prompt({501type: Severity.Warning,502message: workspace ?503localize('unableToOpenExternalWorkspace', "The browser blocked opening a new tab or window for '{0}'. Press 'Retry' to try again.", this.getRecentLabel(workspace)) :504localize('unableToOpenExternal', "The browser blocked opening a new tab or window. Press 'Retry' to try again."),505custom: {506markdownDetails: [{ markdown: new MarkdownString(localize('unableToOpenWindowDetail', "Please allow pop-ups for this website in your [browser settings]({0}).", 'https://aka.ms/allow-vscode-popup'), true) }]507},508buttons: [509{510label: localize({ key: 'retry', comment: ['&& denotes a mnemonic'] }, "&&Retry"),511run: () => this.workspaceProvider.open(workspace, options)512}513],514cancelButton: true515});516}517}518519async toggleFullScreen(targetWindow: Window): Promise<void> {520const target = this.layoutService.getContainer(targetWindow);521522// Chromium523if (targetWindow.document.fullscreen !== undefined) {524if (!targetWindow.document.fullscreen) {525try {526return await target.requestFullscreen();527} catch (error) {528this.logService.warn('toggleFullScreen(): requestFullscreen failed'); // https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen529}530} else {531try {532return await targetWindow.document.exitFullscreen();533} catch (error) {534this.logService.warn('toggleFullScreen(): exitFullscreen failed');535}536}537}538539// Safari and Edge 14 are all using webkit prefix540if ((<any>targetWindow.document).webkitIsFullScreen !== undefined) {541try {542if (!(<any>targetWindow.document).webkitIsFullScreen) {543(<any>target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise.544} else {545(<any>targetWindow.document).webkitExitFullscreen(); // it's async, but doesn't return a real promise.546}547} catch {548this.logService.warn('toggleFullScreen(): requestFullscreen/exitFullscreen failed');549}550}551}552553async moveTop(targetWindow: Window): Promise<void> {554// There seems to be no API to bring a window to front in browsers555}556557async getCursorScreenPoint(): Promise<undefined> {558return undefined;559}560561//#endregion562563//#region Lifecycle564565async restart(): Promise<void> {566this.reload();567}568569async reload(): Promise<void> {570await this.handleExpectedShutdown(ShutdownReason.RELOAD);571572mainWindow.location.reload();573}574575async close(): Promise<void> {576await this.handleExpectedShutdown(ShutdownReason.CLOSE);577578mainWindow.close();579}580581async withExpectedShutdown<T>(expectedShutdownTask: () => Promise<T>): Promise<T> {582const previousShutdownReason = this.shutdownReason;583try {584this.shutdownReason = HostShutdownReason.Api;585return await expectedShutdownTask();586} finally {587this.shutdownReason = previousShutdownReason;588}589}590591private async handleExpectedShutdown(reason: ShutdownReason): Promise<void> {592593// Update shutdown reason in a way that we do594// not show a dialog because this is a expected595// shutdown.596this.shutdownReason = HostShutdownReason.Api;597598// Signal shutdown reason to lifecycle599return this.lifecycleService.withExpectedShutdown(reason);600}601602//#endregion603604//#region Screenshots605606async getScreenshot(): Promise<VSBuffer | undefined> {607// Gets a screenshot from the browser. This gets the screenshot via the browser's display608// media API which will typically offer a picker of all available screens and windows for609// the user to select. Using the video stream provided by the display media API, this will610// capture a single frame of the video and convert it to a JPEG image.611const store = new DisposableStore();612613// Create a video element to play the captured screen source614const video = document.createElement('video');615store.add(toDisposable(() => video.remove()));616let stream: MediaStream | undefined;617try {618// Create a stream from the screen source (capture screen without audio)619stream = await navigator.mediaDevices.getDisplayMedia({620audio: false,621video: true622});623624// Set the stream as the source of the video element625video.srcObject = stream;626video.play();627628// Wait for the video to load properly before capturing the screenshot629await Promise.all([630new Promise<void>(r => store.add(addDisposableListener(video, 'loadedmetadata', () => r()))),631new Promise<void>(r => store.add(addDisposableListener(video, 'canplaythrough', () => r())))632]);633634const canvas = document.createElement('canvas');635canvas.width = video.videoWidth;636canvas.height = video.videoHeight;637638const ctx = canvas.getContext('2d');639if (!ctx) {640return undefined;641}642643// Draw the portion of the video (x, y) with the specified width and height644ctx.drawImage(video, 0, 0, canvas.width, canvas.height);645646// Convert the canvas to a Blob (JPEG format), use .95 for quality647const blob: Blob | null = await new Promise((resolve) => canvas.toBlob((blob) => resolve(blob), 'image/jpeg', 0.95));648if (!blob) {649throw new Error('Failed to create blob from canvas');650}651652const buf = await blob.bytes();653return VSBuffer.wrap(buf);654655} catch (error) {656console.error('Error taking screenshot:', error);657return undefined;658} finally {659store.dispose();660if (stream) {661for (const track of stream.getTracks()) {662track.stop();663}664}665}666}667668async getBrowserId(): Promise<string | undefined> {669return undefined;670}671672//#endregion673674//#region Native Handle675676async getNativeWindowHandle(_windowId: number) {677return undefined;678}679680//#endregion681}682683registerSingleton(IHostService, BrowserHostService, InstantiationType.Delayed);684685686