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, powerMonitor, 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 26
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
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';
29
import { ITelemetryService, machineIdKey, trueMachineIdKey } 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 } 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 { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService';
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 { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
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';
79
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
K
kieferrm 已提交
80
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
81 82
import { IFileService } from 'vs/platform/files/common/files';
import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider';
83

84
export class CodeApplication extends Disposable {
85
	private windowsMainService: IWindowsMainService | undefined;
86
	private dialogMainService: IDialogMainService | undefined;
87 88

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

101
		this.registerListeners();
102 103

		this._register(new WebviewProtocolProvider(fileService));
104 105 106 107 108
	}

	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
		// app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Matt revisit this need for <webview>
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
		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 已提交
162 163
			if (this.environmentService.args.driver) {
				return; // the driver needs access to web contents
D
Daniel Imms 已提交
164
			}
B
Benjamin Pasero 已提交
165 166 167 168

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

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

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

178
					if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%20role%3D%22document%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
179 180 181
						return true;
					}

182
					const srcUri = URI.parse(source).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:exit', (event: Event, code: number) => {
J
Joao Moreno 已提交
268
			this.logService.trace('IPC#vscode:exit', code);
269 270

			this.dispose();
271
			this.lifecycleMainService.kill(code);
272 273
		});

274
		ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
275
			const webContents = event.sender;
276 277

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

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

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

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

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

B
Benjamin Pasero 已提交
300
			// After waking up from sleep  (after window opened)
301 302 303 304 305 306
			powerMonitor.on('resume', () => {
				if (this.windowsMainService) {
					this.windowsMainService.sendToAll('vscode:osResume', undefined);
				}
			});

B
Benjamin Pasero 已提交
307
			// Keyboard layout changes (after window opened)
308 309 310 311 312 313 314
			const nativeKeymap = await import('native-keymap');
			nativeKeymap.onDidChangeKeyboardLayout(() => {
				if (this.windowsMainService) {
					this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
				}
			});
		})();
315 316
	}

B
Benjamin Pasero 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	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);
		}
	}

338
	async startup(): Promise<void> {
J
Joao Moreno 已提交
339 340 341
		this.logService.debug('Starting VS Code');
		this.logService.debug(`from: ${this.environmentService.appRoot}`);
		this.logService.debug('args:', this.environmentService.args);
342

343 344 345 346
		// 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.
347 348 349
		const win32AppUserModelId = product.win32AppUserModelId;
		if (isWindows && win32AppUserModelId) {
			app.setAppUserModelId(win32AppUserModelId);
350
		}
351

352 353 354 355 356 357 358
		// 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 {
359
			if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
360
				systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
361 362 363 364 365
			}
		} catch (error) {
			this.logService.error(error);
		}

366
		// Create Electron IPC Server
367
		const electronIpcServer = new ElectronIPCServer();
368

369 370
		// Resolve unique machine ID
		this.logService.trace('Resolving machine identifier...');
371 372
		const { machineId, trueMachineId } = await this.resolveMachineId();
		this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);
373

374 375
		// Spawn shared process after the first window has opened and 3s have passed
		const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
376 377 378 379 380 381 382 383
		const sharedProcessClient = sharedProcess.whenIpcReady().then(() => {
			this.logService.trace('Shared process: IPC ready');
			return connect(this.environmentService.sharedIPCHandle, 'main');
		});
		const sharedProcessReady = sharedProcess.whenReady().then(() => {
			this.logService.trace('Shared process: init ready');
			return sharedProcessClient;
		});
384
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
385
			this._register(new RunOnceScheduler(async () => {
386
				const userEnv = await getShellEnvironment(this.logService, this.environmentService);
387 388 389 390

				sharedProcess.spawn(userEnv);
			}, 3000)).schedule();
		});
391

392
		// Services
393
		const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessReady);
394

395 396
		// Create driver
		if (this.environmentService.driverHandle) {
397
			const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService);
398

B
Benjamin Pasero 已提交
399 400
			this.logService.info('Driver started at:', this.environmentService.driverHandle);
			this._register(server);
401
		}
402

403
		// Setup Auth Handler
404
		this._register(new ProxyAuthHandler());
B
Benjamin Pasero 已提交
405

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

409
		// Post Open Windows Tasks
410
		appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
B
Benjamin Pasero 已提交
411

412 413 414
		// Tracing: Stop tracing after windows are ready if enabled
		if (this.environmentService.args.trace) {
			this.stopTracingEventually(windows);
B
Benjamin Pasero 已提交
415 416 417
		}
	}

418
	private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: string }> {
B
Benjamin Pasero 已提交
419

420 421
		// We cache the machineId for faster lookups on startup
		// and resolve it only once initially if not cached
422
		let machineId = this.stateService.getItem<string>(machineIdKey);
423 424
		if (!machineId) {
			machineId = await getMachineId();
B
Benjamin Pasero 已提交
425

426
			this.stateService.setItem(machineIdKey, machineId);
427
		}
428

429 430 431
		// Check if machineId is hashed iBridge Device
		let trueMachineId: string | undefined;
		if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') {
432
			trueMachineId = this.stateService.getItem<string>(trueMachineIdKey);
433 434 435
			if (!trueMachineId) {
				trueMachineId = await getMachineId();

436
				this.stateService.setItem(trueMachineIdKey, trueMachineId);
437 438 439 440
			}
		}

		return { machineId, trueMachineId };
441 442
	}

443
	private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
444
		const services = new ServiceCollection();
445

446 447 448 449
		switch (process.platform) {
			case 'win32':
				services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
				break;
450

451 452 453
			case 'linux':
				if (process.env.SNAP && process.env.SNAP_REVISION) {
					services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
454
				} else {
455
					services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
B
Benjamin Pasero 已提交
456
				}
457
				break;
458

459 460 461
			case 'darwin':
				services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
				break;
462 463
		}

464
		services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
465
		services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
466
		services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
467
		services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
468

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

472
		services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
473
		services.set(IElectronMainService, new SyncDescriptor(ElectronMainService));
474
		services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
B
Benjamin Pasero 已提交
475
		services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
476 477 478

		const storageMainService = new StorageMainService(this.logService, this.environmentService);
		services.set(IStorageMainService, storageMainService);
479
		this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
480 481 482 483

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

484
		services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
B
Benjamin Pasero 已提交
485
		services.set(IURLService, new SyncDescriptor(NativeURLService));
486
		services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
487

488
		// Telemetry
489
		if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
490
			const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
491
			const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
492
			const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
493
			const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
494
			const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId, sendErrorTelemetry: true };
495

496
			services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
497 498 499 500
		} else {
			services.set(ITelemetryService, NullTelemetryService);
		}

501
		// Init services that require it
502
		await backupMainService.initialize();
503

504
		return this.instantiationService.createChild(services);
505 506
	}

507 508
	private stopTracingEventually(windows: ICodeWindow[]): void {
		this.logService.info(`Tracing: waiting for windows to get ready...`);
509

510
		let recordingStopped = false;
R
Robo 已提交
511
		const stopRecording = async (timeout: boolean) => {
512 513 514
			if (recordingStopped) {
				return;
			}
B
wip  
Benjamin Pasero 已提交
515

516
			recordingStopped = true; // only once
517

R
Robo 已提交
518 519 520 521 522 523 524 525
			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 已提交
526
						buttons: [localize('trace.ok', "OK")]
R
Robo 已提交
527
					}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
528
				}
R
Robo 已提交
529 530 531
			} else {
				this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
			}
532
		};
533

534 535 536 537 538 539 540 541
		// 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);
		});
542 543
	}

544
	private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
545 546

		// Register more Main IPC services
547
		const launchMainService = accessor.get(ILaunchMainService);
548
		const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
549 550 551 552 553
		this.mainIpcServer.registerChannel('launch', launchChannel);

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

556 557
		const issueMainService = accessor.get(IIssueMainService);
		const issueChannel = createChannelReceiver(issueMainService);
558
		electronIpcServer.registerChannel('issue', issueChannel);
559

560 561
		const electronMainService = accessor.get(IElectronMainService);
		const electronChannel = createChannelReceiver(electronMainService);
562
		electronIpcServer.registerChannel('electron', electronChannel);
563
		sharedProcessClient.then(client => client.registerChannel('electron', electronChannel));
564

565
		const sharedProcessMainService = accessor.get(ISharedProcessMainService);
566
		const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
567 568
		electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);

569 570
		const workspacesService = accessor.get(IWorkspacesService);
		const workspacesChannel = createChannelReceiver(workspacesService);
571
		electronIpcServer.registerChannel('workspaces', workspacesChannel);
B
Benjamin Pasero 已提交
572

B
Benjamin Pasero 已提交
573 574
		const menubarMainService = accessor.get(IMenubarMainService);
		const menubarChannel = createChannelReceiver(menubarMainService);
575
		electronIpcServer.registerChannel('menubar', menubarChannel);
576

J
Joao Moreno 已提交
577
		const urlService = accessor.get(IURLService);
578
		const urlChannel = createChannelReceiver(urlService);
579
		electronIpcServer.registerChannel('url', urlChannel);
J
Joao Moreno 已提交
580

581
		const storageMainService = accessor.get(IStorageMainService);
582
		const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
583
		electronIpcServer.registerChannel('storage', storageChannel);
584
		sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
B
wip  
Benjamin Pasero 已提交
585

586 587 588 589 590
		const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService);
		const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService);
		electronIpcServer.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel);
		sharedProcessClient.then(client => client.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel));

591 592 593
		const loggerChannel = new LoggerChannel(accessor.get(ILogService));
		electronIpcServer.registerChannel('logger', loggerChannel);
		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
S
Sandeep Somavarapu 已提交
594

595
		// ExtensionHost Debug broadcast service
596
		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
597
		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
598

599
		// Signal phase: ready (services set)
600
		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
601 602

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

605 606 607 608 609
		// 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
610
			...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
611 612 613 614 615 616 617 618 619 620

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

626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
			// 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;
641
		const environmentService = this.environmentService;
642
		urlService.registerHandler({
643
			async handleURL(uri: URI): Promise<boolean> {
K
kieferrm 已提交
644 645 646 647
				// if URI should be blocked, behave as if it's handled
				if (app.shouldBlockURI(uri)) {
					return true;
				}
648 649 650 651 652 653 654 655 656 657

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

659 660
					return true;
				}
661

662 663 664 665 666 667 668 669 670
				// 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
					});
671

672 673 674
					await window.ready();

					return urlService.open(uri);
675 676 677 678 679 680
				}

				return false;
			}
		});

J
Joao Moreno 已提交
681
		// Create a URL handler which forwards to the last active window
682
		const activeWindowManager = new ActiveWindowManager(electronMainService);
J
Joao Moreno 已提交
683
		const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
684 685
		const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
		const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
686
		urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
J
Joao Moreno 已提交
687

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

691
		// Open our first window
692
		const args = this.environmentService.args;
693
		const macOpenFiles: string[] = (<any>global).macOpenFiles;
694
		const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
695 696 697
		const hasCliArgs = args._.length;
		const hasFolderURIs = !!args['folder-uri'];
		const hasFileURIs = !!args['file-uri'];
698
		const noRecentEntry = args['skip-add-to-recently-opened'] === true;
M
Martin Aeschlimann 已提交
699
		const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
700

701 702 703 704 705 706 707 708 709 710 711 712 713
		// 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
			});
		}

714 715
		// new window if "-n" or "--remote" was used without paths
		if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
716
			return windowsMainService.open({
M
Martin Aeschlimann 已提交
717 718 719 720 721 722 723 724
				context,
				cli: args,
				forceNewWindow: true,
				forceEmpty: true,
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
725
		}
B
Benjamin Pasero 已提交
726

727
		// mac: open-file event received on startup
B
Benjamin Pasero 已提交
728
		if (macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
729
			return windowsMainService.open({
M
Martin Aeschlimann 已提交
730 731
				context: OpenContext.DOCK,
				cli: args,
732
				urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
M
Martin Aeschlimann 已提交
733 734 735 736
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
B
Benjamin Pasero 已提交
737 738
		}

M
Martin Aeschlimann 已提交
739
		// default: read paths from cli
740
		return windowsMainService.open({
M
Martin Aeschlimann 已提交
741 742 743 744 745 746
			context,
			cli: args,
			forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
			diffMode: args.diff,
			noRecentEntry,
			waitMarkerFileURI,
747
			gotoLineMode: args.goto,
M
Martin Aeschlimann 已提交
748 749
			initialStartup: true
		});
750 751
	}

K
kieferrm 已提交
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
	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;
	}

775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
	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;
	}

809
	private getWindowOpenableFromPathSync(path: string): IWindowOpenable {
810 811 812 813
		try {
			const fileStat = statSync(path);
			if (fileStat.isDirectory()) {
				return { folderUri: URI.file(path) };
814
			}
815

816 817
			if (hasWorkspaceFileExtension(path)) {
				return { workspaceUri: URI.file(path) };
818
			}
819 820
		} catch (error) {
			// ignore errors
821
		}
822

823 824
		return { fileUri: URI.file(path) };
	}
825

826
	private afterWindowOpen(accessor: ServicesAccessor): void {
827

828
		// Signal phase: after window open
829
		this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
830

831 832
		// Remote Authorities
		this.handleRemoteAuthorities();
833 834 835 836 837 838

		// Initialize update service
		const updateService = accessor.get(IUpdateService);
		if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) {
			updateService.initialize();
		}
839
	}
840 841

	private handleRemoteAuthorities(): void {
842
		protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
843
			callback({
844
				url: request.url.replace(/^vscode-remote-resource:/, 'http:'),
845 846
				method: request.method
			});
847 848
		});
	}
849
}
850 851 852 853 854 855 856

class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {

	constructor(private windowsMainService: IWindowsMainService) {
		super();
	}

857
	async call(ctx: TContext, command: string, arg?: any): Promise<any> {
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
		if (command === 'openExtensionDevelopmentHostWindow') {
			const env = arg[1];
			const pargs = parseArgs(arg[0], OPTIONS);
			const extDevPaths = pargs.extensionDevelopmentPath;
			if (extDevPaths) {
				this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
					context: OpenContext.API,
					cli: pargs,
					userEnv: Object.keys(env).length > 0 ? env : undefined
				});
			}
		} else {
			return super.call(ctx, command, arg);
		}
	}
}