mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-03 23:06:49 +01:00
Merge pull request #21879 from Microsoft/joao/shared-process
Move shared process to a browser window
This commit is contained in:
@@ -20,6 +20,6 @@ exports.collectModules= function() {
|
||||
createModuleDescription('vs/code/electron-main/main', []),
|
||||
createModuleDescription('vs/code/node/cli', []),
|
||||
createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']),
|
||||
createModuleDescription('vs/code/node/sharedProcessMain', [])
|
||||
createModuleDescription('vs/code/electron-browser/sharedProcessMain', [])
|
||||
];
|
||||
};
|
||||
16
src/vs/code/electron-browser/sharedProcess.html
Normal file
16
src/vs/code/electron-browser/sharedProcess.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
Shared Process
|
||||
</body>
|
||||
|
||||
<!-- Startup via index.js -->
|
||||
<script src="sharedProcess.js"></script>
|
||||
|
||||
</html>
|
||||
105
src/vs/code/electron-browser/sharedProcess.js
Normal file
105
src/vs/code/electron-browser/sharedProcess.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Warning: Do not use the `let` declarator in this file, it breaks our minification
|
||||
|
||||
'use strict';
|
||||
|
||||
/*global window,document,define*/
|
||||
|
||||
const path = require('path');
|
||||
const electron = require('electron');
|
||||
const remote = electron.remote;
|
||||
const ipc = electron.ipcRenderer;
|
||||
|
||||
function assign(destination, source) {
|
||||
return Object.keys(source)
|
||||
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
|
||||
}
|
||||
|
||||
function parseURLQueryArgs() {
|
||||
const search = window.location.search || '';
|
||||
|
||||
return search.split(/[?&]/)
|
||||
.filter(function (param) { return !!param; })
|
||||
.map(function (param) { return param.split('='); })
|
||||
.filter(function (param) { return param.length === 2; })
|
||||
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
|
||||
}
|
||||
|
||||
function createScript(src, onload) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.addEventListener('load', onload);
|
||||
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
head.insertBefore(script, head.lastChild);
|
||||
}
|
||||
|
||||
function uriFromPath(_path) {
|
||||
var pathName = path.resolve(_path).replace(/\\/g, '/');
|
||||
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
|
||||
pathName = '/' + pathName;
|
||||
}
|
||||
|
||||
return encodeURI('file://' + pathName);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
// Correctly inherit the parent's environment
|
||||
assign(process.env, configuration.userEnv);
|
||||
|
||||
// Get the nls configuration into the process.env as early as possible.
|
||||
var nlsConfig = { availableLanguages: {} };
|
||||
const config = process.env['VSCODE_NLS_CONFIG'];
|
||||
if (config) {
|
||||
process.env['VSCODE_NLS_CONFIG'] = config;
|
||||
try {
|
||||
nlsConfig = JSON.parse(config);
|
||||
} catch (e) { /*noop*/ }
|
||||
}
|
||||
|
||||
var locale = nlsConfig.availableLanguages['*'] || 'en';
|
||||
if (locale === 'zh-tw') {
|
||||
locale = 'zh-Hant';
|
||||
} else if (locale === 'zh-cn') {
|
||||
locale = 'zh-Hans';
|
||||
}
|
||||
|
||||
window.document.documentElement.setAttribute('lang', locale);
|
||||
|
||||
// Load the loader and start loading the workbench
|
||||
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
|
||||
|
||||
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
|
||||
// loads as soon as the loader loads. To be able to have pseudo translation
|
||||
createScript(rootUrl + '/vs/loader.js', function () {
|
||||
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
|
||||
|
||||
window.MonacoEnvironment = {};
|
||||
|
||||
const nodeCachedDataErrors = window.MonacoEnvironment.nodeCachedDataErrors = [];
|
||||
require.config({
|
||||
baseUrl: rootUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
nodeCachedDataDir: configuration.nodeCachedDataDir,
|
||||
onNodeCachedDataError: function (err) { nodeCachedDataErrors.push(err) },
|
||||
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
|
||||
});
|
||||
|
||||
if (nlsConfig.pseudo) {
|
||||
require(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
require(['vs/code/electron-browser/sharedProcessMain'], function () { });
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -21,39 +21,23 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/ex
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { RequestService } from 'vs/platform/request/node/requestService';
|
||||
import { RequestService } from 'vs/platform/request/electron-browser/requestService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { ISharedProcessInitData } from './sharedProcess';
|
||||
import { IChoiceService } from 'vs/platform/message/common/message';
|
||||
import { ChoiceChannelClient } from 'vs/platform/message/common/messageIpc';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { ActiveWindowManager } from 'vs/code/common/windows';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
function quit(err?: Error) {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
}
|
||||
|
||||
process.exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan B is to kill oneself if one's parent dies. Much drama.
|
||||
*/
|
||||
function setupPlanB(parentPid: number): void {
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
process.exit();
|
||||
}
|
||||
}, 5000);
|
||||
interface ISharedProcessInitData {
|
||||
sharedIPCHandle: string;
|
||||
args: ParsedArgs;
|
||||
}
|
||||
|
||||
const eventPrefix = 'monacoworkbench';
|
||||
@@ -159,16 +143,17 @@ function setupIPC(hook: string): TPromise<Server> {
|
||||
return setup(true);
|
||||
}
|
||||
|
||||
function handshake(): TPromise<ISharedProcessInitData> {
|
||||
function startHandshake(): TPromise<ISharedProcessInitData> {
|
||||
return new TPromise<ISharedProcessInitData>((c, e) => {
|
||||
process.once('message', c);
|
||||
process.once('error', e);
|
||||
process.send('hello');
|
||||
ipcRenderer.once('handshake:hey there', (_, r) => c(r));
|
||||
ipcRenderer.send('handshake:hello');
|
||||
});
|
||||
}
|
||||
|
||||
setupIPC(process.env['VSCODE_SHARED_IPC_HOOK'])
|
||||
.then(server => handshake()
|
||||
.then(data => main(server, data))
|
||||
.then(() => setupPlanB(process.env['VSCODE_PID']))
|
||||
.done(null, quit));
|
||||
function handshake(): TPromise<void> {
|
||||
return startHandshake()
|
||||
.then((data) => setupIPC(data.sharedIPCHandle).then(server => main(server, data)))
|
||||
.then(() => ipcRenderer.send('handshake:im ready'));
|
||||
}
|
||||
|
||||
handshake();
|
||||
@@ -27,9 +27,8 @@ import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc';
|
||||
import { GitAskpassService } from 'vs/workbench/parts/git/electron-main/askpassService';
|
||||
import { spawnSharedProcess } from 'vs/code/node/sharedProcess';
|
||||
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
|
||||
import { Mutex } from 'windows-mutex';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient, ILaunchService } from './launch';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
@@ -45,7 +44,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { RequestService } from 'vs/platform/request/node/requestService';
|
||||
import { RequestService } from 'vs/platform/request/electron-main/requestService';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLChannel } from 'vs/platform/url/common/urlIpc';
|
||||
import { URLService } from 'vs/platform/url/electron-main/urlService';
|
||||
@@ -143,18 +142,9 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
|
||||
const electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
// Spawn shared process
|
||||
const initData = { args: environmentService.args };
|
||||
const options = {
|
||||
allowOutput: !environmentService.isBuilt || environmentService.verbose,
|
||||
debugPort: environmentService.isBuilt ? null : 5871
|
||||
};
|
||||
|
||||
let sharedProcessDisposable: IDisposable;
|
||||
|
||||
const sharedProcess = spawnSharedProcess(initData, options).then(disposable => {
|
||||
sharedProcessDisposable = disposable;
|
||||
return connect(environmentService.sharedIPCHandle, 'main');
|
||||
});
|
||||
const sharedProcess = new SharedProcess(environmentService, userEnv);
|
||||
const sharedProcessClient = sharedProcess.onReady
|
||||
.then(() => connect(environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
// Create a new service collection, because the telemetry service
|
||||
// requires a connection to shared process, which was only established
|
||||
@@ -163,11 +153,11 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
|
||||
|
||||
services.set(IUpdateService, new SyncDescriptor(UpdateService));
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, sharedProcess));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
|
||||
if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version);
|
||||
const piiPaths = [environmentService.appRoot, environmentService.extensionsPath];
|
||||
@@ -183,6 +173,13 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
|
||||
// TODO@Joao: unfold this
|
||||
windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
// TODO@Joao: so ugly...
|
||||
windowsMainService.onWindowClose(() => {
|
||||
if (!platform.isMacintosh && windowsMainService.getWindowCount() === 0) {
|
||||
sharedProcess.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchService = accessor.get(ILaunchService);
|
||||
const launchChannel = new LaunchChannel(launchService);
|
||||
@@ -204,7 +201,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
const windowsChannel = new WindowsChannel(windowsService);
|
||||
electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
sharedProcess.done(client => client.registerChannel('windows', windowsChannel));
|
||||
sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
// Make sure we associate the program with the app user model id
|
||||
// This will help Windows to associate the running program with
|
||||
@@ -220,15 +217,12 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
|
||||
mainIpcServer = null;
|
||||
}
|
||||
|
||||
if (sharedProcessDisposable) {
|
||||
sharedProcessDisposable.dispose();
|
||||
}
|
||||
|
||||
if (windowsMutex) {
|
||||
windowsMutex.release();
|
||||
}
|
||||
|
||||
configurationService.dispose();
|
||||
sharedProcess.dispose();
|
||||
}
|
||||
|
||||
// Dispose on app quit
|
||||
@@ -399,7 +393,6 @@ function start(): void {
|
||||
const instanceEnv: typeof process.env = {
|
||||
VSCODE_PID: String(process.pid),
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
|
||||
VSCODE_SHARED_IPC_HOOK: environmentService.sharedIPCHandle,
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG']
|
||||
};
|
||||
|
||||
|
||||
93
src/vs/code/electron-main/sharedProcess.ts
Normal file
93
src/vs/code/electron-main/sharedProcess.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
export class SharedProcess {
|
||||
|
||||
private window: Electron.BrowserWindow;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@memoize
|
||||
get onReady(): TPromise<void> {
|
||||
this.window = new BrowserWindow({ show: false });
|
||||
const config = assign({
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
userEnv: this.userEnv
|
||||
});
|
||||
|
||||
const url = `${require.toUrl('vs/code/electron-browser/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
this.window.loadURL(url);
|
||||
|
||||
// Prevent the window from dying
|
||||
const onClose = e => {
|
||||
if (this.window.isVisible()) {
|
||||
e.preventDefault();
|
||||
this.window.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.window.on('close', onClose);
|
||||
this.disposables.push(toDisposable(() => this.window.removeListener('close', onClose)));
|
||||
|
||||
this.disposables.push(toDisposable(() => {
|
||||
// Electron seems to crash on Windows without this setTimeout :|
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.window.close();
|
||||
} catch (err) {
|
||||
// ignore, as electron is already shutting down
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
}, 0);
|
||||
}));
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
ipcMain.once('handshake:hello', ({ sender }) => {
|
||||
sender.send('handshake:hey there', {
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args
|
||||
});
|
||||
|
||||
sender.once('handshake:im ready', () => c(null));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private environmentService: IEnvironmentService,
|
||||
private userEnv: IProcessEnvironment
|
||||
) { }
|
||||
|
||||
toggle(): void {
|
||||
if (this.window.isVisible()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.window.show();
|
||||
this.window.webContents.openDevTools();
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export interface ISharedProcessInitData {
|
||||
args: ParsedArgs;
|
||||
}
|
||||
|
||||
export interface ISharedProcessOptions {
|
||||
allowOutput?: boolean;
|
||||
debugPort?: number;
|
||||
}
|
||||
|
||||
const boostrapPath = URI.parse(require.toUrl('bootstrap')).fsPath;
|
||||
|
||||
function _spawnSharedProcess(initData: ISharedProcessInitData, options: ISharedProcessOptions): cp.ChildProcess {
|
||||
const execArgv: string[] = [];
|
||||
const env = assign({}, process.env, {
|
||||
AMD_ENTRYPOINT: 'vs/code/node/sharedProcessMain',
|
||||
ELECTRON_NO_ASAR: '1'
|
||||
});
|
||||
|
||||
if (options.allowOutput) {
|
||||
env['VSCODE_ALLOW_IO'] = 'true';
|
||||
}
|
||||
|
||||
if (options.debugPort) {
|
||||
execArgv.push(`--debug=${options.debugPort}`);
|
||||
}
|
||||
|
||||
const result = cp.fork(boostrapPath, ['--type=SharedProcess'], { env, execArgv });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function spawnSharedProcess(initData: ISharedProcessInitData, options: ISharedProcessOptions = {}): TPromise<IDisposable> {
|
||||
let spawnCount = 0;
|
||||
let child: cp.ChildProcess;
|
||||
|
||||
let promise: TPromise<IDisposable>;
|
||||
|
||||
const spawn = () => {
|
||||
if (++spawnCount > 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
child = _spawnSharedProcess(initData, options);
|
||||
promise = new TPromise<IDisposable>((c, e) => {
|
||||
// handshake
|
||||
child.once('message', () => {
|
||||
child.send(initData);
|
||||
c({
|
||||
dispose: () => {
|
||||
if (child) {
|
||||
child.removeListener('exit', spawn);
|
||||
child.kill();
|
||||
child = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
child.on('exit', spawn);
|
||||
};
|
||||
|
||||
spawn();
|
||||
|
||||
return promise;
|
||||
}
|
||||
Reference in New Issue
Block a user