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:
Matt Bierner
2021-01-07 15:07:40 -08:00
parent aa73c2d435
commit acda4aed82
8 changed files with 103 additions and 70 deletions

View File

@@ -9,9 +9,9 @@ import { toDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen';
import type * as vscode from 'vscode';
import { ExtHostUriOpenersShape, IMainContext, MainContext, MainThreadUriOpenersShape } from './extHost.protocol';
import { Cache } from './cache';
import { ChainedCacheId, ExtHostUriOpenersShape, IMainContext, MainContext, MainThreadUriOpenersShape } from './extHost.protocol';
export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
@@ -19,18 +19,16 @@ export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
private readonly _proxy: MainThreadUriOpenersShape;
private readonly _commands: ExtHostCommands;
private readonly _quickOpen: ExtHostQuickOpen;
private readonly _cache = new Cache<vscode.Command>('CodeAction');
private readonly _openers = new Map<number, { schemes: ReadonlySet<string>, opener: vscode.ExternalUriOpener }>();
constructor(
mainContext: IMainContext,
commands: ExtHostCommands,
quickOpen: ExtHostQuickOpen,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadUriOpeners);
this._commands = commands;
this._quickOpen = quickOpen;
}
registerUriOpener(
@@ -49,7 +47,7 @@ export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
});
}
async $openUri(uriComponents: UriComponents, ctx: { originalUri: UriComponents }, token: CancellationToken): Promise<boolean> {
async $getOpenersForUri(uriComponents: UriComponents, token: CancellationToken): Promise<{ cacheId: number, openers: Array<{ id: number, title: string }> }> {
const uri = URI.revive(uriComponents);
const promises = Array.from(this._openers.values()).map(async ({ schemes, opener }): Promise<vscode.Command | undefined> => {
@@ -58,9 +56,7 @@ export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
}
try {
const result = await opener.openExternalUri(uri, {
originalUri: URI.revive(ctx.originalUri),
}, token);
const result = await opener.openExternalUri(uri, {}, token);
if (result) {
return result;
@@ -71,30 +67,24 @@ export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
return undefined;
});
const results = coalesce(await Promise.all(promises));
const commands = coalesce(await Promise.all(promises));
const cacheId = this._cache.add(commands);
return {
cacheId,
openers: commands.map((command, i) => ({ title: command.title, id: i })),
};
}
if (results.length === 0) {
return false;
} else if (results.length === 1) {
const [command] = results;
await this._commands.executeCommand(command.command, ...(command.arguments ?? []));
return true;
} else {
type PickItem = vscode.QuickPickItem & { index: number };
const items = results.map((command, i): PickItem => {
return {
label: command.title,
index: i
};
});
const picked = await this._quickOpen.showQuickPick(items, false, {});
if (picked) {
const command = results[(picked as PickItem).index];
await this._commands.executeCommand(command.command, ...(command.arguments ?? []));
return true;
}
return false;
async $openUri(id: ChainedCacheId, uri: UriComponents): Promise<void> {
const command = this._cache.get(id[0], id[1]);
if (!command) {
return;
}
return this._commands.executeCommand(command.command, URI.revive(uri), ...(command.arguments || []));
}
$releaseOpener(cacheId: number): void {
this._cache.delete(cacheId);
}
}