This PR adds an opt-in enable-render-process-reuse flag so that users can self-host on Insiders using this flag while VS Code is still on Electron 13. After moving to Electron 15, the app.allowRendererProcessReuse property cannot be changed at all (it'll default to true), and the flag will become irrelevant.
635 lines
18 KiB
JavaScript
635 lines
18 KiB
JavaScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
//@ts-check
|
|
'use strict';
|
|
|
|
/**
|
|
* @typedef {import('./vs/base/common/product').IProductConfiguration} IProductConfiguration
|
|
* @typedef {import('./vs/base/node/languagePacks').NLSConfiguration} NLSConfiguration
|
|
* @typedef {import('./vs/platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs
|
|
*/
|
|
|
|
const perf = require('./vs/base/common/performance');
|
|
perf.mark('code/didStartMain');
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const bootstrap = require('./bootstrap');
|
|
const bootstrapNode = require('./bootstrap-node');
|
|
const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath');
|
|
/** @type {Partial<IProductConfiguration>} */
|
|
const product = require('../product.json');
|
|
const { app, protocol, crashReporter } = require('electron');
|
|
|
|
app.allowRendererProcessReuse = false;
|
|
|
|
// Enable portable support
|
|
const portable = bootstrapNode.configurePortable(product);
|
|
|
|
// Enable ASAR support
|
|
bootstrap.enableASARSupport();
|
|
|
|
// Set userData path before app 'ready' event
|
|
const args = parseCLIArgs();
|
|
const userDataPath = getUserDataPath(args);
|
|
app.setPath('userData', userDataPath);
|
|
|
|
// Resolve code cache path
|
|
const codeCachePath = getCodeCachePath();
|
|
|
|
// Configure static command line arguments
|
|
const argvConfig = configureCommandlineSwitchesSync(args);
|
|
|
|
// Configure crash reporter
|
|
perf.mark('code/willStartCrashReporter');
|
|
// 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.
|
|
//
|
|
// Appcenter crash reporting is enabled if
|
|
// * enable-crash-reporter runtime argument is set to 'true'
|
|
// * --disable-crash-reporter command line parameter is not set
|
|
//
|
|
// Disable crash reporting in all other cases.
|
|
if (args['crash-reporter-directory'] ||
|
|
(argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) {
|
|
configureCrashReporter();
|
|
}
|
|
perf.mark('code/didStartCrashReporter');
|
|
|
|
// 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)
|
|
if (portable && portable.isPortable) {
|
|
app.setAppLogsPath(path.join(userDataPath, 'logs'));
|
|
}
|
|
|
|
// Register custom schemes with privileges
|
|
protocol.registerSchemesAsPrivileged([
|
|
{
|
|
scheme: 'vscode-webview',
|
|
privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, allowServiceWorkers: true, }
|
|
},
|
|
{
|
|
scheme: 'vscode-file',
|
|
privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true }
|
|
}
|
|
]);
|
|
|
|
// Global app listeners
|
|
registerListeners();
|
|
|
|
/**
|
|
* Support user defined locale: load it early before app('ready')
|
|
* to have more things running in parallel.
|
|
*
|
|
* @type {Promise<NLSConfiguration> | undefined}
|
|
*/
|
|
let nlsConfigurationPromise = undefined;
|
|
|
|
const metaDataFile = path.join(__dirname, 'nls.metadata.json');
|
|
const locale = getUserDefinedLocale(argvConfig);
|
|
if (locale) {
|
|
const { getNLSConfiguration } = require('./vs/base/node/languagePacks');
|
|
nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
|
|
}
|
|
|
|
// Load our code once ready
|
|
app.once('ready', function () {
|
|
if (args['trace']) {
|
|
const contentTracing = require('electron').contentTracing;
|
|
|
|
const traceOptions = {
|
|
categoryFilter: args['trace-category-filter'] || '*',
|
|
traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'
|
|
};
|
|
|
|
contentTracing.startRecording(traceOptions).finally(() => onReady());
|
|
} else {
|
|
onReady();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Main startup routine
|
|
*
|
|
* @param {string | undefined} codeCachePath
|
|
* @param {NLSConfiguration} nlsConfig
|
|
*/
|
|
function startup(codeCachePath, nlsConfig) {
|
|
nlsConfig._languagePackSupport = true;
|
|
|
|
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
|
|
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
|
|
|
|
// Load main in AMD
|
|
perf.mark('code/willLoadMainBundle');
|
|
require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
|
|
perf.mark('code/didLoadMainBundle');
|
|
});
|
|
}
|
|
|
|
async function onReady() {
|
|
perf.mark('code/mainAppReady');
|
|
|
|
try {
|
|
const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]);
|
|
|
|
startup(codeCachePath, nlsConfig);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {NativeParsedArgs} cliArgs
|
|
*/
|
|
function configureCommandlineSwitchesSync(cliArgs) {
|
|
const SUPPORTED_ELECTRON_SWITCHES = [
|
|
|
|
// alias from us for --disable-gpu
|
|
'disable-hardware-acceleration',
|
|
|
|
// provided by Electron
|
|
'disable-color-correct-rendering',
|
|
|
|
// override for the color profile to use
|
|
'force-color-profile'
|
|
];
|
|
|
|
if (process.platform === 'linux') {
|
|
|
|
// Force enable screen readers on Linux via this flag
|
|
SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
|
|
}
|
|
|
|
const SUPPORTED_MAIN_PROCESS_SWITCHES = [
|
|
|
|
// Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775
|
|
'enable-proposed-api',
|
|
|
|
// Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.
|
|
'log-level',
|
|
|
|
// Enables render process reuse. Default value is 'false'. See https://github.com/electron/electron/issues/18397
|
|
'enable-render-process-reuse'
|
|
];
|
|
|
|
// Read argv config
|
|
const argvConfig = readArgvConfigSync();
|
|
|
|
Object.keys(argvConfig).forEach(argvKey => {
|
|
const argvValue = argvConfig[argvKey];
|
|
|
|
// Append Electron flags to Electron
|
|
if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) {
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append main process flags to process.argv
|
|
else if (SUPPORTED_MAIN_PROCESS_SWITCHES.indexOf(argvKey) !== -1) {
|
|
switch (argvKey) {
|
|
case '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.`);
|
|
}
|
|
break;
|
|
|
|
case 'log-level':
|
|
if (typeof argvValue === 'string') {
|
|
process.argv.push('--log', argvValue);
|
|
}
|
|
break;
|
|
|
|
case 'enable-render-process-reuse':
|
|
if (argvValue === true) {
|
|
app.allowRendererProcessReuse = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Support JS Flags
|
|
const jsFlags = getJSFlags(cliArgs);
|
|
if (jsFlags) {
|
|
app.commandLine.appendSwitch('js-flags', jsFlags);
|
|
}
|
|
|
|
return argvConfig;
|
|
}
|
|
|
|
function readArgvConfigSync() {
|
|
|
|
// 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') {
|
|
createDefaultArgvConfigSync(argvConfigPath);
|
|
} else {
|
|
console.warn(`Unable to read argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`);
|
|
}
|
|
}
|
|
|
|
// Fallback to default
|
|
if (!argvConfig) {
|
|
argvConfig = {
|
|
'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/microsoft/vscode/issues/51791)
|
|
};
|
|
}
|
|
|
|
return argvConfig;
|
|
}
|
|
|
|
/**
|
|
* @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 = [
|
|
'// This configuration file allows you to pass permanent command line arguments to VS Code.',
|
|
'// Only a subset of arguments is currently supported to reduce the likelihood of breaking',
|
|
'// the installation.',
|
|
'//',
|
|
'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
|
|
'//',
|
|
'// NOTE: Changing this file requires a restart of VS Code.',
|
|
'{',
|
|
' // Use software rendering instead of hardware accelerated rendering.',
|
|
' // This can help in cases where you see rendering issues in VS Code.',
|
|
' // "disable-hardware-acceleration": true,',
|
|
'',
|
|
' // Enabled by default by VS Code to resolve color issues in the renderer',
|
|
' // See https://github.com/microsoft/vscode/issues/51791 for details',
|
|
' "disable-color-correct-rendering": true',
|
|
'}'
|
|
];
|
|
|
|
// 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})`);
|
|
}
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
function configureCrashReporter() {
|
|
|
|
let crashReporterDirectory = args['crash-reporter-directory'];
|
|
let submitURL = '';
|
|
if (crashReporterDirectory) {
|
|
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
|
|
|
if (!path.isAbsolute(crashReporterDirectory)) {
|
|
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
|
|
app.exit(1);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Crashes are stored in the crashDumps directory by default, so we
|
|
// need to change that directory to the provided one
|
|
console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
|
|
app.setPath('crashDumps', crashReporterDirectory);
|
|
}
|
|
|
|
// Otherwise we configure the crash reporter from product.json
|
|
else {
|
|
const appCenter = product.appCenter;
|
|
if (appCenter) {
|
|
const isWindows = (process.platform === 'win32');
|
|
const isLinux = (process.platform === 'linux');
|
|
const isDarwin = (process.platform === 'darwin');
|
|
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)) {
|
|
if (isWindows) {
|
|
switch (process.arch) {
|
|
case 'ia32':
|
|
submitURL = appCenter['win32-ia32'];
|
|
break;
|
|
case 'x64':
|
|
submitURL = appCenter['win32-x64'];
|
|
break;
|
|
case 'arm64':
|
|
submitURL = appCenter['win32-arm64'];
|
|
break;
|
|
}
|
|
} else if (isDarwin) {
|
|
if (product.darwinUniversalAssetId) {
|
|
submitURL = appCenter['darwin-universal'];
|
|
} else {
|
|
switch (process.arch) {
|
|
case 'x64':
|
|
submitURL = appCenter['darwin'];
|
|
break;
|
|
case 'arm64':
|
|
submitURL = appCenter['darwin-arm64'];
|
|
break;
|
|
}
|
|
}
|
|
} else if (isLinux) {
|
|
submitURL = appCenter['linux-x64'];
|
|
}
|
|
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.
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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';
|
|
const uploadToServer = !process.env['VSCODE_DEV'] && submitURL && !crashReporterDirectory;
|
|
crashReporter.start({
|
|
companyName,
|
|
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
|
|
submitURL,
|
|
uploadToServer,
|
|
compress: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {NativeParsedArgs} cliArgs
|
|
* @returns {string | null}
|
|
*/
|
|
function getJSFlags(cliArgs) {
|
|
const jsFlags = [];
|
|
|
|
// Add any existing JS flags we already got from the command line
|
|
if (cliArgs['js-flags']) {
|
|
jsFlags.push(cliArgs['js-flags']);
|
|
}
|
|
|
|
// Support max-memory flag
|
|
if (cliArgs['max-memory'] && !/max_old_space_size=(\d+)/g.exec(cliArgs['js-flags'])) {
|
|
jsFlags.push(`--max_old_space_size=${cliArgs['max-memory']}`);
|
|
}
|
|
|
|
return jsFlags.length > 0 ? jsFlags.join(' ') : null;
|
|
}
|
|
|
|
/**
|
|
* @returns {NativeParsedArgs}
|
|
*/
|
|
function parseCLIArgs() {
|
|
const minimist = require('minimist');
|
|
|
|
return minimist(process.argv, {
|
|
string: [
|
|
'user-data-dir',
|
|
'locale',
|
|
'js-flags',
|
|
'max-memory',
|
|
'crash-reporter-directory'
|
|
]
|
|
});
|
|
}
|
|
|
|
function registerListeners() {
|
|
|
|
/**
|
|
* macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
|
|
* the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
|
|
*
|
|
* @type {string[]}
|
|
*/
|
|
const macOpenFiles = [];
|
|
global['macOpenFiles'] = macOpenFiles;
|
|
app.on('open-file', function (event, path) {
|
|
macOpenFiles.push(path);
|
|
});
|
|
|
|
/**
|
|
* macOS: react to open-url requests.
|
|
*
|
|
* @type {string[]}
|
|
*/
|
|
const openUrls = [];
|
|
const onOpenUrl =
|
|
/**
|
|
* @param {{ preventDefault: () => void; }} event
|
|
* @param {string} url
|
|
*/
|
|
function (event, url) {
|
|
event.preventDefault();
|
|
|
|
openUrls.push(url);
|
|
};
|
|
|
|
app.on('will-finish-launching', function () {
|
|
app.on('open-url', onOpenUrl);
|
|
});
|
|
|
|
global['getOpenUrls'] = function () {
|
|
app.removeListener('open-url', onOpenUrl);
|
|
|
|
return openUrls;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @returns {string | undefined} the location to use for the code cache
|
|
* or `undefined` if disabled.
|
|
*/
|
|
function getCodeCachePath() {
|
|
|
|
// explicitly disabled via CLI args
|
|
if (process.argv.indexOf('--no-cached-data') > 0) {
|
|
return undefined;
|
|
}
|
|
|
|
// running out of sources
|
|
if (process.env['VSCODE_DEV']) {
|
|
return undefined;
|
|
}
|
|
|
|
// require commit id
|
|
const commit = product.commit;
|
|
if (!commit) {
|
|
return undefined;
|
|
}
|
|
|
|
return path.join(userDataPath, 'CachedData', commit);
|
|
}
|
|
|
|
/**
|
|
* @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));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string | undefined} dir
|
|
* @returns {Promise<string | undefined>}
|
|
*/
|
|
async function mkdirpIgnoreError(dir) {
|
|
if (typeof dir === 'string') {
|
|
try {
|
|
await mkdirp(dir);
|
|
|
|
return dir;
|
|
} catch (error) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
//#region NLS Support
|
|
|
|
/**
|
|
* Resolve the NLS configuration
|
|
*
|
|
* @return {Promise<NLSConfiguration>}
|
|
*/
|
|
async function resolveNlsConfiguration() {
|
|
|
|
// 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.
|
|
let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined;
|
|
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 sensitiveness
|
|
appLocale = appLocale.toLowerCase();
|
|
|
|
const { getNLSConfiguration } = require('./vs/base/node/languagePacks');
|
|
nlsConfiguration = await 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;
|
|
}
|
|
|
|
/**
|
|
* @param {string} content
|
|
* @returns {string}
|
|
*/
|
|
function stripComments(content) {
|
|
const regexp = /("(?:[^\\"]*(?:\\.)?)*")|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
|
|
|
|
return content.replace(regexp, function (match, m1, m2, m3, m4) {
|
|
// Only one of m1, m2, m3, m4 matches
|
|
if (m3) {
|
|
// A block comment. Replace with nothing
|
|
return '';
|
|
} else if (m4) {
|
|
// A line comment. If it ends in \r?\n then keep it.
|
|
const length_1 = m4.length;
|
|
if (length_1 > 2 && m4[length_1 - 1] === '\n') {
|
|
return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
} else {
|
|
// We match a string
|
|
return match;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param {{ locale: string | undefined; }} argvConfig
|
|
* @returns {string | undefined}
|
|
*/
|
|
function getUserDefinedLocale(argvConfig) {
|
|
const locale = args['locale'];
|
|
if (locale) {
|
|
return locale.toLowerCase(); // a directly provided --locale always wins
|
|
}
|
|
|
|
return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
|
|
}
|
|
|
|
//#endregion
|