提交 14210747 编写于 作者: J Joao Moreno

inactive extension url handler

上级 9c4ad2db
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
function isExtensionId(value: string): boolean {
return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
}
export const IInactiveExtensionUrlHandler = createDecorator<IInactiveExtensionUrlHandler>('inactiveExtensionUrlHandler');
export interface IInactiveExtensionUrlHandler {
readonly _serviceBrand: any;
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: string): void;
}
/**
* This class handles URLs which are directed towards inactive extensions.
* If a URL is directed towards an inactive extension, it buffers it,
* activates the extension and re-opens the URL once the extension registers
* a URL handler. If the extension never registers a URL handler, the urls
* will eventually be garbage collected.
*/
export class InactiveExtensionUrlHandler implements IInactiveExtensionUrlHandler, IURLHandler {
readonly _serviceBrand: any;
private extensionIds = new Set<string>();
private uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
private disposable: IDisposable;
constructor(
@IURLService urlService: IURLService,
@IExtensionService private extensionService: IExtensionService
) {
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
this.disposable = combinedDisposable([
urlService.registerHandler(this),
toDisposable(() => clearInterval(interval))
]);
}
handleURL(uri: URI): TPromise<boolean> {
if (!isExtensionId(uri.authority)) {
return TPromise.as(false);
}
const extensionId = uri.authority;
// let the ExtensionUrlHandler instance handle this
if (this.extensionIds.has(extensionId)) {
return TPromise.as(false);
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(extensionId);
if (!uris) {
uris = [];
this.uriBuffer.set(extensionId, uris);
}
uris.push({ timestamp, uri });
// activate the extension
return this.extensionService.activateByEvent(`onExternalUri:${extensionId}`)
.then(() => true);
}
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
this.extensionIds.add(extensionId);
const uris = this.uriBuffer.get(extensionId) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
}
this.uriBuffer.delete(extensionId);
}
unregisterExtensionHandler(extensionId: string): void {
this.extensionIds.delete(extensionId);
}
// forget about all uris buffered more than 5 minutes ago
private garbageCollect(): void {
console.log('garbage collect');
const now = new Date().getTime();
const uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
this.uriBuffer.forEach((uris, extensionId) => {
uris = uris.filter(({ timestamp }) => now - timestamp < FIVE_MINUTES);
if (uris.length > 0) {
uriBuffer.set(extensionId, uris);
}
});
this.uriBuffer = uriBuffer;
}
dispose(): void {
this.disposable.dispose();
this.extensionIds.clear();
this.uriBuffer.clear();
}
}
\ No newline at end of file
......@@ -9,13 +9,14 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IInactiveExtensionUrlHandler } from 'vs/platform/url/electron-browser/inactiveExtensionUrlHandler';
class ExtensionUrlHandler implements IURLHandler {
constructor(
private readonly proxy: ExtHostUrlsShape,
private readonly handle: number,
private readonly extensionId: string
readonly extensionId: string
) { }
handleURL(uri: URI): TPromise<boolean> {
......@@ -31,12 +32,12 @@ class ExtensionUrlHandler implements IURLHandler {
export class MainThreadUrls implements MainThreadUrlsShape {
private readonly proxy: ExtHostUrlsShape;
private handlers = new Map<number, IDisposable>();
private handlers = new Map<number, { extensionId: string, disposable: IDisposable }>();
constructor(
context: IExtHostContext,
@IURLService private urlService: IURLService
@IURLService private urlService: IURLService,
@IInactiveExtensionUrlHandler private inactiveExtensionUrlHandler: IInactiveExtensionUrlHandler
) {
this.proxy = context.getProxy(ExtHostContext.ExtHostUrls);
}
......@@ -44,25 +45,31 @@ export class MainThreadUrls implements MainThreadUrlsShape {
$registerExternalUriHandler(handle: number, extensionId: string): TPromise<void> {
const handler = new ExtensionUrlHandler(this.proxy, handle, extensionId);
const disposable = this.urlService.registerHandler(handler);
this.handlers.set(handle, disposable);
this.handlers.set(handle, { extensionId, disposable });
this.inactiveExtensionUrlHandler.registerExtensionHandler(extensionId, handler);
return TPromise.as(null);
}
$unregisterExternalUriHandler(handle: number): TPromise<void> {
const disposable = this.handlers.get(handle);
const tuple = this.handlers.get(handle);
if (!disposable) {
if (!tuple) {
return TPromise.as(null);
}
disposable.dispose();
const { extensionId, disposable } = tuple;
this.inactiveExtensionUrlHandler.unregisterExtensionHandler(extensionId);
this.handlers.delete(handle);
disposable.dispose();
return TPromise.as(null);
}
dispose(): void {
this.handlers.forEach(({ disposable }) => disposable.dispose());
this.handlers.clear();
}
}
......@@ -110,6 +110,7 @@ import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService';
import { IInactiveExtensionUrlHandler, InactiveExtensionUrlHandler } from 'vs/platform/url/electron-browser/inactiveExtensionUrlHandler';
export const EditorsVisibleContext = new RawContextKey<boolean>('editorIsOpen', false);
export const InZenModeContext = new RawContextKey<boolean>('inZenMode', false);
......@@ -616,6 +617,9 @@ export class Workbench implements IPartService {
// SCM Service
serviceCollection.set(ISCMService, new SyncDescriptor(SCMService));
// Inactive extension URL handler
serviceCollection.set(IInactiveExtensionUrlHandler, new SyncDescriptor(InactiveExtensionUrlHandler));
// Text Model Resolver Service
serviceCollection.set(ITextModelService, new SyncDescriptor(TextModelResolverService));
......
......@@ -33,6 +33,12 @@ export class ExtensionDescriptionRegistry {
if (Array.isArray(extensionDescription.activationEvents)) {
for (let j = 0, lenJ = extensionDescription.activationEvents.length; j < lenJ; j++) {
let activationEvent = extensionDescription.activationEvents[j];
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onExternalUri') {
activationEvent = `onExternalUri:${extensionDescription.id}`;
}
this._activationMap[activationEvent] = this._activationMap[activationEvent] || [];
this._activationMap[activationEvent].push(extensionDescription);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册