extHostExtensionService.ts 16.8 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

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

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

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

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

A
Alex Dima 已提交
35
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
36 37 38 39 40 41 42 43
		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;
		});
44 45 46 47 48 49

		this._storageListener = this._storage.onDidChangeStorage(e => {
			if (e.shared === this._shared && e.key === this._id) {
				this._value = e.value;
			}
		});
A
Alex Dima 已提交
50 51
	}

52
	get whenReady(): Thenable<ExtensionMemento> {
A
Alex Dima 已提交
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
		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);
	}
70 71 72 73

	dispose(): void {
		this._storageListener.dispose();
	}
A
Alex Dima 已提交
74 75
}

76 77
class ExtensionStoragePath {

78
	private readonly _workspace: IWorkspaceData;
79 80
	private readonly _environment: IEnvironment;

81
	private readonly _ready: Promise<string>;
82 83
	private _value: string;

84
	constructor(workspace: IWorkspaceData, environment: IEnvironment) {
85
		this._workspace = workspace;
86 87 88 89
		this._environment = environment;
		this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
	}

90
	get whenReady(): Promise<any> {
91 92 93
		return this._ready;
	}

94 95
	value(extension: IExtensionDescription): string {
		if (this._value) {
96
			return join(this._value, extension.id);
97
		}
M
Matt Bierner 已提交
98
		return undefined;
99 100
	}

101
	private async _getOrCreateWorkspaceStoragePath(): Promise<string> {
102
		if (!this._workspace) {
103 104
			return TPromise.as(undefined);
		}
J
Johannes Rieken 已提交
105

106
		const storageName = this._workspace.id;
107
		const storagePath = join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName);
108

J
Johannes Rieken 已提交
109
		const exists = await dirExists(storagePath);
110

J
Johannes Rieken 已提交
111 112 113 114 115 116 117 118
		if (exists) {
			return storagePath;
		}

		try {
			await mkdirp(storagePath);
			await writeFile(
				join(storagePath, 'meta.json'),
119 120
				JSON.stringify({
					id: this._workspace.id,
J
Johannes Rieken 已提交
121
					configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
122 123
					name: this._workspace.name
				}, undefined, 2)
J
Johannes Rieken 已提交
124 125 126 127 128 129 130
			);
			return storagePath;

		} catch (e) {
			console.error(e);
			return undefined;
		}
131 132 133
	}
}

A
Alex Dima 已提交
134
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
A
Alex Dima 已提交
135 136 137

	private readonly _barrier: Barrier;
	private readonly _registry: ExtensionDescriptionRegistry;
138
	private readonly _mainThreadTelemetry: MainThreadTelemetryShape;
A
Alex Dima 已提交
139 140
	private readonly _storage: ExtHostStorage;
	private readonly _storagePath: ExtensionStoragePath;
141
	private readonly _proxy: MainThreadExtensionServiceShape;
142
	private readonly _extHostLogService: ExtHostLogService;
A
Alex Dima 已提交
143
	private _activator: ExtensionsActivator;
144
	private _extensionPathIndex: TPromise<TernarySearchTree<IExtensionDescription>>;
A
Alex Dima 已提交
145 146 147
	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
148
	constructor(initData: IInitData,
149
		extHostContext: IMainContext,
150
		extHostWorkspace: ExtHostWorkspace,
J
Joao Moreno 已提交
151
		extHostConfiguration: ExtHostConfiguration,
A
Alex Dima 已提交
152
		extHostLogService: ExtHostLogService
153
	) {
A
Alex Dima 已提交
154 155
		this._barrier = new Barrier();
		this._registry = new ExtensionDescriptionRegistry(initData.extensions);
156
		this._extHostLogService = extHostLogService;
A
Alex Dima 已提交
157 158
		this._mainThreadTelemetry = extHostContext.getProxy(MainContext.MainThreadTelemetry);
		this._storage = new ExtHostStorage(extHostContext);
A
Alex Dima 已提交
159
		this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
A
Alex Dima 已提交
160
		this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService);
A
Alex Dima 已提交
161
		this._activator = null;
162

A
Alex Dima 已提交
163
		// initialize API first (i.e. do not release barrier until the API is initialized)
164
		const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, this._extHostLogService, this._storage);
165

A
Alex Dima 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
		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 已提交
184 185
				actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> => {
					return this._activateExtension(extensionDescription, reason);
A
Alex Dima 已提交
186
				}
187 188
			});

A
Alex Dima 已提交
189 190
			this._barrier.open();
		});
191 192
	}

A
Alex Dima 已提交
193 194
	public onExtensionAPIReady(): TPromise<boolean> {
		return this._barrier.wait();
195 196 197
	}

	public isActivated(extensionId: string): boolean {
A
Alex Dima 已提交
198 199 200 201
		if (this._barrier.isOpen()) {
			return this._activator.isActivated(extensionId);
		}
		return false;
202 203
	}

204
	public activateByEvent(activationEvent: string, startup: boolean): TPromise<void> {
A
Alex Dima 已提交
205
		const reason = new ExtensionActivatedByEvent(startup, activationEvent);
A
Alex Dima 已提交
206
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
207
			return this._activator.activateByEvent(activationEvent, reason);
208
		} else {
A
Alex Dima 已提交
209
			return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason));
210 211 212
		}
	}

A
Alex Dima 已提交
213
	public activateById(extensionId: string, reason: ExtensionActivationReason): TPromise<void> {
A
Alex Dima 已提交
214
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
215
			return this._activator.activateById(extensionId, reason);
216
		} else {
A
Alex Dima 已提交
217
			return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason));
218 219 220
		}
	}

221 222 223 224 225 226 227 228 229 230 231
	public activateByIdWithErrors(extensionId: string, reason: ExtensionActivationReason): TPromise<void> {
		return this.activateById(extensionId, reason).then(() => {
			const extension = this._activator.getActivatedExtension(extensionId);
			if (extension.activationFailed) {
				// activation failed => bubble up the error as the promise result
				return TPromise.wrapError(extension.activationFailedError);
			}
			return void 0;
		});
	}

232 233 234 235 236 237 238 239
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

A
Alex Dima 已提交
240 241 242 243 244
	public getExtensionExports(extensionId: string): IExtensionAPI {
		if (this._barrier.isOpen()) {
			return this._activator.getActivatedExtension(extensionId).exports;
		} else {
			return null;
E
Erich Gamma 已提交
245 246 247
		}
	}

248
	// create trie to enable fast 'filename -> extension id' look up
249
	public getExtensionPathIndex(): TPromise<TernarySearchTree<IExtensionDescription>> {
250
		if (!this._extensionPathIndex) {
251
			const tree = TernarySearchTree.forPaths<IExtensionDescription>();
252 253 254 255
			const extensions = this.getAllExtensionDescriptions().map(ext => {
				if (!ext.main) {
					return undefined;
				}
256
				return realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
257
			});
258
			this._extensionPathIndex = TPromise.join(extensions).then(() => tree);
259 260 261 262 263
		}
		return this._extensionPathIndex;
	}


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

A
Alex Dima 已提交
267 268 269 270 271
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
272 273 274
			return result;
		}

A
Alex Dima 已提交
275
		let extension = this._activator.getActivatedExtension(extensionId);
A
Alex Dima 已提交
276
		if (!extension) {
277
			return result;
278 279 280 281
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
282
			if (typeof extension.module.deactivate === 'function') {
283 284 285 286
				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);
				});
287
			}
B
Benjamin Pasero 已提交
288
		} catch (err) {
289 290 291 292 293
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
294
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
295
		} catch (err) {
296 297
			// TODO: Do something with err if this is not the shutdown case
		}
298 299

		return result;
300
	}
E
Erich Gamma 已提交
301

A
Alex Dima 已提交
302 303 304 305
	public addMessage(extensionId: string, severity: Severity, message: string): void {
		this._proxy.$addMessage(extensionId, severity, message);
	}

A
Alex Dima 已提交
306
	// --- impl
A
Alex Dima 已提交
307

A
Alex Dima 已提交
308 309
	private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
		return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
310
			const activationTimes = activatedExtension.activationTimes;
A
Alex Dima 已提交
311 312
			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 已提交
313 314 315 316 317
			return activatedExtension;
		}, (err) => {
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
			throw err;
		});
E
Erich Gamma 已提交
318 319
	}

A
Alex Dima 已提交
320
	private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TPromise<ActivatedExtension> {
R
Rob Lourens 已提交
321
		let event = getTelemetryActivationEvent(extensionDescription, reason);
K
kieferrm 已提交
322
		/* __GDPR__
K
kieferrm 已提交
323 324 325 326 327 328
			"activatePlugin" : {
				"${include}": [
					"${TelemetryActivationEvent}"
				]
			}
		*/
329
		this._mainThreadTelemetry.$publicLog('activatePlugin', event);
A
Alex Dima 已提交
330 331
		if (!extensionDescription.main) {
			// Treat the extension as being empty => NOT AN ERROR CASE
332
			return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
A
Alex Dima 已提交
333
		}
334

335
		this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.id} ${JSON.stringify(reason)}`);
336

A
Alex Dima 已提交
337
		const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
A
Alex Dima 已提交
338
		return TPromise.join<any>([
339
			loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder),
A
Alex Dima 已提交
340 341
			this._loadExtensionContext(extensionDescription)
		]).then(values => {
342
			return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.id, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
A
Alex Dima 已提交
343 344 345 346 347 348 349 350 351 352
		}, (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 已提交
353 354
	}

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

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

360
		this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.id}`);
361 362 363 364 365
		return TPromise.join([
			globalState.whenReady,
			workspaceState.whenReady,
			this._storagePath.whenReady
		]).then(() => {
366
			const that = this;
A
Alex Dima 已提交
367
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
368 369 370
				globalState,
				workspaceState,
				subscriptions: [],
371
				get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
372
				storagePath: this._storagePath.value(extensionDescription),
373
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); },
374
				logPath: that._extHostLogService.getLogDirectory(extensionDescription.id)
E
Erich Gamma 已提交
375 376 377 378
			});
		});
	}

379
	private static _callActivate(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<ActivatedExtension> {
A
Alex Dima 已提交
380 381
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
382 383 384 385
			activate: undefined,
			deactivate: undefined
		};

386
		return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => {
387
			return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
388 389 390
		});
	}

391
	private static _callActivateOptional(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<IExtensionAPI> {
A
Alex Dima 已提交
392
		if (typeof extensionModule.activate === 'function') {
393
			try {
394
				activationTimesBuilder.activateCallStart();
395
				logService.trace(`ExtensionService#_callActivateOptional ${extensionId}`);
396
				const activateResult: TPromise<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
397 398 399 400 401 402 403
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
				return TPromise.as(activateResult).then((value) => {
					activationTimesBuilder.activateResolveStop();
					return value;
				});
404
			} catch (err) {
A
Alex Dima 已提交
405
				return TPromise.wrapError(err);
406 407
			}
		} else {
A
Alex Dima 已提交
408 409
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
410 411 412
		}
	}

E
Erich Gamma 已提交
413 414
	// -- called by main thread

A
Alex Dima 已提交
415
	public $activateByEvent(activationEvent: string): Thenable<void> {
416
		return this.activateByEvent(activationEvent, false);
417 418 419
	}
}

420
function loadCommonJSModule<T>(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): TPromise<T> {
421
	let r: T | null = null;
422
	activationTimesBuilder.codeLoadingStart();
423
	logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`);
E
Erich Gamma 已提交
424 425
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
426
	} catch (e) {
427
		return TPromise.wrapError<T>(e);
428 429
	} finally {
		activationTimesBuilder.codeLoadingStop();
E
Erich Gamma 已提交
430
	}
A
Alex Dima 已提交
431
	return TPromise.as(r);
E
Erich Gamma 已提交
432
}
A
Alex Dima 已提交
433

R
Rob Lourens 已提交
434 435 436 437 438
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): any {
	const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent :
		reason instanceof ExtensionActivatedByAPI ? 'api' :
			'';

K
kieferrm 已提交
439
	/* __GDPR__FRAGMENT__
K
kieferrm 已提交
440 441 442
		"TelemetryActivationEvent" : {
			"id": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
443
			"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
444
			"activationEvents": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
R
Rob Lourens 已提交
445 446
			"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
			"reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
K
kieferrm 已提交
447 448
		}
	*/
A
Alex Dima 已提交
449 450 451
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
452
		publisherDisplayName: extensionDescription.publisher,
453
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
R
Rob Lourens 已提交
454 455
		isBuiltin: extensionDescription.isBuiltin,
		reason: reasonStr
A
Alex Dima 已提交
456 457 458
	};

	return event;
459
}