Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -8,35 +8,20 @@ import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionIdentifier, IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
export interface IExtensionDescription {
readonly id: string;
readonly name: string;
export interface IExtensionDescription extends IExtensionManifest {
readonly identifier: ExtensionIdentifier;
readonly uuid?: string;
readonly displayName?: string;
readonly version: string;
readonly publisher: string;
readonly isBuiltin: boolean;
readonly isUnderDevelopment: boolean;
readonly extensionLocation: URI;
readonly extensionDependencies?: string[];
readonly activationEvents?: string[];
readonly engines: {
vscode: string;
// {{SQL CARBON EDIT}}
azdata?: string;
};
readonly main?: string;
readonly contributes?: { [point: string]: any; };
readonly keywords?: string[];
readonly repository?: {
url: string;
};
enableProposedApi?: boolean;
}
export const nullExtensionDescription = Object.freeze(<IExtensionDescription>{
id: 'nullExtensionDescription',
identifier: new ExtensionIdentifier('nullExtensionDescription'),
name: 'Null Extension Description',
version: '0.0.0',
publisher: 'vscode',
@@ -51,7 +36,7 @@ export const IExtensionService = createDecorator<IExtensionService>('extensionSe
export interface IMessage {
type: Severity;
message: string;
extensionId: string;
extensionId: ExtensionIdentifier;
extensionPointId: string;
}
@@ -104,7 +89,7 @@ export interface IExtensionHostProfile {
/**
* Extension id or one of the four known program states.
*/
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self' | null;
export class ActivationTimes {
constructor(
@@ -131,7 +116,7 @@ export const ExtensionHostLogFileName = 'exthost';
export interface IWillActivateEvent {
readonly event: string;
readonly activation: Thenable<void>;
readonly activation: Promise<void>;
}
export interface IResponsiveStateChangeEvent {
@@ -156,7 +141,12 @@ export interface IExtensionService extends ICpuProfilerTarget {
* Fired when extensions status changes.
* The event contains the ids of the extensions that have changed.
*/
onDidChangeExtensionsStatus: Event<string[]>;
onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;
/**
* Fired when the available extensions change (i.e. when extensions are added or removed).
*/
onDidChangeExtensions: Event<void>;
/**
* An event that is fired when activation happens.
@@ -172,7 +162,7 @@ export interface IExtensionService extends ICpuProfilerTarget {
/**
* Send an activation event and activate interested extensions.
*/
activateByEvent(activationEvent: string): Thenable<void>;
activateByEvent(activationEvent: string): Promise<void>;
/**
* An promise that resolves when the installed extensions are registered after
@@ -191,6 +181,18 @@ export interface IExtensionService extends ICpuProfilerTarget {
*/
getExtension(id: string): Promise<IExtensionDescription | undefined>;
/**
* Returns `true` if the given extension can be added. Otherwise `false`.
* @param extension An extension
*/
canAddExtension(extension: IExtensionDescription): boolean;
/**
* Returns `true` if the given extension can be removed. Otherwise `false`.
* @param extension An extension
*/
canRemoveExtension(extension: IExtensionDescription): boolean;
/**
* Read all contributions to an extension point.
*/
@@ -246,5 +248,14 @@ export function checkProposedApiEnabled(extension: IExtensionDescription): void
}
export function throwProposedApiError(extension: IExtensionDescription): never {
throw new Error(`[${extension.id}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.id}`);
throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
}
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
return {
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
identifier: { id: getGalleryExtensionId(extensionDescription.publisher, extensionDescription.name), uuid: extensionDescription.uuid },
manifest: extensionDescription,
location: extensionDescription.extensionLocation,
};
}

View File

@@ -11,6 +11,7 @@ import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/co
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionDescription, IMessage } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
@@ -35,7 +36,7 @@ export class ExtensionMessageCollector {
this._messageHandler({
type: type,
message: message,
extensionId: this._extension.id,
extensionId: this._extension.identifier,
extensionPointId: this._extensionPointId
});
}
@@ -60,7 +61,7 @@ export interface IExtensionPointUser<T> {
}
export interface IExtensionPointHandler<T> {
(extensions: IExtensionPointUser<T>[]): void;
(extensions: IExtensionPointUser<T>[], delta: ExtensionPointUserDelta<T>): void;
}
export interface IExtensionPoint<T> {
@@ -68,22 +69,60 @@ export interface IExtensionPoint<T> {
setHandler(handler: IExtensionPointHandler<T>): void;
}
export class ExtensionPointUserDelta<T> {
private static _toSet<T>(arr: IExtensionPointUser<T>[]): Set<string> {
const result = new Set<string>();
for (let i = 0, len = arr.length; i < len; i++) {
result.add(ExtensionIdentifier.toKey(arr[i].description.identifier));
}
return result;
}
public static compute<T>(previous: IExtensionPointUser<T>[] | null, current: IExtensionPointUser<T>[]): ExtensionPointUserDelta<T> {
if (!previous || !previous.length) {
return new ExtensionPointUserDelta<T>(current, []);
}
if (!current || !current.length) {
return new ExtensionPointUserDelta<T>([], previous);
}
const previousSet = this._toSet(previous);
const currentSet = this._toSet(current);
let added = current.filter(user => !previousSet.has(ExtensionIdentifier.toKey(user.description.identifier)));
let removed = previous.filter(user => !currentSet.has(ExtensionIdentifier.toKey(user.description.identifier)));
return new ExtensionPointUserDelta<T>(added, removed);
}
constructor(
public readonly added: IExtensionPointUser<T>[],
public readonly removed: IExtensionPointUser<T>[],
) { }
}
export class ExtensionPoint<T> implements IExtensionPoint<T> {
public readonly name: string;
private _handler: IExtensionPointHandler<T> | null;
private _users: IExtensionPointUser<T>[] | null;
private _done: boolean;
public readonly isDynamic: boolean;
constructor(name: string) {
private _handler: IExtensionPointHandler<T> | null;
private _handlerCalled: boolean;
private _users: IExtensionPointUser<T>[] | null;
private _delta: ExtensionPointUserDelta<T> | null;
constructor(name: string, isDynamic: boolean) {
this.name = name;
this.isDynamic = isDynamic;
this._handler = null;
this._handlerCalled = false;
this._users = null;
this._done = false;
this._delta = null;
}
setHandler(handler: IExtensionPointHandler<T>): void {
if (this._handler !== null || this._done) {
if (this._handler !== null) {
throw new Error('Handler already set!');
}
this._handler = handler;
@@ -91,27 +130,23 @@ export class ExtensionPoint<T> implements IExtensionPoint<T> {
}
acceptUsers(users: IExtensionPointUser<T>[]): void {
if (this._users !== null || this._done) {
throw new Error('Users already set!');
}
this._delta = ExtensionPointUserDelta.compute(this._users, users);
this._users = users;
this._handle();
}
private _handle(): void {
if (this._handler === null || this._users === null) {
if (this._handler === null || this._users === null || this._delta === null) {
return;
}
this._done = true;
let handler = this._handler;
this._handler = null;
let users = this._users;
this._users = null;
if (this._handlerCalled && !this.isDynamic) {
throw new Error('The extension point is not dynamic!');
}
try {
handler(users);
this._handlerCalled = true;
this._handler(this._users, this._delta);
} catch (err) {
onUnexpectedError(err);
}
@@ -331,6 +366,13 @@ export const schema = {
}
};
export interface IExtensionPointDescriptor {
isDynamic?: boolean;
extensionPoint: string;
deps?: IExtensionPoint<any>[];
jsonSchema: IJSONSchema;
}
export class ExtensionsRegistryImpl {
private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; };
@@ -339,14 +381,14 @@ export class ExtensionsRegistryImpl {
this._extensionPoints = {};
}
public registerExtensionPoint<T>(extensionPoint: string, deps: IExtensionPoint<any>[], jsonSchema: IJSONSchema): IExtensionPoint<T> {
if (hasOwnProperty.call(this._extensionPoints, extensionPoint)) {
throw new Error('Duplicate extension point: ' + extensionPoint);
public registerExtensionPoint<T>(desc: IExtensionPointDescriptor): IExtensionPoint<T> {
if (hasOwnProperty.call(this._extensionPoints, desc.extensionPoint)) {
throw new Error('Duplicate extension point: ' + desc.extensionPoint);
}
let result = new ExtensionPoint<T>(extensionPoint);
this._extensionPoints[extensionPoint] = result;
let result = new ExtensionPoint<T>(desc.extensionPoint, desc.isDynamic || false);
this._extensionPoints[desc.extensionPoint] = result;
schema.properties['contributes'].properties[extensionPoint] = jsonSchema;
schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema;
schemaRegistry.registerSchema(schemaId, schema);
return result;
@@ -355,6 +397,10 @@ export class ExtensionsRegistryImpl {
public getExtensionPoints(): ExtensionPoint<any>[] {
return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]);
}
public getExtensionPointsMap(): { [extPoint: string]: ExtensionPoint<any>; } {
return this._extensionPoints;
}
}
const PRExtensions = {

View File

@@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -50,6 +50,7 @@ export class CachedExtensionScanner {
public readonly scannedExtensions: Promise<IExtensionDescription[]>;
private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void;
private _scannedExtensionsReject: (err: any) => void;
public readonly translationConfig: Promise<Translations>;
constructor(
@INotificationService private readonly _notificationService: INotificationService,
@@ -61,31 +62,58 @@ export class CachedExtensionScanner {
this._scannedExtensionsResolve = resolve;
this._scannedExtensionsReject = reject;
});
this.translationConfig = CachedExtensionScanner._readTranslationConfig();
}
public startScanningExtensions(log: ILog): void {
CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log)
.then(({ system, user, development }) => {
let result: { [extensionId: string]: IExtensionDescription; } = {};
system.forEach((systemExtension) => {
result[systemExtension.id] = systemExtension;
});
user.forEach((userExtension) => {
if (result.hasOwnProperty(userExtension.id)) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result[userExtension.id] = userExtension;
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
if (result.hasOwnProperty(developedExtension.id)) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result[developedExtension.id] = developedExtension;
});
return Object.keys(result).map(name => result[name]);
})
.then(this._scannedExtensionsResolve, this._scannedExtensionsReject);
public async scanSingleExtension(path: string, isBuiltin: boolean, log: ILog): Promise<IExtensionDescription | null> {
const translations = await this.translationConfig;
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations);
return ExtensionScanner.scanSingleExtension(input, log);
}
public async startScanningExtensions(log: ILog): Promise<void> {
try {
const translations = await this.translationConfig;
const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations);
let result = new Map<string, IExtensionDescription>();
system.forEach((systemExtension) => {
const extensionKey = ExtensionIdentifier.toKey(systemExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(systemExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, systemExtension.extensionLocation.fsPath));
}
result.set(extensionKey, systemExtension);
});
user.forEach((userExtension) => {
const extensionKey = ExtensionIdentifier.toKey(userExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result.set(extensionKey, userExtension);
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
const extensionKey = ExtensionIdentifier.toKey(developedExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result.set(extensionKey, developedExtension);
});
let r: IExtensionDescription[] = [];
result.forEach((value) => r.push(value));
this._scannedExtensionsResolve(r);
} catch (err) {
this._scannedExtensionsReject(err);
}
}
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
@@ -198,86 +226,90 @@ export class CachedExtensionScanner {
return result;
}
private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, log: ILog): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const translationConfig: Promise<Translations> = platform.translationsConfigFile
? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => {
try {
return JSON.parse(content) as Translations;
} catch (err) {
return Object.create(null);
}
}, (err) => {
return Object.create(null);
})
: Promise.resolve(Object.create(null));
return translationConfig.then((translations) => {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
private static async _readTranslationConfig(): Promise<Translations> {
if (platform.translationsConfigFile) {
try {
const content = await pfs.readFile(platform.translationsConfigFile, 'utf8');
return JSON.parse(content) as Translations;
} catch (err) {
// no problemo
}
}
return Object.create(null);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
private static _scanInstalledExtensions(
windowService: IWindowService,
notificationService: INotificationService,
environmentService: IEnvironmentService,
extensionEnablementService: IExtensionEnablementService,
log: ILog,
translations: Translations
): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
);
// Always load developed extensions while extensions development
let developedExtensions: Promise<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
// Always load developed extensions while extensions development
let developedExtensions: Promise<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(null, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(undefined, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
}
}

View File

@@ -10,8 +10,7 @@ import { Server, Socket, createServer } from 'net';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { timeout } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event, anyEvent, debounceEvent, fromNodeEventEmitter, mapEvent } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
@@ -21,9 +20,8 @@ import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
import { Protocol, generateRandomPipeName, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net';
import { IBroadcast, IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService';
import { getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -34,15 +32,14 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IConfigurationInitData, IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
export interface IExtensionHostStarter {
readonly onCrashed: Event<[number, string]>;
start(): Thenable<IMessagePassingProtocol>;
start(): Promise<IMessagePassingProtocol>;
getInspectPort(): number;
dispose(): void;
}
@@ -103,7 +100,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
@IBroadcastService private readonly _broadcastService: IBroadcastService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
@ILogService private readonly _logService: ILogService,
@@ -176,7 +172,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
const opts = {
env: objects.mixin(objects.deepClone(process.env), {
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: true,
VSCODE_IPC_HOOK_EXTHOST: pipeName,
@@ -217,15 +213,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
type Output = { data: string, format: string[] };
this._extensionHostProcess.stdout.setEncoding('utf8');
this._extensionHostProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
const onStdout = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
const onStderr = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
const onOutput = Event.any(
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
const onDebouncedOutput = Event.debounce<Output>(onOutput, (r, o) => {
return r
? { data: r.data + o.data, format: [...r.format, ...o.format] }
: { data: o.data, format: o.format };
@@ -356,7 +352,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._namedPipeServer.close();
this._namedPipeServer = null;
this._extensionHostConnection = socket;
resolve(new Protocol(this._extensionHostConnection));
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new BufferedProtocol(new Protocol(this._extensionHostConnection)));
});
}).then((protocol) => {
@@ -402,10 +402,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
disposable.dispose();
// release this promise
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new BufferedMessagePassingProtocol(protocol));
resolve(protocol);
return;
}
@@ -420,15 +417,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private _createExtHostInitData(): Promise<IInitData> {
return Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions])
.then(([telemetryInfo, extensionDescriptions]) => {
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
parentPid: process.pid,
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : void 0,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : void 0,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsPath: this._environmentService.extensionTestsPath,
globalStorageHome: URI.file(this._environmentService.globalStorageHome)
@@ -439,9 +435,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
resolvedExtensions: [],
extensions: extensionDescriptions,
// Send configurations scopes only in development mode.
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: this._extensionHostLogsLocation,
@@ -574,57 +569,3 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
}
}
}
/**
* Will ensure no messages are lost from creation time until the first user of onMessage comes in.
*/
class BufferedMessagePassingProtocol implements IMessagePassingProtocol {
private readonly _actual: IMessagePassingProtocol;
private _bufferedMessagesListener: IDisposable;
private _bufferedMessages: Buffer[];
constructor(actual: IMessagePassingProtocol) {
this._actual = actual;
this._bufferedMessages = [];
this._bufferedMessagesListener = this._actual.onMessage((buff) => this._bufferedMessages.push(buff));
}
public send(buffer: Buffer): void {
this._actual.send(buffer);
}
public onMessage(listener: (e: Buffer) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable {
if (!this._bufferedMessages) {
// second caller gets nothing
return this._actual.onMessage(listener, thisArgs, disposables);
}
// prepare result
const result = this._actual.onMessage(listener, thisArgs, disposables);
// stop listening to buffered messages
this._bufferedMessagesListener.dispose();
// capture buffered messages
const bufferedMessages = this._bufferedMessages;
this._bufferedMessages = null;
// it is important to deliver these messages after this call, but before
// other messages have a chance to be received (to guarantee in order delivery)
// that's why we're using here nextTick and not other types of timeouts
process.nextTick(() => {
// deliver buffered messages
while (bufferedMessages.length > 0) {
const msg = bufferedMessages.shift();
try {
listener.call(thisArgs, msg);
} catch (e) {
onUnexpectedError(e);
}
}
});
return result;
}
}

View File

@@ -12,18 +12,27 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
import { ProfileSession, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledResourceInput } from 'vs/workbench/common/editor';
import { StopWatch } from 'vs/base/common/stopwatch';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export class ExtensionHostProcessManager extends Disposable {
@@ -42,7 +51,7 @@ export class ExtensionHostProcessManager extends Disposable {
/**
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
*/
private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>;
private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; }>;
constructor(
extensionHostProcessWorker: IExtensionHostStarter,
@@ -70,6 +79,9 @@ export class ExtensionHostProcessManager extends Disposable {
);
this._extensionHostProcessProxy.then(() => {
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
this._register(registerLatencyTestProvider({
measure: () => this.measure()
}));
});
}
@@ -93,12 +105,63 @@ export class ExtensionHostProcessManager extends Disposable {
super.dispose();
}
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}} - Add new getExtensionHostProcessWorker method
public getExtenstionHostProcessWorker(): IExtensionHostStarter {
return this._extensionHostProcessWorker;
}
// {{SQL CARBON EDIT}} - End
private async measure(): Promise<ExtHostLatencyResult> {
const latency = await this._measureLatency();
const down = await this._measureDown();
const up = await this._measureUp();
return {
remoteAuthority: this._remoteAuthority,
latency,
down,
up
};
}
private async _measureLatency(): Promise<number> {
const COUNT = 10;
const { value: proxy } = await this._extensionHostProcessProxy;
let sum = 0;
for (let i = 0; i < COUNT; i++) {
const sw = StopWatch.create(true);
await proxy.$test_latency(i);
sw.stop();
sum += sw.elapsed();
}
return (sum / COUNT);
}
private static _convert(byteCount: number, elapsedMillis: number): number {
return (byteCount * 1000 * 8) / elapsedMillis;
}
private async _measureUp(): Promise<number> {
const SIZE = 10 * 1024 * 1024; // 10MB
const { value: proxy } = await this._extensionHostProcessProxy;
let b = Buffer.alloc(SIZE, Math.random() % 256);
const sw = StopWatch.create(true);
await proxy.$test_up(b);
sw.stop();
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
}
private async _measureDown(): Promise<number> {
const SIZE = 10 * 1024 * 1024; // 10MB
const { value: proxy } = await this._extensionHostProcessProxy;
const sw = StopWatch.create(true);
await proxy.$test_down(SIZE);
sw.stop();
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
}
public canProfileExtensionHost(): boolean {
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
}
@@ -130,8 +193,7 @@ export class ExtensionHostProcessManager extends Disposable {
// Customers
const customers = ExtHostCustomersRegistry.getCustomers();
for (let i = 0, len = customers.length; i < len; i++) {
const ctor = customers[i];
for (const ctor of customers) {
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
}
@@ -143,7 +205,13 @@ export class ExtensionHostProcessManager extends Disposable {
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
}
public activateByEvent(activationEvent: string): Thenable<void> {
public activate(extension: ExtensionIdentifier, activationEvent: string): Promise<void> {
return this._extensionHostProcessProxy.then((proxy) => {
return proxy.value.$activate(extension, activationEvent);
});
}
public activateByEvent(activationEvent: string): Promise<void> {
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
return NO_OP_VOID_PROMISE;
}
@@ -179,13 +247,28 @@ export class ExtensionHostProcessManager extends Disposable {
return 0;
}
public resolveAuthority(remoteAuthority: string): Thenable<ResolvedAuthority> {
public resolveAuthority(remoteAuthority: string): Promise<ResolvedAuthority> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
// This authority does not need to be resolved, simply parse the port number
const pieces = remoteAuthority.split(':');
return Promise.resolve({
authority: remoteAuthority,
host: pieces[0],
port: parseInt(pieces[1], 10),
syncExtensions: false
});
}
return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority));
}
public start(enabledExtensionIds: string[]): Thenable<void> {
public start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds));
}
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$deltaExtensions(toAdd, toRemove));
}
}
const colorTables = [
@@ -230,7 +313,7 @@ class RPCLogger implements IRPCProtocolLogger {
} else {
args.push(data);
}
console.log.apply(console, args);
console.log.apply(console, args as [string, ...string[]]);
}
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
@@ -243,3 +326,68 @@ class RPCLogger implements IRPCProtocolLogger {
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
}
}
interface ExtHostLatencyResult {
remoteAuthority: string;
up: number;
down: number;
latency: number;
}
interface ExtHostLatencyProvider {
measure(): Promise<ExtHostLatencyResult>;
}
let providers: ExtHostLatencyProvider[] = [];
function registerLatencyTestProvider(provider: ExtHostLatencyProvider): IDisposable {
providers.push(provider);
return {
dispose: () => {
for (let i = 0; i < providers.length; i++) {
if (providers[i] === provider) {
providers.splice(i, 1);
return;
}
}
}
};
}
function getLatencyTestProviders(): ExtHostLatencyProvider[] {
return providers.slice(0);
}
export class MeasureExtHostLatencyAction extends Action {
public static readonly ID = 'editor.action.measureExtHostLatency';
public static readonly LABEL = nls.localize('measureExtHostLatency', "Measure Extension Host Latency");
constructor(
id: string,
label: string,
@IEditorService private readonly _editorService: IEditorService
) {
super(id, label);
}
public async run(): Promise<any> {
const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure()));
this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledResourceInput);
}
private static _print(m: ExtHostLatencyResult): string {
return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`;
}
private static _printSpeed(n: number): string {
if (n <= 1024) {
return `${n} bps`;
}
if (n < 1024 * 1024) {
return `${(n / 1024).toFixed(1)} kbps`;
}
return `${(n / 1024 / 1024).toFixed(1)} Mbps`;
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer"));

View File

@@ -15,7 +15,7 @@ export class ExtensionHostProfiler {
public async start(): Promise<ProfileSession> {
const profiler = await import('v8-inspect-profiler');
const session = await profiler.startProfiling({ port: this._port });
const session = await profiler.startProfiling({ port: this._port, checkForPaused: true });
return {
stop: async () => {
const profile = await session.stop();
@@ -56,26 +56,29 @@ export class ExtensionHostProfiler {
} else if (segmentId === 'self' && node.callFrame.url) {
let extension = searchTree.findSubstr(node.callFrame.url);
if (extension) {
segmentId = extension.id;
segmentId = extension.identifier.value;
}
}
idsToSegmentId.set(node.id, segmentId);
if (node.children) {
for (let child of node.children) {
visit(idsToNodes.get(child), segmentId);
for (const child of node.children) {
const childNode = idsToNodes.get(child);
if (childNode) {
visit(childNode, segmentId);
}
}
}
}
visit(nodes[0], null);
let samples = profile.samples;
let timeDeltas = profile.timeDeltas;
const samples = profile.samples || [];
let timeDeltas = profile.timeDeltas || [];
let distilledDeltas: number[] = [];
let distilledIds: ProfileSegmentId[] = [];
let currSegmentTime = 0;
let currSegmentId: string = void 0;
let currSegmentId: string | undefined;
for (let i = 0; i < samples.length; i++) {
let id = samples[i];
let segmentId = idsToSegmentId.get(id);
@@ -84,7 +87,7 @@ export class ExtensionHostProfiler {
distilledIds.push(currSegmentId);
distilledDeltas.push(currSegmentTime);
}
currSegmentId = segmentId;
currSegmentId = segmentId || undefined;
currSegmentTime = 0;
}
currSegmentTime += timeDeltas[i];
@@ -93,9 +96,6 @@ export class ExtensionHostProfiler {
distilledIds.push(currSegmentId);
distilledDeltas.push(currSegmentTime);
}
idsToNodes = null;
idsToSegmentId = null;
searchTree = null;
return {
startTime: profile.startTime,

View File

@@ -13,8 +13,8 @@ import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/node/package';
@@ -22,19 +22,40 @@ import product from 'vs/platform/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent } from 'vs/workbench/services/extensions/common/extensions';
import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager';
import { ExtensionIdentifier, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
let productAllowProposedApi: Set<string> = null;
function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
// create set if needed
if (productAllowProposedApi === null) {
productAllowProposedApi = new Set<string>();
if (isNonEmptyArray(product.extensionAllowedProposedApi)) {
product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
}
}
return productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
}
class DeltaExtensionsQueueItem {
constructor(
public readonly toAdd: IExtension[],
public readonly toRemove: string[]
) { }
}
export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
@@ -43,15 +64,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
private _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
private readonly _isDev: boolean;
private readonly _extensionsMessages: { [id: string]: IMessage[] };
private readonly _extensionsMessages: Map<string, IMessage[]>;
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
private readonly _extensionScanner: CachedExtensionScanner;
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>({ leakWarningThreshold: 500 }));
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
private readonly _onDidChangeExtensionsStatus: Emitter<string[]> = this._register(new Emitter<string[]>());
public readonly onDidChangeExtensionsStatus: Event<string[]> = this._onDidChangeExtensionsStatus.event;
private readonly _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
private readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
@@ -61,8 +86,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
// --- Members used per extension host process
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; };
private _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@@ -70,22 +96,24 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService
@ILifecycleService private readonly _lifecycleService: ILifecycleService
) {
super();
this._extensionHostLogsLocation = URI.file(path.posix.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`));
this._registry = null;
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = {};
this._extensionsMessages = new Map<string, IMessage[]>();
this._allRequestedActivateEvents = Object.create(null);
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
this._deltaExtensionsQueue = [];
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
this._startDelayed(this._lifecycleService);
@@ -97,6 +125,255 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}]);
}
this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
for (const extension of extensions) {
if (this._extensionEnablementService.isEnabled(extension)) {
// an extension has been enabled
toAdd.push(extension);
} else {
// an extension has been disabled
toRemove.push(extension.identifier.id);
}
}
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
});
this._extensionManagementService.onDidInstallExtension((event) => {
if (event.local) {
if (this._extensionEnablementService.isEnabled(event.local)) {
// an extension has been installed
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
}
}
});
this._extensionManagementService.onDidUninstallExtension((event) => {
if (!event.error) {
// an extension has been uninstalled
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}
});
}
private _inHandleDeltaExtensions = false;
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
this._deltaExtensionsQueue.push(item);
if (this._inHandleDeltaExtensions) {
// Let the current item finish, the new one will be picked up
return;
}
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift();
try {
this._inHandleDeltaExtensions = true;
await this._deltaExtensions(item.toAdd, item.toRemove);
} finally {
this._inHandleDeltaExtensions = false;
}
}
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
if (this._windowService.getConfiguration().remoteAuthority) {
return;
}
let toAdd: IExtensionDescription[] = [];
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
if (extension.location.scheme !== Schemas.file) {
continue;
}
const existingExtensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (existingExtensionDescription) {
// this extension is already running (most likely at a different version)
continue;
}
const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
if (!extensionDescription || !this._usesOnlyDynamicExtensionPoints(extensionDescription)) {
// uses non-dynamic extension point
continue;
}
toAdd.push(extensionDescription);
}
let toRemove: IExtensionDescription[] = [];
for (let i = 0, len = _toRemove.length; i < len; i++) {
const extensionId = _toRemove[i];
const extensionDescription = this._registry.getExtensionDescription(extensionId);
if (!extensionDescription) {
// ignore disabling/uninstalling an extension which is not running
continue;
}
if (!this._canRemoveExtension(extensionDescription)) {
// uses non-dynamic extension point or is activated
continue;
}
toRemove.push(extensionDescription);
}
if (toAdd.length === 0 && toRemove.length === 0) {
return;
}
// Update the local registry
this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
// Update extension points
this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
// Update the extension host
if (this._extensionHostProcessManagers.length > 0) {
await this._extensionHostProcessManagers[0].deltaExtensions(toAdd, toRemove.map(e => e.identifier));
}
this._onDidChangeExtensions.fire(undefined);
for (let i = 0; i < toAdd.length; i++) {
this._activateAddedExtensionIfNeeded(toAdd[i]);
}
}
private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void {
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
for (let extensionDescription of extensionDescriptions) {
if (extensionDescription.contributes) {
for (let extPointName in extensionDescription.contributes) {
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
affectedExtensionPoints[extPointName] = true;
}
}
}
}
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
const availableExtensions = this._registry.getAllExtensionDescriptions();
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
for (let i = 0, len = extensionPoints.length; i < len; i++) {
if (affectedExtensionPoints[extensionPoints[i].name]) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
}
}
private _usesOnlyDynamicExtensionPoints(extension: IExtensionDescription): boolean {
const extensionPoints = ExtensionsRegistry.getExtensionPointsMap();
if (extension.contributes) {
for (let extPointName in extension.contributes) {
if (hasOwnProperty.call(extension.contributes, extPointName)) {
const extPoint = extensionPoints[extPointName];
if (extPoint) {
if (!extPoint.isDynamic) {
return false;
}
} else {
// This extension has a 3rd party (unknown) extension point
// ===> require a reload for now...
return false;
}
}
}
}
return true;
}
public canAddExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
return false;
}
if (extension.extensionLocation.scheme !== Schemas.file) {
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (extensionDescription) {
// ignore adding an extension which is already running and cannot be removed
if (!this._canRemoveExtension(extensionDescription)) {
return false;
}
}
return this._usesOnlyDynamicExtensionPoints(extension);
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
return false;
}
if (extension.extensionLocation.scheme !== Schemas.file) {
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (!extensionDescription) {
// ignore removing an extension which is not running
return false;
}
return this._canRemoveExtension(extensionDescription);
}
private _canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) {
// Extension is running, cannot remove it safely
return false;
}
return this._usesOnlyDynamicExtensionPoints(extension);
}
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
let shouldActivate = false;
let shouldActivateReason: string | null = null;
if (Array.isArray(extensionDescription.activationEvents)) {
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
if (this._allRequestedActivateEvents[activationEvent]) {
// This activation event was fired before the extension was added
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (activationEvent === '*') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (/^workspaceContains/.test(activationEvent)) {
// do not trigger a search, just activate in this case...
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
}
}
if (shouldActivate) {
await Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason))
).then(() => { });
}
}
private _startDelayed(lifecycleService: ILifecycleService): void {
@@ -137,14 +414,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _stopExtensionHostProcess(): void {
const previouslyActivatedExtensionIds = Object.keys(this._extensionHostProcessActivationTimes);
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
this._extensionHostActiveExtensions.forEach((value) => {
previouslyActivatedExtensionIds.push(value);
});
for (let i = 0; i < this._extensionHostProcessManagers.length; i++) {
this._extensionHostProcessManagers[i].dispose();
for (const manager of this._extensionHostProcessManagers) {
manager.dispose();
}
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
if (previouslyActivatedExtensionIds.length > 0) {
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
@@ -154,7 +435,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, !isInitialStart, this.getExtensions(), this._extensionHostLogsLocation);
let autoStart: boolean;
let extensions: Promise<IExtensionDescription[]>;
if (isInitialStart) {
autoStart = false;
extensions = this._extensionScanner.scannedExtensions;
} else {
// restart case
autoStart = true;
extensions = this.getExtensions();
}
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ target: extHostProcessManager, isResponsive: responsiveState === ResponsiveState.Responsive }); });
@@ -205,14 +497,14 @@ export class ExtensionService extends Disposable implements IExtensionService {
if (this._installedExtensionsReady.isOpen()) {
// Extensions have been scanned and interpreted
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents[activationEvent] = true;
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return NO_OP_VOID_PROMISE;
}
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents[activationEvent] = true;
return this._activateByEvent(activationEvent);
} else {
// Extensions have not been scanned yet.
@@ -272,13 +564,12 @@ export class ExtensionService extends Disposable implements IExtensionService {
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
if (this._registry) {
const extensions = this._registry.getAllExtensionDescriptions();
for (let i = 0, len = extensions.length; i < len; i++) {
const extension = extensions[i];
const id = extension.id;
result[id] = {
messages: this._extensionsMessages[id],
activationTimes: this._extensionHostProcessActivationTimes[id],
runtimeErrors: this._extensionHostExtensionRuntimeErrors[id],
for (const extension of extensions) {
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
result[extension.identifier.value] = {
messages: this._extensionsMessages.get(extensionKey),
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey),
};
}
}
@@ -316,23 +607,29 @@ export class ExtensionService extends Disposable implements IExtensionService {
// --- impl
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(new Logger((severity, source, message) => {
private createLogger(): Logger {
return new Logger((severity, source, message) => {
if (this._isDev && source) {
this._logOrShowMessage(severity, `[${source}]: ${message}`);
} else {
this._logOrShowMessage(severity, message);
}
}));
});
}
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
extensionHost.start(enabledExtensions.map(extension => extension.id));
this._onHasExtensions(enabledExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier));
this._releaseBarrier();
}
private _onHasExtensions(allExtensions: IExtensionDescription[]): void {
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
this._registry = new ExtensionDescriptionRegistry(allExtensions);
let availableExtensions = this._registry.getAllExtensionDescriptions();
@@ -343,58 +640,65 @@ export class ExtensionService extends Disposable implements IExtensionService {
for (let i = 0, len = extensionPoints.length; i < len; i++) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
}
private _releaseBarrier(): void {
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(void 0);
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
this._onDidRegisterExtensions.fire(undefined);
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
return this._extensionEnablementService.getDisabledExtensions()
.then(disabledExtensions => {
const result: { [extensionId: string]: IExtensionDescription; } = {};
const extensionsToDisable: IExtensionIdentifier[] = [];
const runtimeExtensions: IExtensionDescription[] = [];
const extensionsToDisable: IExtensionDescription[] = [];
const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
const enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id);
if (enableProposedApiFor.length) {
let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]);
allProposed.forEach(id => {
if (!allExtensions.some(description => description.id === id)) {
if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) {
console.error(notFound(id));
}
});
// Make enabled proposed API be lowercase for case insensitive comparison
if (Array.isArray(enableProposedApiFor)) {
enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase());
} else {
enableProposedApiFor = enableProposedApiFor.toLowerCase();
}
}
const enableProposedApiForAll = !this._environmentService.isBuilt ||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong.indexOf('Insiders') >= 0) ||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') ||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
for (const extension of allExtensions) {
const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI);
// Do not disable extensions under development
if (!isExtensionUnderDevelopment) {
if (disabledExtensions.some(disabled => areSameExtensions(disabled, extension))) {
if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) {
continue;
}
}
if (!extension.isBuiltin) {
// Check if the extension is changed to system extension
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.id }))[0];
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0];
if (userMigratedSystemExtension) {
extensionsToDisable.push(userMigratedSystemExtension);
extensionsToDisable.push(extension);
continue;
}
}
result[extension.id] = this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor);
runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor));
}
const runtimeExtensions = Object.keys(result).map(name => result[name]);
this._telemetryService.publicLog('extensionsScanned', {
totalCount: runtimeExtensions.length,
@@ -402,14 +706,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
if (extensionsToDisable.length) {
return this._extensionManagementService.getInstalled(LocalExtensionType.User)
.then(installed => {
const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e)));
return Promise.all(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
})
.then(() => {
return runtimeExtensions;
});
return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled)
.then(() => runtimeExtensions);
} else {
return runtimeExtensions;
}
@@ -417,9 +715,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
if (isNonEmptyArray(product.extensionAllowedProposedApi)
&& product.extensionAllowedProposedApi.indexOf(extension.id) >= 0
) {
if (allowProposedApiFromProduct(extension.identifier)) {
// fast lane -> proposed api is available to all extensions
// that are listed in product.json-files
extension.enableProposedApi = true;
@@ -427,29 +723,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
} else if (extension.enableProposedApi && !extension.isBuiltin) {
if (
!enableProposedApiForAll &&
enableProposedApiFor.indexOf(extension.id) < 0
enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
) {
extension.enableProposedApi = false;
console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
} else {
// proposed api is available when developing or when an extension was explicitly
// spelled out via a command line argument
console.warn(`Extension '${extension.id}' uses PROPOSED API which is subject to change and removal without notice.`);
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
}
}
return extension;
}
private _handleExtensionPointMessage(msg: IMessage) {
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
if (!this._extensionsMessages[msg.extensionId]) {
this._extensionsMessages[msg.extensionId] = [];
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages[msg.extensionId].push(msg);
this._extensionsMessages.get(extensionKey).push(msg);
const extension = this._registry.getExtensionDescription(msg.extensionId);
const strMsg = `[${msg.extensionId}]: ${msg.message}`;
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
if (extension && extension.isUnderDevelopment) {
// This message is about the extension currently being developed
this._showMessageToUser(msg.type, strMsg);
@@ -468,7 +765,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
*/
this._telemetryService.publicLog('extensionsMessage', {
type, extensionId, extensionPointId, message
type, extensionId: extensionId.value, extensionPointId, message
});
}
}
@@ -518,24 +815,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent);
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
}
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
public _onExtensionRuntimeError(extensionId: string, err: Error): void {
if (!this._extensionHostExtensionRuntimeErrors[extensionId]) {
this._extensionHostExtensionRuntimeErrors[extensionId] = [];
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
}
this._extensionHostExtensionRuntimeErrors[extensionId].push(err);
this._extensionHostExtensionRuntimeErrors.get(extensionKey).push(err);
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
public _addMessage(extensionId: string, severity: Severity, message: string): void {
if (!this._extensionsMessages[extensionId]) {
this._extensionsMessages[extensionId] = [];
public _addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages[extensionId].push({
this._extensionsMessages.get(extensionKey).push({
type: severity,
message: message,
extensionId: null,

View File

@@ -7,7 +7,6 @@ import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -17,6 +16,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
@@ -30,8 +30,8 @@ export const IExtensionUrlHandler = createDecorator<IExtensionUrlHandler>('inact
export interface IExtensionUrlHandler {
readonly _serviceBrand: any;
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: string): void;
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void;
}
/**
@@ -53,14 +53,14 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
constructor(
@IURLService urlService: IURLService,
@IExtensionService private extensionService: IExtensionService,
@IDialogService private dialogService: IDialogService,
@INotificationService private notificationService: INotificationService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IWindowService private windowService: IWindowService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IStorageService private storageService: IStorageService
@IExtensionService private readonly extensionService: IExtensionService,
@IDialogService private readonly dialogService: IDialogService,
@INotificationService private readonly notificationService: INotificationService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly windowService: IWindowService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IStorageService private readonly storageService: IStorageService
) {
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE);
@@ -75,87 +75,80 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
]);
}
handleURL(uri: URI, confirmed?: boolean): TPromise<boolean> {
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {
if (!isExtensionId(uri.authority)) {
return TPromise.as(false);
return false;
}
const extensionId = uri.authority;
const wasHandlerAvailable = this.extensionHandlers.has(extensionId);
const wasHandlerAvailable = this.extensionHandlers.has(ExtensionIdentifier.toKey(extensionId));
const extension = await this.extensionService.getExtension(extensionId);
return this.extensionService.getExtension(extensionId).then(extension => {
if (!extension) {
await this.handleUnhandledURL(uri, { id: extensionId });
return true;
}
if (!extension) {
return this.handleUnhandledURL(uri, { id: extensionId }).then(() => false);
}
const handleURL = () => {
const handler = this.extensionHandlers.get(extensionId);
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
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(`onUri:${extensionId}`)
.then(() => true);
};
if (confirmed) {
return handleURL();
}
return this.dialogService.confirm({
if (!confirmed) {
const result = await this.dialogService.confirm({
message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId),
detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('open', "&&Open"),
type: 'question'
}).then(result => {
if (!result.confirmed) {
return TPromise.as(true);
}
return handleURL();
});
});
if (!result.confirmed) {
return true;
}
}
const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return await handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
return false;
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId));
if (!uris) {
uris = [];
this.uriBuffer.set(ExtensionIdentifier.toKey(extensionId), uris);
}
uris.push({ timestamp, uri });
// activate the extension
await this.extensionService.activateByEvent(`onUri:${ExtensionIdentifier.toKey(extensionId)}`);
return true;
}
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
this.extensionHandlers.set(extensionId, handler);
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void {
this.extensionHandlers.set(ExtensionIdentifier.toKey(extensionId), handler);
const uris = this.uriBuffer.get(extensionId) || [];
const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
}
this.uriBuffer.delete(extensionId);
this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
}
unregisterExtensionHandler(extensionId: string): void {
this.extensionHandlers.delete(extensionId);
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void {
this.extensionHandlers.delete(ExtensionIdentifier.toKey(extensionId));
}
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const extension = installedExtensions.filter(e => areSameExtensions(e.galleryIdentifier, extensionIdentifier))[0];
const extension = installedExtensions.filter(e => areSameExtensions(e.identifier, extensionIdentifier))[0];
// Extension is installed
if (extension) {
@@ -163,88 +156,91 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
// Extension is not running. Reload the window to handle.
if (enabled) {
this.dialogService.confirm({
const result = await this.dialogService.confirm({
message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('reloadAndOpen', "&&Reload Window and Open"),
type: 'question'
}).then(result => {
if (result.confirmed) {
return this.reloadAndHandle(uri);
}
return null;
});
if (!result.confirmed) {
return;
}
await this.reloadAndHandle(uri);
}
// Extension is disabled. Enable the extension and reload the window to handle.
else {
this.dialogService.confirm({
const result = await this.dialogService.confirm({
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('enableAndReload', "&&Enable and Open"),
type: 'question'
}).then((result): TPromise<void> | null => {
if (result.confirmed) {
return this.extensionEnablementService.setEnablement(extension, EnablementState.Enabled)
.then(() => this.reloadAndHandle(uri));
}
return null;
});
if (!result.confirmed) {
return;
}
await this.extensionEnablementService.setEnablement([extension], EnablementState.Enabled);
await this.reloadAndHandle(uri);
}
}
// Extension is not installed
else {
const galleryExtension = await this.galleryService.getExtension(extensionIdentifier);
if (galleryExtension) {
// Install the Extension and reload the window to handle.
this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('install', "&&Install"),
type: 'question'
}).then(async result => {
if (result.confirmed) {
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
notificationHandle.progress.infinite();
notificationHandle.onDidClose(() => notificationHandle = null);
try {
await this.extensionManagementService.installFromGallery(galleryExtension);
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
const reloadActionLabel = localize('Reload', "Reload Window and Open");
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateMessage(reloadMessage);
notificationHandle.updateActions({
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
});
} else {
this.notificationService.prompt(Severity.Info, reloadMessage,
[{
label: reloadActionLabel,
run: () => this.reloadAndHandle(uri)
}],
{ sticky: true }
);
}
} catch (e) {
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateSeverity(Severity.Error);
notificationHandle.updateMessage(e);
} else {
this.notificationService.error(e);
}
}
}
});
const galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier);
if (!galleryExtension) {
return;
}
// Install the Extension and reload the window to handle.
const result = await this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('install', "&&Install"),
type: 'question'
});
if (!result.confirmed) {
return;
}
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
notificationHandle.progress.infinite();
notificationHandle.onDidClose(() => notificationHandle = null);
try {
await this.extensionManagementService.installFromGallery(galleryExtension);
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
const reloadActionLabel = localize('Reload', "Reload Window and Open");
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateMessage(reloadMessage);
notificationHandle.updateActions({
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
});
} else {
this.notificationService.prompt(Severity.Info, reloadMessage, [{ label: reloadActionLabel, run: () => this.reloadAndHandle(uri) }], { sticky: true });
}
} catch (e) {
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateSeverity(Severity.Error);
notificationHandle.updateMessage(e);
} else {
this.notificationService.error(e);
}
}
}
}
private reloadAndHandle(url: URI): TPromise<void> {
private async reloadAndHandle(url: URI): Promise<void> {
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
return this.windowService.reloadWindow();
await this.windowService.reloadWindow();
}
// forget about all uris buffered more than 5 minutes ago

View File

@@ -30,7 +30,7 @@ export class RuntimeExtensionsInput extends EditorInput {
return true;
}
resolve(): Thenable<any> {
resolve(): Promise<any> {
return Promise.resolve(null);
}

View File

@@ -4,14 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { Emitter } from 'vs/base/common/event';
export class ExtensionDescriptionRegistry {
private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange = this._onDidChange.event;
private _extensionDescriptions: IExtensionDescription[];
private _extensionsMap: { [extensionId: string]: IExtensionDescription; };
private _extensionsMap: Map<string, IExtensionDescription>;
private _extensionsArr: IExtensionDescription[];
private _activationMap: { [activationEvent: string]: IExtensionDescription[]; };
private _activationMap: Map<string, IExtensionDescription[]>;
constructor(extensionDescriptions: IExtensionDescription[]) {
this._extensionDescriptions = extensionDescriptions;
@@ -19,64 +22,68 @@ export class ExtensionDescriptionRegistry {
}
private _initialize(): void {
this._extensionsMap = {};
this._extensionsMap = new Map<string, IExtensionDescription>();
this._extensionsArr = [];
this._activationMap = {};
this._activationMap = new Map<string, IExtensionDescription[]>();
for (let i = 0, len = this._extensionDescriptions.length; i < len; i++) {
let extensionDescription = this._extensionDescriptions[i];
if (hasOwnProperty.call(this._extensionsMap, extensionDescription.id)) {
for (const extensionDescription of this._extensionDescriptions) {
if (this._extensionsMap.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
// No overwriting allowed!
console.error('Extension `' + extensionDescription.id + '` is already registered');
console.error('Extension `' + extensionDescription.identifier.value + '` is already registered');
continue;
}
this._extensionsMap[extensionDescription.id] = extensionDescription;
this._extensionsMap.set(ExtensionIdentifier.toKey(extensionDescription.identifier), extensionDescription);
this._extensionsArr.push(extensionDescription);
if (Array.isArray(extensionDescription.activationEvents)) {
for (let j = 0, lenJ = extensionDescription.activationEvents.length; j < lenJ; j++) {
let activationEvent = extensionDescription.activationEvents[j];
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${extensionDescription.id}`;
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
this._activationMap[activationEvent] = this._activationMap[activationEvent] || [];
this._activationMap[activationEvent].push(extensionDescription);
if (!this._activationMap.has(activationEvent)) {
this._activationMap.set(activationEvent, []);
}
this._activationMap.get(activationEvent)!.push(extensionDescription);
}
}
}
}
public keepOnly(extensionIds: string[]): void {
let toKeep = new Set<string>();
extensionIds.forEach(extensionId => toKeep.add(extensionId));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(extension.id));
public keepOnly(extensionIds: ExtensionIdentifier[]): void {
const toKeep = new Set<string>();
extensionIds.forEach(extensionId => toKeep.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(ExtensionIdentifier.toKey(extension.identifier)));
this._initialize();
this._onDidChange.fire(undefined);
}
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) {
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
const toRemoveSet = new Set<string>();
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
this._initialize();
this._onDidChange.fire(undefined);
}
public containsActivationEvent(activationEvent: string): boolean {
return hasOwnProperty.call(this._activationMap, activationEvent);
return this._activationMap.has(activationEvent);
}
public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
if (!hasOwnProperty.call(this._activationMap, activationEvent)) {
return [];
}
return this._activationMap[activationEvent].slice(0);
const extensions = this._activationMap.get(activationEvent);
return extensions ? extensions.slice(0) : [];
}
public getAllExtensionDescriptions(): IExtensionDescription[] {
return this._extensionsArr.slice(0);
}
public getExtensionDescription(extensionId: string): IExtensionDescription | null {
if (!hasOwnProperty.call(this._extensionsMap, extensionId)) {
return null;
}
return this._extensionsMap[extensionId];
public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | null {
const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId));
return extension ? extension : null;
}
}

View File

@@ -0,0 +1,162 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Counter } from 'vs/base/common/numbers';
import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { IEnvironment, IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
// we don't (yet) throw when extensions parse
// uris that have no scheme
setUriThrowOnMissingScheme(false);
const nativeExit = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
process.exit = function (code?: number) {
if (allowExit) {
exit(code);
} else {
const err = new Error('An extension called process.exit() and this was prevented.');
console.warn(err.stack);
}
} as (code?: number) => never;
process.crash = function () {
const err = new Error('An extension called process.crash() and this was prevented.');
console.warn(err.stack);
};
}
export function exit(code?: number) {
nativeExit(code);
}
export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _environment: IEnvironment;
private readonly _extensionService: ExtHostExtensionService;
private readonly _extHostConfiguration: ExtHostConfiguration;
private readonly _extHostLogService: ExtHostLogService;
private disposables: IDisposable[] = [];
private _searchRequestIdProvider: Counter;
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
this._isTerminating = false;
const uriTransformer: IURITransformer = null;
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = this.transform(initData, rpcProtocol);
this._environment = initData.environment;
const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
patchProcess(allowExit);
this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));
// services
this._extHostLogService = new ExtHostLogService(initData.logLevel, initData.logsLocation.fsPath);
this.disposables.push(this._extHostLogService);
this._searchRequestIdProvider = new Counter();
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace, this._extHostLogService, this._searchRequestIdProvider);
this._extHostLogService.info('extension host started');
this._extHostLogService.trace('initData', initData);
this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService);
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const extensionErrors = new WeakMap<Error, IExtensionDescription>();
this._extensionService.getExtensionPathIndex().then(map => {
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription;
let fileName: string;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(fileName);
}
}
extensionErrors.set(error, extension);
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
};
});
const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
if (extension) {
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
} else {
mainThreadErrors.$onUnexpectedError(data);
}
});
}
private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
// The console is already patched to use `process.send()`
const nativeProcessSend = process.send;
process.send = (...args: any[]) => {
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
return nativeProcessSend.apply(process, args);
}
mainThreadConsole.$logExtensionHostMessage(args[0]);
};
}
terminate(): void {
if (this._isTerminating) {
// we are already shutting down...
return;
}
this._isTerminating = true;
this.disposables = dispose(this.disposables);
errors.setUnexpectedErrorHandler((err) => {
// TODO: write to log once we have one
});
const extensionsDeactivated = this._extensionService.deactivateAll();
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
setTimeout(() => {
Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
}, 1000);
}
private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
return initData;
}
}

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nativeWatchdog from 'native-watchdog';
import { createConnection } from 'net';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/node/product';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain';
// With Electron 2.x and node.js 8.x the "natives" module
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
// https://github.com/electron/electron/issues/10905). To prevent this from
// happening we essentially blocklist this module from getting loaded in any
// extension by patching the node require() function.
(function () {
const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load;
Module._load = function (request) {
if (request === 'natives') {
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
}
return originalLoad.apply(this, arguments);
};
})();
interface IRendererConnection {
protocol: IMessagePassingProtocol;
initData: IInitData;
}
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
let onTerminate = function () {
exit();
};
function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST;
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
const socket = createConnection(pipeName, () => {
socket.removeListener('error', reject);
resolve(new Protocol(socket));
});
socket.once('error', reject);
}).then(protocol => {
return new class implements IMessagePassingProtocol {
private _terminating = false;
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
if (!isMessageOfType(msg, MessageType.Terminate)) {
return true;
}
this._terminating = true;
onTerminate();
return false;
});
send(msg: any): void {
if (!this._terminating) {
protocol.send(msg);
}
}
};
});
}
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
return new Promise<IRendererConnection>((c, e) => {
// Listen init data message
const first = protocol.onMessage(raw => {
first.dispose();
const initData = <IInitData>JSON.parse(raw.toString());
const rendererCommit = initData.commit;
const myCommit = product.commit;
if (rendererCommit && myCommit) {
// Running in the built version where commits are defined
if (rendererCommit !== myCommit) {
exit(55);
}
}
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
const unhandledPromises: Promise<any>[] = [];
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
unhandledPromises.push(promise);
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
console.warn('rejected promise not handled within 1 second');
onUnexpectedError(reason);
}
}, 1000);
});
process.on('rejectionHandled', (promise: Promise<any>) => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
}
});
// Print a console message when an exception isn't handled.
process.on('uncaughtException', function (err: Error) {
onUnexpectedError(err);
});
// {{SQL CARBON EDIT}}
process.on('SIGPIPE', () => {
onUnexpectedError(new Error('Unexpected SIGPIPE'));
});
// {{SQL CARBON EDIT}} - End
// Kill oneself if one's parent dies. Much drama.
setInterval(function () {
try {
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
} catch (e) {
onTerminate();
}
}, 1000);
// In certain cases, the event loop can become busy and never yield
// e.g. while-true or process.nextTick endless loops
// So also use the native node module to do it from a separate thread
let watchdog: typeof nativeWatchdog;
try {
watchdog = require.__$__nodeRequire('native-watchdog');
watchdog.start(initData.parentPid);
} catch (err) {
// no problem...
onUnexpectedError(err);
}
// Tell the outside that we are initialized
protocol.send(createMessageOfType(MessageType.Initialized));
c({ protocol, initData });
});
// Tell the outside that we are ready to receive messages
protocol.send(createMessageOfType(MessageType.Ready));
});
}
patchExecArgv();
createExtHostProtocol().then(protocol => {
// connect to main side
return connectToRenderer(protocol);
}).then(renderer => {
// setup things
const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData);
onTerminate = () => extensionHostMain.terminate();
}).catch(err => console.error(err));
function patchExecArgv() {
// when encountering the prevent-inspect flag we delete this
// and the prior flag
if (process.env.VSCODE_PREVENT_FOREIGN_INSPECT) {
for (let i = 0; i < process.execArgv.length; i++) {
if (process.execArgv[i].match(/--inspect-brk=\d+|--inspect=\d+/)) {
process.execArgv.splice(i, 1);
break;
}
}
}
}

View File

@@ -42,28 +42,4 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
}
return null;
}
}
export class SingleServerExtensionManagementServerService implements IExtensionManagementServerService {
_serviceBrand: any;
constructor(
private readonly extensionManagementServer: IExtensionManagementServer
) {
}
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
const authority = location.scheme === Schemas.file ? localExtensionManagementServerAuthority : location.authority;
return this.extensionManagementServer.authority === authority ? this.extensionManagementServer : null;
}
get localExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority === localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
get remoteExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority !== localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
}

View File

@@ -12,10 +12,10 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { getGalleryExtensionId, getLocalExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions';
const MANIFEST_FILE = 'package.json';
@@ -304,6 +304,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
// Relax the readonly properties here, it is the one place where we check and normalize values
export interface IRelaxedExtensionDescription {
id: string;
uuid?: string;
identifier: ExtensionIdentifier;
name: string;
version: string;
publisher: string;
@@ -343,6 +345,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
// id := `publisher.name`
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id);
// main := absolutePath(`main`)
if (extensionDescription.main) {
@@ -507,7 +510,7 @@ export class ExtensionScanner {
/**
* Read the extension defined in `absoluteFolderPath`
*/
public static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
absoluteFolderPath = path.normalize(absoluteFolderPath);
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
@@ -557,31 +560,17 @@ export class ExtensionScanner {
refs.sort((a, b) => a.name < b.name ? -1 : 1);
if (!isBuiltin) {
// TODO: align with extensionsService
const nonGallery: IExtensionReference[] = [];
const gallery: IExtensionReference[] = [];
refs.forEach(ref => {
if (ref.name.indexOf('.') !== 0) { // Do not consider user extension folder starting with `.`
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);
if (!id || !version) {
nonGallery.push(ref);
} else {
gallery.push(ref);
}
}
});
refs = [...nonGallery, ...gallery];
refs = refs.filter(ref => ref.name.indexOf('.') !== 0); // Do not consider user extension folder starting with `.`
}
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
let extensionDescriptions = arrays.coalesce(_extensionDescriptions);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[getLocalExtensionId(getGalleryExtensionId(item.publisher, item.name), item.version)]);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]);
if (!isBuiltin) {
// Filter out outdated extensions
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.id, uuid: e.uuid }));
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.identifier.value, uuid: e.uuid }));
extensionDescriptions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
}
@@ -624,15 +613,23 @@ export class ExtensionScanner {
});
}
public static scanSingleExtension(input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription | null> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment;
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
}
public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> {
return Promise.all([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
for (let i = 0, len = builtinExtensions.length; i < len; i++) {
resultMap[builtinExtensions[i].id] = builtinExtensions[i];
resultMap[ExtensionIdentifier.toKey(builtinExtensions[i].identifier)] = builtinExtensions[i];
}
// Overwrite with extensions found in extra
for (let i = 0, len = extraBuiltinExtensions.length; i < len; i++) {
resultMap[extraBuiltinExtensions[i].id] = extraBuiltinExtensions[i];
resultMap[ExtensionIdentifier.toKey(extraBuiltinExtensions[i].identifier)] = extraBuiltinExtensions[i];
}
let resultArr = Object.keys(resultMap).map((id) => resultMap[id]);

View File

@@ -5,7 +5,7 @@
import { onUnexpectedError } from 'vs/base/common/errors';
export class LazyPromise implements Thenable<any> {
export class LazyPromise implements Promise<any> {
private _actual: Promise<any> | null;
private _actualOk: ((value?: any) => any) | null;
@@ -78,4 +78,12 @@ export class LazyPromise implements Thenable<any> {
public then(success: any, error: any): any {
return this._ensureActual().then(success, error);
}
public catch(error: any): any {
return this._ensureActual().then(undefined, error);
}
public finally(callback): any {
return this._ensureActual().finally(callback);
}
}

View File

@@ -0,0 +1,317 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as http from 'http';
import * as https from 'https';
import * as nodeurl from 'url';
import { assign } from 'vs/base/common/objects';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
import { ProxyAgent } from 'vscode-proxy-agent';
import { MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { URI } from 'vs/base/common/uri';
interface ConnectionResult {
proxy: string;
connection: string;
code: string;
count: number;
}
export function connectProxyResolver(
extHostWorkspace: ExtHostWorkspace,
configProvider: ExtHostConfigProvider,
extensionService: ExtHostExtensionService,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry);
const lookup = createPatchedModules(configProvider, agents);
return configureModuleLoading(extensionService, lookup);
}
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
function createProxyAgents(
extHostWorkspace: ExtHostWorkspace,
configProvider: ExtHostConfigProvider,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
let settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
configProvider.onDidChangeConfiguration(e => {
settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
});
const env = process.env;
let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized.
let cacheRolls = 0;
let oldCache = new Map<string, string>();
let cache = new Map<string, string>();
function getCacheKey(url: nodeurl.UrlWithStringQuery) {
// Expecting proxies to usually be the same per scheme://host:port. Assuming that for performance.
return nodeurl.format({ ...url, ...{ pathname: undefined, search: undefined, hash: undefined } });
}
function getCachedProxy(key: string) {
let proxy = cache.get(key);
if (proxy) {
return proxy;
}
proxy = oldCache.get(key);
if (proxy) {
oldCache.delete(key);
cacheProxy(key, proxy);
}
return proxy;
}
function cacheProxy(key: string, proxy: string) {
cache.set(key, proxy);
if (cache.size >= maxCacheEntries) {
oldCache = cache;
cache = new Map();
cacheRolls++;
extHostLogService.trace('ProxyResolver#cacheProxy cacheRolls', cacheRolls);
}
}
let timeout: NodeJS.Timer | undefined;
let count = 0;
let duration = 0;
let errorCount = 0;
let cacheCount = 0;
let envCount = 0;
let settingsCount = 0;
let localhostCount = 0;
let results: ConnectionResult[] = [];
function logEvent() {
timeout = undefined;
/* __GDPR__
"resolveProxy" : {
"count": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheRolls": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = 0;
results = [];
}
function resolveProxy(req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
if (!timeout) {
timeout = setTimeout(logEvent, 10 * 60 * 1000);
}
const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that.
const hostname = parsedUrl.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '::ffff:127.0.0.1') {
localhostCount++;
callback('DIRECT');
extHostLogService.trace('ProxyResolver#resolveProxy localhost', url, 'DIRECT');
return;
}
if (settingsProxy) {
settingsCount++;
callback(settingsProxy);
extHostLogService.trace('ProxyResolver#resolveProxy settings', url, settingsProxy);
return;
}
if (envProxy) {
envCount++;
callback(envProxy);
extHostLogService.trace('ProxyResolver#resolveProxy env', url, envProxy);
return;
}
const key = getCacheKey(parsedUrl);
const proxy = getCachedProxy(key);
if (proxy) {
cacheCount++;
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
callback(proxy);
extHostLogService.trace('ProxyResolver#resolveProxy cached', url, proxy);
return;
}
const start = Date.now();
extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one.
.then(proxy => {
cacheProxy(key, proxy);
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
callback(proxy);
extHostLogService.debug('ProxyResolver#resolveProxy', url, proxy);
}).then(() => {
count++;
duration = Date.now() - start + duration;
}, err => {
errorCount++;
callback();
extHostLogService.error('ProxyResolver#resolveProxy', toErrorMessage(err));
});
}
const httpAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpAgent).defaultPort = 80;
const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpsAgent).defaultPort = 443;
return { http: httpAgent, https: httpsAgent };
}
function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) {
const proxy = resolveProxy ? String(resolveProxy).trim().split(/\s+/, 1)[0] : 'EMPTY';
req.on('response', res => {
const code = `HTTP_${res.statusCode}`;
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
req.on('error', err => {
const code = err && typeof (<any>err).code === 'string' && (<any>err).code || 'UNKNOWN_ERROR';
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
}
function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult | undefined {
for (const result of results) {
if (result.proxy === proxy && result.connection === connection && result.code === code) {
return result;
}
}
const result = { proxy, connection, code, count: 0 };
results.push(result);
return result;
}
function proxyFromConfigURL(configURL: string) {
const url = (configURL || '').trim();
const i = url.indexOf('://');
if (i === -1) {
return undefined;
}
const scheme = url.substr(0, i).toLowerCase();
const proxy = url.substr(i + 3);
if (scheme === 'http') {
return 'PROXY ' + proxy;
} else if (scheme === 'https') {
return 'HTTPS ' + proxy;
} else if (scheme === 'socks') {
return 'SOCKS ' + proxy;
}
return undefined;
}
function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) {
const setting = {
config: configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off'
};
configProvider.onDidChangeConfiguration(e => {
setting.config = configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off';
});
return {
http: {
off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)),
on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)),
override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)),
onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)),
default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last
},
https: {
off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)),
on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)),
override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)),
onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)),
default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last
}
};
}
function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) {
return {
get: patch(originals.get),
request: patch(originals.request)
};
function patch(original: typeof http.get) {
function patched(url: string | URL, options?: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest {
if (typeof url !== 'string' && !(url && (<any>url).searchParams)) {
callback = <any>options;
options = url;
url = null;
}
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || setting.config;
if (config === 'off') {
return original.apply(null, arguments as unknown as any[]);
}
if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) {
if (url) {
const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url;
const urlOptions = {
protocol: parsed.protocol,
hostname: parsed.hostname.lastIndexOf('[', 0) === 0 ? parsed.hostname.slice(1, -1) : parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`
};
if (parsed.username || parsed.password) {
options.auth = `${parsed.username}:${parsed.password}`;
}
options = { ...urlOptions, ...options };
} else {
options = { ...options };
}
options.agent = agent;
return original(options, callback);
}
return original.apply(null, arguments as unknown as any[]);
}
return patched;
}
}
function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType<typeof createPatchedModules>): Promise<void> {
return extensionService.getExtensionPathIndex()
.then(extensionPaths => {
const node_module = <any>require.__$__nodeRequire('module');
const original = node_module._load;
node_module._load = function load(request: string, parent: any, isMain: any) {
if (request !== 'http' && request !== 'https') {
return original.apply(this, arguments);
}
const modules = lookup[request];
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
if (ext && ext.enableProposedApi) {
return modules[(<any>ext).proxySupport] || modules.onRequest;
}
return modules.default;
};
});
}

View File

@@ -9,9 +9,7 @@ import { CharCode } from 'vs/base/common/charCode';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { MarshalledObject } from 'vs/base/common/marshalling';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IURITransformer, transformIncomingURIs } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
import { IRPCProtocol, ProxyIdentifier, getStringIdentifierForProxy } from 'vs/workbench/services/extensions/node/proxyIdentifier';
@@ -40,75 +38,6 @@ function createURIReplacer(transformer: IURITransformer | null): JSONStringifyRe
};
}
function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if (obj instanceof URI) {
return transformer.transformOutgoing(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformOutgoingURIs(obj[key], transformer, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
export function transformOutgoingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformOutgoingURIs(obj, transformer, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
function _transformIncomingURIs(obj: any, transformer: IURITransformer, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if ((<MarshalledObject>obj).$mid === 1) {
return transformer.transformIncoming(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformIncomingURIs(obj[key], transformer, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
function transformIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformIncomingURIs(obj, transformer, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
export const enum RequestInitiator {
LocalSide = 0,
OtherSide = 1
@@ -354,7 +283,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
}
const callId = String(req);
let promise: Thenable<any>;
let promise: Promise<any>;
let cancel: () => void;
if (usesCancellationToken) {
const cancellationTokenSource = new CancellationTokenSource();
@@ -441,7 +370,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
pendingReply.resolveErr(err);
}
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Thenable<any> {
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Promise<any> {
try {
return Promise.resolve(this._doInvokeHandler(rpcId, methodName, args));
} catch (err) {
@@ -461,7 +390,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
return method.apply(actor, args);
}
private _remoteCall(rpcId: number, methodName: string, args: any[]): Thenable<any> {
private _remoteCall(rpcId: number, methodName: string, args: any[]): Promise<any> {
if (this._isDisposed) {
return Promise.reject<any>(errors.canceled());
}
@@ -592,7 +521,7 @@ class MessageBuffer {
return buff;
}
public static sizeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): number {
public static sizeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): number {
let size = 0;
size += 1; // arr length
for (let i = 0, len = arr.length; i < len; i++) {
@@ -608,7 +537,7 @@ class MessageBuffer {
return size;
}
public writeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): void {
public writeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): void {
this._buff.writeUInt8(arr.length, this._offset, true); this._offset += 1;
for (let i = 0, len = arr.length; i < len; i++) {
const el = arr[i];
@@ -623,9 +552,9 @@ class MessageBuffer {
}
}
public readMixedArray(): (string | Buffer)[] {
public readMixedArray(): Array<string | Buffer> {
const arrLen = this._buff.readUInt8(this._offset, true); this._offset += 1;
let arr: (string | Buffer)[] = new Array(arrLen);
let arr: Array<string | Buffer> = new Array(arrLen);
for (let i = 0; i < arrLen; i++) {
const argType = <ArgType>this.readUInt8();
if (argType === ArgType.String) {
@@ -651,7 +580,7 @@ class MessageIO {
public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): Buffer {
if (this._arrayContainsBuffer(args)) {
let massagedArgs: (string | Buffer)[] = new Array(args.length);
let massagedArgs: Array<string | Buffer> = new Array(args.length);
let argsLengths: number[] = new Array(args.length);
for (let i = 0, len = args.length; i < len; i++) {
const arg = args[i];
@@ -695,7 +624,7 @@ class MessageIO {
};
}
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: (string | Buffer)[], argsLengths: number[], usesCancellationToken: boolean): Buffer {
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: Array<string | Buffer>, argsLengths: number[], usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
let len = 0;

View File

@@ -15,7 +15,7 @@ suite('RPCProtocol', () => {
class MessagePassingProtocol implements IMessagePassingProtocol {
private _pair: MessagePassingProtocol;
private readonly _onMessage: Emitter<Buffer> = new Emitter<Buffer>();
private readonly _onMessage = new Emitter<Buffer>();
public readonly onMessage: Event<Buffer> = this._onMessage.event;
public setPair(other: MessagePassingProtocol) {
@@ -32,7 +32,7 @@ suite('RPCProtocol', () => {
let delegate: (a1: any, a2: any) => any;
let bProxy: BClass;
class BClass {
$m(a1: any, a2: any): Thenable<any> {
$m(a1: any, a2: any): Promise<any> {
return Promise.resolve(delegate.call(null, a1, a2));
}
}
@@ -46,8 +46,6 @@ suite('RPCProtocol', () => {
let A = new RPCProtocol(a_protocol);
let B = new RPCProtocol(b_protocol);
delegate = null;
const bIdentifier = new ProxyIdentifier<BClass>(false, 'bb');
const bInstance = new BClass();
B.set(bIdentifier, bInstance);