app.ts 36.5 KB
Newer Older
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 { app, ipcMain as ipc, systemPreferences, contentTracing, protocol, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
7
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
8
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
9 10
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/node/window';
11
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
B
Benjamin Pasero 已提交
12
import { getShellEnvironment } from 'vs/code/node/shellEnv';
13
import { IUpdateService } from 'vs/platform/update/common/update';
14
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
15
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
A
Alex Dima 已提交
16 17
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
18
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
19
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
20 21 22
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
23
import { ILogService } from 'vs/platform/log/common/log';
24
import { IStateService } from 'vs/platform/state/node/state';
25
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
26
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
27
import { IURLService } from 'vs/platform/url/common/url';
28
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
S
SteVen Batten 已提交
29
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
S
Sandeep Somavarapu 已提交
30
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
J
Joao Moreno 已提交
31
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
32
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
33
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
34
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
35
import product from 'vs/platform/product/common/product';
36
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
37
import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2';
38
import { Disposable } from 'vs/base/common/lifecycle';
39
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
40
import { URI } from 'vs/base/common/uri';
41 42
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { WorkspacesService } from 'vs/platform/workspaces/electron-main/workspacesService';
43
import { getMachineId } from 'vs/base/node/id';
44 45 46
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
47
import { IssueMainService, IIssueMainService } from 'vs/platform/issue/electron-main/issueMainService';
48
import { LoggerChannel } from 'vs/platform/log/common/logIpc';
49
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
50
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
J
Joao Moreno 已提交
51
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
B
Benjamin Pasero 已提交
52
import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
53
import { RunOnceScheduler } from 'vs/base/common/async';
54
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
55 56
import { sep, posix } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
B
Benjamin Pasero 已提交
57
import { localize } from 'vs/nls';
B
Benjamin Pasero 已提交
58
import { Schemas } from 'vs/base/common/network';
J
Joao Moreno 已提交
59
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
B
wip  
Benjamin Pasero 已提交
60 61
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
62
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
63
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
64
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
B
Benjamin Pasero 已提交
65
import { NativeURLService } from 'vs/platform/url/common/urlService';
B
Benjamin Pasero 已提交
66
import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
B
Benjamin Pasero 已提交
67
import { statSync } from 'fs';
68
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
69
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
70
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
71
import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
72
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
73 74
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
import { withNullAsUndefined } from 'vs/base/common/types';
75
import { coalesce } from 'vs/base/common/arrays';
K
kieferrm 已提交
76
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
77 78
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
R
Robo 已提交
79 80 81 82
import { IFileService } from 'vs/platform/files/common/files';
import { stripComments } from 'vs/base/common/json';
import { generateUuid } from 'vs/base/common/uuid';
import { VSBuffer } from 'vs/base/common/buffer';
83
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
S
Sandeep Somavarapu 已提交
84
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
A
Alex Dima 已提交
85
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
86
import { toErrorMessage } from 'vs/base/common/errorMessage';
87

88
export class CodeApplication extends Disposable {
89
	private windowsMainService: IWindowsMainService | undefined;
90
	private dialogMainService: IDialogMainService | undefined;
91
	private nativeHostMainService: INativeHostMainService | undefined;
92 93

	constructor(
94 95
		private readonly mainIpcServer: Server,
		private readonly userEnv: IProcessEnvironment,
96 97
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@ILogService private readonly logService: ILogService,
98
		@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
99
		@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
S
Sandeep Somavarapu 已提交
100
		@IConfigurationService private readonly configurationService: IConfigurationService,
101
		@IStateService private readonly stateService: IStateService
102
	) {
103 104
		super();

105 106 107 108 109 110
		this.registerListeners();
	}

	private registerListeners(): void {

		// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
111
		setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
B
Benjamin Pasero 已提交
112
		process.on('uncaughtException', err => this.onUnexpectedError(err));
113
		process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
114

B
Benjamin Pasero 已提交
115
		// Dispose on shutdown
116
		this.lifecycleMainService.onWillShutdown(() => this.dispose());
117

118 119 120
		// Contextmenu via IPC support
		registerContextMenuListener();

121 122
		// Accessibility change event
		app.on('accessibility-support-changed', (event, accessibilitySupportEnabled) => {
123
			this.windowsMainService?.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
124 125
		});

126 127 128
		// macOS dock activate
		app.on('activate', (event, hasVisibleWindows) => {
			this.logService.trace('app#activate');
129 130

			// Mac only event: open new window when we get activated
131 132
			if (!hasVisibleWindows) {
				this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
133 134 135
			}
		});

136
		//#region Security related measures (https://electronjs.org/docs/tutorial/security)
137 138 139
		//
		// !!! DO NOT CHANGE without consulting the documentation !!!
		//
140
		app.on('remote-require', (event, sender, module) => {
141
			this.logService.trace('app#on(remote-require): prevented');
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

			event.preventDefault();
		});
		app.on('remote-get-global', (event, sender, module) => {
			this.logService.trace(`App#on(remote-get-global): prevented on ${module}`);

			event.preventDefault();
		});
		app.on('remote-get-builtin', (event, sender, module) => {
			this.logService.trace(`App#on(remote-get-builtin): prevented on ${module}`);

			if (module !== 'clipboard') {
				event.preventDefault();
			}
		});
		app.on('remote-get-current-window', event => {
			this.logService.trace(`App#on(remote-get-current-window): prevented`);

			event.preventDefault();
		});
		app.on('remote-get-current-web-contents', event => {
B
Benjamin Pasero 已提交
163 164
			if (this.environmentService.args.driver) {
				return; // the driver needs access to web contents
D
Daniel Imms 已提交
165
			}
B
Benjamin Pasero 已提交
166 167 168 169

			this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);

			event.preventDefault();
170
		});
171 172
		app.on('web-contents-created', (event, contents) => {
			contents.on('will-attach-webview', (event, webPreferences, params) => {
173

R
Robo 已提交
174
				const isValidWebviewSource = (source: string | undefined): boolean => {
175 176 177 178
					if (!source) {
						return false;
					}

179 180 181
					const uri = URI.parse(source);
					if (uri.scheme === Schemas.vscodeWebview) {
						return uri.path === '/index.html' || uri.path === '/electron-browser/index.html';
182 183
					}

184
					const srcUri = uri.fsPath.toLowerCase();
185 186
					const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();

B
Benjamin Pasero 已提交
187
					return srcUri.startsWith(rootUri + sep);
188 189
				};

190
				// Ensure defaults
M
Matt Bierner 已提交
191 192 193 194
				delete webPreferences.preload;
				webPreferences.nodeIntegration = false;

				// Verify URLs being loaded
R
Robo 已提交
195 196
				// https://github.com/electron/electron/issues/21553
				if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) {
M
Matt Bierner 已提交
197 198
					return;
				}
M
Matt Bierner 已提交
199

M
Matt Bierner 已提交
200
				delete (webPreferences as { preloadURL: string | undefined }).preloadURL; // https://github.com/electron/electron/issues/21553
201

M
Matt Bierner 已提交
202
				// Otherwise prevent loading
B
Benjamin Pasero 已提交
203
				this.logService.error('webContents#web-contents-created: Prevented webview attach');
204

M
Matt Bierner 已提交
205 206 207 208
				event.preventDefault();
			});

			contents.on('will-navigate', event => {
B
Benjamin Pasero 已提交
209
				this.logService.error('webContents#will-navigate: Prevented webcontent navigation');
210

M
Matt Bierner 已提交
211 212
				event.preventDefault();
			});
213

214
			contents.on('new-window', (event, url) => {
215 216
				event.preventDefault(); // prevent code that wants to open links

217
				this.nativeHostMainService?.openExternal(undefined, url);
218
			});
219 220 221 222

			session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => {
				return callback(false);
			});
223 224 225 226

			session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => {
				return false;
			});
M
Matt Bierner 已提交
227 228
		});

229 230
		//#endregion

231
		let macOpenFileURIs: IWindowOpenable[] = [];
232
		let runningTimeout: NodeJS.Timeout | null = null;
233 234
		app.on('open-file', (event, path) => {
			this.logService.trace('app#open-file: ', path);
235 236 237
			event.preventDefault();

			// Keep in array because more might come!
238
			macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path));
239 240 241 242 243 244 245 246 247

			// Clear previous handler if any
			if (runningTimeout !== null) {
				clearTimeout(runningTimeout);
				runningTimeout = null;
			}

			// Handle paths delayed in case more are coming!
			runningTimeout = setTimeout(() => {
248 249 250 251 252 253 254 255 256 257
				this.windowsMainService?.open({
					context: OpenContext.DOCK /* can also be opening from finder while app is running */,
					cli: this.environmentService.args,
					urisToOpen: macOpenFileURIs,
					gotoLineMode: false,
					preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
				});

				macOpenFileURIs = [];
				runningTimeout = null;
258 259 260
			}, 100);
		});

261
		app.on('new-window-for-tab', () => {
262
			this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
263 264
		});

265
		ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
266
			const webContents = event.sender;
267
			const window = this.windowsMainService?.getWindowByWebContents(event.sender);
268

269
			let replied = false;
270

271 272 273 274 275 276
			function acceptShellEnv(env: NodeJS.ProcessEnv): void {
				clearTimeout(shellEnvTimeoutWarningHandle);

				if (!replied) {
					webContents.send('vscode:acceptShellEnv', env);
					replied = true;
J
Johannes Rieken 已提交
277
				}
278 279 280
			}

			const shellEnvTimeoutWarningHandle = setTimeout(function () {
281
				window?.sendWhenReady('vscode:showShellEnvTimeoutWarning'); // notify inside window if we have one
282 283 284 285 286 287
				acceptShellEnv({});
			}, 10000);

			try {
				const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
				acceptShellEnv(shellEnv);
288
			} catch (error) {
289
				window?.sendWhenReady('vscode:showShellEnvError', toErrorMessage(error)); // notify inside window if we have one
290
				acceptShellEnv({});
B
Benjamin Pasero 已提交
291

292 293
				this.logService.error('Error fetching shell env', error);
			}
294
		});
295

296 297
		ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools());
		ipc.on('vscode:openDevTools', (event: IpcMainEvent) => event.sender.openDevTools());
B
Benjamin Pasero 已提交
298

299
		ipc.on('vscode:reloadWindow', (event: IpcMainEvent) => event.sender.reload());
300 301
	}

B
Benjamin Pasero 已提交
302 303 304 305 306 307 308 309 310 311
	private onUnexpectedError(err: Error): void {
		if (err) {

			// take only the message and stack property
			const friendlyError = {
				message: err.message,
				stack: err.stack
			};

			// handle on client side
312
			this.windowsMainService?.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
B
Benjamin Pasero 已提交
313 314 315 316 317 318 319 320
		}

		this.logService.error(`[uncaught exception in main]: ${err}`);
		if (err.stack) {
			this.logService.error(err.stack);
		}
	}

321
	async startup(): Promise<void> {
J
Joao Moreno 已提交
322 323 324
		this.logService.debug('Starting VS Code');
		this.logService.debug(`from: ${this.environmentService.appRoot}`);
		this.logService.debug('args:', this.environmentService.args);
325

326 327 328 329
		// Make sure we associate the program with the app user model id
		// This will help Windows to associate the running program with
		// any shortcut that is pinned to the taskbar and prevent showing
		// two icons in the taskbar for the same app.
330 331 332
		const win32AppUserModelId = product.win32AppUserModelId;
		if (isWindows && win32AppUserModelId) {
			app.setAppUserModelId(win32AppUserModelId);
333
		}
334

335 336 337 338 339
		// Fix native tabs on macOS 10.13
		// macOS enables a compatibility patch for any bundle ID beginning with
		// "com.microsoft.", which breaks native tabs for VS Code when using this
		// identifier (from the official build).
		// Explicitly opt out of the patch here before creating any windows.
C
ChaseKnowlden 已提交
340
		// See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085
341
		try {
342
			if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
343
				systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
344 345 346 347 348
			}
		} catch (error) {
			this.logService.error(error);
		}

349
		// Create Electron IPC Server
350
		const electronIpcServer = new ElectronIPCServer();
351

352 353
		// Resolve unique machine ID
		this.logService.trace('Resolving machine identifier...');
S
SteVen Batten 已提交
354 355
		const machineId = await this.resolveMachineId();
		this.logService.trace(`Resolved machine identifier: ${machineId}`);
356

357 358
		// Spawn shared process after the first window has opened and 3s have passed
		const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
359 360
		const sharedProcessClient = sharedProcess.whenIpcReady().then(() => {
			this.logService.trace('Shared process: IPC ready');
361

362 363 364 365
			return connect(this.environmentService.sharedIPCHandle, 'main');
		});
		const sharedProcessReady = sharedProcess.whenReady().then(() => {
			this.logService.trace('Shared process: init ready');
366

367 368
			return sharedProcessClient;
		});
369
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
370
			this._register(new RunOnceScheduler(async () => {
371
				sharedProcess.spawn(await getShellEnvironment(this.logService, this.environmentService));
372 373
			}, 3000)).schedule();
		});
374

375
		// Services
S
SteVen Batten 已提交
376
		const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
377

378 379
		// Create driver
		if (this.environmentService.driverHandle) {
380
			const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService);
381

B
Benjamin Pasero 已提交
382 383
			this.logService.info('Driver started at:', this.environmentService.driverHandle);
			this._register(server);
384
		}
385

386
		// Setup Auth Handler (TODO@ben remove old auth handler eventually)
387 388
		if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') !== true) {
			this._register(new ProxyAuthHandler());
389 390
		} else {
			this._register(appInstantiationService.createInstance(ProxyAuthHandler2));
391
		}
B
Benjamin Pasero 已提交
392

393
		// Open Windows
394
		const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
B
Benjamin Pasero 已提交
395

396
		// Post Open Windows Tasks
397
		appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
B
Benjamin Pasero 已提交
398

399 400 401
		// Tracing: Stop tracing after windows are ready if enabled
		if (this.environmentService.args.trace) {
			this.stopTracingEventually(windows);
B
Benjamin Pasero 已提交
402 403 404
		}
	}

S
SteVen Batten 已提交
405
	private async resolveMachineId(): Promise<string> {
B
Benjamin Pasero 已提交
406

407
		// We cache the machineId for faster lookups on startup
S
SteVen Batten 已提交
408
		// and resolve it only once initially if not cached or we need to replace the macOS iBridge device
409
		let machineId = this.stateService.getItem<string>(machineIdKey);
S
SteVen Batten 已提交
410
		if (!machineId || (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead')) {
411
			machineId = await getMachineId();
B
Benjamin Pasero 已提交
412

413
			this.stateService.setItem(machineIdKey, machineId);
414
		}
415

S
SteVen Batten 已提交
416
		return machineId;
417 418
	}

S
SteVen Batten 已提交
419
	private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
420
		const services = new ServiceCollection();
421

422 423 424 425
		switch (process.platform) {
			case 'win32':
				services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
				break;
426

427 428 429
			case 'linux':
				if (process.env.SNAP && process.env.SNAP_REVISION) {
					services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
430
				} else {
431
					services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
B
Benjamin Pasero 已提交
432
				}
433
				break;
434

435 436 437
			case 'darwin':
				services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
				break;
438 439
		}

440
		services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
441
		services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
442
		services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
443
		services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
444
		services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
445

446
		services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
447
		services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
A
Alex Dima 已提交
448
		services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
449
		services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService));
450
		services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
451
		services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
B
Benjamin Pasero 已提交
452
		services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
453 454 455

		const storageMainService = new StorageMainService(this.logService, this.environmentService);
		services.set(IStorageMainService, storageMainService);
456
		this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
457 458 459 460

		const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
		services.set(IBackupMainService, backupMainService);

461
		services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
B
Benjamin Pasero 已提交
462
		services.set(IURLService, new SyncDescriptor(NativeURLService));
463
		services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
464

465
		// Telemetry
466
		if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
467
			const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
S
Sandeep Somavarapu 已提交
468
			const appender = new TelemetryAppenderClient(channel);
469
			const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
470
			const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
S
SteVen Batten 已提交
471
			const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
472

473
			services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
474 475 476 477
		} else {
			services.set(ITelemetryService, NullTelemetryService);
		}

478
		// Init services that require it
479
		await backupMainService.initialize();
480

481
		return this.instantiationService.createChild(services);
482 483
	}

484 485
	private stopTracingEventually(windows: ICodeWindow[]): void {
		this.logService.info(`Tracing: waiting for windows to get ready...`);
486

487
		let recordingStopped = false;
R
Robo 已提交
488
		const stopRecording = async (timeout: boolean) => {
489 490 491
			if (recordingStopped) {
				return;
			}
B
wip  
Benjamin Pasero 已提交
492

493
			recordingStopped = true; // only once
494

495
			const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
R
Robo 已提交
496 497

			if (!timeout) {
498 499 500 501 502 503
				this.dialogMainService?.showMessageBox({
					type: 'info',
					message: localize('trace.message', "Successfully created trace."),
					detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
					buttons: [localize('trace.ok', "OK")]
				}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
R
Robo 已提交
504 505 506
			} else {
				this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
			}
507
		};
508

509 510 511 512 513 514 515 516
		// Wait up to 30s before creating the trace anyways
		const timeoutHandle = setTimeout(() => stopRecording(true), 30000);

		// Wait for all windows to get ready and stop tracing then
		Promise.all(windows.map(window => window.ready())).then(() => {
			clearTimeout(timeoutHandle);
			stopRecording(false);
		});
517 518
	}

519
	private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
520 521

		// Register more Main IPC services
522
		const launchMainService = accessor.get(ILaunchMainService);
523
		const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
524 525 526 527 528
		this.mainIpcServer.registerChannel('launch', launchChannel);

		// Register more Electron IPC services
		const updateService = accessor.get(IUpdateService);
		const updateChannel = new UpdateChannel(updateService);
529
		electronIpcServer.registerChannel('update', updateChannel);
530

531 532
		const issueMainService = accessor.get(IIssueMainService);
		const issueChannel = createChannelReceiver(issueMainService);
533
		electronIpcServer.registerChannel('issue', issueChannel);
534

535 536 537 538
		const encryptionMainService = accessor.get(IEncryptionMainService);
		const encryptionChannel = createChannelReceiver(encryptionMainService);
		electronIpcServer.registerChannel('encryption', encryptionChannel);

A
Alex Dima 已提交
539 540 541
		const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService);
		const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService);
		electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
542

543 544
		const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
		const nativeHostChannel = createChannelReceiver(this.nativeHostMainService);
545 546
		electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
		sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
547

548
		const sharedProcessMainService = accessor.get(ISharedProcessMainService);
549
		const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
550 551
		electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);

552 553
		const workspacesService = accessor.get(IWorkspacesService);
		const workspacesChannel = createChannelReceiver(workspacesService);
554
		electronIpcServer.registerChannel('workspaces', workspacesChannel);
B
Benjamin Pasero 已提交
555

B
Benjamin Pasero 已提交
556 557
		const menubarMainService = accessor.get(IMenubarMainService);
		const menubarChannel = createChannelReceiver(menubarMainService);
558
		electronIpcServer.registerChannel('menubar', menubarChannel);
559

J
Joao Moreno 已提交
560
		const urlService = accessor.get(IURLService);
561
		const urlChannel = createChannelReceiver(urlService);
562
		electronIpcServer.registerChannel('url', urlChannel);
J
Joao Moreno 已提交
563

564
		const webviewManagerService = accessor.get(IWebviewManagerService);
565
		const webviewChannel = createChannelReceiver(webviewManagerService);
566 567
		electronIpcServer.registerChannel('webview', webviewChannel);

568
		const storageMainService = accessor.get(IStorageMainService);
569
		const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
570
		electronIpcServer.registerChannel('storage', storageChannel);
571
		sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
B
wip  
Benjamin Pasero 已提交
572

573 574 575
		const loggerChannel = new LoggerChannel(accessor.get(ILogService));
		electronIpcServer.registerChannel('logger', loggerChannel);
		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
S
Sandeep Somavarapu 已提交
576

577
		// ExtensionHost Debug broadcast service
578
		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
579
		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
580

581
		// Signal phase: ready (services set)
582
		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
583 584

		// Propagate to clients
585
		this.dialogMainService = accessor.get(IDialogMainService);
J
Joao Moreno 已提交
586

587 588 589 590 591
		// Check for initial URLs to handle from protocol link invocations
		const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
		const pendingProtocolLinksToHandle = coalesce([

			// Windows/Linux: protocol handler invokes CLI with --open-url
592
			...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
593 594 595 596 597 598 599 600 601 602

			// macOS: open-url events
			...((<any>global).getOpenUrls() || []) as string[]
		].map(pendingUrlToHandle => {
			try {
				return URI.parse(pendingUrlToHandle);
			} catch (error) {
				return undefined;
			}
		})).filter(pendingUriToHandle => {
603 604

			// If URI should be blocked, filter it out
K
kieferrm 已提交
605 606 607 608
			if (this.shouldBlockURI(pendingUriToHandle)) {
				return false;
			}

609
			// Filter out any protocol link that wants to open as window so that
610 611 612 613 614 615 616 617 618 619 620 621 622 623
			// we open the right set of windows on startup and not restore the
			// previous workspace too.
			const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle);
			if (windowOpenable) {
				pendingWindowOpenablesFromProtocolLinks.push(windowOpenable);

				return false;
			}

			return true;
		});

		// Create a URL handler to open file URIs in the active window
		const app = this;
624
		const environmentService = this.environmentService;
625
		urlService.registerHandler({
626
			async handleURL(uri: URI): Promise<boolean> {
627 628

				// If URI should be blocked, behave as if it's handled
K
kieferrm 已提交
629 630 631
				if (app.shouldBlockURI(uri)) {
					return true;
				}
632 633 634 635 636 637 638 639 640 641

				// Check for URIs to open in window
				const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
				if (windowOpenableFromProtocolLink) {
					windowsMainService.open({
						context: OpenContext.API,
						cli: { ...environmentService.args },
						urisToOpen: [windowOpenableFromProtocolLink],
						gotoLineMode: true
					});
642

643 644
					return true;
				}
645

646 647 648 649 650 651 652 653 654
				// If we have not yet handled the URI and we have no window opened (macOS only)
				// we first open a window and then try to open that URI within that window
				if (isMacintosh && windowsMainService.getWindowCount() === 0) {
					const [window] = windowsMainService.open({
						context: OpenContext.API,
						cli: { ...environmentService.args },
						forceEmpty: true,
						gotoLineMode: true
					});
655

656 657 658
					await window.ready();

					return urlService.open(uri);
659 660 661 662 663 664
				}

				return false;
			}
		});

J
Joao Moreno 已提交
665
		// Create a URL handler which forwards to the last active window
666 667 668 669 670
		const activeWindowManager = new ActiveWindowManager({
			onDidOpenWindow: nativeHostMainService.onDidOpenWindow,
			onDidFocusWindow: nativeHostMainService.onDidFocusWindow,
			getActiveWindowId: () => nativeHostMainService.getActiveWindowId(-1)
		});
J
Joao Moreno 已提交
671
		const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
672 673
		const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
		const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
674
		urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
J
Joao Moreno 已提交
675

J
Joao Moreno 已提交
676
		// Watch Electron URLs and forward them to the UrlService
677
		this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService));
J
Joao Moreno 已提交
678

679
		// Open our first window
680
		const args = this.environmentService.args;
681
		const macOpenFiles: string[] = (<any>global).macOpenFiles;
682
		const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
683 684 685
		const hasCliArgs = args._.length;
		const hasFolderURIs = !!args['folder-uri'];
		const hasFileURIs = !!args['file-uri'];
686
		const noRecentEntry = args['skip-add-to-recently-opened'] === true;
M
Martin Aeschlimann 已提交
687
		const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
688

689 690 691 692 693 694 695 696 697 698 699 700 701
		// check for a pending window to open from URI
		// e.g. when running code with --open-uri from
		// a protocol handler
		if (pendingWindowOpenablesFromProtocolLinks.length > 0) {
			return windowsMainService.open({
				context,
				cli: args,
				urisToOpen: pendingWindowOpenablesFromProtocolLinks,
				gotoLineMode: true,
				initialStartup: true
			});
		}

702 703
		// new window if "-n"
		if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
704
			return windowsMainService.open({
M
Martin Aeschlimann 已提交
705 706 707 708 709 710 711 712
				context,
				cli: args,
				forceNewWindow: true,
				forceEmpty: true,
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
713
		}
B
Benjamin Pasero 已提交
714

715
		// mac: open-file event received on startup
B
Benjamin Pasero 已提交
716
		if (macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
717
			return windowsMainService.open({
M
Martin Aeschlimann 已提交
718 719
				context: OpenContext.DOCK,
				cli: args,
720
				urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
M
Martin Aeschlimann 已提交
721 722 723 724
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
B
Benjamin Pasero 已提交
725 726
		}

M
Martin Aeschlimann 已提交
727
		// default: read paths from cli
728
		return windowsMainService.open({
M
Martin Aeschlimann 已提交
729 730 731 732 733 734
			context,
			cli: args,
			forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
			diffMode: args.diff,
			noRecentEntry,
			waitMarkerFileURI,
735
			gotoLineMode: args.goto,
M
Martin Aeschlimann 已提交
736 737
			initialStartup: true
		});
738 739
	}

K
kieferrm 已提交
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
	private shouldBlockURI(uri: URI): boolean {
		if (uri.authority === Schemas.file && isWindows) {
			const res = dialog.showMessageBoxSync({
				title: product.nameLong,
				type: 'question',
				buttons: [
					mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")),
					mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")),
				],
				cancelId: 1,
				message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath), product.nameShort),
				detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"),
				noLink: true
			});

			if (res === 1) {
				return true;
			}
		}

		return false;
	}

763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
	private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined {
		if (!uri.path) {
			return undefined;
		}

		// File path
		if (uri.authority === Schemas.file) {
			// we configure as fileUri, but later validation will
			// make sure to open as folder or workspace if possible
			return { fileUri: URI.file(uri.fsPath) };
		}

		// Remote path
		else if (uri.authority === Schemas.vscodeRemote) {
			// Example conversion:
			// From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco
			//   To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco
			const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */);
			if (secondSlash !== -1) {
				const authority = uri.path.substring(1, secondSlash);
				const path = uri.path.substring(secondSlash);
				const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment });

				if (hasWorkspaceFileExtension(path)) {
					return { workspaceUri: remoteUri };
				} else {
					return { folderUri: remoteUri };
				}
			}
		}

		return undefined;
	}

797
	private getWindowOpenableFromPathSync(path: string): IWindowOpenable {
798 799 800 801
		try {
			const fileStat = statSync(path);
			if (fileStat.isDirectory()) {
				return { folderUri: URI.file(path) };
802
			}
803

804 805
			if (hasWorkspaceFileExtension(path)) {
				return { workspaceUri: URI.file(path) };
806
			}
807 808
		} catch (error) {
			// ignore errors
809
		}
810

811 812
		return { fileUri: URI.file(path) };
	}
813

R
Robo 已提交
814
	private async afterWindowOpen(accessor: ServicesAccessor): Promise<void> {
815

816
		// Signal phase: after window open
817
		this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
818

819 820
		// Remote Authorities
		this.handleRemoteAuthorities();
821 822 823 824 825 826

		// Initialize update service
		const updateService = accessor.get(IUpdateService);
		if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) {
			updateService.initialize();
		}
R
Robo 已提交
827

828 829 830
		// Start to fetch shell environment after window has opened
		getShellEnvironment(this.logService, this.environmentService);

R
Robo 已提交
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
		// If enable-crash-reporter argv is undefined then this is a fresh start,
		// based on telemetry.enableCrashreporter settings, generate a UUID which
		// will be used as crash reporter id and also update the json file.
		try {
			const fileService = accessor.get(IFileService);
			const argvContent = await fileService.readFile(this.environmentService.argvResource);
			const argvString = argvContent.value.toString();
			const argvJSON = JSON.parse(stripComments(argvString));
			if (argvJSON['enable-crash-reporter'] === undefined) {
				const enableCrashReporter = this.configurationService.getValue<boolean>('telemetry.enableCrashReporter') ?? true;
				const additionalArgvContent = [
					'',
					'	// Allows to disable crash reporting.',
					'	// Should restart the app if the value is changed.',
					`	"enable-crash-reporter": ${enableCrashReporter},`,
					'',
					'	// Unique id used for correlating crash reports sent from this instance.',
					'	// Do not edit this value.',
					`	"crash-reporter-id": "${generateUuid()}"`,
					'}'
				];
				const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
				await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
			}
		} catch (error) {
			this.logService.error(error);
		}
858
	}
859 860

	private handleRemoteAuthorities(): void {
861
		protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
862
			callback({
863
				url: request.url.replace(/^vscode-remote-resource:/, 'http:'),
864 865
				method: request.method
			});
866 867
		});
	}
868
}