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

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

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

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

A
Alex Dima 已提交
34
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
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;
		});
43 44 45 46 47 48

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

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

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

A
Alex Dima 已提交
75
export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
A
Alex Dima 已提交
76 77 78

	private readonly _barrier: Barrier;
	private readonly _registry: ExtensionDescriptionRegistry;
79
	private readonly _mainThreadTelemetry: MainThreadTelemetryShape;
A
Alex Dima 已提交
80
	private readonly _storage: ExtHostStorage;
81
	private readonly _workspaceStoragePath: string;
82
	private readonly _proxy: MainThreadExtensionServiceShape;
83
	private readonly _extHostLogService: ExtHostLogService;
A
Alex Dima 已提交
84
	private _activator: ExtensionsActivator;
85
	private _extensionPathIndex: Promise<TernarySearchTree<IExtensionDescription>>;
A
Alex Dima 已提交
86 87 88
	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
89
	constructor(initData: IInitData,
90
		extHostContext: IMainContext,
91
		extHostWorkspace: ExtHostWorkspace,
J
Joao Moreno 已提交
92
		extHostConfiguration: ExtHostConfiguration,
A
Alex Dima 已提交
93
		extHostLogService: ExtHostLogService
94
	) {
A
Alex Dima 已提交
95 96
		this._barrier = new Barrier();
		this._registry = new ExtensionDescriptionRegistry(initData.extensions);
97
		this._extHostLogService = extHostLogService;
A
Alex Dima 已提交
98 99
		this._mainThreadTelemetry = extHostContext.getProxy(MainContext.MainThreadTelemetry);
		this._storage = new ExtHostStorage(extHostContext);
B
Benjamin Pasero 已提交
100
		this._workspaceStoragePath = initData.workspace ? join(initData.environment.appSettingsHome.fsPath, 'workspaceStorage', initData.workspace.id) : undefined;
A
Alex Dima 已提交
101
		this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService);
A
Alex Dima 已提交
102
		this._activator = null;
103

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

A
Alex Dima 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
		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);
					}
				},

125
				actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
A
Alex Dima 已提交
126
					return this._activateExtension(extensionDescription, reason);
A
Alex Dima 已提交
127
				}
128 129
			});

A
Alex Dima 已提交
130 131
			this._barrier.open();
		});
132 133
	}

134
	public onExtensionAPIReady(): Thenable<boolean> {
A
Alex Dima 已提交
135
		return this._barrier.wait();
136 137 138
	}

	public isActivated(extensionId: string): boolean {
A
Alex Dima 已提交
139 140 141 142
		if (this._barrier.isOpen()) {
			return this._activator.isActivated(extensionId);
		}
		return false;
143 144
	}

145
	public activateByEvent(activationEvent: string, startup: boolean): Thenable<void> {
A
Alex Dima 已提交
146
		const reason = new ExtensionActivatedByEvent(startup, activationEvent);
A
Alex Dima 已提交
147
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
148
			return this._activator.activateByEvent(activationEvent, reason);
149
		} else {
A
Alex Dima 已提交
150
			return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason));
151 152 153
		}
	}

154
	public activateById(extensionId: string, reason: ExtensionActivationReason): Thenable<void> {
A
Alex Dima 已提交
155
		if (this._barrier.isOpen()) {
A
Alex Dima 已提交
156
			return this._activator.activateById(extensionId, reason);
157
		} else {
A
Alex Dima 已提交
158
			return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason));
159 160 161
		}
	}

162
	public activateByIdWithErrors(extensionId: string, reason: ExtensionActivationReason): Thenable<void> {
163 164 165 166
		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
167
				return Promise.reject(extension.activationFailedError);
168 169 170 171 172
			}
			return void 0;
		});
	}

173 174 175 176 177 178 179 180
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

A
Alex Dima 已提交
181 182 183 184 185
	public getExtensionExports(extensionId: string): IExtensionAPI {
		if (this._barrier.isOpen()) {
			return this._activator.getActivatedExtension(extensionId).exports;
		} else {
			return null;
E
Erich Gamma 已提交
186 187 188
		}
	}

189
	// create trie to enable fast 'filename -> extension id' look up
190
	public getExtensionPathIndex(): Promise<TernarySearchTree<IExtensionDescription>> {
191
		if (!this._extensionPathIndex) {
192
			const tree = TernarySearchTree.forPaths<IExtensionDescription>();
193 194 195 196
			const extensions = this.getAllExtensionDescriptions().map(ext => {
				if (!ext.main) {
					return undefined;
				}
197
				return realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
198
			});
199
			this._extensionPathIndex = Promise.all(extensions).then(() => tree);
200 201 202 203 204
		}
		return this._extensionPathIndex;
	}


205 206
	public deactivate(extensionId: string): Thenable<void> {
		let result = Promise.resolve(void 0);
207

A
Alex Dima 已提交
208 209 210 211 212
		if (!this._barrier.isOpen()) {
			return result;
		}

		if (!this._activator.isActivated(extensionId)) {
A
Alex Dima 已提交
213 214 215
			return result;
		}

A
Alex Dima 已提交
216
		let extension = this._activator.getActivatedExtension(extensionId);
A
Alex Dima 已提交
217
		if (!extension) {
218
			return result;
219 220 221 222
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
223
			if (typeof extension.module.deactivate === 'function') {
224
				result = Promise.resolve(extension.module.deactivate()).then(null, (err) => {
225
					// TODO: Do something with err if this is not the shutdown case
226
					return Promise.resolve(void 0);
227
				});
228
			}
B
Benjamin Pasero 已提交
229
		} catch (err) {
230 231 232 233 234
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
235
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
236
		} catch (err) {
237 238
			// TODO: Do something with err if this is not the shutdown case
		}
239 240

		return result;
241
	}
E
Erich Gamma 已提交
242

A
Alex Dima 已提交
243 244 245 246
	public addMessage(extensionId: string, severity: Severity, message: string): void {
		this._proxy.$addMessage(extensionId, severity, message);
	}

A
Alex Dima 已提交
247
	// --- impl
A
Alex Dima 已提交
248

249
	private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
A
Alex Dima 已提交
250
		return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
251
			const activationTimes = activatedExtension.activationTimes;
A
Alex Dima 已提交
252 253
			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 已提交
254 255 256 257 258
			return activatedExtension;
		}, (err) => {
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
			throw err;
		});
E
Erich Gamma 已提交
259 260
	}

261
	private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
R
Rob Lourens 已提交
262
		let event = getTelemetryActivationEvent(extensionDescription, reason);
K
kieferrm 已提交
263
		/* __GDPR__
K
kieferrm 已提交
264 265 266 267 268 269
			"activatePlugin" : {
				"${include}": [
					"${TelemetryActivationEvent}"
				]
			}
		*/
270
		this._mainThreadTelemetry.$publicLog('activatePlugin', event);
A
Alex Dima 已提交
271 272
		if (!extensionDescription.main) {
			// Treat the extension as being empty => NOT AN ERROR CASE
273
			return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
A
Alex Dima 已提交
274
		}
275

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

A
Alex Dima 已提交
278
		const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
279
		return Promise.all<any>([
280
			loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder),
A
Alex Dima 已提交
281 282
			this._loadExtensionContext(extensionDescription)
		]).then(values => {
283
			return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.id, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
A
Alex Dima 已提交
284
		});
E
Erich Gamma 已提交
285 286
	}

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

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

292
		this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.id}`);
293
		return Promise.all([
294
			globalState.whenReady,
295
			workspaceState.whenReady
296
		]).then(() => {
297
			const that = this;
A
Alex Dima 已提交
298
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
299 300 301
				globalState,
				workspaceState,
				subscriptions: [],
302
				get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
303
				storagePath: this._workspaceStoragePath ? join(this._workspaceStoragePath, extensionDescription.id) : undefined,
304
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); },
305
				logPath: that._extHostLogService.getLogDirectory(extensionDescription.id)
E
Erich Gamma 已提交
306 307 308 309
			});
		});
	}

310
	private static _callActivate(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<ActivatedExtension> {
A
Alex Dima 已提交
311 312
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
313 314 315 316
			activate: undefined,
			deactivate: undefined
		};

317
		return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => {
318
			return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions);
319 320 321
		});
	}

322
	private static _callActivateOptional(logService: ILogService, extensionId: string, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Thenable<IExtensionAPI> {
A
Alex Dima 已提交
323
		if (typeof extensionModule.activate === 'function') {
324
			try {
325
				activationTimesBuilder.activateCallStart();
326
				logService.trace(`ExtensionService#_callActivateOptional ${extensionId}`);
327
				const activateResult: Thenable<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
328 329 330
				activationTimesBuilder.activateCallStop();

				activationTimesBuilder.activateResolveStart();
331
				return Promise.resolve(activateResult).then((value) => {
332 333 334
					activationTimesBuilder.activateResolveStop();
					return value;
				});
335
			} catch (err) {
336
				return Promise.reject(err);
337 338
			}
		} else {
A
Alex Dima 已提交
339
			// No activate found => the module is the extension's exports
340
			return Promise.resolve<IExtensionAPI>(extensionModule);
341 342 343
		}
	}

E
Erich Gamma 已提交
344 345
	// -- called by main thread

A
Alex Dima 已提交
346
	public $activateByEvent(activationEvent: string): Thenable<void> {
347
		return this.activateByEvent(activationEvent, false);
348 349 350
	}
}

351
function loadCommonJSModule<T>(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
352
	let r: T | null = null;
353
	activationTimesBuilder.codeLoadingStart();
354
	logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`);
E
Erich Gamma 已提交
355 356
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
357
	} catch (e) {
358
		return Promise.reject(e);
359 360
	} finally {
		activationTimesBuilder.codeLoadingStop();
E
Erich Gamma 已提交
361
	}
362
	return Promise.resolve(r);
E
Erich Gamma 已提交
363
}
A
Alex Dima 已提交
364

R
Rob Lourens 已提交
365 366 367 368 369
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): any {
	const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent :
		reason instanceof ExtensionActivatedByAPI ? 'api' :
			'';

K
kieferrm 已提交
370
	/* __GDPR__FRAGMENT__
K
kieferrm 已提交
371 372 373
		"TelemetryActivationEvent" : {
			"id": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
			"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
374
			"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
375
			"activationEvents": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
R
Rob Lourens 已提交
376 377
			"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
			"reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
K
kieferrm 已提交
378 379
		}
	*/
A
Alex Dima 已提交
380 381 382
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
383
		publisherDisplayName: extensionDescription.publisher,
384
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
R
Rob Lourens 已提交
385 386
		isBuiltin: extensionDescription.isBuiltin,
		reason: reasonStr
A
Alex Dima 已提交
387 388 389
	};

	return event;
390
}