diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index a8144075dd5..b0b266557a6 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -14,7 +14,7 @@ const nlsConfig = bootstrap.setupNLS(); // Bootstrap: Loader loader.config({ - baseUrl: bootstrap.uriFromPath(__dirname), + baseUrl: bootstrap.fileUriFromPath(__dirname), catchError: true, nodeRequire: require, nodeMain: __filename, diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index a417c1deb58..76c00585ad8 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -21,8 +21,9 @@ globalThis.MonacoBootstrapWindow = factory(); } }(this, function () { - const path = require.__$__nodeRequire('path'); - const bootstrap = globalThis.MonacoBootstrap; + const preloadGlobals = globals(); + const sandbox = preloadGlobals.context.sandbox; + const safeProcess = sandbox ? preloadGlobals.process : process; /** * @param {string[]} modulePaths @@ -43,29 +44,33 @@ const configuration = JSON.parse(args['config'] || '{}') || {}; // Error handler - process.on('uncaughtException', function (error) { + safeProcess.on('uncaughtException', function (error) { onUnexpectedError(error, enableDeveloperTools); }); // Developer tools - const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; + const enableDeveloperTools = (safeProcess.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; let developerToolsUnbind; if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) { developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding); } - // Correctly inherit the parent's environment - Object.assign(process.env, configuration.userEnv); + // Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only) + if (!sandbox) { + Object.assign(safeProcess.env, configuration.userEnv); + } - // Enable ASAR support - bootstrap.enableASARSupport(path.join(configuration.appRoot, 'node_modules')); + // Enable ASAR support (TODO@sandbox non-sandboxed only) + if (!sandbox) { + globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot); + } if (options && typeof options.canModifyDOM === 'function') { options.canModifyDOM(configuration); } - // Get the nls configuration into the process.env as early as possible. - const nlsConfig = bootstrap.setupNLS(); + // Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only) + const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS(); let locale = nlsConfig.availableLanguages['*'] || 'en'; if (locale === 'zh-tw') { @@ -76,16 +81,20 @@ window.document.documentElement.setAttribute('lang', locale); - // do not advertise AMD to avoid confusing UMD modules loaded with nodejs - window['define'] = undefined; + // do not advertise AMD to avoid confusing UMD modules loaded with nodejs (TODO@sandbox non-sandboxed only) + if (!sandbox) { + window['define'] = undefined; + } - // replace the patched electron fs with the original node fs for all AMD code - require.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); + // replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only) + if (!sandbox) { + require.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); + } window['MonacoEnvironment'] = {}; const loaderConfig = { - baseUrl: `${bootstrap.uriFromPath(configuration.appRoot)}/out`, + baseUrl: `${uriFromPath(configuration.appRoot)}/out`, 'vs/nls': nlsConfig, amdModulesPattern: /^vs\//, }; @@ -150,7 +159,7 @@ * @returns {() => void} */ function registerDeveloperKeybindings(disallowReloadKeybinding) { - const ipcRenderer = globals().ipcRenderer; + const ipcRenderer = preloadGlobals.ipcRenderer; const extractKey = function (e) { return [ @@ -163,9 +172,9 @@ }; // Devtools & reload support - const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I + const TOGGLE_DEV_TOOLS_KB = (safeProcess.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12 - const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R + const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R let listener = function (e) { const key = extractKey(e); @@ -192,7 +201,7 @@ */ function onUnexpectedError(error, enableDeveloperTools) { if (enableDeveloperTools) { - const ipcRenderer = globals().ipcRenderer; + const ipcRenderer = preloadGlobals.ipcRenderer; ipcRenderer.send('vscode:openDevTools'); } @@ -211,6 +220,31 @@ return window.vscode; } + /** + * TODO@sandbox this should not use the file:// protocol at all + * and be consolidated with the fileUriFromPath() method in + * bootstrap.js. + * + * @param {string} path + * @returns {string} + */ + function uriFromPath(path) { + let pathName = path.replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = `/${pathName}`; + } + + /** @type {string} */ + let uri; + if (safeProcess.platform === 'win32' && pathName.startsWith('//')) { // specially handle Windows UNC paths + uri = encodeURI(`file:${pathName}`); + } else { + uri = encodeURI(`file://${pathName}`); + } + + return uri.replace(/#/g, '%23'); + } + return { load, globals diff --git a/src/bootstrap.js b/src/bootstrap.js index f1f579c7b7c..d1abd5502df 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -16,7 +16,11 @@ // Browser else { - globalThis.MonacoBootstrap = factory(); + try { + globalThis.MonacoBootstrap = factory(); + } catch (error) { + console.warn(error); // expected when e.g. running with sandbox: true (TODO@sandbox eventually consolidate this) + } } }(this, function () { const Module = require('module'); @@ -40,10 +44,10 @@ //#region Add support for using node_modules.asar /** - * @param {string=} nodeModulesPath + * @param {string} appRoot */ - function enableASARSupport(nodeModulesPath) { - let NODE_MODULES_PATH = nodeModulesPath; + function enableASARSupport(appRoot) { + let NODE_MODULES_PATH = appRoot ? path.join(appRoot, 'node_modules') : undefined; if (!NODE_MODULES_PATH) { NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); } else { @@ -83,7 +87,7 @@ * @param {string} _path * @returns {string} */ - function uriFromPath(_path) { + function fileUriFromPath(_path) { let pathName = path.resolve(_path).replace(/\\/g, '/'); if (pathName.length > 0 && pathName.charAt(0) !== '/') { pathName = `/${pathName}`; @@ -132,7 +136,7 @@ } const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`); - readFile(bundleFile).then(function (content) { + fs.promises.readFile(bundleFile, 'utf8').then(function (content) { const json = JSON.parse(content); bundles[bundle] = json; @@ -140,7 +144,7 @@ }).catch((error) => { try { if (nlsConfig._corruptedFile) { - writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); }); } } finally { cb(error, undefined); @@ -152,23 +156,6 @@ return nlsConfig; } - /** - * @param {string} file - * @returns {Promise} - */ - function readFile(file) { - return fs.promises.readFile(file, 'utf8'); - } - - /** - * @param {string} file - * @param {string} content - * @returns {Promise} - */ - function writeFile(file, content) { - return fs.promises.writeFile(file, content, 'utf8'); - } - //#endregion @@ -254,6 +241,6 @@ avoidMonkeyPatchFromAppInsights, configurePortable, setupNLS, - uriFromPath + fileUriFromPath }; })); diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index f0718ffc8e9..d10c4be3ae1 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -7,16 +7,13 @@ (function () { 'use strict'; - const { ipcRenderer, webFrame, crashReporter } = require('electron'); + const { ipcRenderer, webFrame, crashReporter, contextBridge } = require('electron'); - // @ts-ignore - window.vscode = { + const globals = { /** - * A minimal set of methods exposed from ipcRenderer - * to support communication to electron-main - * - * @type {typeof import('../electron-sandbox/globals').ipcRenderer} + * A minimal set of methods exposed from Electron's `ipcRenderer` + * to support communication to main process. */ ipcRenderer: { @@ -25,9 +22,9 @@ * @param {any[]} args */ send(channel, ...args) { - validateIPC(channel); - - ipcRenderer.send(channel, ...args); + if (validateIPC(channel)) { + ipcRenderer.send(channel, ...args); + } }, /** @@ -35,9 +32,9 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ on(channel, listener) { - validateIPC(channel); - - ipcRenderer.on(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.on(channel, listener); + } }, /** @@ -45,9 +42,9 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ once(channel, listener) { - validateIPC(channel); - - ipcRenderer.once(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.once(channel, listener); + } }, /** @@ -55,16 +52,14 @@ * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener */ removeListener(channel, listener) { - validateIPC(channel); - - ipcRenderer.removeListener(channel, listener); + if (validateIPC(channel)) { + ipcRenderer.removeListener(channel, listener); + } } }, /** - * Support for methods of webFrame type. - * - * @type {typeof import('../electron-sandbox/globals').webFrame} + * Support for subset of methods of Electron's `webFrame` type. */ webFrame: { @@ -72,14 +67,14 @@ * @param {number} level */ setZoomLevel(level) { - webFrame.setZoomLevel(level); + if (typeof level === 'number') { + webFrame.setZoomLevel(level); + } } }, /** - * Support for methods of crashReporter type. - * - * @type {typeof import('../electron-sandbox/globals').crashReporter} + * Support for subset of methods of Electron's `crashReporter` type. */ crashReporter: { @@ -89,9 +84,53 @@ start(options) { crashReporter.start(options); } + }, + + /** + * Support for a subset of access to node.js global `process`. + */ + process: { + platform: process.platform, + env: process.env, + on: + /** + * @param {string} type + * @param {() => void} callback + */ + function (type, callback) { + if (validateProcessEventType(type)) { + process.on(type, callback); + } + } + }, + + /** + * Some information about the context we are running in. + */ + context: { + sandbox: process.argv.includes('--enable-sandbox') } }; + // Use `contextBridge` APIs to expose globals to VSCode + // only if context isolation is enabled, otherwise just + // add to the DOM global. + let useContextBridge = process.argv.includes('--context-isolation'); + if (useContextBridge) { + try { + contextBridge.exposeInMainWorld('vscode', globals); + } catch (error) { + console.error(error); + + useContextBridge = false; + } + } + + if (!useContextBridge) { + // @ts-ignore + window.vscode = globals; + } + //#region Utilities /** @@ -101,6 +140,20 @@ if (!channel || !channel.startsWith('vscode:')) { throw new Error(`Unsupported event IPC channel '${channel}'`); } + + return true; + } + + /** + * @param {string} type + * @returns {type is 'uncaughtException'} + */ + function validateProcessEventType(type) { + if (type !== 'uncaughtException') { + throw new Error(`Unsupported process event '${type}'`); + } + + return true; } //#endregion diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 47d3700efd7..573e4735933 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -81,3 +81,30 @@ export const crashReporter = (window as any).vscode.crashReporter as { */ start(options: CrashReporterStartOptions): void; }; + +export const process = (window as any).vscode.process as { + + /** + * The process.platform property returns a string identifying the operating system platform + * on which the Node.js process is running. + */ + platform: 'win32' | 'linux' | 'darwin'; + + /** + * The process.env property returns an object containing the user environment. See environ(7). + */ + env: { [key: string]: string | undefined }; + + /** + * A listener on the process. Only a small subset of listener types are allowed. + */ + on: (type: string, callback: Function) => void; +}; + +export const context = (window as any).vscode.context as { + + /** + * Wether the renderer runs with `sandbox` enabled or not. + */ + sandbox: boolean; +}; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 92027912edc..d225e6eb31d 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -43,12 +43,12 @@ export class SharedProcess implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - images: false, nodeIntegration: true, - webgl: false, enableWebSQL: false, enableRemoteModule: false, nativeWindowOpen: true, + images: false, + webgl: false, disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer } }); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index daf6df757cd..c300a83ac0e 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -168,10 +168,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { webPreferences: { preload: URI.parse(this.doGetPreloadUrl()).fsPath, nodeIntegration: true, - webviewTag: true, enableWebSQL: false, enableRemoteModule: false, nativeWindowOpen: true, + webviewTag: true, zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel) } }; diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 5a4adbc6c0f..f8dc1807c15 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -275,7 +275,7 @@ class ProcessExplorer { tableHead.innerHTML = ` ${localize('cpu', "CPU %")} ${localize('memory', "Memory (MB)")} - ${localize('pid', "pid")} + ${localize('pid', "PID")} ${localize('name', "Name")} `; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index d3179e46b2f..2379b626c81 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -70,12 +70,13 @@ export interface ParsedArgs { 'file-chmod'?: boolean; 'driver'?: string; 'driver-verbose'?: boolean; - remote?: string; + 'remote'?: string; 'disable-user-env-probe'?: boolean; 'force'?: boolean; 'do-not-sync'?: boolean; 'force-user-env'?: boolean; 'sync'?: 'on' | 'off'; + '__sandbox'?: boolean; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; @@ -195,6 +196,7 @@ export const OPTIONS: OptionDescriptions> = { 'trace-options': { type: 'string' }, 'force-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, + '__sandbox': { type: 'boolean' }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 571418e3df3..5c0dc4ad4ae 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -43,6 +43,8 @@ export interface INativeEnvironmentService extends IEnvironmentService { driverVerbose: boolean; disableUpdates: boolean; + + sandbox: boolean; } export class EnvironmentService implements INativeEnvironmentService { @@ -262,6 +264,8 @@ export class EnvironmentService implements INativeEnvironmentService { get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; } + get sandbox(): boolean { return !!this._args['__sandbox']; } + constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 0b901d36975..9a333650a30 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -250,11 +250,22 @@ export class IssueMainService implements ICommonIssueService { title: localize('processExplorer', "Process Explorer"), webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel) + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + ...this.environmentService.sandbox ? + + // Sandbox + { + sandbox: true, + contextIsolation: true + } : + + // No Sandbox + { + nodeIntegration: true + } } }); diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 6d1c7653799..e555619012d 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -112,14 +112,14 @@ app.on('ready', () => { width: 800, show: false, webPreferences: { - backgroundThrottling: false, - nodeIntegration: true, - webSecurity: false, - webviewTag: true, preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-browser', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this + nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, - nativeWindowOpen: true + nativeWindowOpen: true, + webSecurity: false, + webviewTag: true, + backgroundThrottling: false } }); diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 734c0837841..e2a4b435690 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -32,7 +32,7 @@ function initLoader(opts) { nodeRequire: require, nodeMain: __filename, catchError: true, - baseUrl: bootstrap.uriFromPath(path.join(__dirname, '../../../src')), + baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../src')), paths: { 'vs': `../${outdir}/vs`, 'lib': `../${outdir}/lib`,