extHostExtensionService.ts 16.0 KB
Newer Older
E
Erich Gamma 已提交
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';

A
Alex Dima 已提交
7
import { dispose } from 'vs/base/common/lifecycle';
8
import { join } from 'path';
9
import { mkdirp, dirExists } from 'vs/base/node/pfs';
E
Erich Gamma 已提交
10
import Severity from 'vs/base/common/severity';
J
Johannes Rieken 已提交
11
import { TPromise } from 'vs/base/common/winjs.base';
B
Benjamin Pasero 已提交
12
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
A
Alex Dima 已提交
13
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
J
Johannes Rieken 已提交
14
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
15
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
16
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape } from './extHost.protocol';
A
Alex Dima 已提交
17
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator';
B
Benjamin Pasero 已提交
18
import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService';
19 20
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
21
import { realpath } from 'fs';
22
import { TernarySearchTree } from 'vs/base/common/map';
J
Joao Moreno 已提交
23
import { Barrier } from 'vs/base/common/async';
J
Joao Moreno 已提交
24
import { ILogService } from 'vs/platform/log/common/log';
A
Alex Dima 已提交
25

A
Alex Dima 已提交
26
class ExtensionMemento implements IExtensionMemento {
A
Alex Dima 已提交
27 28 29

	private _id: string;
	private _shared: boolean;
A
Alex Dima 已提交
30
	private _storage: ExtHostStorage;
A
Alex Dima 已提交
31

A
Alex Dima 已提交
32
	private _init: TPromise<ExtensionMemento>;
A
Alex Dima 已提交
33 34
	private _value: { [n: string]: any; };

A
Alex Dima 已提交
35
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
36 37 38 39 40 41 42 43 44 45
		this._id = id;
		this._shared = global;
		this._storage = storage;

		this._init = this._storage.getValue(this._shared, this._id, Object.create(null)).then(value => {
			this._value = value;
			return this;
		});
	}

A
Alex Dima 已提交
46
	get whenReady(): TPromise<ExtensionMemento> {
A
Alex Dima 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
		return this._init;
	}

	get<T>(key: string, defaultValue: T): T {
		let value = this._value[key];
		if (typeof value === 'undefined') {
			value = defaultValue;
		}
		return value;
	}

	update(key: string, value: any): Thenable<boolean> {
		this._value[key] = value;
		return this._storage
			.setValue(this._shared, this._id, this._value)
			.then(() => true);
	}
}

66 67
class ExtensionStoragePath {

68
	private readonly _workspace: IWorkspaceData;
69 70 71 72 73
	private readonly _environment: IEnvironment;

	private readonly _ready: TPromise<string>;
	private _value: string;

74
	constructor(workspace: IWorkspaceData, environment: IEnvironment) {
75
		this._workspace = workspace;
76 77 78 79 80 81 82 83
		this._environment = environment;
		this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
	}

	get whenReady(): TPromise<any> {
		return this._ready;
	}

84 85
	value(extension: IExtensionDescription): string {
		if (this._value) {
86
			return join(this._value, extension.id);
87
		}
M
Matt Bierner 已提交
88
		return undefined;
89 90 91
	}

	private _getOrCreateWorkspaceStoragePath(): TPromise<string> {
92
		if (!this._workspace) {
93 94
			return TPromise.as(undefined);
		}
95
		const storageName = this._workspace.id;
96
		const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName);
97 98 99 100 101 102

		return dirExists(storagePath).then(exists => {
			if (exists) {
				return storagePath;
			}

J
Johannes Rieken 已提交
103
			return mkdirp(storagePath).then(success => {
104 105 106 107 108 109 110 111
				return storagePath;
			}, err => {
				return undefined;
			});
		});
	}
}

A
Alex Dima 已提交
112
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
A
Alex Dima 已提交
113 114 115

	private readonly _barrier: Barrier;
	private readonly _registry: ExtensionDescriptionRegistry;
116
	private readonly _threadService: ExtHostThreadService;
117
	private readonly _mainThreadTelemetry: MainThreadTelemetryShape;
A
Alex Dima 已提交
118 119
	private readonly _storage: ExtHostStorage;
	private readonly _storagePath: ExtensionStoragePath;
120
	private readonly _proxy: MainThreadExtensionServiceShape;
A
Alex Dima 已提交
121
	private _activator: ExtensionsActivator;
122
	private _extensionPathIndex: TPromise<TernarySearchTree<IExtensionDescription>>;
A
Alex Dima 已提交
123 124 125
	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
126 127 128
	constructor(initData: IInitData,
		threadService: ExtHostThreadService,
		extHostWorkspace: ExtHostWorkspace,
J
Joao Moreno 已提交
129 130
		extHostConfiguration: ExtHostConfiguration,
		logService: ILogService
131
	) {
A
Alex Dima 已提交
132 133 134
		this._barrier = new Barrier();
		this._registry = new ExtensionDescriptionRegistry(initData.extensions);
		this._threadService = threadService;
135
		this._mainThreadTelemetry = threadService.get(MainContext.MainThreadTelemetry);
A
Alex Dima 已提交
136 137
		this._storage = new ExtHostStorage(threadService);
		this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
138
		this._proxy = this._threadService.get(MainContext.MainThreadExtensionService);
A
Alex Dima 已提交
139
		this._activator = null;
140

A
Alex Dima 已提交
141
		// initialize API first (i.e. do not release barrier until the API is initialized)
J
Joao Moreno 已提交
142
		const apiFactory = createApiFactory(initData, threadService, extHostWorkspace, extHostConfiguration, this, logService);
143

A
Alex Dima 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
		initializeExtensionApi(this, apiFactory).then(() => {

			this._activator = new ExtensionsActivator(this._registry, {
				showMessage: (severity: Severity, message: string): void => {
					this._proxy.$localShowMessage(severity, message);

					switch (severity) {
						case Severity.Error:
							console.error(message);
							break;
						case Severity.Warning:
							console.warn(message);
							break;
						default:
							console.log(message);
					}
				},

A
Alex Dima 已提交
162 163
				actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> => {
					return this._activateExtension(extensionDescription, reason);
A
Alex Dima 已提交
164
				}
165 166
			});

A
Alex Dima 已提交
167 168
			this._barrier.open();
		});
169 170
	}

A
Alex Dima 已提交
171 172
	public onExtensionAPIReady(): TPromise<boolean> {
		return this._barrier.wait();
173 174 175
	}

	public isActivated(extensionId: string): boolean {
A
Alex Dima 已提交
176 177 178 179
		if (this._barrier.isOpen()) {
			return this._activator.isActivated(extensionId);
		}
		return false;
180 181
	}

182
	public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
A
Alex Dima 已提交
183
		const reason = new ExtensionActivatedByEvent(startup, activationEvent);
A
Alex Dima 已提交
184
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
185
			return this._activator.activateByEvent(activationEvent, reason);
186
		} else {
A
Alex Dima 已提交
187
			return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason));
188 189 190
		}
	}

A
Alex Dima 已提交
191
	public activateById(extensionId: string, reason: ExtensionActivationReason): TPromise<void> {
A
Alex Dima 已提交
192
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
193
			return this._activator.activateById(extensionId, reason);
194
		} else {
A
Alex Dima 已提交
195
			return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason));
196 197 198
		}
	}

199 200 201 202 203 204 205 206
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

	public getExtensionDescription(extensionId: string): IExtensionDescription {
		return this._registry.getExtensionDescription(extensionId);
	}

A
Alex Dima 已提交
207 208 209 210 211
	public getExtensionExports(extensionId: string): IExtensionAPI {
		if (this._barrier.isOpen()) {
			return this._activator.getActivatedExtension(extensionId).exports;
		} else {
			return null;
E
Erich Gamma 已提交
212 213 214
		}
	}

215
	// create trie to enable fast 'filename -> extension id' look up
216
	public getExtensionPathIndex(): TPromise<TernarySearchTree<IExtensionDescription>> {
217
		if (!this._extensionPathIndex) {
218
			const tree = TernarySearchTree.forPaths<IExtensionDescription>();
219 220 221 222 223 224 225 226 227
			const extensions = this.getAllExtensionDescriptions().map(ext => {
				if (!ext.main) {
					return undefined;
				}
				return new TPromise((resolve, reject) => {
					realpath(ext.extensionFolderPath, (err, path) => {
						if (err) {
							reject(err);
						} else {
228
							tree.set(path, ext);
229 230 231 232 233
							resolve(void 0);
						}
					});
				});
			});
234
			this._extensionPathIndex = TPromise.join(extensions).then(() => tree);
235 236 237 238 239
		}
		return this._extensionPathIndex;
	}


240
	public deactivate(extensionId: string): TPromise<void> {
J
Johannes Rieken 已提交
241
		let result: TPromise<void> = TPromise.as(void 0);
242

A
Alex Dima 已提交
243 244 245 246 247
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
248 249 250
			return result;
		}

A
Alex Dima 已提交
251
		let extension = this._activator.getActivatedExtension(extensionId);
A
Alex Dima 已提交
252
		if (!extension) {
253
			return result;
254 255 256 257
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
258
			if (typeof extension.module.deactivate === 'function') {
259 260 261 262
				result = TPromise.wrap(extension.module.deactivate()).then(null, (err) => {
					// TODO: Do something with err if this is not the shutdown case
					return TPromise.as(void 0);
				});
263
			}
B
Benjamin Pasero 已提交
264
		} catch (err) {
265 266 267 268 269
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
270
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
271
		} catch (err) {
272 273
			// TODO: Do something with err if this is not the shutdown case
		}
274 275

		return result;
276
	}
E
Erich Gamma 已提交
277

A
Alex Dima 已提交
278 279 280 281
	public addMessage(extensionId: string, severity: Severity, message: string): void {
		this._proxy.$addMessage(extensionId, severity, message);
	}

A
Alex Dima 已提交
282
	// --- impl
A
Alex Dima 已提交
283

A
Alex Dima 已提交
284 285
	private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
		return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
286
			const activationTimes = activatedExtension.activationTimes;
A
Alex Dima 已提交
287 288
			let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null);
			this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent);
A
Alex Dima 已提交
289 290 291 292 293
			return activatedExtension;
		}, (err) => {
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
			throw err;
		});
E
Erich Gamma 已提交
294 295
	}

A
Alex Dima 已提交
296
	private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
297
		let event = getTelemetryActivationEvent(extensionDescription);
K
kieferrm 已提交
298
		/* __GDPR__
K
kieferrm 已提交
299 300 301 302 303 304
			"activatePlugin" : {
				"${include}": [
					"${TelemetryActivationEvent}"
				]
			}
		*/
305
		this._mainThreadTelemetry.$publicLog('activatePlugin', event);
A
Alex Dima 已提交
306 307
		if (!extensionDescription.main) {
			// Treat the extension as being empty => NOT AN ERROR CASE
308
			return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
A
Alex Dima 已提交
309
		}
310

A
Alex Dima 已提交
311
		const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
A
Alex Dima 已提交
312
		return TPromise.join<any>([
313
			loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
A
Alex Dima 已提交
314 315
			this._loadExtensionContext(extensionDescription)
		]).then(values => {
316
			return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
A
Alex Dima 已提交
317 318 319 320 321 322 323 324 325 326
		}, (errors: any[]) => {
			// Avoid failing with an array of errors, fail with a single error
			if (errors[0]) {
				return TPromise.wrapError<ActivatedExtension>(errors[0]);
			}
			if (errors[1]) {
				return TPromise.wrapError<ActivatedExtension>(errors[1]);
			}
			return undefined;
		});
E
Erich Gamma 已提交
327 328
	}

A
Alex Dima 已提交
329
	private _loadExtensionContext(extensionDescription: IExtensionDescription): TPromise<IExtensionContext> {
E
Erich Gamma 已提交
330

A
Alex Dima 已提交
331 332
		let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
		let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
E
Erich Gamma 已提交
333

334 335 336 337 338
		return TPromise.join([
			globalState.whenReady,
			workspaceState.whenReady,
			this._storagePath.whenReady
		]).then(() => {
A
Alex Dima 已提交
339
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
340 341 342
				globalState,
				workspaceState,
				subscriptions: [],
A
Alex Dima 已提交
343
				get extensionPath() { return extensionDescription.extensionFolderPath; },
344
				storagePath: this._storagePath.value(extensionDescription),
345
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }
E
Erich Gamma 已提交
346 347 348 349
			});
		});
	}

350
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<ActivatedExtension> {
A
Alex Dima 已提交
351 352
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
353 354 355 356
			activate: undefined,
			deactivate: undefined
		};

357 358
		return this._callActivateOptional(extensionModule, context, activationTimesBuilder).then((extensionExports) => {
			return new ActivatedExtension(false, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
359 360 361
		});
	}

362
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<IExtensionAPI> {
A
Alex Dima 已提交
363
		if (typeof extensionModule.activate === 'function') {
364
			try {
365
				activationTimesBuilder.activateCallStart();
366
				const activateResult: TPromise<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
367 368 369 370 371 372 373
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
				return TPromise.as(activateResult).then((value) => {
					activationTimesBuilder.activateResolveStop();
					return value;
				});
374
			} catch (err) {
A
Alex Dima 已提交
375
				return TPromise.wrapError(err);
376 377
			}
		} else {
A
Alex Dima 已提交
378 379
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
380 381 382
		}
	}

E
Erich Gamma 已提交
383 384
	// -- called by main thread

385
	public $activateByEvent(activationEvent: string): TPromise<void> {
386
		return this.activateByEvent(activationEvent, false);
387 388 389
	}
}

390
function loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<T> {
B
Benjamin Pasero 已提交
391
	let r: T = null;
392
	activationTimesBuilder.codeLoadingStart();
E
Erich Gamma 已提交
393 394
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
395
	} catch (e) {
396
		return TPromise.wrapError<T>(e);
397 398
	} finally {
		activationTimesBuilder.codeLoadingStop();
E
Erich Gamma 已提交
399
	}
A
Alex Dima 已提交
400
	return TPromise.as(r);
E
Erich Gamma 已提交
401
}
A
Alex Dima 已提交
402 403

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
K
kieferrm 已提交
404
	/* __GDPR__FRAGMENT__
K
kieferrm 已提交
405 406 407 408 409 410 411 412 413 414 415
		"TelemetryActivationEvent" : {
			"id": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"publisherDisplayName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"activationEvents": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
			"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
			"${wildcard}": [
				{
					"${prefix}": "contribution.",
					"${classification}": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
K
kieferrm 已提交
416
			]
K
kieferrm 已提交
417 418
		}
	*/
A
Alex Dima 已提交
419 420 421 422
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
423 424
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
	};

	for (let contribution in extensionDescription.contributes) {
		let contributionDetails = extensionDescription.contributes[contribution];

		if (!contributionDetails) {
			continue;
		}

		switch (contribution) {
			case 'debuggers':
				let types = contributionDetails.reduce((p, c) => p ? p + ',' + c['type'] : c['type'], '');
				event['contribution.debuggers'] = types;
				break;
			case 'grammars':
				let grammers = contributionDetails.reduce((p, c) => p ? p + ',' + c['language'] : c['language'], '');
				event['contribution.grammars'] = grammers;
				break;
			case 'languages':
				let languages = contributionDetails.reduce((p, c) => p ? p + ',' + c['id'] : c['id'], '');
				event['contribution.languages'] = languages;
				break;
			case 'tmSnippets':
				let tmSnippets = contributionDetails.reduce((p, c) => p ? p + ',' + c['languageId'] : c['languageId'], '');
				event['contribution.tmSnippets'] = tmSnippets;
				break;
			default:
				event[`contribution.${contribution}`] = true;
		}
	}

	return event;
457
}