diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 65f37d5e283..fd37a4d1431 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -82,6 +82,10 @@ "name": "vs/workbench/services/assignment", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/auxiliaryWindow", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/extensions", "project": "vscode-workbench" diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 9838d2b99c5..86e2ec7c78a 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -2448,7 +2448,7 @@ export class ExperimentalMoveEditorIntoNewWindowAction extends Action2 { return; } - const auxiliaryEditorPart = editorGroupService.createAuxiliaryEditorPart(); + const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); activeEditorPane.group.moveEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup); } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 3aa3974137d..08297cc6210 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -38,10 +38,10 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd //#region Auxiliary Editor Parts - createAuxiliaryEditorPart(options?: { position?: IRectangle }): IAuxiliaryEditorPart { + async createAuxiliaryEditorPart(options?: { position?: IRectangle }): Promise { const disposables = new DisposableStore(); - const auxiliaryWindow = disposables.add(this.auxiliaryWindowService.open(options)); + const auxiliaryWindow = disposables.add(await this.auxiliaryWindowService.open(options)); disposables.add(Event.once(auxiliaryWindow.onDidClose)(() => disposables.dispose())); const partContainer = document.createElement('div'); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 771de64a129..b174c469230 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -378,7 +378,7 @@ export class ExperimentalSplitWindowAction extends Action2 { }, { targetWindowId: activeWindowId }); // Then create a new window next to the active window - const auxiliaryEditorPart = editorGroupService.createAuxiliaryEditorPart({ + const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart({ position: { x: activeWindow.screen.availWidth / 2, y: 0, diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index fbf577298d8..e422b90a2e3 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Dimension, EventHelper, EventType, addDisposableListener, copyAttributes, getClientArea, position, registerWindow, size, trackAttributes } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, EventType, addDisposableListener, copyAttributes, getActiveWindow, getClientArea, position, registerWindow, size, trackAttributes } from 'vs/base/browser/dom'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { assertIsDefined } from 'vs/base/common/types'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { isWeb } from 'vs/base/common/platform'; import { IRectangle } from 'vs/platform/window/common/window'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; export const IAuxiliaryWindowService = createDecorator('auxiliaryWindowService'); @@ -20,7 +22,7 @@ export interface IAuxiliaryWindowService { readonly _serviceBrand: undefined; - open(options?: { position?: IRectangle }): IAuxiliaryWindow; + open(options?: { position?: IRectangle }): Promise; } export interface IAuxiliaryWindow extends IDisposable { @@ -39,14 +41,21 @@ export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { declare readonly _serviceBrand: undefined; + private static readonly DEFAULT_SIZE = { width: 800, height: 600 }; + constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IDialogService private readonly dialogService: IDialogService ) { } - open(options?: { position?: IRectangle }): IAuxiliaryWindow { + async open(options?: { position?: IRectangle }): Promise { const disposables = new DisposableStore(); - const auxiliaryWindow = this.doOpen(options); + const auxiliaryWindow = await this.doOpen(options); + if (!auxiliaryWindow) { + throw new Error(localize('unableToOpenWindowError', "Unable to open a new window.")); + } + disposables.add(registerWindow(auxiliaryWindow)); disposables.add(toDisposable(() => auxiliaryWindow.close())); @@ -61,15 +70,35 @@ export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { }; } - private doOpen(options?: { position?: IRectangle }): AuxiliaryWindow { - let auxiliaryWindow: Window | null; - if (options?.position) { - auxiliaryWindow = window.open('about:blank', undefined, `left=${options.position.x},top=${options.position.y},width=${options.position.width},height=${options.position.height}`); - } else { - auxiliaryWindow = window.open('about:blank'); + private async doOpen(options?: { position?: IRectangle }): Promise { + let position: IRectangle | undefined = options?.position; + if (!position) { + const activeWindow = getActiveWindow(); + position = { + x: activeWindow.screen.availWidth / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.width / 2, + y: activeWindow.screen.availHeight / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.height / 2, + width: BrowserAuxiliaryWindowService.DEFAULT_SIZE.width, + height: BrowserAuxiliaryWindowService.DEFAULT_SIZE.height + }; } - return assertIsDefined(auxiliaryWindow).window as AuxiliaryWindow; + const auxiliaryWindow = window.open('about:blank', undefined, `popup=yes,left=${position.x},top=${position.y},width=${position.width},height=${position.height}`); + if (!auxiliaryWindow && isWeb) { + return (await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('unableToOpenWindow', "The browser interrupted the opening of a new window. Press 'Retry' to try again."), + detail: localize('unableToOpenWindowDetail', "To avoid this problem in the future, please ensure to allow popups for this website."), + buttons: [ + { + label: localize({ key: 'retry', comment: ['&& denotes a mnemonic'] }, "&&Retry"), + run: () => this.doOpen(options) + } + ], + cancelButton: true + })).result; + } + + return auxiliaryWindow?.window; } protected create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) { diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 3d931ca6fd1..c348bb92047 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -12,6 +12,7 @@ import { IWindowsConfiguration } from 'vs/platform/window/common/window'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { INativeHostService } from 'vs/platform/native/common/native'; import { DeferredPromise } from 'vs/base/common/async'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; type AuxiliaryWindow = BaseAuxiliaryWindow & { readonly vscodeWindowId: Promise; @@ -30,9 +31,10 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService, - @INativeHostService private readonly nativeHostService: INativeHostService + @INativeHostService private readonly nativeHostService: INativeHostService, + @IDialogService dialogService: IDialogService ) { - super(layoutService); + super(layoutService, dialogService); } protected override create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) { diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index df939bcb94f..1b48ec30bca 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -482,7 +482,7 @@ export interface IEditorGroupsService extends IEditorGroupsContainer { * Opens a new window with a full editor part instantiated * in there at the optional position on screen. */ - createAuxiliaryEditorPart(options?: { position?: IRectangle }): IAuxiliaryEditorPart; + createAuxiliaryEditorPart(options?: { position?: IRectangle }): Promise; } export const enum OpenEditorContext { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d1ba09d3c00..315f909a7ed 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -869,7 +869,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { readonly activePart = this; readonly mainPart = this; registerEditorPart(part: any): IDisposable { return Disposable.None; } - createAuxiliaryEditorPart(): IAuxiliaryEditorPart { throw new Error('Method not implemented.'); } + createAuxiliaryEditorPart(): Promise { throw new Error('Method not implemented.'); } } export class TestEditorGroupView implements IEditorGroupView { @@ -1753,7 +1753,7 @@ export class TestEditorPart extends MainEditorPart implements IEditorGroupsServi return Disposable.None; } - createAuxiliaryEditorPart(): IAuxiliaryEditorPart { + createAuxiliaryEditorPart(): Promise { throw new Error('Method not implemented.'); }