web - register external opener to prevent unload on expected href changes

This commit is contained in:
Benjamin Pasero 2020-12-10 09:16:58 +01:00
parent 039f15a0f9
commit 6c9dab1259
4 changed files with 81 additions and 1 deletions

View file

@ -61,6 +61,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { BrowserWindow } from 'vs/workbench/browser/window';
class BrowserMain extends Disposable {
@ -103,6 +104,12 @@ class BrowserMain extends Disposable {
// Startup
const instantiationService = workbench.startup();
// Window
this._register(instantiationService.createInstance(BrowserWindow));
// Logging
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
// Return API Facade
return instantiationService.invokeFunction(accessor => {
const commandService = accessor.get(ICommandService);

View file

@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { windowOpenNoOpener } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
export class BrowserWindow extends Disposable {
constructor(
@IOpenerService private readonly openerService: IOpenerService,
@ILifecycleService private readonly lifecycleService: BrowserLifecycleService
) {
super();
this.create();
}
private create(): void {
// Handle open calls
this.setupOpenHandlers();
}
private setupOpenHandlers(): void {
// Block window.open() calls
window.open = function (): Window | null {
throw new Error('Prevented call to window.open(). Use IOpenerService instead!');
};
// We need to ignore the `beforeunload` event while
// we handle external links to open specifically for
// the case of application protocols that e.g. invoke
// vscode itself. We do not want to open these links
// in a new window because that would leave a blank
// window to the user, but using `window.location.href`
// will trigger the `beforeunload`.
this.openerService.setExternalOpener({
openExternal: async (href: string) => {
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
windowOpenNoOpener(href);
} else {
this.lifecycleService.withExpectedUnload(() => window.location.href = href);
}
return true;
}
});
}
}

View file

@ -125,7 +125,7 @@ export class NativeWindow extends Disposable {
// React to editor input changes
this._register(this.editorService.onDidActiveEditorChange(() => this.updateTouchbarMenu()));
// prevent opening a real URL inside the shell
// prevent opening a real URL inside the window
[EventType.DRAG_OVER, EventType.DROP].forEach(event => {
window.document.body.addEventListener(event, (e: DragEvent) => {
EventHelper.stop(e);

View file

@ -16,6 +16,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
declare readonly _serviceBrand: undefined;
private beforeUnloadDisposable: IDisposable | undefined = undefined;
private expectedUnload = false;
constructor(
@ILogService readonly logService: ILogService
@ -32,6 +33,13 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
}
private onBeforeUnload(event: BeforeUnloadEvent): void {
if (this.expectedUnload) {
this.logService.info('[lifecycle] onBeforeUnload expected, ignoring once');
this.expectedUnload = false;
return; // ignore expected unload only once
}
this.logService.info('[lifecycle] onBeforeUnload triggered');
this.doShutdown(() => {
@ -41,6 +49,15 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
});
}
withExpectedUnload(callback: Function): void {
this.expectedUnload = true;
try {
callback();
} finally {
this.expectedUnload = false;
}
}
shutdown(): void {
this.logService.info('[lifecycle] shutdown triggered');