mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 19:44:25 +01:00
Make the external opener a two phase process
This changes makes a few changes to the openers: - Move the opener prompting from the extension host into the main thread - Make the external opener process two phases: get openers and then open. This would let us skip the trusted domain validation for extension handled links if we want to in the future - Add lifecycle to commands used by the uri opener For #109277
This commit is contained in:
@@ -7,13 +7,14 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExternalOpener, IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IExternalOpener, IExternalOpenerProvider, IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ExtHostContext, ExtHostUriOpenersShape, IExtHostContext, MainContext, MainThreadUriOpenersShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadUriOpeners)
|
||||
export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpener {
|
||||
export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpenerProvider {
|
||||
|
||||
private readonly proxy: ExtHostUriOpenersShape;
|
||||
private readonly handlers = new Map<number, { schemes: ReadonlySet<string> }>();
|
||||
@@ -22,19 +23,19 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe
|
||||
context: IExtHostContext,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = context.getProxy(ExtHostContext.ExtHostUriOpeners);
|
||||
|
||||
this._register(this.openerService.registerAdditionalExternalOpener(this));
|
||||
this._register(this.openerService.registerExternalOpenerProvider(this));
|
||||
}
|
||||
|
||||
public async openExternal(href: string, originalUri: URI): Promise<boolean> {
|
||||
const targetUri = URI.parse(href);
|
||||
public async provideExternalOpener(href: string | URI): Promise<IExternalOpener | undefined> {
|
||||
const targetUri = typeof href === 'string' ? URI.parse(href) : href;
|
||||
|
||||
// Currently we only allow openers for http and https urls
|
||||
if (targetUri.scheme !== Schemas.http && targetUri.scheme !== Schemas.https) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this.extensionService.activateByEvent(`onUriOpen:${targetUri.scheme}`);
|
||||
@@ -42,12 +43,47 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe
|
||||
// If there are no handlers there is no point in making a round trip
|
||||
const hasHandler = Array.from(this.handlers.values()).some(x => x.schemes.has(targetUri.scheme));
|
||||
if (!hasHandler) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return await this.proxy.$openUri(targetUri, {
|
||||
originalUri: originalUri,
|
||||
}, CancellationToken.None);
|
||||
|
||||
const { openers, cacheId } = await this.proxy.$getOpenersForUri(targetUri, CancellationToken.None);
|
||||
if (openers.length === 0) {
|
||||
return undefined;
|
||||
} else if (openers.length === 1) {
|
||||
return this.openerForCommand(cacheId, openers[0].id);
|
||||
} else {
|
||||
type PickItem = IQuickPickItem & { index: number };
|
||||
const items = openers.map((opener, i): PickItem => {
|
||||
return {
|
||||
label: opener.title,
|
||||
index: i
|
||||
};
|
||||
});
|
||||
|
||||
const picked = await this.quickInputService.pick(items, {});
|
||||
if (picked) {
|
||||
const opener = openers[(picked as PickItem).index];
|
||||
return this.openerForCommand(cacheId, opener.id);
|
||||
}
|
||||
|
||||
this.proxy.$releaseOpener(cacheId);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private openerForCommand(cacheId: number, commandId: number): IExternalOpener {
|
||||
return {
|
||||
openExternal: async (href) => {
|
||||
const targetUri = URI.parse(href);
|
||||
try {
|
||||
await this.proxy.$openUri([cacheId, commandId], targetUri);
|
||||
} finally {
|
||||
this.proxy.$releaseOpener(cacheId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async $registerUriOpener(handle: number, schemes: readonly string[]): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user