extHostExtensionService.ts 15.4 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';
17
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes } from 'vs/workbench/api/node/extHostExtensionActivator';
B
Benjamin Pasero 已提交
18 19
import { Barrier } from 'vs/workbench/services/extensions/node/barrier';
import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService';
20 21
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
22
import { realpath } from 'fs';
23
import { TernarySearchTree } from 'vs/base/common/map';
A
Alex Dima 已提交
24

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

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

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

A
Alex Dima 已提交
34
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
35 36 37 38 39 40 41 42 43 44
		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 已提交
45
	get whenReady(): TPromise<ExtensionMemento> {
A
Alex Dima 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
		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);
	}
}

65 66
class ExtensionStoragePath {

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

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

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

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

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

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

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

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

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

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

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

A
Alex Dima 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
		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);
					}
				},

160 161
				actualActivateExtension: (extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> => {
					return this._activateExtension(extensionDescription, startup);
A
Alex Dima 已提交
162
				}
163 164
			});

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

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

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

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

188
	public activateById(extensionId: string, startup: boolean): TPromise<void> {
A
Alex Dima 已提交
189
		if (this._barrier.isOpen()) {
190
			return this._activator.activateById(extensionId, startup);
191
		} else {
192
			return this._barrier.wait().then(() => this._activator.activateById(extensionId, startup));
193 194 195
		}
	}

196 197 198 199 200 201 202 203
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

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

212
	// create trie to enable fast 'filename -> extension id' look up
213
	public getExtensionPathIndex(): TPromise<TernarySearchTree<IExtensionDescription>> {
214
		if (!this._extensionPathIndex) {
215
			const tree = TernarySearchTree.forPaths<IExtensionDescription>();
216 217 218 219 220 221 222 223 224
			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 {
225
							tree.set(path, ext);
226 227 228 229 230
							resolve(void 0);
						}
					});
				});
			});
231
			this._extensionPathIndex = TPromise.join(extensions).then(() => tree);
232 233 234 235 236
		}
		return this._extensionPathIndex;
	}


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

A
Alex Dima 已提交
240 241 242 243 244
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
245 246 247
			return result;
		}

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

		// call deactivate if available
		try {
A
Alex Dima 已提交
255
			if (typeof extension.module.deactivate === 'function') {
256 257 258 259
				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);
				});
260
			}
B
Benjamin Pasero 已提交
261
		} catch (err) {
262 263 264 265 266
			// TODO: Do something with err if this is not the shutdown case
		}

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

		return result;
273
	}
E
Erich Gamma 已提交
274

A
Alex Dima 已提交
275
	// --- impl
A
Alex Dima 已提交
276

277 278
	private _activateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
		return this._doActivateExtension(extensionDescription, startup).then((activatedExtension) => {
279
			const activationTimes = activatedExtension.activationTimes;
280
			this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime);
A
Alex Dima 已提交
281 282 283 284 285
			return activatedExtension;
		}, (err) => {
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
			throw err;
		});
E
Erich Gamma 已提交
286 287
	}

288
	private _doActivateExtension(extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
289
		let event = getTelemetryActivationEvent(extensionDescription);
K
kieferrm 已提交
290
		/* __GDPR__
K
kieferrm 已提交
291 292 293 294 295 296
			"activatePlugin" : {
				"${include}": [
					"${TelemetryActivationEvent}"
				]
			}
		*/
297
		this._mainThreadTelemetry.$publicLog('activatePlugin', event);
A
Alex Dima 已提交
298 299
		if (!extensionDescription.main) {
			// Treat the extension as being empty => NOT AN ERROR CASE
300
			return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
A
Alex Dima 已提交
301
		}
302

303
		const activationTimesBuilder = new ExtensionActivationTimesBuilder(startup);
A
Alex Dima 已提交
304
		return TPromise.join<any>([
305
			loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
A
Alex Dima 已提交
306 307
			this._loadExtensionContext(extensionDescription)
		]).then(values => {
308
			return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
A
Alex Dima 已提交
309 310 311 312 313 314 315 316 317 318
		}, (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 已提交
319 320
	}

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

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

326 327 328 329 330
		return TPromise.join([
			globalState.whenReady,
			workspaceState.whenReady,
			this._storagePath.whenReady
		]).then(() => {
A
Alex Dima 已提交
331
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
332 333 334
				globalState,
				workspaceState,
				subscriptions: [],
A
Alex Dima 已提交
335
				get extensionPath() { return extensionDescription.extensionFolderPath; },
336
				storagePath: this._storagePath.value(extensionDescription),
337
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }
E
Erich Gamma 已提交
338 339 340 341
			});
		});
	}

342
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<ActivatedExtension> {
A
Alex Dima 已提交
343 344
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
345 346 347 348
			activate: undefined,
			deactivate: undefined
		};

349 350
		return this._callActivateOptional(extensionModule, context, activationTimesBuilder).then((extensionExports) => {
			return new ActivatedExtension(false, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
351 352 353
		});
	}

354
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<IExtensionAPI> {
A
Alex Dima 已提交
355
		if (typeof extensionModule.activate === 'function') {
356
			try {
357
				activationTimesBuilder.activateCallStart();
358
				const activateResult: TPromise<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
359 360 361 362 363 364 365
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
				return TPromise.as(activateResult).then((value) => {
					activationTimesBuilder.activateResolveStop();
					return value;
				});
366
			} catch (err) {
A
Alex Dima 已提交
367
				return TPromise.wrapError(err);
368 369
			}
		} else {
A
Alex Dima 已提交
370 371
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
372 373 374
		}
	}

E
Erich Gamma 已提交
375 376
	// -- called by main thread

377
	public $activateByEvent(activationEvent: string): TPromise<void> {
378
		return this.activateByEvent(activationEvent, false);
379 380 381
	}
}

382
function loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<T> {
B
Benjamin Pasero 已提交
383
	let r: T = null;
384
	activationTimesBuilder.codeLoadingStart();
E
Erich Gamma 已提交
385 386
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
387
	} catch (e) {
388
		return TPromise.wrapError<T>(e);
389 390
	} finally {
		activationTimesBuilder.codeLoadingStop();
E
Erich Gamma 已提交
391
	}
A
Alex Dima 已提交
392
	return TPromise.as(r);
E
Erich Gamma 已提交
393
}
A
Alex Dima 已提交
394 395

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
K
kieferrm 已提交
396
	/* __GDPR__FRAGMENT__
K
kieferrm 已提交
397 398 399 400 401 402 403 404 405 406 407
		"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 已提交
408
			]
K
kieferrm 已提交
409 410
		}
	*/
A
Alex Dima 已提交
411 412 413 414
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
415 416
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
	};

	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;
449
}