diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index c92e5a70c76..7632af1b45c 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -19,10 +19,11 @@ import { localize } from 'vs/nls'; import { IProductService } from 'vs/platform/product/common/product'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import Severity from 'vs/base/common/severity'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class OpenerService implements IOpenerService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private readonly _opener = new LinkedList(); @@ -41,7 +42,7 @@ export class OpenerService implements IOpenerService { return { dispose: remove }; } - async open(resource: URI, options?: { openToSide?: boolean }): Promise { + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { // no scheme ?!? if (!resource.scheme) { return Promise.resolve(false); @@ -57,13 +58,13 @@ export class OpenerService implements IOpenerService { return this._doOpen(resource, options); } - private _doOpen(resource: URI, options?: { openToSide?: boolean }): Promise { + private _doOpen(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { const { scheme, authority, path, query, fragment } = resource; - if (equalsIgnoreCase(scheme, Schemas.mailto)) { + if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) { // open default mail application - return this.openExternal(resource); + return this._doOpenExternal(resource); } if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { @@ -78,7 +79,7 @@ export class OpenerService implements IOpenerService { const domainToOpen = `${scheme}://${authority}`; if (isDomainTrusted(domainToOpen, trustedDomains)) { - return this.openExternal(resource); + return this._doOpenExternal(resource); } else { return this._dialogService.show( Severity.Info, @@ -97,11 +98,11 @@ export class OpenerService implements IOpenerService { cancelId: 1 }).then((choice) => { if (choice === 0) { - return this.openExternal(resource); + return this._doOpenExternal(resource); } else if (choice === 2) { return this._commandService.executeCommand('workbench.action.configureTrustedDomains', domainToOpen).then((pickedDomains: string[]) => { if (pickedDomains.indexOf(domainToOpen) !== -1) { - return this.openExternal(resource); + return this._doOpenExternal(resource); } return Promise.resolve(false); }); @@ -152,7 +153,7 @@ export class OpenerService implements IOpenerService { } } - openExternal(resource: URI): Promise { + private _doOpenExternal(resource: URI): Promise { dom.windowOpenNoOpener(encodeURI(resource.toString(true))); return Promise.resolve(true); diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index c8336fc712c..17cea7f5b3a 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -11,6 +11,7 @@ export const IOpenerService = createDecorator('openerService'); export interface IOpener { open(resource: URI, options?: { openToSide?: boolean }): Promise; + open(resource: URI, options?: { openExternal?: boolean }): Promise; } export interface IOpenerService { @@ -29,18 +30,11 @@ export interface IOpenerService { * @return A promise that resolves when the opening is done. */ open(resource: URI, options?: { openToSide?: boolean }): Promise; - - /** - * Opens a URL externally. - * - * @param url A resource to open externally. - */ - openExternal(resource: URI): Promise; + open(resource: URI, options?: { openExternal?: boolean }): Promise; } export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, registerOpener() { return { dispose() { } }; }, open() { return Promise.resolve(false); }, - openExternal() { return Promise.resolve(false); } }); diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/platform/url/electron-browser/urlService.ts index 06b5d7c31f0..1b1c88721b7 100644 --- a/src/vs/platform/url/electron-browser/urlService.ts +++ b/src/vs/platform/url/electron-browser/urlService.ts @@ -27,12 +27,16 @@ export class RelayURLService extends URLService implements IURLHandler { openerService.registerOpener(this); } - async open(uri: URI): Promise { - if (uri.scheme !== product.urlProtocol) { + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + if (options && options.openExternal) { return false; } - return await this.urlService.open(uri); + if (resource.scheme !== product.urlProtocol) { + return false; + } + + return await this.urlService.open(resource); } handleURL(uri: URI): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index acbb5c6f306..df059758f65 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -59,7 +59,7 @@ export class MainThreadWindow implements MainThreadWindowShape { } } - return this.openerService.openExternal(uri); + return this.openerService.open(uri, { openExternal: true }); } private getOrCreateTunnel(remotePort: number): Promise | undefined { diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index d6ce1130e86..9c0808d9ad1 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -38,7 +38,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { BinaryFileEditor.ID, { openInternal: (input, options) => this.openInternal(input, options), - openExternal: resource => this.openerService.openExternal(resource) + openExternal: resource => this.openerService.open(resource, { openExternal: true }) }, telemetryService, themeService, diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 57dd6170e9a..a2ee3629a46 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -54,6 +54,8 @@ import { IPreferencesService } from '../services/preferences/common/preferences' import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { Schemas } from 'vs/base/common/network'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -101,7 +103,8 @@ export class ElectronWindow extends Disposable { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITextFileService private readonly textFileService: ITextFileService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IOpenerService private readonly openerService: IOpenerService ) { super(); @@ -312,13 +315,8 @@ export class ElectronWindow extends Disposable { this._register(this.instantiationService.createInstance(NativeMenubarControl)); } - // Handle window.open() calls - const $this = this; - window.open = function (url: string, target: string, features: string, replace: boolean): Window | null { - $this.windowsService.openExternal(url); - - return null; - }; + // Handle open calls + this.setupOpenHandlers(); // Emit event when vscode is ready this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.windowService.windowId)); @@ -356,6 +354,35 @@ export class ElectronWindow extends Disposable { } } + private setupOpenHandlers(): void { + + // Handle window.open() calls + const $this = this; + window.open = function (url: string, target: string, features: string, replace: boolean): Window | null { + $this.windowsService.openExternal(url); + + return null; + }; + + // Handle external open calls + this.openerService.registerOpener({ + async open(resource: URI, options?: { openToSide?: boolean; openExternal?: boolean; } | undefined): Promise { + if (!options || !options.openExternal) { + return false; // only override behaviour for external open() + } + + const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); + if (!success && resource.scheme === Schemas.file) { + await $this.windowsService.showItemInFolder(resource); + + return true; + } + + return success; + } + }); + } + private updateTouchbarMenu(): void { if (!isMacintosh) { return; // macOS only @@ -741,4 +768,4 @@ class NativeMenubarControl extends MenubarControl { return undefined; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/files/common/workspaceWatcher.ts b/src/vs/workbench/services/files/common/workspaceWatcher.ts index 1f9049858d3..da8c120643a 100644 --- a/src/vs/workbench/services/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/services/files/common/workspaceWatcher.ts @@ -79,7 +79,7 @@ export class WorkspaceWatcher extends Disposable { localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), [{ label: localize('installNet', "Download .NET Framework 4.5"), - run: () => this.openerService.openExternal(URI.parse('https://go.microsoft.com/fwlink/?LinkId=786533')) + run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=786533')) }], { sticky: true, @@ -95,7 +95,7 @@ export class WorkspaceWatcher extends Disposable { localize('enospcError', "Unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue."), [{ label: localize('learnMore', "Instructions"), - run: () => this.openerService.openExternal(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) + run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) }], { sticky: true, diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 81f5317fe99..36c80d8c148 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -93,7 +93,7 @@ export class IntegrityServiceImpl implements IIntegrityService { [ { label: nls.localize('integrity.moreInformation', "More Information"), - run: () => this.openerService.openExternal(URI.parse(product.checksumFailMoreInfoUrl)) + run: () => this.openerService.open(URI.parse(product.checksumFailMoreInfoUrl)) }, { label: nls.localize('integrity.dontShowAgain', "Don't Show Again"), diff --git a/src/vs/workbench/services/opener/electron-browser/openerService.ts b/src/vs/workbench/services/opener/electron-browser/openerService.ts deleted file mode 100644 index d6cc8bb5bec..00000000000 --- a/src/vs/workbench/services/opener/electron-browser/openerService.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { OpenerService as BaseOpenerService } from 'vs/editor/browser/services/openerService'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IStorageService } from 'vs/platform/storage/common/storage'; - -export class OpenerService extends BaseOpenerService { - - _serviceBrand!: ServiceIdentifier; - - constructor( - @ICodeEditorService codeEditorService: ICodeEditorService, - @ICommandService commandService: ICommandService, - @IWindowsService private readonly windowsService: IWindowsService, - @IStorageService readonly storageService: IStorageService, - @IDialogService readonly dialogService: IDialogService, - @IProductService readonly productService: IProductService - ) { - super(codeEditorService, commandService, storageService, dialogService, productService); - } - - async openExternal(resource: URI): Promise { - const success = this.windowsService.openExternal(encodeURI(resource.toString(true))); - if (!success && resource.scheme === Schemas.file) { - await this.windowsService.showItemInFolder(resource); - - return true; - } - - return success; - } -} - -registerSingleton(IOpenerService, OpenerService, true); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index e58f7058329..198e68e65bb 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -96,6 +96,8 @@ import { IMenuService } from 'vs/platform/actions/common/actions'; import { MenuService } from 'vs/platform/actions/common/menuService'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadService } from 'vs/platform/download/common/downloadService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IContextViewService, ContextViewService, true); @@ -108,6 +110,7 @@ registerSingleton(IModelService, ModelServiceImpl, true); registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IMenuService, MenuService, true); registerSingleton(IDownloadService, DownloadService, true); +registerSingleton(IOpenerService, OpenerService, true); //#endregion diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7aa1334e1f4..b44e341f9c1 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -48,7 +48,6 @@ import 'vs/workbench/services/extensionManagement/node/extensionManagementServic import 'vs/workbench/services/accessibility/node/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; -import 'vs/workbench/services/opener/electron-browser/openerService'; import 'vs/workbench/services/credentials/node/credentialsService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 40384b249b0..20db7d07378 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -56,8 +56,6 @@ import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuS import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); @@ -67,7 +65,6 @@ registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(ILifecycleService, BrowserLifecycleService); registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(IOpenerService, OpenerService, true); //#endregion