extHostExtensionService.ts 14.5 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
import { realpath } from 'fs';
21
import { StringTrieMap } from 'vs/base/common/map';
A
Alex Dima 已提交
22

A
Alex Dima 已提交
23
class ExtensionMemento implements IExtensionMemento {
A
Alex Dima 已提交
24 25 26

	private _id: string;
	private _shared: boolean;
A
Alex Dima 已提交
27
	private _storage: ExtHostStorage;
A
Alex Dima 已提交
28

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

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

63 64
class ExtensionStoragePath {

65
	private readonly _workspace: IWorkspaceData;
66 67 68 69 70
	private readonly _environment: IEnvironment;

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

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

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

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

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

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

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

A
Alex Dima 已提交
109
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
A
Alex Dima 已提交
110 111 112

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

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

A
Alex Dima 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
		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);
					}
				},

154 155
				actualActivateExtension: (extensionDescription: IExtensionDescription, startup: boolean): TPromise<ActivatedExtension> => {
					return this._activateExtension(extensionDescription, startup);
A
Alex Dima 已提交
156
				}
157 158
			});

A
Alex Dima 已提交
159 160
			this._barrier.open();
		});
161 162
	}

A
Alex Dima 已提交
163 164
	public onExtensionAPIReady(): TPromise<boolean> {
		return this._barrier.wait();
165 166 167
	}

	public isActivated(extensionId: string): boolean {
A
Alex Dima 已提交
168 169 170 171
		if (this._barrier.isOpen()) {
			return this._activator.isActivated(extensionId);
		}
		return false;
172 173
	}

174
	public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
A
Alex Dima 已提交
175
		if (this._barrier.isOpen()) {
176
			return this._activator.activateByEvent(activationEvent, startup);
177
		} else {
178
			return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, startup));
179 180 181
		}
	}

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

190 191 192 193 194 195 196 197
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

A
Alex Dima 已提交
198 199 200 201 202
	public getExtensionExports(extensionId: string): IExtensionAPI {
		if (this._barrier.isOpen()) {
			return this._activator.getActivatedExtension(extensionId).exports;
		} else {
			return null;
E
Erich Gamma 已提交
203 204 205
		}
	}

206
	// create trie to enable fast 'filename -> extension id' look up
207
	public getExtensionPathIndex(): TPromise<StringTrieMap<IExtensionDescription>> {
208
		if (!this._extensionPathIndex) {
209
			const trie = new StringTrieMap<IExtensionDescription>();
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
			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 {
							trie.insert(path, ext);
							resolve(void 0);
						}
					});
				});
			});
			this._extensionPathIndex = TPromise.join(extensions).then(() => trie);
		}
		return this._extensionPathIndex;
	}


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

A
Alex Dima 已提交
234 235 236 237 238
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
239 240 241
			return result;
		}

A
Alex Dima 已提交
242
		let extension = this._activator.getActivatedExtension(extensionId);
A
Alex Dima 已提交
243
		if (!extension) {
244
			return result;
245 246 247 248
		}

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

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
261
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
262
		} catch (err) {
263 264
			// TODO: Do something with err if this is not the shutdown case
		}
265 266

		return result;
267
	}
E
Erich Gamma 已提交
268

A
Alex Dima 已提交
269
	// --- impl
A
Alex Dima 已提交
270

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

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

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

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

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

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

336
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
337 338
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
339 340 341 342
			activate: undefined,
			deactivate: undefined
		};

343 344
		return this._callActivateOptional(extensionModule, context, activationTimesBuilder).then((extensionExports) => {
			return new ActivatedExtension(false, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
345 346 347
		});
	}

348
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<IExtensionAPI> {
A
Alex Dima 已提交
349
		if (typeof extensionModule.activate === 'function') {
350
			try {
351
				activationTimesBuilder.activateCallStart();
352
				const activateResult: TPromise<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
353 354 355 356 357 358 359
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
				return TPromise.as(activateResult).then((value) => {
					activationTimesBuilder.activateResolveStop();
					return value;
				});
360
			} catch (err) {
A
Alex Dima 已提交
361
				return TPromise.wrapError(err);
362 363
			}
		} else {
A
Alex Dima 已提交
364 365
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
366 367 368
		}
	}

E
Erich Gamma 已提交
369 370
	// -- called by main thread

371
	public $activateByEvent(activationEvent: string): TPromise<void> {
372
		return this.activateByEvent(activationEvent, false);
373 374 375
	}
}

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

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
394 395
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
	};

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