app.ts 36.1 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, shell, Event, 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 { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
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';
30
import { NullTelemetryService, combinedAppender, LogAppender } 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 } 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 { Disposable } from 'vs/base/common/lifecycle';
38 39
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { ActiveWindowManager } from 'vs/platform/windows/electron-main/windowTracker';
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, timeout } from 'vs/base/common/async';
54
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
B
Benjamin Pasero 已提交
55
import { homedir } from 'os';
56
import { join, sep, posix } from 'vs/base/common/path';
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 { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
69
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
70
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
71
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
72
import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService';
73
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
74 75
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
import { withNullAsUndefined } from 'vs/base/common/types';
76
import { coalesce } from 'vs/base/common/arrays';
77 78
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
K
kieferrm 已提交
79
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
80 81
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
R
Robo 已提交
82 83 84 85
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';
86

87
export class CodeApplication extends Disposable {
88
	private windowsMainService: IWindowsMainService | undefined;
89
	private dialogMainService: IDialogMainService | undefined;
90 91

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

103 104 105 106 107 108
		this.registerListeners();
	}

	private registerListeners(): void {

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

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

116 117 118
		// Contextmenu via IPC support
		registerContextMenuListener();

119 120 121 122 123 124 125
		app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
			if (this.windowsMainService) {
				this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
			}
		});

		app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
J
Joao Moreno 已提交
126
			this.logService.trace('App#activate');
127 128 129

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

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

			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 已提交
161 162
			if (this.environmentService.args.driver) {
				return; // the driver needs access to web contents
D
Daniel Imms 已提交
163
			}
B
Benjamin Pasero 已提交
164 165 166 167

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

			event.preventDefault();
168
		});
169 170
		app.on('web-contents-created', (_event: Event, contents) => {
			contents.on('will-attach-webview', (event: Event, webPreferences, params) => {
171

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

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

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

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

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

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

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

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

M
Matt Bierner 已提交
203 204 205 206
				event.preventDefault();
			});

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

M
Matt Bierner 已提交
209 210
				event.preventDefault();
			});
211 212 213 214 215 216

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

				shell.openExternal(url);
			});
217 218 219 220

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

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

227 228
		//#endregion

229
		let macOpenFileURIs: IWindowOpenable[] = [];
230
		let runningTimeout: NodeJS.Timeout | null = null;
231
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
232
			this.logService.trace('App#open-file: ', path);
233 234 235
			event.preventDefault();

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

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

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

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

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

267
		ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
268
			const webContents = event.sender;
269 270

			try {
271
				const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
272 273 274 275 276 277

				// TODO@sandbox workaround for https://github.com/electron/electron/issues/25119
				if (this.environmentService.sandbox) {
					await timeout(100);
				}

J
Johannes Rieken 已提交
278 279 280
				if (!webContents.isDestroyed()) {
					webContents.send('vscode:acceptShellEnv', shellEnv);
				}
281
			} catch (error) {
J
Johannes Rieken 已提交
282 283 284
				if (!webContents.isDestroyed()) {
					webContents.send('vscode:acceptShellEnv', {});
				}
B
Benjamin Pasero 已提交
285

286 287
				this.logService.error('Error fetching shell env', error);
			}
288
		});
289

290 291
		ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools());
		ipc.on('vscode:openDevTools', (event: IpcMainEvent) => event.sender.openDevTools());
B
Benjamin Pasero 已提交
292

293
		ipc.on('vscode:reloadWindow', (event: IpcMainEvent) => event.sender.reload());
294

B
Benjamin Pasero 已提交
295
		// Some listeners after window opened
296
		(async () => {
297
			await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
298

B
Benjamin Pasero 已提交
299
			// Keyboard layout changes (after window opened)
300 301 302
			const nativeKeymap = await import('native-keymap');
			nativeKeymap.onDidChangeKeyboardLayout(() => {
				if (this.windowsMainService) {
303
					this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged');
304 305 306
				}
			});
		})();
307 308
	}

B
Benjamin Pasero 已提交
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	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
			if (this.windowsMainService) {
				this.windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
			}
		}

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

330
	async startup(): Promise<void> {
J
Joao Moreno 已提交
331 332 333
		this.logService.debug('Starting VS Code');
		this.logService.debug(`from: ${this.environmentService.appRoot}`);
		this.logService.debug('args:', this.environmentService.args);
334

335 336 337 338
		// 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.
339 340 341
		const win32AppUserModelId = product.win32AppUserModelId;
		if (isWindows && win32AppUserModelId) {
			app.setAppUserModelId(win32AppUserModelId);
342
		}
343

344 345 346 347 348 349 350
		// 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.
		// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
		try {
351
			if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
352
				systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
353 354 355 356 357
			}
		} catch (error) {
			this.logService.error(error);
		}

358
		// Create Electron IPC Server
359
		const electronIpcServer = new ElectronIPCServer();
360

361 362
		// Resolve unique machine ID
		this.logService.trace('Resolving machine identifier...');
S
SteVen Batten 已提交
363 364
		const machineId = await this.resolveMachineId();
		this.logService.trace(`Resolved machine identifier: ${machineId}`);
365

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

371 372 373 374
			return connect(this.environmentService.sharedIPCHandle, 'main');
		});
		const sharedProcessReady = sharedProcess.whenReady().then(() => {
			this.logService.trace('Shared process: init ready');
375

376 377
			return sharedProcessClient;
		});
378
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
379
			this._register(new RunOnceScheduler(async () => {
380
				sharedProcess.spawn(await getShellEnvironment(this.logService, this.environmentService));
381 382
			}, 3000)).schedule();
		});
383

384
		// Services
S
SteVen Batten 已提交
385
		const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
386

387 388
		// Create driver
		if (this.environmentService.driverHandle) {
389
			const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService);
390

B
Benjamin Pasero 已提交
391 392
			this.logService.info('Driver started at:', this.environmentService.driverHandle);
			this._register(server);
393
		}
394

395
		// Setup Auth Handler
396
		this._register(new ProxyAuthHandler());
B
Benjamin Pasero 已提交
397

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

401
		// Post Open Windows Tasks
402
		appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
B
Benjamin Pasero 已提交
403

404 405 406
		// Tracing: Stop tracing after windows are ready if enabled
		if (this.environmentService.args.trace) {
			this.stopTracingEventually(windows);
B
Benjamin Pasero 已提交
407 408 409
		}
	}

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

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

418
			this.stateService.setItem(machineIdKey, machineId);
419
		}
420

S
SteVen Batten 已提交
421
		return machineId;
422 423
	}

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

427 428 429 430
		switch (process.platform) {
			case 'win32':
				services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
				break;
431

432 433 434
			case 'linux':
				if (process.env.SNAP && process.env.SNAP_REVISION) {
					services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
435
				} else {
436
					services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
B
Benjamin Pasero 已提交
437
				}
438
				break;
439

440 441 442
			case 'darwin':
				services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
				break;
443 444
		}

445
		services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
446
		services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
447
		services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
448
		services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
449

450
		const diagnosticsChannel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')));
451 452
		services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel]));

453
		services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
454
		services.set(IElectronMainService, new SyncDescriptor(ElectronMainService));
455
		services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
456
		services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
B
Benjamin Pasero 已提交
457
		services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
458 459 460

		const storageMainService = new StorageMainService(this.logService, this.environmentService);
		services.set(IStorageMainService, storageMainService);
461
		this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
462 463 464 465

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

466
		services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
B
Benjamin Pasero 已提交
467
		services.set(IURLService, new SyncDescriptor(NativeURLService));
468
		services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
469

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

478
			services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
479 480 481 482
		} else {
			services.set(ITelemetryService, NullTelemetryService);
		}

483
		// Init services that require it
484
		await backupMainService.initialize();
485

486
		return this.instantiationService.createChild(services);
487 488
	}

489 490
	private stopTracingEventually(windows: ICodeWindow[]): void {
		this.logService.info(`Tracing: waiting for windows to get ready...`);
491

492
		let recordingStopped = false;
R
Robo 已提交
493
		const stopRecording = async (timeout: boolean) => {
494 495 496
			if (recordingStopped) {
				return;
			}
B
wip  
Benjamin Pasero 已提交
497

498
			recordingStopped = true; // only once
499

R
Robo 已提交
500 501 502 503 504 505 506 507
			const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`));

			if (!timeout) {
				if (this.dialogMainService) {
					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),
M
Martin Aeschlimann 已提交
508
						buttons: [localize('trace.ok', "OK")]
R
Robo 已提交
509
					}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
510
				}
R
Robo 已提交
511 512 513
			} else {
				this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
			}
514
		};
515

516 517 518 519 520 521 522 523
		// 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);
		});
524 525
	}

526
	private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
527 528

		// Register more Main IPC services
529
		const launchMainService = accessor.get(ILaunchMainService);
530
		const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
531 532 533 534 535
		this.mainIpcServer.registerChannel('launch', launchChannel);

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

538 539
		const issueMainService = accessor.get(IIssueMainService);
		const issueChannel = createChannelReceiver(issueMainService);
540
		electronIpcServer.registerChannel('issue', issueChannel);
541

542 543
		const electronMainService = accessor.get(IElectronMainService);
		const electronChannel = createChannelReceiver(electronMainService);
544
		electronIpcServer.registerChannel('electron', electronChannel);
545
		sharedProcessClient.then(client => client.registerChannel('electron', electronChannel));
546

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

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

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

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

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

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

572 573 574 575 576
		const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService);
		const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService);
		electronIpcServer.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel);
		sharedProcessClient.then(client => client.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel));

577 578 579
		const loggerChannel = new LoggerChannel(accessor.get(ILogService));
		electronIpcServer.registerChannel('logger', loggerChannel);
		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
S
Sandeep Somavarapu 已提交
580

581
		// ExtensionHost Debug broadcast service
582
		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
583
		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
584

585
		// Signal phase: ready (services set)
586
		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
587 588

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

591 592 593 594 595
		// 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
596
			...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
597 598 599 600 601 602 603 604 605 606

			// macOS: open-url events
			...((<any>global).getOpenUrls() || []) as string[]
		].map(pendingUrlToHandle => {
			try {
				return URI.parse(pendingUrlToHandle);
			} catch (error) {
				return undefined;
			}
		})).filter(pendingUriToHandle => {
K
kieferrm 已提交
607 608 609 610 611
			// if URI should be blocked, filter it out
			if (this.shouldBlockURI(pendingUriToHandle)) {
				return false;
			}

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
			// filter out any protocol link that wants to open as window so that
			// 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;
627
		const environmentService = this.environmentService;
628
		urlService.registerHandler({
629
			async handleURL(uri: URI): Promise<boolean> {
K
kieferrm 已提交
630 631 632 633
				// if URI should be blocked, behave as if it's handled
				if (app.shouldBlockURI(uri)) {
					return true;
				}
634 635 636 637 638 639 640 641 642 643

				// 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
					});
644

645 646
					return true;
				}
647

648 649 650 651 652 653 654 655 656
				// 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
					});
657

658 659 660
					await window.ready();

					return urlService.open(uri);
661 662 663 664 665 666
				}

				return false;
			}
		});

J
Joao Moreno 已提交
667
		// Create a URL handler which forwards to the last active window
668
		const activeWindowManager = new ActiveWindowManager(electronMainService);
J
Joao Moreno 已提交
669
		const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
670 671
		const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
		const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
672
		urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
J
Joao Moreno 已提交
673

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

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

687 688 689 690 691 692 693 694 695 696 697 698 699
		// 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
			});
		}

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

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

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

K
kieferrm 已提交
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
	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;
	}

761 762 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
	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;
	}

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

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

809 810
		return { fileUri: URI.file(path) };
	}
811

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

814
		// Signal phase: after window open
815
		this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
816

817 818
		// Remote Authorities
		this.handleRemoteAuthorities();
819 820 821 822 823 824

		// Initialize update service
		const updateService = accessor.get(IUpdateService);
		if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) {
			updateService.initialize();
		}
R
Robo 已提交
825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852

		// 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);
		}
853 854 855

		// Start to fetch shell environment after window has opened
		getShellEnvironment(this.logService, this.environmentService);
856
	}
857 858

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