extHostExtensionService.ts 18.0 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 { join } from 'path';
A
Alex Dima 已提交
7 8 9
import { Barrier } from 'vs/base/common/async';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
E
Erich Gamma 已提交
10
import Severity from 'vs/base/common/severity';
A
Alex Dima 已提交
11 12 13
import { URI } from 'vs/base/common/uri';
import { dirExists, mkdirp, realpath, writeFile } from 'vs/base/node/pfs';
import { ILogService } from 'vs/platform/log/common/log';
J
Johannes Rieken 已提交
14
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
A
Alex Dima 已提交
15
import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, IWorkspaceData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol';
16
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
A
Alex Dima 已提交
17
import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule } from 'vs/workbench/api/node/extHostExtensionActivator';
18
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
A
Alex Dima 已提交
19 20
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
21
import { IExtensionDescription, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
A
Alex Dima 已提交
22
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
23
import { connectProxyResolver } from 'vs/workbench/node/proxyResolver';
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
class ExtensionStoragePath {

	private readonly _workspace: IWorkspaceData;
	private readonly _environment: IEnvironment;

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

	constructor(workspace: IWorkspaceData, environment: IEnvironment) {
		this._workspace = workspace;
		this._environment = environment;
		this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
	}

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

94
	workspaceValue(extension: IExtensionDescription): string {
95 96 97 98 99 100
		if (this._value) {
			return join(this._value, extension.id);
		}
		return undefined;
	}

101 102 103 104
	globalValue(extension: IExtensionDescription): string {
		return join(this._environment.globalStorageHome, extension.id);
	}

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	private async _getOrCreateWorkspaceStoragePath(): Promise<string> {
		if (!this._workspace) {
			return Promise.resolve(undefined);
		}

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

		const exists = await dirExists(storagePath);

		if (exists) {
			return storagePath;
		}

		try {
			await mkdirp(storagePath);
			await writeFile(
				join(storagePath, 'meta.json'),
				JSON.stringify({
					id: this._workspace.id,
					configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
					name: this._workspace.name
				}, undefined, 2)
			);
			return storagePath;

		} catch (e) {
			console.error(e);
			return undefined;
		}
	}
}
A
Alex Dima 已提交
137
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
A
Alex Dima 已提交
138 139 140

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

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

A
Alex Dima 已提交
170
		initializeExtensionApi(this, apiFactory).then(() => {
171 172 173
			// Do this when extension service exists, but extensions are not being activated yet.
			return connectProxyResolver(extHostWorkspace, extHostConfiguration, this, this._extHostLogService, this._mainThreadTelemetry);
		}).then(() => {
A
Alex Dima 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

			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);
					}
				},

191
				actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
A
Alex Dima 已提交
192
					return this._activateExtension(extensionDescription, reason);
A
Alex Dima 已提交
193
				}
194 195
			});

A
Alex Dima 已提交
196 197
			this._barrier.open();
		});
198 199
	}

200
	public onExtensionAPIReady(): Thenable<boolean> {
A
Alex Dima 已提交
201
		return this._barrier.wait();
202 203 204
	}

	public isActivated(extensionId: string): boolean {
A
Alex Dima 已提交
205 206 207 208
		if (this._barrier.isOpen()) {
			return this._activator.isActivated(extensionId);
		}
		return false;
209 210
	}

211
	public activateByEvent(activationEvent: string, startup: boolean): Thenable<void> {
A
Alex Dima 已提交
212
		const reason = new ExtensionActivatedByEvent(startup, activationEvent);
A
Alex Dima 已提交
213
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
214
			return this._activator.activateByEvent(activationEvent, reason);
215
		} else {
A
Alex Dima 已提交
216
			return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason));
217 218 219
		}
	}

220
	public activateById(extensionId: string, reason: ExtensionActivationReason): Thenable<void> {
A
Alex Dima 已提交
221
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
222
			return this._activator.activateById(extensionId, reason);
223
		} else {
A
Alex Dima 已提交
224
			return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason));
225 226 227
		}
	}

228
	public activateByIdWithErrors(extensionId: string, reason: ExtensionActivationReason): Thenable<void> {
229 230 231 232
		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
233
				return Promise.reject(extension.activationFailedError);
234 235 236 237 238
			}
			return void 0;
		});
	}

239 240 241 242 243 244 245 246
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

A
Alex Dima 已提交
247 248 249 250 251
	public getExtensionExports(extensionId: string): IExtensionAPI {
		if (this._barrier.isOpen()) {
			return this._activator.getActivatedExtension(extensionId).exports;
		} else {
			return null;
E
Erich Gamma 已提交
252 253 254
		}
	}

255
	// create trie to enable fast 'filename -> extension id' look up
256
	public getExtensionPathIndex(): Promise<TernarySearchTree<IExtensionDescription>> {
257
		if (!this._extensionPathIndex) {
258
			const tree = TernarySearchTree.forPaths<IExtensionDescription>();
259 260 261 262
			const extensions = this.getAllExtensionDescriptions().map(ext => {
				if (!ext.main) {
					return undefined;
				}
263
				return realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
264
			});
265
			this._extensionPathIndex = Promise.all(extensions).then(() => tree);
266 267 268 269 270
		}
		return this._extensionPathIndex;
	}


271 272
	public deactivate(extensionId: string): Thenable<void> {
		let result = Promise.resolve(void 0);
273

A
Alex Dima 已提交
274 275 276 277 278
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
279 280 281
			return result;
		}

A
Alex Dima 已提交
282
		let extension = this._activator.getActivatedExtension(extensionId);
A
Alex Dima 已提交
283
		if (!extension) {
284
			return result;
285 286 287 288
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
289
			if (typeof extension.module.deactivate === 'function') {
290
				result = Promise.resolve(extension.module.deactivate()).then(null, (err) => {
291
					// TODO: Do something with err if this is not the shutdown case
292
					return Promise.resolve(void 0);
293
				});
294
			}
B
Benjamin Pasero 已提交
295
		} catch (err) {
296 297 298 299 300
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
301
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
302
		} catch (err) {
303 304
			// TODO: Do something with err if this is not the shutdown case
		}
305 306

		return result;
307
	}
E
Erich Gamma 已提交
308

A
Alex Dima 已提交
309 310 311 312
	public addMessage(extensionId: string, severity: Severity, message: string): void {
		this._proxy.$addMessage(extensionId, severity, message);
	}

A
Alex Dima 已提交
313
	// --- impl
A
Alex Dima 已提交
314

315
	private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
A
Alex Dima 已提交
316
		return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
317
			const activationTimes = activatedExtension.activationTimes;
A
Alex Dima 已提交
318 319
			let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null);
			this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent);
320
			this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes);
A
Alex Dima 已提交
321 322 323
			return activatedExtension;
		}, (err) => {
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
324
			this._logExtensionActivationTimes(extensionDescription, reason, 'failure');
A
Alex Dima 已提交
325 326
			throw err;
		});
E
Erich Gamma 已提交
327 328
	}

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) {
		let event = getTelemetryActivationEvent(extensionDescription, reason);
		/* __GDPR__
			"extensionActivationTimes" : {
				"${include}": [
					"${TelemetryActivationEvent}",
					"${ExtensionActivationTimes}"
				],
				"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
			}
		*/
		this._mainThreadTelemetry.$publicLog('extensionActivationTimes', {
			...event,
			...(activationTimes || {}),
			outcome,
		});
	}

347
	private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
R
Rob Lourens 已提交
348
		let event = getTelemetryActivationEvent(extensionDescription, reason);
K
kieferrm 已提交
349
		/* __GDPR__
K
kieferrm 已提交
350 351 352 353 354 355
			"activatePlugin" : {
				"${include}": [
					"${TelemetryActivationEvent}"
				]
			}
		*/
356
		this._mainThreadTelemetry.$publicLog('activatePlugin', event);
A
Alex Dima 已提交
357 358
		if (!extensionDescription.main) {
			// Treat the extension as being empty => NOT AN ERROR CASE
359
			return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
A
Alex Dima 已提交
360
		}
361

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

A
Alex Dima 已提交
364
		const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
365
		return Promise.all<any>([
366
			loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder),
A
Alex Dima 已提交
367 368
			this._loadExtensionContext(extensionDescription)
		]).then(values => {
369
			return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.id, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
A
Alex Dima 已提交
370
		});
E
Erich Gamma 已提交
371 372
	}

373
	private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<IExtensionContext> {
E
Erich Gamma 已提交
374

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

378
		this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.id}`);
379
		return Promise.all([
380
			globalState.whenReady,
381 382
			workspaceState.whenReady,
			this._storagePath.whenReady
383
		]).then(() => {
384
			const that = this;
A
Alex Dima 已提交
385
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
386 387 388
				globalState,
				workspaceState,
				subscriptions: [],
389
				get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
390
				storagePath: this._storagePath.workspaceValue(extensionDescription),
391
				get globalStoragePath(): string { checkProposedApiEnabled(extensionDescription); return that._storagePath.globalValue(extensionDescription); },
392
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); },
393
				logPath: that._extHostLogService.getLogDirectory(extensionDescription.id)
E
Erich Gamma 已提交
394 395 396 397
			});
		});
	}

398
	private static _callActivate(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<ActivatedExtension> {
A
Alex Dima 已提交
399 400
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
401 402 403 404
			activate: undefined,
			deactivate: undefined
		};

405
		return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => {
406
			return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
407 408 409
		});
	}

410
	private static _callActivateOptional(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<IExtensionAPI> {
A
Alex Dima 已提交
411
		if (typeof extensionModule.activate === 'function') {
412
			try {
413
				activationTimesBuilder.activateCallStart();
414
				logService.trace(`ExtensionService#_callActivateOptional ${extensionId}`);
415
				const activateResult: Thenable<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
416 417 418
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
419
				return Promise.resolve(activateResult).then((value) => {
420 421 422
					activationTimesBuilder.activateResolveStop();
					return value;
				});
423
			} catch (err) {
424
				return Promise.reject(err);
425 426
			}
		} else {
A
Alex Dima 已提交
427
			// No activate found => the module is the extension's exports
428
			return Promise.resolve<IExtensionAPI>(extensionModule);
429 430 431
		}
	}

E
Erich Gamma 已提交
432 433
	// -- called by main thread

A
Alex Dima 已提交
434
	public $activateByEvent(activationEvent: string): Thenable<void> {
435
		return this.activateByEvent(activationEvent, false);
436 437 438
	}
}

439
function loadCommonJSModule<T>(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
440
	let r: T | null = null;
441
	activationTimesBuilder.codeLoadingStart();
442
	logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`);
E
Erich Gamma 已提交
443 444
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
445
	} catch (e) {
446
		return Promise.reject(e);
447 448
	} finally {
		activationTimesBuilder.codeLoadingStop();
E
Erich Gamma 已提交
449
	}
450
	return Promise.resolve(r);
E
Erich Gamma 已提交
451
}
A
Alex Dima 已提交
452

R
Rob Lourens 已提交
453 454 455 456 457
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): any {
	const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent :
		reason instanceof ExtensionActivatedByAPI ? 'api' :
			'';

K
kieferrm 已提交
458
	/* __GDPR__FRAGMENT__
K
kieferrm 已提交
459 460 461
		"TelemetryActivationEvent" : {
			"id": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
C
Christof Marti 已提交
462
			"extensionVersion": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
463
			"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
464
			"activationEvents": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
R
Rob Lourens 已提交
465 466
			"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
			"reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
K
kieferrm 已提交
467 468
		}
	*/
A
Alex Dima 已提交
469 470 471
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
C
Christof Marti 已提交
472
		extensionVersion: extensionDescription.version,
473
		publisherDisplayName: extensionDescription.publisher,
474
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
R
Rob Lourens 已提交
475 476
		isBuiltin: extensionDescription.isBuiltin,
		reason: reasonStr
A
Alex Dima 已提交
477 478 479
	};

	return event;
480
}