extensionService.ts 19.5 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import * as nls from 'vs/nls';
A
Alex Dima 已提交
8
import * as errors from 'vs/base/common/errors';
9 10 11 12 13
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import pkg from 'vs/platform/node/package';
import * as path from 'path';
import URI from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
14
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
15
import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService, ExtensionPointContribution, ActivationTimes } from 'vs/platform/extensions/common/extensions';
16 17
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, getGloballyDisabledExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
A
Alex Dima 已提交
18
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
19
import { ExtensionScanner, ILog } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
20
import { IMessageService } from 'vs/platform/message/common/message';
21
import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService';
22
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
23 24 25
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/common/storage';
B
Benjamin Pasero 已提交
26
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
27 28
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { MainThreadService } from 'vs/workbench/services/thread/electron-browser/threadService';
B
Benjamin Pasero 已提交
29 30
import { Barrier } from 'vs/workbench/services/extensions/node/barrier';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
31
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
B
Benjamin Pasero 已提交
32 33 34
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Action } from 'vs/base/common/actions';
import { IDisposable } from 'vs/base/common/lifecycle';
35
import { startTimer } from 'vs/base/node/startupTimers';
36 37 38

const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));

39
function messageWithSource(msg: IMessage): string {
40 41 42 43 44 45 46 47
	return messageWithSource2(msg.source, msg.message);
}

function messageWithSource2(source: string, message: string): string {
	if (source) {
		return `[${source}]: ${message}`;
	}
	return message;
48 49
}

50 51 52
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = TPromise.as<void>(void 0);

53
export class ExtensionService implements IExtensionService {
54
	public _serviceBrand: any;
55

56 57
	private _registry: ExtensionDescriptionRegistry;
	private readonly _barrier: Barrier;
58 59
	private readonly _isDev: boolean;
	private readonly _extensionsStatus: { [id: string]: IExtensionsStatus };
A
Alex Dima 已提交
60 61 62 63 64
	private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };


	// --- Members used per extension host process

65
	/**
66
	 * A map of already activated events to speed things up if the same activation event is triggered multiple times.
67
	 */
A
Alex Dima 已提交
68
	private _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; };
69
	private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
A
Alex Dima 已提交
70 71 72 73 74 75 76
	private _extensionHostProcessWorker: ExtensionHostProcessWorker;
	private _extensionHostProcessThreadService: MainThreadService;
	private _extensionHostProcessCustomers: IDisposable[];
	/**
	 * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
	 */
	private _extensionHostProcessProxy: TPromise<{ value: ExtHostExtensionServiceShape; }>;
77

78 79 80
	constructor(
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
		@IMessageService private readonly _messageService: IMessageService,
81
		@IEnvironmentService private readonly _environmentService: IEnvironmentService,
82
		@ITelemetryService private readonly _telemetryService: ITelemetryService,
83 84
		@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
		@IStorageService private readonly _storageService: IStorageService,
85
		@IWindowService private readonly _windowService: IWindowService
86
	) {
87 88
		this._registry = null;
		this._barrier = new Barrier();
89
		this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
90
		this._extensionsStatus = {};
A
Alex Dima 已提交
91 92 93
		this._allRequestedActivateEvents = Object.create(null);

		this._extensionHostProcessFinishedActivateEvents = Object.create(null);
94
		this._extensionHostProcessActivationTimes = Object.create(null);
A
Alex Dima 已提交
95 96 97 98 99 100 101 102 103
		this._extensionHostProcessWorker = null;
		this._extensionHostProcessThreadService = null;
		this._extensionHostProcessCustomers = [];
		this._extensionHostProcessProxy = null;

		this._startExtensionHostProcess([]);
		this._scanAndHandleExtensions();
	}

104 105 106 107 108
	public restartExtensionHost(): void {
		this._stopExtensionHostProcess();
		this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
	}

109 110 111 112 113 114 115 116
	public startExtensionHost(): void {
		this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
	}

	public stopExtensionHost(): void {
		this._stopExtensionHostProcess();
	}

A
Alex Dima 已提交
117 118
	private _stopExtensionHostProcess(): void {
		this._extensionHostProcessFinishedActivateEvents = Object.create(null);
119
		this._extensionHostProcessActivationTimes = Object.create(null);
A
Alex Dima 已提交
120 121 122 123 124
		if (this._extensionHostProcessWorker) {
			this._extensionHostProcessWorker.dispose();
			this._extensionHostProcessWorker = null;
		}
		if (this._extensionHostProcessThreadService) {
125
			this._extensionHostProcessThreadService.dispose();
A
Alex Dima 已提交
126 127 128 129 130 131 132 133
			this._extensionHostProcessThreadService = null;
		}
		for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) {
			const customer = this._extensionHostProcessCustomers[i];
			try {
				customer.dispose();
			} catch (err) {
				errors.onUnexpectedError(err);
134
			}
A
Alex Dima 已提交
135 136 137 138 139 140 141 142 143 144 145
		}
		this._extensionHostProcessCustomers = [];
		this._extensionHostProcessProxy = null;
	}

	private _startExtensionHostProcess(initialActivationEvents: string[]): void {
		this._stopExtensionHostProcess();

		this._extensionHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this);
		this._extensionHostProcessWorker.onCrashed(([code, signal]) => this._onExtensionHostCrashed(code, signal));
		this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then(
146
			(protocol) => {
A
Alex Dima 已提交
147
				return { value: this._createExtensionHostCustomers(protocol) };
148 149 150 151
			},
			(err) => {
				console.error('Error received from starting extension host');
				console.error(err);
A
Alex Dima 已提交
152
				return null;
153 154
			}
		);
A
Alex Dima 已提交
155 156 157 158
		this._extensionHostProcessProxy.then(() => {
			initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
		});
	}
159

A
Alex Dima 已提交
160 161 162 163 164 165
	private _onExtensionHostCrashed(code: number, signal: string): void {
		const openDevTools = new Action('openDevTools', nls.localize('devTools', "Developer Tools"), '', true, (): TPromise<boolean> => {
			return this._windowService.openDevTools().then(() => false);
		});

		const restart = new Action('restart', nls.localize('restart', "Restart Extension Host"), '', true, (): TPromise<boolean> => {
A
Alex Dima 已提交
166
			this._messageService.hideAll();
A
Alex Dima 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
			this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
			return TPromise.as(true);
		});

		console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
		this._stopExtensionHostProcess();

		let message = nls.localize('extensionHostProcess.crash', "Extension host terminated unexpectedly.");
		if (code === 87) {
			message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive.");
		}
		this._messageService.show(Severity.Error, {
			message: message,
			actions: [
				openDevTools,
				restart
			]
		});
185 186
	}

A
Alex Dima 已提交
187
	private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
188

A
Alex Dima 已提交
189 190
		this._extensionHostProcessThreadService = this._instantiationService.createInstance(MainThreadService, protocol);
		const extHostContext: IExtHostContext = this._extensionHostProcessThreadService;
191

192 193 194 195
		// Named customers
		const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
		for (let i = 0, len = namedCustomers.length; i < len; i++) {
			const [id, ctor] = namedCustomers[i];
A
Alex Dima 已提交
196 197 198
			const instance = this._instantiationService.createInstance(ctor, extHostContext);
			this._extensionHostProcessCustomers.push(instance);
			this._extensionHostProcessThreadService.set(id, instance);
199
		}
200

201 202 203 204
		// Customers
		const customers = ExtHostCustomersRegistry.getCustomers();
		for (let i = 0, len = customers.length; i < len; i++) {
			const ctor = customers[i];
A
Alex Dima 已提交
205 206
			const instance = this._instantiationService.createInstance(ctor, extHostContext);
			this._extensionHostProcessCustomers.push(instance);
207
		}
208

209 210
		// Check that no named customers are missing
		const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => MainContext[key]);
A
Alex Dima 已提交
211
		this._extensionHostProcessThreadService.assertRegistered(expected);
212

A
Alex Dima 已提交
213
		return this._extensionHostProcessThreadService.get(ExtHostContext.ExtHostExtensionService);
214
	}
215

216
	// ---- begin IExtensionService
217

218
	public activateByEvent(activationEvent: string): TPromise<void> {
219
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
220 221 222 223 224 225 226 227 228 229
			// Extensions have been scanned and interpreted

			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;

230 231
			return this._activateByEvent(activationEvent);
		} else {
A
Alex Dima 已提交
232 233 234 235 236
			// Extensions have not been scanned yet.

			// Record the fact that this activationEvent was requested (in case of a restart)
			this._allRequestedActivateEvents[activationEvent] = true;

237 238 239
			return this._barrier.wait().then(() => this._activateByEvent(activationEvent));
		}
	}
240

241
	protected _activateByEvent(activationEvent: string): TPromise<void> {
242
		if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
243
			return NO_OP_VOID_PROMISE;
244
		}
A
Alex Dima 已提交
245
		return this._extensionHostProcessProxy.then((proxy) => {
246 247
			return proxy.value.$activateByEvent(activationEvent);
		}).then(() => {
A
Alex Dima 已提交
248
			this._extensionHostProcessFinishedActivateEvents[activationEvent] = true;
249
		});
250 251
	}

252 253 254
	public onReady(): TPromise<boolean> {
		return this._barrier.wait();
	}
255

A
Alex Dima 已提交
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
	public getExtensions(): TPromise<IExtensionDescription[]> {
		return this.onReady().then(() => {
			return this._registry.getAllExtensionDescriptions();
		});
	}

	public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]> {
		return this.onReady().then(() => {
			let availableExtensions = this._registry.getAllExtensionDescriptions();

			let result: ExtensionPointContribution<T>[] = [], resultLen = 0;
			for (let i = 0, len = availableExtensions.length; i < len; i++) {
				let desc = availableExtensions[i];

				if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
					result[resultLen++] = new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name]);
				}
			}

			return result;
		});
	}

279
	public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
280 281 282
		return this._extensionsStatus;
	}

283 284 285 286
	public getExtensionsActivationTimes(): { [id: string]: ActivationTimes; } {
		return this._extensionHostProcessActivationTimes;
	}

287
	// ---- end IExtensionService
288

289 290
	// --- impl

291
	private _scanAndHandleExtensions(): void {
292

293 294 295
		const log = new Logger((severity, source, message) => {
			this._logOrShowMessage(severity, this._isDev ? messageWithSource2(source, message) : message);
		});
296

297
		ExtensionService._scanInstalledExtensions(this._environmentService, log).then((installedExtensions) => {
298 299 300 301 302
			const disabledExtensions = [
				...getGloballyDisabledExtensions(this._extensionEnablementService, this._storageService, installedExtensions),
				...this._extensionEnablementService.getWorkspaceDisabledExtensions()
			];

K
kieferrm 已提交
303
			/* __GDPR__
K
kieferrm 已提交
304 305 306 307 308
				"extensionsScanned" : {
					"totalCount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
					"disabledCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
				}
			*/
309 310 311 312
			this._telemetryService.publicLog('extensionsScanned', {
				totalCount: installedExtensions.length,
				disabledCount: disabledExtensions.length
			});
313

314 315 316 317 318 319
			if (disabledExtensions.length === 0) {
				return installedExtensions;
			}
			return installedExtensions.filter(e => disabledExtensions.every(id => !areSameExtensions({ id }, e)));

		}).then((extensionDescriptions) => {
A
Alex Dima 已提交
320
			this._registry = new ExtensionDescriptionRegistry(extensionDescriptions);
321 322 323 324 325 326 327

			let availableExtensions = this._registry.getAllExtensionDescriptions();
			let extensionPoints = ExtensionsRegistry.getExtensionPoints();

			let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);

			for (let i = 0, len = extensionPoints.length; i < len; i++) {
328 329 330 331 332 333
				const clock = startTimer(`handleExtensionPoint:${extensionPoints[i].name}`);
				try {
					ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
				} finally {
					clock.stop();
				}
334 335 336
			}

			this._barrier.open();
337 338 339
		});
	}

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
	private _handleExtensionPointMessage(msg: IMessage) {

		if (!this._extensionsStatus[msg.source]) {
			this._extensionsStatus[msg.source] = { messages: [] };
		}
		this._extensionsStatus[msg.source].messages.push(msg);

		if (msg.source === this._environmentService.extensionDevelopmentPath) {
			// This message is about the extension currently being developed
			this._showMessageToUser(msg.type, messageWithSource(msg));
		} else {
			this._logMessageInConsole(msg.type, messageWithSource(msg));
		}

		if (!this._isDev && msg.extensionId) {
			const { type, extensionId, extensionPointId, message } = msg;
K
kieferrm 已提交
356
			/* __GDPR__
K
kieferrm 已提交
357 358 359 360 361 362 363
				"extensionsMessage" : {
					"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
					"extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
					"extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
					"message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
				}
			*/
364 365 366 367 368 369 370
			this._telemetryService.publicLog('extensionsMessage', {
				type, extensionId, extensionPointId, message
			});
		}
	}

	private static _scanInstalledExtensions(environmentService: IEnvironmentService, log: ILog): TPromise<IExtensionDescription[]> {
371
		const version = pkg.version;
372 373 374
		const builtinExtensions = ExtensionScanner.scanExtensions(version, log, SystemExtensionsRoot, true);
		const userExtensions = environmentService.disableExtensions || !environmentService.extensionsPath ? TPromise.as([]) : ExtensionScanner.scanExtensions(version, log, environmentService.extensionsPath, false);
		const developedExtensions = environmentService.disableExtensions || !environmentService.isExtensionDevelopment ? TPromise.as([]) : ExtensionScanner.scanOneOrMultipleExtensions(version, log, environmentService.extensionDevelopmentPath, false);
375 376 377 378 379 380 381 382 383 384 385 386

		return TPromise.join([builtinExtensions, userExtensions, developedExtensions]).then<IExtensionDescription[]>((extensionDescriptions: IExtensionDescription[][]) => {
			const builtinExtensions = extensionDescriptions[0];
			const userExtensions = extensionDescriptions[1];
			const developedExtensions = extensionDescriptions[2];

			let result: { [extensionId: string]: IExtensionDescription; } = {};
			builtinExtensions.forEach((builtinExtension) => {
				result[builtinExtension.id] = builtinExtension;
			});
			userExtensions.forEach((userExtension) => {
				if (result.hasOwnProperty(userExtension.id)) {
387
					log.warn(userExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionFolderPath, userExtension.extensionFolderPath));
388 389 390 391
				}
				result[userExtension.id] = userExtension;
			});
			developedExtensions.forEach(developedExtension => {
392
				log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionFolderPath));
393
				if (result.hasOwnProperty(developedExtension.id)) {
394
					log.warn(developedExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionFolderPath, developedExtension.extensionFolderPath));
395 396 397 398 399
				}
				result[developedExtension.id] = developedExtension;
			});

			return Object.keys(result).map(name => result[name]);
R
Ron Buckton 已提交
400
		}).then(null, err => {
401
			log.error('', err);
402 403 404
			return [];
		});
	}
405

406
	private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
		let users: IExtensionPointUser<T>[] = [], usersLen = 0;
		for (let i = 0, len = availableExtensions.length; i < len; i++) {
			let desc = availableExtensions[i];

			if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
				users[usersLen++] = {
					description: desc,
					value: desc.contributes[extensionPoint.name],
					collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
				};
			}
		}

		extensionPoint.acceptUsers(users);
	}

423 424
	private _showMessageToUser(severity: Severity, msg: string): void {
		if (severity === Severity.Error || severity === Severity.Warning) {
425
			this._messageService.show(severity, msg);
426 427 428 429 430 431 432
		} else {
			this._logMessageInConsole(severity, msg);
		}
	}

	private _logMessageInConsole(severity: Severity, msg: string): void {
		if (severity === Severity.Error) {
433 434 435 436 437 438
			console.error(msg);
		} else if (severity === Severity.Warning) {
			console.warn(msg);
		} else {
			console.log(msg);
		}
439
	}
440 441 442 443 444 445 446 447 448 449

	// -- called by extension host

	public _logOrShowMessage(severity: Severity, msg: string): void {
		if (this._isDev) {
			this._showMessageToUser(severity, msg);
		} else {
			this._logMessageInConsole(severity, msg);
		}
	}
450

451 452
	public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number): void {
		this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime);
453
	}
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
}

export class Logger implements ILog {

	private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;

	constructor(
		messageHandler: (severity: Severity, source: string, message: string) => void
	) {
		this._messageHandler = messageHandler;
	}

	public error(source: string, message: string): void {
		this._messageHandler(Severity.Error, source, message);
	}

	public warn(source: string, message: string): void {
		this._messageHandler(Severity.Warning, source, message);
	}

	public info(source: string, message: string): void {
		this._messageHandler(Severity.Info, source, message);
	}
477
}