未验证 提交 f8dbf7dd 编写于 作者: B Benjamin Pasero 提交者: GitHub

Merge pull request #114359 from microsoft/ben/shared-process-message-port

Implement message port support for shared process connection
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCClient` on top of DOM `MessagePort`.
*/
export class Client extends MessagePortClient implements IDisposable {
/**
* @param clientId a way to uniquely identify this client among
* other clients. this is important for routing because every
* client can also be a server
*/
constructor(port: MessagePort, clientId: string) {
super(port, clientId);
}
}
......@@ -11,6 +11,11 @@ export interface Sender {
send(channel: string, msg: unknown): void;
}
/**
* The Electron `Protocol` leverages Electron style IPC communication (`ipcRenderer`, `ipcMain`)
* for the implementation of the `IMessagePassingProtocol`. That style of API requires a channel
* name for sending data.
*/
export class Protocol implements IMessagePassingProtocol {
constructor(private sender: Sender, readonly onMessage: Event<VSBuffer>) { }
......@@ -23,7 +28,7 @@ export class Protocol implements IMessagePassingProtocol {
}
}
dispose(): void {
disconnect(): void {
this.sender.send('vscode:disconnect', null);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { IDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
/**
* Declare minimal `MessageEvent` and `MessagePort` interfaces here
* so that this utility can be used both from `browser` and
* `electron-main` namespace where message ports are available.
*/
export interface MessageEvent {
/**
* For our use we only consider `Uint8Array` a valid data transfer
* via message ports because our protocol implementation is buffer based.
*/
data: Uint8Array;
}
export interface MessagePort {
addEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
removeEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
postMessage(message: Uint8Array): void;
start(): void;
close(): void;
}
/**
* The MessagePort `Protocol` leverages MessagePort style IPC communication
* for the implementation of the `IMessagePassingProtocol`. That style of API
* is a simple `onmessage` / `postMessage` pattern.
*/
export class Protocol implements IMessagePassingProtocol {
readonly onMessage = Event.fromDOMEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data));
constructor(private port: MessagePort) {
// we must call start() to ensure messages are flowing
port.start();
}
send(message: VSBuffer): void {
this.port.postMessage(message.buffer);
}
disconnect(): void {
this.port.close();
}
}
/**
* An implementation of a `IPCClient` on top of MessagePort style IPC communication.
*/
export class Client extends IPCClient implements IDisposable {
private protocol: Protocol;
constructor(port: MessagePort, clientId: string) {
const protocol = new Protocol(port);
super(protocol, clientId);
this.protocol = protocol;
}
dispose(): void {
this.protocol.disconnect();
}
}
......@@ -3,10 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcMain, WebContents } from 'electron';
import { Event, Emitter } from 'vs/base/common/event';
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { ipcMain, WebContents } from 'electron';
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
......@@ -18,9 +18,13 @@ interface IIPCEvent {
function createScopedOnMessageEvent(senderId: number, eventName: string): Event<VSBuffer | null> {
const onMessage = Event.fromNodeEventEmitter<IIPCEvent>(ipcMain, eventName, (event, message) => ({ event, message }));
const onMessageFromSender = Event.filter(onMessage, ({ event }) => event.sender.id === senderId);
return Event.map(onMessageFromSender, ({ message }) => message ? VSBuffer.wrap(message) : message);
}
/**
* An implemention of `IPCServer` on top of Electron `ipcMain` API.
*/
export class Server extends IPCServer {
private static readonly Clients = new Map<number, IDisposable>();
......@@ -41,7 +45,7 @@ export class Server extends IPCServer {
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
const protocol = new Protocol(webContents, onMessage);
const protocol = new ElectronProtocol(webContents, onMessage);
return { protocol, onDidClientDisconnect };
});
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BrowserWindow, ipcMain, IpcMainEvent, MessagePortMain } from 'electron';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { generateUuid } from 'vs/base/common/uuid';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCClient` on top of Electron `MessagePortMain`.
*/
export class Client extends MessagePortClient implements IDisposable {
/**
* @param clientId a way to uniquely identify this client among
* other clients. this is important for routing because every
* client can also be a server
*/
constructor(port: MessagePortMain, clientId: string) {
super({
addEventListener: (type, listener) => port.addListener(type, listener),
removeEventListener: (type, listener) => port.removeListener(type, listener),
postMessage: message => port.postMessage(message),
start: () => port.start(),
close: () => port.close()
}, clientId);
}
}
/**
* This method opens a message channel connection
* in the target window. The target window needs
* to use the `Server` from `electron-sandbox/ipc.mp`.
*/
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();
window.webContents.send('vscode:createMessageChannel', nonce);
// Wait until the window has returned the `MessagePort`
// We need to filter by the `nonce` to ensure we listen
// to the right response.
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePortMain }>(ipcMain, 'vscode:createMessageChannelResult', (e: IpcMainEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
return port;
}
......@@ -5,28 +5,34 @@
import { Event } from 'vs/base/common/event';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { IDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
/**
* An implemention of `IPCClient` on top of Electron `ipcRenderer` IPC communication
* provided from sandbox globals (via preload script).
*/
export class Client extends IPCClient implements IDisposable {
private protocol: Protocol;
private protocol: ElectronProtocol;
private static createProtocol(): Protocol {
private static createProtocol(): ElectronProtocol {
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
ipcRenderer.send('vscode:hello');
return new Protocol(ipcRenderer, onMessage);
return new ElectronProtocol(ipcRenderer, onMessage);
}
constructor(id: string) {
const protocol = Client.createProtocol();
super(protocol, id);
this.protocol = protocol;
}
dispose(): void {
this.protocol.dispose();
this.protocol.disconnect();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { Event } from 'vs/base/common/event';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
* The clients register themselves via Electron IPC transfer.
*/
export class Server extends IPCServer {
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
// Clients connect via `vscode:createMessageChannel` to get a
// `MessagePort` that is ready to be used. For every connection
// we create a pair of message ports and send it back.
//
// The `nonce` is included so that the main side has a chance to
// correlate the response back to the sender.
const onCreateMessageChannel = Event.fromNodeEventEmitter<string>(ipcRenderer, 'vscode:createMessageChannel', (_, nonce: string) => nonce);
return Event.map(onCreateMessageChannel, nonce => {
// Create a new pair of ports and protocol for this connection
const { port1: incomingPort, port2: outgoingPort } = new MessageChannel();
const protocol = new MessagePortProtocol(incomingPort);
const result: ClientConnectionEvent = {
protocol,
// Not part of the standard spec, but in Electron we get a `close` event
// when the other side closes. We can use this to detect disconnects
// (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close)
onDidClientDisconnect: Event.fromDOMEventEmitter(incomingPort, 'close')
};
// Send one port back to the requestor
ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]);
return result;
});
}
constructor() {
super(Server.getOnDidClientConnect());
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Event } from 'vs/base/common/event';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
suite('IPC, MessagePorts', () => {
test('message passing', async () => {
const { port1, port2 } = new MessageChannel();
const client1 = new MessagePortClient(port1, 'client1');
const client2 = new MessagePortClient(port2, 'client2');
client1.registerChannel('client1', {
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
switch (command) {
case 'testMethodClient1': return Promise.resolve('success1');
default: return Promise.reject(new Error('not implemented'));
}
},
listen(_: unknown, event: string, arg?: any): Event<any> {
switch (event) {
default: throw new Error('not implemented');
}
}
});
client2.registerChannel('client2', {
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
switch (command) {
case 'testMethodClient2': return Promise.resolve('success2');
default: return Promise.reject(new Error('not implemented'));
}
},
listen(_: unknown, event: string, arg?: any): Event<any> {
switch (event) {
default: throw new Error('not implemented');
}
}
});
const channelClient1 = client2.getChannel('client1');
assert.equal(await channelClient1.call('testMethodClient1'), 'success1');
const channelClient2 = client1.getChannel('client2');
assert.equal(await channelClient2.call('testMethodClient2'), 'success2');
client1.dispose();
client2.dispose();
});
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
suite('IPC, MessagePorts', () => {
test('message port close event', async () => {
const { port1, port2 } = new MessageChannel();
new MessagePortClient(port1, 'client1');
const client2 = new MessagePortClient(port2, 'client2');
// This test ensures that Electron's API for the close event
// does not break because we rely on it to dispose client
// connections from the server.
//
// This event is not provided by browser MessagePort API though.
const whenClosed = new Promise<boolean>(resolve => port1.addEventListener('close', () => resolve(true)));
client2.dispose();
assert.ok(await whenClosed);
});
});
......@@ -7,7 +7,7 @@
// #######################################################################
// ### ###
// ### electron.d.ts types we need in a common layer for reuse ###
// ### (copied from Electron 9.x) ###
// ### (copied from Electron 11.x) ###
// ### ###
// #######################################################################
......@@ -212,7 +212,7 @@ export interface SaveDialogReturnValue {
export interface FileFilter {
// Docs: http://electronjs.org/docs/api/structures/file-filter
// Docs: https://electronjs.org/docs/api/structures/file-filter
extensions: string[];
name: string;
......@@ -220,7 +220,7 @@ export interface FileFilter {
export interface InputEvent {
// Docs: http://electronjs.org/docs/api/structures/input-event
// Docs: https://electronjs.org/docs/api/structures/input-event
/**
* An array of modifiers of the event, can be `shift`, `control`, `ctrl`, `alt`,
......@@ -232,7 +232,7 @@ export interface InputEvent {
export interface MouseInputEvent extends InputEvent {
// Docs: http://electronjs.org/docs/api/structures/mouse-input-event
// Docs: https://electronjs.org/docs/api/structures/mouse-input-event
/**
* The button pressed, can be `left`, `middle`, `right`.
......
......@@ -35,6 +35,17 @@
}
},
/**
* @param {string} channel
* @param {any} message
* @param {MessagePort[]} transfer
*/
postMessage(channel, message, transfer) {
if (validateIPC(channel)) {
ipcRenderer.postMessage(channel, message, transfer);
}
},
/**
* @param {string} channel
* @param {any[]} args
......
......@@ -7,35 +7,54 @@
// #######################################################################
// ### ###
// ### electron.d.ts types we expose from electron-sandbox ###
// ### (copied from Electron 9.x) ###
// ### (copied from Electron 11.x) ###
// ### ###
// #######################################################################
export interface IpcRendererEvent extends Event {
// Docs: https://electronjs.org/docs/api/structures/ipc-renderer-event
/**
* A list of MessagePorts that were transferred with this message
*/
ports: MessagePort[];
/**
* The `IpcRenderer` instance that emitted the event originally
*/
sender: IpcRenderer;
/**
* The `webContents.id` that sent the message, you can call
* `event.sender.sendTo(event.senderId, ...)` to reply to the message, see
* ipcRenderer.sendTo for more information. This only applies to messages sent from
* a different renderer. Messages sent directly from the main process set
* `event.senderId` to `0`.
*/
senderId: number;
}
export interface IpcRenderer {
/**
* Listens to `channel`, when a new message arrives `listener` would be called with
* `listener(event, args...)`.
*/
on(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
/**
* Adds a one time `listener` function for the event. This `listener` is invoked
* only the next time a message is sent to `channel`, after which it is removed.
*/
once(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
/**
* Removes the specified `listener` from the listener array for the specified
* `channel`.
*/
removeListener(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
removeListener(channel: string, listener: (...args: any[]) => void): this;
/**
* Send an asynchronous message to the main process via `channel`, along with
* arguments. Arguments will be serialized with the Structured Clone Algorithm,
* just like `postMessage`, so prototype chains will not be included. Sending
* Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
* just like `window.postMessage`, so prototype chains will not be included.
* Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an
* exception.
*
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
* Electron objects is deprecated, and will begin throwing an exception starting
......@@ -43,8 +62,51 @@ export interface IpcRenderer {
*
* The main process handles it by listening for `channel` with the `ipcMain`
* module.
*
* If you need to transfer a `MessagePort` to the main process, use
* `ipcRenderer.postMessage`.
*
* If you want to receive a single response from the main process, like the result
* of a method call, consider using `ipcRenderer.invoke`.
*/
send(channel: string, ...args: any[]): void;
/**
* Resolves with the response from the main process.
*
* Send a message to the main process via `channel` and expect a result
* asynchronously. Arguments will be serialized with the Structured Clone
* Algorithm, just like `window.postMessage`, so prototype chains will not be
* included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw
* an exception.
*
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
* Electron objects is deprecated, and will begin throwing an exception starting
* with Electron 9.
*
* The main process should listen for `channel` with `ipcMain.handle()`.
*
* For example:
*
* If you need to transfer a `MessagePort` to the main process, use
* `ipcRenderer.postMessage`.
*
* If you do not need a response to the message, consider using `ipcRenderer.send`.
*/
invoke(channel: string, ...args: any[]): Promise<any>;
/**
* Send a message to the main process, optionally transferring ownership of zero or
* more `MessagePort` objects.
*
* The transferred `MessagePort` objects will be available in the main process as
* `MessagePortMain` objects by accessing the `ports` property of the emitted
* event.
*
* For example:
*
* For more information on using `MessagePort` and `MessageChannel`, see the MDN
* documentation.
*/
postMessage(channel: string, message: any, transfer?: MessagePort[]): void;
}
export interface WebFrame {
......@@ -70,16 +132,23 @@ export interface CrashReporter {
* with crashes that occur in other renderer processes or in the main process.
*
* **Note:** Parameters have limits on the length of the keys and values. Key names
* must be no longer than 39 bytes, and values must be no longer than 127 bytes.
* must be no longer than 39 bytes, and values must be no longer than 20320 bytes.
* Keys with names longer than the maximum will be silently ignored. Key values
* longer than the maximum length will be truncated.
*
* **Note:** On linux values that are longer than 127 bytes will be chunked into
* multiple keys, each 127 bytes in length. E.g. `addExtraParameter('foo',
* 'a'.repeat(130))` will result in two chunked keys `foo__1` and `foo__2`, the
* first will contain the first 127 bytes and the second will contain the remaining
* 3 bytes. On your crash reporting backend you should stitch together keys in
* this format.
*/
addExtraParameter(key: string, value: string): void;
}
export interface ProcessMemoryInfo {
// Docs: http://electronjs.org/docs/api/structures/process-memory-info
// Docs: https://electronjs.org/docs/api/structures/process-memory-info
/**
* The amount of memory not shared by other processes, such as JS heap or HTML
......@@ -133,10 +202,7 @@ export interface CrashReporterStartOptions {
rateLimit?: boolean;
/**
* If true, crash reports will be compressed and uploaded with `Content-Encoding:
* gzip`. Not all collection servers support compressed payloads. Default is
* `false`.
*
* @platform darwin,win32
* gzip`. Default is `false`.
*/
compress?: boolean;
/**
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
export class DeprecatedExtensionsCleaner extends Disposable {
constructor(
@IExtensionManagementService private readonly extensionManagementService: ExtensionManagementService
) {
super();
this._register(extensionManagementService); // TODO@sandy081 this seems fishy
this.cleanUpDeprecatedExtensions();
}
private cleanUpDeprecatedExtensions(): void {
this.extensionManagementService.removeDeprecatedExtensions();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
export class LocalizationsUpdater extends Disposable {
constructor(
@ILocalizationsService private readonly localizationsService: LocalizationsService
) {
super();
this.updateLocalizations();
}
private updateLocalizations(): void {
this.localizationsService.update();
}
}
......@@ -15,10 +15,7 @@
// Load shared process into window
bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) {
sharedProcess.startup({
machineId: configuration.machineId,
windowId: configuration.windowId
});
return sharedProcess.main(configuration);
});
......
......@@ -23,7 +23,7 @@
'vs/nls!vs/workbench/workbench.desktop.main',
'vs/css!vs/workbench/workbench.desktop.main'
],
async function (workbench, configuration) {
function (_, configuration) {
// Mark start of workbench
performance.mark('code/didLoadWorkbenchMain');
......
......@@ -11,9 +11,10 @@ import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle
import { resolveShellEnv } from 'vs/code/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron';
import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
......@@ -30,7 +31,6 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import product from 'vs/platform/product/common/product';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
......@@ -68,7 +68,7 @@ import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsSer
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
import { ISharedProcessManagementMainService, SharedProcessManagementMainService } from 'vs/platform/sharedProcess/electron-main/sharedProcessManagementMainService';
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
......@@ -96,7 +96,7 @@ export class CodeApplication extends Disposable {
private nativeHostMainService: INativeHostMainService | undefined;
constructor(
private readonly mainIpcServer: Server,
private readonly mainIpcServer: NodeIPCServer,
private readonly userEnv: IProcessEnvironment,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService,
......@@ -426,16 +426,20 @@ export class CodeApplication extends Disposable {
// Spawn shared process after the first window has opened and 3s have passed
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
const sharedProcessClient = sharedProcess.whenIpcReady().then(() => {
this.logService.trace('Shared process: IPC ready');
const sharedProcessClient = (async () => {
this.logService.trace('Main->SharedProcess#connect');
return connect(this.environmentService.sharedIPCHandle, 'main');
});
const sharedProcessReady = sharedProcess.whenReady().then(() => {
this.logService.trace('Shared process: init ready');
const port = await sharedProcess.connect();
this.logService.trace('Main->SharedProcess#connect: connection established');
return new MessagePortClient(port, 'main');
})();
const sharedProcessReady = (async () => {
await sharedProcess.whenReady();
return sharedProcessClient;
});
})();
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this._register(new RunOnceScheduler(async () => {
sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env));
......@@ -482,7 +486,7 @@ export class CodeApplication extends Disposable {
return machineId;
}
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
const services = new ServiceCollection();
switch (process.platform) {
......@@ -505,7 +509,7 @@ export class CodeApplication extends Disposable {
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
services.set(ISharedProcessManagementMainService, new SyncDescriptor(SharedProcessManagementMainService, [sharedProcess]));
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
......@@ -584,7 +588,7 @@ export class CodeApplication extends Disposable {
});
}
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
// Register more Main IPC services
const launchMainService = accessor.get(ILaunchMainService);
......@@ -617,9 +621,9 @@ export class CodeApplication extends Disposable {
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
const sharedProcessMainService = accessor.get(ISharedProcessMainService);
const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);
const sharedProcessMainManagementService = accessor.get(ISharedProcessManagementMainService);
const sharedProcessManagementChannel = createChannelReceiver(sharedProcessMainManagementService);
electronIpcServer.registerChannel('sharedProcessManagement', sharedProcessManagementChannel);
const workspacesService = accessor.get(IWorkspacesService);
const workspacesChannel = createChannelReceiver(workspacesService);
......
......@@ -13,8 +13,9 @@ import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvH
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
import { mkdirp } from 'vs/base/node/pfs';
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { Server, serve, connect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net';
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
......@@ -35,11 +36,10 @@ import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { once } from 'vs/base/common/functional';
import { ISignService } from 'vs/platform/sign/common/sign';
import { SignService } from 'vs/platform/sign/node/signService';
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { FileService } from 'vs/platform/files/common/fileService';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
......@@ -54,6 +54,7 @@ import { basename, resolve } from 'vs/base/common/path';
import { coalesce, distinct } from 'vs/base/common/arrays';
import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
class ExpectedError extends Error {
readonly isExpected = true;
......@@ -213,14 +214,14 @@ class CodeMain {
return instanceEnvironment;
}
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<NodeIPCServer> {
// Try to setup a server for running. If that succeeds it means
// we are the first instance to startup. Otherwise it is likely
// that another instance is already running.
let server: Server;
let server: NodeIPCServer;
try {
server = await serve(environmentService.mainIPCHandle);
server = await nodeIPCServe(environmentService.mainIPCHandle);
once(lifecycleMainService.onWillShutdown)(() => server.dispose());
} catch (error) {
......@@ -236,9 +237,9 @@ class CodeMain {
}
// there's a running instance, let's connect to it
let client: Client<string>;
let client: NodeIPCClient<string>;
try {
client = await connect(environmentService.mainIPCHandle, 'main');
client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main');
} catch (error) {
// Handle unexpected connection errors by showing a dialog to the user
......@@ -294,11 +295,7 @@ class CodeMain {
// Process Info
if (args.status) {
return instantiationService.invokeFunction(async () => {
// Create a diagnostic service connected to the existing shared process
const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main');
const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics');
const diagnosticsService = createChannelSender<IDiagnosticsService>(diagnosticsChannel);
const diagnosticsService = new DiagnosticsService(NullTelemetryService);
const mainProcessInfo = await launchService.getMainProcessInfo();
const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
......
......@@ -3,25 +3,25 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { memoize } from 'vs/base/common/decorators';
import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron';
import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
import { Disposable } from 'vs/base/common/lifecycle';
import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { assertIsDefined } from 'vs/base/common/types';
export class SharedProcess implements ISharedProcess {
export class SharedProcess extends Disposable implements ISharedProcess {
private readonly barrier = new Barrier();
private readonly _whenReady: Promise<void>;
private readonly whenSpawnedBarrier = new Barrier();
private window: BrowserWindow | null = null;
private window: BrowserWindow | undefined = undefined;
private windowCloseListener: ((event: Event) => void) | undefined = undefined;
constructor(
private readonly machineId: string,
......@@ -31,12 +31,102 @@ export class SharedProcess implements ISharedProcess {
@ILogService private readonly logService: ILogService,
@IThemeMainService private readonly themeMainService: IThemeMainService
) {
// overall ready promise when shared process signals initialization is done
this._whenReady = new Promise<void>(c => ipcMain.once('vscode:shared-process->electron-main=init-done', () => c(undefined)));
super();
this.registerListeners();
}
private registerListeners(): void {
// Lifecycle
this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown()));
// Shared process connections
ipcMain.on('vscode:createSharedProcessMessageChannel', async (e, nonce: string) => {
this.logService.trace('SharedProcess: on vscode:createSharedProcessMessageChannel');
const port = await this.connect();
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
});
}
@memoize
private get _whenIpcReady(): Promise<void> {
private onWillShutdown(): void {
const window = this.window;
if (!window) {
return; // possibly too early before created
}
// Signal exit to shared process when shutting down
window.webContents.send('vscode:electron-main->shared-process=exit');
// Shut the shared process down when we are quitting
//
// Note: because we veto the window close, we must first remove our veto.
// Otherwise the application would never quit because the shared process
// window is refusing to close!
//
if (this.windowCloseListener) {
window.removeListener('close', this.windowCloseListener);
this.windowCloseListener = undefined;
}
// Electron seems to crash on Windows without this setTimeout :|
setTimeout(() => {
try {
window.close();
} catch (err) {
// ignore, as electron is already shutting down
}
this.window = undefined;
}, 0);
}
private _whenReady: Promise<void> | undefined = undefined;
whenReady(): Promise<void> {
if (!this._whenReady) {
// Overall signal that the shared process window was loaded and
// all services within have been created.
this._whenReady = new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => {
this.logService.trace('SharedProcess: Overall ready');
resolve();
}));
}
return this._whenReady;
}
private _whenIpcReady: Promise<void> | undefined = undefined;
private get whenIpcReady() {
if (!this._whenIpcReady) {
this._whenIpcReady = (async () => {
// Always wait for `spawn()`
await this.whenSpawnedBarrier.wait();
// Create window for shared process
this.createWindow();
// Listeners
this.registerWindowListeners();
// Wait for window indicating that IPC connections are accepted
await new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => {
this.logService.trace('SharedProcess: IPC ready');
resolve();
}));
})();
}
return this._whenIpcReady;
}
private createWindow(): void {
// shared process is a hidden window by default
this.window = new BrowserWindow({
show: false,
backgroundColor: this.themeMainService.getBackgroundColor(),
......@@ -54,98 +144,67 @@ export class SharedProcess implements ISharedProcess {
}
});
const config = {
appRoot: this.environmentService.appRoot,
const config: ISharedProcessConfiguration = {
machineId: this.machineId,
windowId: this.window.id,
appRoot: this.environmentService.appRoot,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
backupWorkspacesPath: this.environmentService.backupWorkspacesPath,
userEnv: this.userEnv,
windowId: this.window.id
sharedIPCHandle: this.environmentService.sharedIPCHandle,
args: this.environmentService.args,
logLevel: this.logService.getLevel()
};
// Load with config
this.window.loadURL(FileAccess
.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
.toString(true)
);
}
private registerWindowListeners(): void {
if (!this.window) {
return;
}
// Prevent the window from dying
const onClose = (e: ElectronEvent) => {
// Prevent the window from closing
this.windowCloseListener = (e: Event) => {
this.logService.trace('SharedProcess#close prevented');
// We never allow to close the shared process unless we get explicitly disposed()
e.preventDefault();
// Still hide the window though if visible
if (this.window && this.window.isVisible()) {
if (this.window?.isVisible()) {
this.window.hide();
}
};
this.window.on('close', onClose);
const disposables = new DisposableStore();
this.lifecycleMainService.onWillShutdown(() => {
disposables.dispose();
// Shut the shared process down when we are quitting
//
// Note: because we veto the window close, we must first remove our veto.
// Otherwise the application would never quit because the shared process
// window is refusing to close!
//
if (this.window) {
this.window.removeListener('close', onClose);
}
// Electron seems to crash on Windows without this setTimeout :|
setTimeout(() => {
try {
if (this.window) {
this.window.close();
}
} catch (err) {
// ignore, as electron is already shutting down
}
this.window = null;
}, 0);
});
return new Promise<void>(resolve => {
this.window.on('close', this.windowCloseListener);
// send payload once shared process is ready to receive it
disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'vscode:shared-process->electron-main=ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => {
sender.send('vscode:electron-main->shared-process=payload', {
sharedIPCHandle: this.environmentService.sharedIPCHandle,
args: this.environmentService.args,
logLevel: this.logService.getLevel(),
backupWorkspacesPath: this.environmentService.backupWorkspacesPath,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir
});
// signal exit to shared process when we get disposed
disposables.add(toDisposable(() => sender.send('vscode:electron-main->shared-process=exit')));
// complete IPC-ready promise when shared process signals this to us
ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => resolve(undefined));
}));
});
// Crashes & Unrsponsive & Failed to load
this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`[VS Code]: sharedProcess crashed (detail: ${details?.reason})`));
this.window.on('unresponsive', () => this.logService.error('[VS Code]: detected unresponsive sharedProcess window'));
this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('[VS Code]: fail to load sharedProcess window, ', errorDescription));
}
spawn(userEnv: NodeJS.ProcessEnv): void {
this.userEnv = { ...this.userEnv, ...userEnv };
this.barrier.open();
}
async whenReady(): Promise<void> {
await this.barrier.wait();
await this._whenReady;
// Release barrier
this.whenSpawnedBarrier.open();
}
async whenIpcReady(): Promise<void> {
await this.barrier.wait();
await this._whenIpcReady;
async connect(): Promise<MessagePortMain> {
// Wait for shared process being ready to accept connection
await this.whenIpcReady;
// Connect and return message port
const window = assertIsDefined(this.window);
return connectMessagePort(window);
}
toggle(): void {
......@@ -157,16 +216,20 @@ export class SharedProcess implements ISharedProcess {
}
show(): void {
if (this.window) {
this.window.show();
this.window.webContents.openDevTools();
if (!this.window) {
return; // possibly too early before created
}
this.window.show();
this.window.webContents.openDevTools();
}
hide(): void {
if (this.window) {
this.window.webContents.closeDevTools();
this.window.hide();
if (!this.window) {
return; // possibly too early before created
}
this.window.webContents.closeDevTools();
this.window.hide();
}
}
......@@ -102,29 +102,29 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private hiddenTitleBarStyle: boolean | undefined;
private showTimeoutHandle: NodeJS.Timeout | undefined;
private _lastFocusTime: number;
private _readyState: ReadyState;
private _lastFocusTime = -1;
private _readyState = ReadyState.NONE;
private windowState: IWindowState;
private currentMenuBarVisibility: MenuBarVisibility | undefined;
private representedFilename: string | undefined;
private documentEdited: boolean | undefined;
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = [];
private marketplaceHeadersPromise: Promise<object>;
private readonly touchBarGroups: TouchBarSegmentedControl[];
private readonly touchBarGroups: TouchBarSegmentedControl[] = [];
private currentHttpProxy?: string;
private currentNoProxy?: string;
private currentHttpProxy: string | undefined = undefined;
private currentNoProxy: string | undefined = undefined;
constructor(
config: IWindowCreationOptions,
@ILogService private readonly logService: ILogService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@IFileService private readonly fileService: IFileService,
@IStorageMainService private readonly storageService: IStorageMainService,
@IStorageMainService storageService: IStorageMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeMainService private readonly themeMainService: IThemeMainService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
......@@ -135,11 +135,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
) {
super();
this.touchBarGroups = [];
this._lastFocusTime = -1;
this._readyState = ReadyState.NONE;
this.whenReadyCallbacks = [];
//#region create browser window
{
// Load window state
......@@ -280,10 +275,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.createTouchBar();
// Request handling
const that = this;
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, {
get(key) { return that.storageService.get(key); },
store(key, value) { that.storageService.store(key, value); }
get(key) { return storageService.get(key); },
store(key, value) { storageService.store(key, value); }
});
// Eventing
......@@ -418,9 +412,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private registerListeners(): void {
// Crashes & Unrsponsive
// Crashes & Unrsponsive & Failed to load
this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details));
this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE));
this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('[VS Code]: fail to load workbench window, ', errorDescription));
// Window close
this._win.on('closed', () => {
......@@ -531,11 +526,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None);
});
// Window Failed to load
this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
this.logService.warn('[electron event]: fail to load, ', errorDescription);
});
// Handle configuration changes
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
......
......@@ -22,7 +22,7 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage';
import { localize } from 'vs/nls';
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { Codicon } from 'vs/base/common/codicons';
......@@ -266,7 +266,7 @@ export class IssueReporter extends Disposable {
private initServices(configuration: IssueReporterConfiguration): void {
const serviceCollection = new ServiceCollection();
const mainProcessService = new MainProcessService(configuration.windowId);
const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId);
serviceCollection.set(IMainProcessService, mainProcessService);
this.nativeHostService = new NativeHostService(configuration.windowId, mainProcessService) as INativeHostService;
......
......@@ -17,7 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes';
import * as dom from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ByteSize } from 'vs/platform/files/common/files';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
......@@ -233,7 +233,7 @@ class ProcessExplorer {
private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined;
constructor(windowId: number, private data: ProcessExplorerData) {
const mainProcessService = new MainProcessService(windowId);
const mainProcessService = new ElectronIPCMainProcessService(windowId);
this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService;
this.applyStyles(data.styles);
......
......@@ -23,7 +23,7 @@
'vs/nls!vs/workbench/workbench.desktop.main',
'vs/css!vs/workbench/workbench.desktop.main'
],
async function (workbench, configuration) {
function (_, configuration) {
// Mark start of workbench
performance.mark('code/didLoadWorkbenchMain');
......@@ -45,6 +45,22 @@
}
);
// add default trustedTypes-policy for logging and to workaround
// lib/platform limitations
window.trustedTypes?.createPolicy('default', {
createHTML(value) {
// see https://github.com/electron/electron/issues/27211
// Electron webviews use a static innerHTML default value and
// that isn't trusted. We use a default policy to check for the
// exact value of that innerHTML-string and only allow that.
if (value === '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>') {
return value;
}
// throw new Error('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
console.trace('UNTRUSTED html usage, default trusted types policy should NEVER be reached');
return value;
}
});
//region Helpers
......
......@@ -15,6 +15,5 @@ export interface ISharedProcessService {
getChannel(channelName: string): IChannel;
registerChannel(channelName: string, channel: IServerChannel<string>): void;
whenSharedProcessReady(): Promise<void>;
toggleSharedProcessWindow(): Promise<void>;
toggleWindow(): Promise<void>;
}
......@@ -3,10 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron-sandbox';
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
export const IMainProcessService = createDecorator<IMainProcessService>('mainProcessService');
......@@ -19,18 +20,21 @@ export interface IMainProcessService {
registerChannel(channelName: string, channel: IServerChannel<string>): void;
}
export class MainProcessService extends Disposable implements IMainProcessService {
/**
* An implementation of `IMainProcessService` that leverages Electron's IPC.
*/
export class ElectronIPCMainProcessService extends Disposable implements IMainProcessService {
declare readonly _serviceBrand: undefined;
private mainProcessConnection: Client;
private mainProcessConnection: IPCElectronClient;
constructor(
windowId: number
) {
super();
this.mainProcessConnection = this._register(new Client(`window:${windowId}`));
this.mainProcessConnection = this._register(new IPCElectronClient(`window:${windowId}`));
}
getChannel(channelName: string): IChannel {
......@@ -41,3 +45,24 @@ export class MainProcessService extends Disposable implements IMainProcessServic
this.mainProcessConnection.registerChannel(channelName, channel);
}
}
/**
* An implementation of `IMainProcessService` that leverages MessagePorts.
*/
export class MessagePortMainProcessService implements IMainProcessService {
declare readonly _serviceBrand: undefined;
constructor(
private server: MessagePortServer,
private router: StaticRouter
) { }
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.router);
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.server.registerChannel(channelName, channel);
}
}
......@@ -9,8 +9,8 @@ import { URI } from 'vs/base/common/uri';
import { basename, extname, dirname } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { FileLogService } from 'vs/platform/log/common/fileLogService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { IFileService } from 'vs/platform/files/common/files';
export class LoggerService extends Disposable implements ILoggerService {
......@@ -20,7 +20,7 @@ export class LoggerService extends Disposable implements ILoggerService {
constructor(
@ILogService private logService: ILogService,
@IInstantiationService private instantiationService: IInstantiationService,
@IFileService private fileService: IFileService
) {
super();
this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level))));
......@@ -34,7 +34,7 @@ export class LoggerService extends Disposable implements ILoggerService {
const ext = extname(resource);
logger = new SpdLogService(baseName.substring(0, baseName.length - ext.length), dirname(resource).fsPath, this.logService.getLevel());
} else {
logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel());
logger = new FileLogService(basename(resource), resource, this.logService.getLevel(), this.fileService);
}
this.loggers.set(resource.toString(), logger);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ISharedProcessManagementService = createDecorator<ISharedProcessManagementService>('sharedProcessManagement');
export interface ISharedProcessManagementService {
readonly _serviceBrand: undefined;
whenReady(): Promise<void>;
toggleWindow(): Promise<void>;
}
......@@ -4,33 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ISharedProcess } from 'vs/platform/sharedProcess/node/sharedProcess';
import { ISharedProcessManagementService } from 'vs/platform/sharedProcess/common/sharedProcessManagement';
export const ISharedProcessMainService = createDecorator<ISharedProcessMainService>('sharedProcessMainService');
export const ISharedProcessManagementMainService = createDecorator<ISharedProcessManagementMainService>('sharedProcessManagementMainService');
export interface ISharedProcessMainService {
export interface ISharedProcessManagementMainService extends ISharedProcessManagementService { }
readonly _serviceBrand: undefined;
whenSharedProcessReady(): Promise<void>;
toggleSharedProcessWindow(): Promise<void>;
}
export interface ISharedProcess {
whenReady(): Promise<void>;
toggle(): void;
}
export class SharedProcessMainService implements ISharedProcessMainService {
export class SharedProcessManagementMainService implements ISharedProcessManagementMainService {
declare readonly _serviceBrand: undefined;
constructor(private sharedProcess: ISharedProcess) { }
whenSharedProcessReady(): Promise<void> {
whenReady(): Promise<void> {
return this.sharedProcess.whenReady();
}
async toggleSharedProcessWindow(): Promise<void> {
async toggleWindow(): Promise<void> {
return this.sharedProcess.toggle();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { LogLevel } from 'vs/platform/log/common/log';
export interface ISharedProcess {
/**
* Signals the shared process has finished initialization.
*/
whenReady(): Promise<void>;
/**
* Toggles the visibility of the otherwise hidden
* shared process window.
*/
toggle(): void;
}
export interface ISharedProcessConfiguration {
readonly machineId: string;
readonly windowId: number;
readonly appRoot: string;
readonly userEnv: NodeJS.ProcessEnv;
readonly sharedIPCHandle: string;
readonly args: NativeParsedArgs;
readonly logLevel: LogLevel;
readonly nodeCachedDataDir?: string;
readonly backupWorkspacesPath: string;
}
......@@ -21,7 +21,6 @@ import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/ele
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions';
import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
......@@ -62,11 +61,10 @@ const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExte
class ExtensionsContributions implements IWorkbenchContribution {
constructor(
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
@IExtensionRecommendationNotificationService extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
) {
sharedProcessService.registerChannel('IExtensionRecommendationNotificationService', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService));
sharedProcessService.registerChannel('extensionRecommendationNotification', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService));
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
}
......
......@@ -21,7 +21,7 @@ class ToggleSharedProcessAction extends Action2 {
}
async run(accessor: ServicesAccessor): Promise<void> {
return accessor.get(ISharedProcessService).toggleSharedProcessWindow();
return accessor.get(ISharedProcessService).toggleWindow();
}
}
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
import { gracefulify } from 'graceful-fs';
import { createHash } from 'crypto';
import { exists, stat } from 'vs/base/node/pfs';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
......@@ -32,7 +32,7 @@ import { IWorkbenchConfigurationService } from 'vs/workbench/services/configurat
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle';
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
......@@ -70,7 +70,7 @@ class DesktopMain extends Disposable {
private init(): void {
// Enable gracefulFs
gracefulFs.gracefulify(fs);
gracefulify(fs);
// Massage configuration file URIs
this.reviveUris();
......@@ -181,7 +181,7 @@ class DesktopMain extends Disposable {
// Main Process
const mainProcessService = this._register(new MainProcessService(this.configuration.windowId));
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
serviceCollection.set(IMainProcessService, mainProcessService);
// Environment
......
......@@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService';
......@@ -153,7 +153,7 @@ class DesktopMain extends Disposable {
// Main Process
const mainProcessService = this._register(new MainProcessService(this.configuration.windowId));
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
serviceCollection.set(IMainProcessService, mainProcessService);
// Environment
......
......@@ -39,7 +39,7 @@ export class NativeLogService extends DelegatedLogService {
bufferSpdLogService = disposables.add(new BufferLogService(environmentService.configuration.logLevel));
loggers.push(
disposables.add(new ConsoleLogService(environmentService.configuration.logLevel)),
bufferSpdLogService,
bufferSpdLogService
);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISharedProcessManagementService } from 'vs/platform/sharedProcess/common/sharedProcessManagement';
// @ts-ignore: interface is implemented via proxy
export class SharedProcessManagementService implements ISharedProcessManagementService {
declare readonly _serviceBrand: undefined;
constructor(
@IMainProcessService mainProcessService: IMainProcessService
) {
return createChannelSender<ISharedProcessManagementService>(mainProcessService.getChannel('sharedProcessManagement'));
}
}
registerSingleton(ISharedProcessManagementService, SharedProcessManagementService, true);
......@@ -3,35 +3,65 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { connect } from 'vs/base/parts/ipc/node/ipc.net';
import { Event } from 'vs/base/common/event';
import { IpcRendererEvent } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { generateUuid } from 'vs/base/common/uuid';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { ISharedProcessManagementService } from 'vs/platform/sharedProcess/common/sharedProcessManagement';
export class SharedProcessService implements ISharedProcessService {
export class SharedProcessService extends Disposable implements ISharedProcessService {
declare readonly _serviceBrand: undefined;
private withSharedProcessConnection: Promise<Client<string>>;
private sharedProcessMainChannel: IChannel;
private readonly withSharedProcessConnection: Promise<MessagePortClient>;
constructor(
@IMainProcessService mainProcessService: IMainProcessService,
@INativeHostService nativeHostService: INativeHostService,
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
@ISharedProcessManagementService private readonly sharedProcessManagementService: ISharedProcessManagementService,
@INativeHostService private readonly nativeHostService: INativeHostService,
@ILogService private readonly logService: ILogService,
@ILifecycleService private readonly lifecycleService: ILifecycleService
) {
this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess');
super();
this.withSharedProcessConnection = this.whenSharedProcessReady()
.then(() => connect(environmentService.sharedIPCHandle, `window:${nativeHostService.windowId}`));
this.withSharedProcessConnection = this.connect();
this.registerListeners();
}
whenSharedProcessReady(): Promise<void> {
return this.sharedProcessMainChannel.call('whenSharedProcessReady');
private registerListeners(): void {
// Lifecycle
this.lifecycleService.onWillShutdown(() => this.dispose());
}
private async connect(): Promise<MessagePortClient> {
this.logService.trace('Workbench->SharedProcess#connect');
// await the shared process to be ready
await this.sharedProcessManagementService.whenReady();
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();
ipcRenderer.send('vscode:createSharedProcessMessageChannel', nonce);
// Wait until the main side has returned the `MessagePort`
// We need to filter by the `nonce` to ensure we listen
// to the right response.
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePort }>(ipcRenderer, 'vscode:createSharedProcessMessageChannelResult', (e: IpcRendererEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
this.logService.trace('Workbench->SharedProcess#connect: connection established');
return this._register(new MessagePortClient(port, `window:${this.nativeHostService.windowId}`));
}
getChannel(channelName: string): IChannel {
......@@ -42,8 +72,8 @@ export class SharedProcessService implements ISharedProcessService {
this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel));
}
toggleSharedProcessWindow(): Promise<void> {
return this.sharedProcessMainChannel.call('toggleSharedProcessWindow');
toggleWindow(): Promise<void> {
return this.sharedProcessManagementService.toggleWindow();
}
}
......
......@@ -152,8 +152,7 @@ export class TestSharedProcessService implements ISharedProcessService {
registerChannel(channelName: string, channel: any): void { }
async toggleSharedProcessWindow(): Promise<void> { }
async whenSharedProcessReady(): Promise<void> { }
async toggleWindow(): Promise<void> { }
}
export class TestNativeHostService implements INativeHostService {
......
......@@ -78,6 +78,7 @@ import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncAccountService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreManagementService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService';
import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessManagementService';
import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessService';
import 'vs/workbench/services/localizations/electron-browser/localizationsService';
import 'vs/workbench/services/diagnostics/electron-browser/diagnosticsService';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册