main.js 17.0 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
5 6

//@ts-check
7 8
'use strict';

J
Joao Moreno 已提交
9
const perf = require('./vs/base/common/performance');
10 11
const lp = require('./vs/base/node/languagePacks');

12 13
perf.mark('main:started');

J
Joao Moreno 已提交
14
const path = require('path');
15 16
const fs = require('fs');
const os = require('os');
17 18
const bootstrap = require('./bootstrap');
const paths = require('./paths');
19
/** @type {any} */
20
const product = require('../product.json');
R
Robo 已提交
21 22 23 24 25
const { app, protocol, crashReporter } = require('electron');

// Disable render process reuse, we still have
// non-context aware native modules in the renderer.
app.allowRendererProcessReuse = false;
J
Joao Moreno 已提交
26

27
// Enable portable support
28
const portable = bootstrap.configurePortable(product);
29 30

// Enable ASAR support
31
bootstrap.enableASARSupport(undefined);
32

B
Benjamin Pasero 已提交
33
// Set userData path before app 'ready' event
34 35 36 37
const args = parseCLIArgs();
const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);

R
Robo 已提交
38 39
// Configure static command line arguments
const argvConfig = configureCommandlineSwitchesSync(args);
40

R
Robo 已提交
41 42
// If a crash-reporter-directory is specified we store the crash reports
// in the specified directory and don't upload them to the crash server.
43
let crashReporterDirectory = args['crash-reporter-directory'];
R
Robo 已提交
44
let submitURL = '';
45
if (crashReporterDirectory) {
46 47
	crashReporterDirectory = path.normalize(crashReporterDirectory);

48 49 50 51 52
	if (!path.isAbsolute(crashReporterDirectory)) {
		console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
		app.exit(1);
	}

53 54 55 56 57 58 59 60
	if (!fs.existsSync(crashReporterDirectory)) {
		try {
			fs.mkdirSync(crashReporterDirectory);
		} catch (error) {
			console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`);
			app.exit(1);
		}
	}
61

R
Robo 已提交
62
	// Crashes are stored in the crashDumps directory by default, so we
63
	// need to change that directory to the provided one
R
Robo 已提交
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
	console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
	app.setPath('crashDumps', crashReporterDirectory);
} else {
	const appCenter = product.appCenter;
	// Disable Appcenter crash reporting if
	// * --crash-reporter-directory is specified
	// * enable-crash-reporter runtime argument is set to 'false'
	// * --disable-crash-reporter command line parameter is set
	if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) {
		const isWindows = (process.platform === 'win32');
		const isLinux = (process.platform === 'linux');
		const crashReporterId = argvConfig['crash-reporter-id'];
		const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
		if (uuidPattern.test(crashReporterId)) {
			submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin;
			submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
			// Send the id for child node process that are explicitly starting crash reporter.
			// For vscode this is ExtensionHost process currently.
82 83 84 85 86 87 88 89 90 91
			const argv = process.argv;
			const endOfArgsMarkerIndex = argv.indexOf('--');
			if (endOfArgsMarkerIndex === -1) {
				argv.push('--crash-reporter-id', crashReporterId);
			} else {
				// if the we have an argument "--" (end of argument marker)
				// we cannot add arguments at the end. rather, we add
				// arguments before the "--" marker.
				argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId);
			}
R
Robo 已提交
92 93
		}
	}
94 95
}

R
Robo 已提交
96 97 98 99 100 101 102 103 104 105
// Start crash reporter for all processes
const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort;
const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft';
crashReporter.start({
	companyName: companyName,
	productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
	submitURL,
	uploadToServer: !crashReporterDirectory
});

B
Benjamin Pasero 已提交
106 107 108 109
// Set logs path before app 'ready' event if running portable
// to ensure that no 'logs' folder is created on disk at a
// location outside of the portable directory
// (https://github.com/microsoft/vscode/issues/56651)
B
Benjamin Pasero 已提交
110
if (portable && portable.isPortable) {
B
Benjamin Pasero 已提交
111 112 113
	app.setAppLogsPath(path.join(userDataPath, 'logs'));
}

114 115 116
// Update cwd based on environment and platform
setCurrentWorkingDirectory();

117 118
// Register custom schemes with privileges
protocol.registerSchemesAsPrivileged([
119
	{
120
		scheme: 'vscode-webview',
121
		privileges: {
122
			standard: true,
123
			secure: true,
124 125
			supportFetchAPI: true,
			corsEnabled: true,
126 127
		}
	}, {
128 129 130 131 132 133 134 135
		scheme: 'vscode-webview-resource',
		privileges: {
			secure: true,
			standard: true,
			supportFetchAPI: true,
			corsEnabled: true,
		}
	},
136 137
]);

138 139 140
// Global app listeners
registerListeners();

141 142 143
// Cached data
const nodeCachedDataDir = getNodeCachedDir();

144
/**
B
Benjamin Pasero 已提交
145 146
 * Support user defined locale: load it early before app('ready')
 * to have more things running in parallel.
147
 *
148
 * @type {Promise<import('./vs/base/node/languagePacks').NLSConfiguration> | undefined}
149
 */
B
Benjamin Pasero 已提交
150
let nlsConfigurationPromise = undefined;
151

152 153 154 155 156
const metaDataFile = path.join(__dirname, 'nls.metadata.json');
const locale = getUserDefinedLocale(argvConfig);
if (locale) {
	nlsConfigurationPromise = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
}
157 158 159

// Load our code once ready
app.once('ready', function () {
B
Benjamin Pasero 已提交
160 161 162 163 164 165 166 167
	if (args['trace']) {
		const contentTracing = require('electron').contentTracing;

		const traceOptions = {
			categoryFilter: args['trace-category-filter'] || '*',
			traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'
		};

168
		contentTracing.startRecording(traceOptions).finally(() => onReady());
B
Benjamin Pasero 已提交
169 170 171 172 173
	} else {
		onReady();
	}
});

B
Benjamin Pasero 已提交
174 175 176 177 178 179 180 181
/**
 * Main startup routine
 *
 * @param {string | undefined} cachedDataDir
 * @param {import('./vs/base/node/languagePacks').NLSConfiguration} nlsConfig
 */
function startup(cachedDataDir, nlsConfig) {
	nlsConfig._languagePackSupport = true;
182

B
Benjamin Pasero 已提交
183 184
	process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
	process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
185

B
Benjamin Pasero 已提交
186 187 188 189 190 191
	// Load main in AMD
	perf.mark('willLoadMainBundle');
	require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
		perf.mark('didLoadMainBundle');
	});
}
192

B
Benjamin Pasero 已提交
193 194
async function onReady() {
	perf.mark('main:appReady');
195

B
Benjamin Pasero 已提交
196
	try {
197
		const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
B
Benjamin Pasero 已提交
198

199
		startup(cachedDataDir, nlsConfig);
B
Benjamin Pasero 已提交
200 201 202
	} catch (error) {
		console.error(error);
	}
B
Benjamin Pasero 已提交
203
}
204

205
/**
206
 * @typedef	 {{ [arg: string]: any; '--'?: string[]; _: string[]; }} NativeParsedArgs
207
 *
208
 * @param {NativeParsedArgs} cliArgs
209
 */
210 211 212 213 214 215 216
function configureCommandlineSwitchesSync(cliArgs) {
	const SUPPORTED_ELECTRON_SWITCHES = [

		// alias from us for --disable-gpu
		'disable-hardware-acceleration',

		// provided by Electron
217 218 219 220
		'disable-color-correct-rendering',

		// override for the color profile to use
		'force-color-profile'
221
	];
222

223
	if (process.platform === 'linux') {
B
Benjamin Pasero 已提交
224 225

		// Force enable screen readers on Linux via this flag
226 227
		SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
	}
228

229 230 231 232 233 234
	const SUPPORTED_MAIN_PROCESS_SWITCHES = [

		// Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775
		'enable-proposed-api'
	];

235
	// Read argv config
236
	const argvConfig = readArgvConfigSync();
237

238 239
	Object.keys(argvConfig).forEach(argvKey => {
		const argvValue = argvConfig[argvKey];
240

241 242
		// Append Electron flags to Electron
		if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) {
B
Benjamin Pasero 已提交
243

244 245 246 247 248 249 250 251 252 253 254 255 256 257
			// Color profile
			if (argvKey === 'force-color-profile') {
				if (argvValue) {
					app.commandLine.appendSwitch(argvKey, argvValue);
				}
			}

			// Others
			else if (argvValue === true || argvValue === 'true') {
				if (argvKey === 'disable-hardware-acceleration') {
					app.disableHardwareAcceleration(); // needs to be called explicitly
				} else {
					app.commandLine.appendSwitch(argvKey);
				}
258 259 260
			}
		}

261 262 263 264 265 266 267 268
		// Append main process flags to process.argv
		else if (SUPPORTED_MAIN_PROCESS_SWITCHES.indexOf(argvKey) !== -1) {
			if (argvKey === 'enable-proposed-api') {
				if (Array.isArray(argvValue)) {
					argvValue.forEach(id => id && typeof id === 'string' && process.argv.push('--enable-proposed-api', id));
				} else {
					console.error(`Unexpected value for \`enable-proposed-api\` in argv.json. Expected array of extension ids.`);
				}
269 270 271
			}
		}
	});
272 273

	// Support JS Flags
274
	const jsFlags = getJSFlags(cliArgs);
275
	if (jsFlags) {
276
		app.commandLine.appendSwitch('js-flags', jsFlags);
J
Joao Moreno 已提交
277
	}
278 279

	return argvConfig;
J
Joao Moreno 已提交
280 281
}

282
function readArgvConfigSync() {
283 284 285 286 287 288 289 290

	// Read or create the argv.json config file sync before app('ready')
	const argvConfigPath = getArgvConfigPath();
	let argvConfig;
	try {
		argvConfig = JSON.parse(stripComments(fs.readFileSync(argvConfigPath).toString()));
	} catch (error) {
		if (error && error.code === 'ENOENT') {
291
			createDefaultArgvConfigSync(argvConfigPath);
292 293 294 295 296 297 298 299
		} else {
			console.warn(`Unable to read argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`);
		}
	}

	// Fallback to default
	if (!argvConfig) {
		argvConfig = {
C
ChaseKnowlden 已提交
300
			'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/microsoft/vscode/issues/51791)
301 302 303 304 305 306
		};
	}

	return argvConfig;
}

307 308 309 310 311 312 313 314 315 316 317 318 319 320
/**
 * @param {string} argvConfigPath
 */
function createDefaultArgvConfigSync(argvConfigPath) {
	try {

		// Ensure argv config parent exists
		const argvConfigPathDirname = path.dirname(argvConfigPath);
		if (!fs.existsSync(argvConfigPathDirname)) {
			fs.mkdirSync(argvConfigPathDirname);
		}

		// Default argv content
		const defaultArgvConfigContent = [
G
Greg Van Liew 已提交
321
			'// This configuration file allows you to pass permanent command line arguments to VS Code.',
322
			'// Only a subset of arguments is currently supported to reduce the likelihood of breaking',
323 324 325 326
			'// the installation.',
			'//',
			'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
			'//',
G
Greg Van Liew 已提交
327
			'// NOTE: Changing this file requires a restart of VS Code.',
328
			'{',
B
Benjamin Pasero 已提交
329 330
			'	// Use software rendering instead of hardware accelerated rendering.',
			'	// This can help in cases where you see rendering issues in VS Code.',
B
Benjamin Pasero 已提交
331 332 333
			'	// "disable-hardware-acceleration": true,',
			'',
			'	// Enabled by default by VS Code to resolve color issues in the renderer',
C
ChaseKnowlden 已提交
334
			'	// See https://github.com/microsoft/vscode/issues/51791 for details',
335 336
			'	"disable-color-correct-rendering": true',
			'}'
337 338 339 340 341 342 343 344 345
		];

		// Create initial argv.json with default content
		fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n'));
	} catch (error) {
		console.error(`Unable to create argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`);
	}
}

346 347 348 349 350 351 352 353 354 355 356 357 358 359
function getArgvConfigPath() {
	const vscodePortable = process.env['VSCODE_PORTABLE'];
	if (vscodePortable) {
		return path.join(vscodePortable, 'argv.json');
	}

	let dataFolderName = product.dataFolderName;
	if (process.env['VSCODE_DEV']) {
		dataFolderName = `${dataFolderName}-dev`;
	}

	return path.join(os.homedir(), dataFolderName, 'argv.json');
}

360
/**
361
 * @param {NativeParsedArgs} cliArgs
362
 * @returns {string | null}
363
 */
364 365
function getJSFlags(cliArgs) {
	const jsFlags = [];
366 367

	// Add any existing JS flags we already got from the command line
368 369
	if (cliArgs['js-flags']) {
		jsFlags.push(cliArgs['js-flags']);
370 371
	}

372
	// Support max-memory flag
373 374
	if (cliArgs['max-memory'] && !/max_old_space_size=(\d+)/g.exec(cliArgs['js-flags'])) {
		jsFlags.push(`--max_old_space_size=${cliArgs['max-memory']}`);
375
	}
376 377

	return jsFlags.length > 0 ? jsFlags.join(' ') : null;
378 379
}

380
/**
381
 * @param {NativeParsedArgs} cliArgs
382
 *
383 384
 * @returns {string}
 */
385 386 387 388
function getUserDataPath(cliArgs) {
	if (portable.isPortable) {
		return path.join(portable.portableDataPath, 'user-data');
	}
J
Joao Moreno 已提交
389

390
	return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath());
J
Joao Moreno 已提交
391 392
}

393
/**
394
 * @returns {NativeParsedArgs}
395
 */
396
function parseCLIArgs() {
D
Daniel Imms 已提交
397
	const minimist = require('minimist');
398

399 400 401 402 403
	return minimist(process.argv, {
		string: [
			'user-data-dir',
			'locale',
			'js-flags',
404 405
			'max-memory',
			'crash-reporter-directory'
406 407
		]
	});
J
Joao Moreno 已提交
408 409
}

410 411 412
function setCurrentWorkingDirectory() {
	try {
		if (process.platform === 'win32') {
413
			process.env['VSCODE_CWD'] = process.cwd(); // remember as environment variable
414 415 416
			process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd
		} else if (process.env['VSCODE_CWD']) {
			process.chdir(process.env['VSCODE_CWD']);
A
Alex Dima 已提交
417
		}
418 419 420 421
	} catch (err) {
		console.error(err);
	}
}
A
Alex Dima 已提交
422

423
function registerListeners() {
A
Alex Dima 已提交
424

425
	/**
426
	 * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
427 428 429 430
	 * the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
	 *
	 * @type {string[]}
	 */
431 432
	const macOpenFiles = [];
	global['macOpenFiles'] = macOpenFiles;
433
	app.on('open-file', function (event, path) {
434
		macOpenFiles.push(path);
435
	});
436

437
	/**
438
	 * macOS: react to open-url requests.
439 440 441
	 *
	 * @type {string[]}
	 */
442 443 444
	const openUrls = [];
	const onOpenUrl = function (event, url) {
		event.preventDefault();
445

446 447
		openUrls.push(url);
	};
J
Joao Moreno 已提交
448

449 450 451
	app.on('will-finish-launching', function () {
		app.on('open-url', onOpenUrl);
	});
J
Joao Moreno 已提交
452

453
	global['getOpenUrls'] = function () {
454
		app.removeListener('open-url', onOpenUrl);
455

456 457
		return openUrls;
	};
458 459
}

460
/**
B
Benjamin Pasero 已提交
461
 * @returns {{ ensureExists: () => Promise<string | undefined> }}
462
 */
463
function getNodeCachedDir() {
464
	return new class {
465

466 467 468 469
		constructor() {
			this.value = this._compute();
		}

B
Benjamin Pasero 已提交
470
		async ensureExists() {
471 472 473
			if (typeof this.value === 'string') {
				try {
					await mkdirp(this.value);
B
Benjamin Pasero 已提交
474

475 476 477 478
					return this.value;
				} catch (error) {
					// ignore
				}
B
Benjamin Pasero 已提交
479
			}
480 481 482 483 484 485
		}

		_compute() {
			if (process.argv.indexOf('--no-cached-data') > 0) {
				return undefined;
			}
486

487 488 489 490
			// IEnvironmentService.isBuilt
			if (process.env['VSCODE_DEV']) {
				return undefined;
			}
491

492
			// find commit id
493
			const commit = product.commit;
494 495 496
			if (!commit) {
				return undefined;
			}
497

498 499 500 501
			return path.join(userDataPath, 'CachedData', commit);
		}
	};
}
502

503 504 505 506 507 508 509 510 511 512 513 514
/**
 * @param {string} dir
 * @returns {Promise<string>}
 */
function mkdirp(dir) {
	const fs = require('fs');

	return new Promise((resolve, reject) => {
		fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? reject(err) : resolve(dir));
	});
}

515
//#region NLS Support
516

B
Benjamin Pasero 已提交
517 518 519 520 521
/**
 * Resolve the NLS configuration
 *
 * @return {Promise<import('./vs/base/node/languagePacks').NLSConfiguration>}
 */
522
async function resolveNlsConfiguration() {
B
Benjamin Pasero 已提交
523 524 525

	// First, we need to test a user defined locale. If it fails we try the app locale.
	// If that fails we fall back to English.
526
	let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined;
B
Benjamin Pasero 已提交
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
	if (!nlsConfiguration) {

		// Try to use the app locale. Please note that the app locale is only
		// valid after we have received the app ready event. This is why the
		// code is here.
		let appLocale = app.getLocale();
		if (!appLocale) {
			nlsConfiguration = { locale: 'en', availableLanguages: {} };
		} else {

			// See above the comment about the loader and case sensitiviness
			appLocale = appLocale.toLowerCase();

			nlsConfiguration = await lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale);
			if (!nlsConfiguration) {
				nlsConfiguration = { locale: appLocale, availableLanguages: {} };
			}
		}
	} else {
		// We received a valid nlsConfig from a user defined locale
	}

	return nlsConfiguration;
}

552 553 554 555
/**
 * @param {string} content
 * @returns {string}
 */
556
function stripComments(content) {
A
Alex Dima 已提交
557
	const regexp = /("(?:[^\\"]*(?:\\.)?)*")|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
558 559

	return content.replace(regexp, function (match, m1, m2, m3, m4) {
560 561 562 563
		// Only one of m1, m2, m3, m4 matches
		if (m3) {
			// A block comment. Replace with nothing
			return '';
D
Dirk Baeumer 已提交
564
		} else if (m4) {
565
			// A line comment. If it ends in \r?\n then keep it.
566
			const length_1 = m4.length;
567 568 569 570 571 572
			if (length_1 > 2 && m4[length_1 - 1] === '\n') {
				return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
			}
			else {
				return '';
			}
D
Dirk Baeumer 已提交
573
		} else {
574 575 576 577
			// We match a string
			return match;
		}
	});
J
lint  
Joao Moreno 已提交
578
}
579

580
/**
B
Benjamin Pasero 已提交
581 582 583 584 585
 * Language tags are case insensitive however an amd loader is case sensitive
 * To make this work on case preserving & insensitive FS we do the following:
 * the language bundles have lower case language tags and we always lower case
 * the locale we receive from the user or OS.
 *
586 587
 * @param {{ locale: string | undefined; }} argvConfig
 * @returns {string | undefined}
588
 */
589
function getUserDefinedLocale(argvConfig) {
590
	const locale = args['locale'];
D
Dirk Baeumer 已提交
591
	if (locale) {
592
		return locale.toLowerCase(); // a directly provided --locale always wins
D
Dirk Baeumer 已提交
593 594
	}

595 596
	return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
}
B
Benjamin Pasero 已提交
597

598
//#endregion