diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index dd200cde511..073b3587f81 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IURLService } from 'vs/platform/url/common/url'; +import { IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -588,7 +588,7 @@ export class CodeApplication extends Disposable { // Create a URL handler to open file URIs in the active window const environmentService = accessor.get(IEnvironmentService); urlService.registerHandler({ - async handleURL(uri: URI): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { @@ -615,7 +615,7 @@ export class CodeApplication extends Disposable { // if there is none if (isMacintosh) { urlService.registerHandler({ - async handleURL(uri: URI): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (windowsMainService.getWindowCount() === 0) { const cli = { ...environmentService.args }; const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true }); diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index 479bc0bca73..097a2398e9b 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -9,8 +9,19 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IURLService = createDecorator('urlService'); +export interface IOpenURLOptions { + + /** + * If not provided or `false`, signals that the + * URL to open did not originate from the product + * but outside. As such, a confirmation dialog + * might be shown to the user. + */ + trusted?: boolean; +} + export interface IURLHandler { - handleURL(uri: URI): Promise; + handleURL(uri: URI, options?: IOpenURLOptions): Promise; } export interface IURLService { @@ -24,7 +35,7 @@ export interface IURLService { */ create(options?: Partial): URI; - open(url: URI): Promise; + open(url: URI, options?: IOpenURLOptions): Promise; registerHandler(handler: IURLHandler): IDisposable; } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 84396c2400e..88a3d20e1d0 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -6,7 +6,7 @@ import { IChannel, IServerChannel, IClientRouter, IConnectionHub, Client } from 'vs/base/parts/ipc/common/ipc'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; -import { IURLHandler } from 'vs/platform/url/common/url'; +import { IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { CancellationToken } from 'vs/base/common/cancellation'; import { first } from 'vs/base/common/arrays'; @@ -31,7 +31,7 @@ export class URLHandlerChannelClient implements IURLHandler { constructor(private channel: IChannel) { } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { return this.channel.call('handleURL', uri.toJSON()); } } diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index 322929c723d..030d52b587e 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/async'; @@ -17,9 +17,9 @@ export abstract class AbstractURLService extends Disposable implements IURLServi abstract create(options?: Partial): URI; - open(uri: URI): Promise { + open(uri: URI, options?: IOpenURLOptions): Promise { const handlers = values(this.handlers); - return first(handlers.map(h => () => h.handleURL(uri)), undefined, false).then(val => val || false); + return first(handlers.map(h => () => h.handleURL(uri, options)), undefined, false).then(val => val || false); } registerHandler(handler: IURLHandler): IDisposable { diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index c5f54274e72..a89f291d6c2 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -5,7 +5,7 @@ import { ExtHostContext, IExtHostContext, MainContext, MainThreadUrlsShape, ExtHostUrlsShape } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/browser/extensionUrlHandler'; @@ -19,7 +19,7 @@ class ExtensionUrlHandler implements IURLHandler { readonly extensionId: ExtensionIdentifier ) { } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!ExtensionIdentifier.equals(this.extensionId, uri.authority)) { return Promise.resolve(false); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 087dd63f6f8..2f7add6821a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -24,7 +24,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -1048,7 +1048,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.notificationService.error(err); } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!/^extension/.test(uri.path)) { return Promise.resolve(false); } diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 4ba68ecd95b..10c3edf2150 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -35,7 +35,7 @@ export class OpenUrlAction extends Action { run(): Promise { return this.quickInputService.input({ prompt: 'URL to open' }).then(input => { const uri = URI.parse(input); - this.urlService.open(uri); + this.urlService.open(uri, { trusted: true }); }); } } diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index c969bd3df55..62d36c968e1 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; +import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -74,7 +74,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); if (urlToHandleValue) { this.storageService.remove(URL_TO_HANDLE, StorageScope.WORKSPACE); - this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true); + this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), { trusted: true }); } this.disposable = combinedDisposable( @@ -86,7 +86,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { setTimeout(() => cache.forEach(uri => this.handleURL(uri))); } - async handleURL(uri: URI, confirmed?: boolean): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!isExtensionId(uri.authority)) { return false; } @@ -100,12 +100,15 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return true; } - if (!confirmed) { + let showConfirm: boolean; + if (options && options.trusted) { + showConfirm = false; + } else { const confirmedExtensionIds = this.getConfirmedExtensionIds(); - confirmed = confirmedExtensionIds.has(ExtensionIdentifier.toKey(extensionId)); + showConfirm = !confirmedExtensionIds.has(ExtensionIdentifier.toKey(extensionId)); } - if (!confirmed) { + if (showConfirm) { let uriString = uri.toString(); if (uriString.length > 40) { @@ -136,7 +139,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { if (handler) { if (!wasHandlerAvailable) { // forward it directly - return await handler.handleURL(uri); + return await handler.handleURL(uri, options); } // let the ExtensionUrlHandler instance handle this @@ -343,7 +346,7 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle ExtensionUrlBootstrapHandler.disposable = urlService.registerHandler(this); } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { ExtensionUrlBootstrapHandler._cache.push(uri); return Promise.resolve(true); } diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts index b073447fc26..eef5070c450 100644 --- a/src/vs/workbench/services/url/browser/urlService.ts +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -54,7 +54,7 @@ export class BrowserURLService extends AbstractURLService { private registerListeners(): void { if (this.provider) { - this._register(this.provider.onCallback(uri => this.open(uri))); + this._register(this.provider.onCallback(uri => this.open(uri, { trusted: true }))); } } diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index 319de3fbd12..73fa15e1f8e 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; @@ -15,6 +15,11 @@ import { IElectronEnvironmentService } from 'vs/workbench/services/electron/elec import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { IElectronService } from 'vs/platform/electron/node/electron'; +export interface IRelayOpenURLOptions extends IOpenURLOptions { + openToSide?: boolean; + openExternal?: boolean; +} + export class RelayURLService extends URLService implements IURLHandler { private urlService: IURLService; @@ -46,16 +51,16 @@ export class RelayURLService extends URLService implements IURLHandler { return uri.with({ query }); } - async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + async open(resource: URI, options?: IRelayOpenURLOptions): Promise { if (resource.scheme !== product.urlProtocol) { return false; } - return await this.urlService.open(resource); + return await this.urlService.open(resource, options); } - async handleURL(uri: URI): Promise { - const result = await super.open(uri); + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + const result = await super.open(uri, options); if (result) { await this.electronService.focusWindow();