mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge pull request #114359 from microsoft/ben/shared-process-message-port
Implement message port support for shared process connection
This commit is contained in:
commit
f8dbf7dd07
22
src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
22
src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
78
src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
78
src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 };
|
||||
});
|
52
src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
52
src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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();
|
||||
}
|
||||
}
|
51
src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
51
src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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());
|
||||
}
|
||||
}
|
58
src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
58
src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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();
|
||||
});
|
||||
});
|
28
src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts
Normal file
28
src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import * as fs from 'fs';
|
||||
import { gracefulify } from 'graceful-fs';
|
||||
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
|
||||
import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
|
||||
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, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
@ -25,22 +25,21 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|||
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { ILogService, LogLevel, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogService } from 'vs/platform/log/common/log';
|
||||
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DownloadService } from 'vs/platform/download/common/downloadService';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { IChannel, IServerChannel, StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
|
||||
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
|
||||
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
|
@ -48,7 +47,7 @@ import { IFileService } from 'vs/platform/files/common/files';
|
|||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
|
@ -72,141 +71,179 @@ import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogA
|
|||
import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
|
||||
import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
|
||||
import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater';
|
||||
import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner';
|
||||
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
readonly windowId: number;
|
||||
}
|
||||
class SharedProcessMain extends Disposable {
|
||||
|
||||
export function startup(configuration: ISharedProcessConfiguration) {
|
||||
handshake(configuration);
|
||||
}
|
||||
private server = this._register(new MessagePortServer());
|
||||
|
||||
interface ISharedProcessInitData {
|
||||
sharedIPCHandle: string;
|
||||
args: NativeParsedArgs;
|
||||
logLevel: LogLevel;
|
||||
nodeCachedDataDir?: string;
|
||||
backupWorkspacesPath: string;
|
||||
}
|
||||
constructor(private configuration: ISharedProcessConfiguration) {
|
||||
super();
|
||||
|
||||
const eventPrefix = 'monacoworkbench';
|
||||
// Enable gracefulFs
|
||||
gracefulify(fs);
|
||||
|
||||
class MainProcessService implements IMainProcessService {
|
||||
|
||||
constructor(
|
||||
private server: Server,
|
||||
private mainRouter: StaticRouter
|
||||
) { }
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
getChannel(channelName: string): IChannel {
|
||||
return this.server.getChannel(channelName, this.mainRouter);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerChannel(channelName: string, channel: IServerChannel<string>): void {
|
||||
this.server.registerChannel(channelName, channel);
|
||||
private registerListeners(): void {
|
||||
|
||||
// Dispose on exit
|
||||
const onExit = () => this.dispose();
|
||||
process.once('exit', onExit);
|
||||
ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit);
|
||||
}
|
||||
}
|
||||
|
||||
async function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): Promise<void> {
|
||||
const services = new ServiceCollection();
|
||||
async open(): Promise<void> {
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
// Services
|
||||
const instantiationService = await this.initServices();
|
||||
|
||||
const onExit = () => disposables.dispose();
|
||||
process.once('exit', onExit);
|
||||
ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit);
|
||||
// Config
|
||||
registerUserDataSyncConfiguration();
|
||||
|
||||
disposables.add(server);
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
const logService = accessor.get(ILogService);
|
||||
|
||||
const environmentService = new NativeEnvironmentService(initData.args);
|
||||
// Log info
|
||||
logService.trace('sharedProcess configuration', JSON.stringify(this.configuration));
|
||||
|
||||
const mainRouter = new StaticRouter(ctx => ctx === 'main');
|
||||
const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter));
|
||||
const logService = new FollowerLogService(loggerClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel));
|
||||
disposables.add(logService);
|
||||
logService.info('main', JSON.stringify(configuration));
|
||||
// Channels
|
||||
this.initChannels(accessor);
|
||||
|
||||
const mainProcessService = new MainProcessService(server, mainRouter);
|
||||
services.set(IMainProcessService, mainProcessService);
|
||||
// Error handler
|
||||
this.registerErrorHandler(logService);
|
||||
});
|
||||
|
||||
// Files
|
||||
const fileService = new FileService(logService);
|
||||
services.set(IFileService, fileService);
|
||||
disposables.add(fileService);
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
|
||||
disposables.add(diskFileSystemProvider);
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
// Instantiate Contributions
|
||||
this._register(combinedDisposable(
|
||||
new NodeCachedDataCleaner(this.configuration.nodeCachedDataDir),
|
||||
instantiationService.createInstance(LanguagePackCachedDataCleaner),
|
||||
instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath),
|
||||
instantiationService.createInstance(LogsDataCleaner),
|
||||
instantiationService.createInstance(LocalizationsUpdater),
|
||||
instantiationService.createInstance(DeprecatedExtensionsCleaner)
|
||||
));
|
||||
}
|
||||
|
||||
// Configuration
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
disposables.add(configurationService);
|
||||
await configurationService.initialize();
|
||||
|
||||
// Storage
|
||||
const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService);
|
||||
await storageService.initialize();
|
||||
services.set(IStorageService, storageService);
|
||||
disposables.add(toDisposable(() => storageService.flush()));
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
services.set(ILogService, logService);
|
||||
services.set(IConfigurationService, configurationService);
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(ILoggerService, new SyncDescriptor(LoggerService));
|
||||
|
||||
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: configuration.windowId });
|
||||
services.set(INativeHostService, nativeHostService);
|
||||
const activeWindowManager = new ActiveWindowManager(nativeHostService);
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
|
||||
services.set(IDownloadService, new SyncDescriptor(DownloadService));
|
||||
services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(server.getChannel('IExtensionRecommendationNotificationService', activeWindowRouter)));
|
||||
|
||||
const instantiationService = new InstantiationService(services);
|
||||
|
||||
let telemetryService: ITelemetryService;
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
private async initServices(): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Environment
|
||||
const environmentService = new NativeEnvironmentService(this.configuration.args);
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
// Log
|
||||
const mainRouter = new StaticRouter(ctx => ctx === 'main');
|
||||
const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels
|
||||
const multiplexLogger = this._register(new MultiplexLogService([
|
||||
this._register(new ConsoleLogService(this.configuration.logLevel)),
|
||||
this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel))
|
||||
]));
|
||||
|
||||
const logService = this._register(new FollowerLogService(loggerClient, multiplexLogger));
|
||||
services.set(ILogService, logService);
|
||||
|
||||
// Main Process
|
||||
const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter);
|
||||
services.set(IMainProcessService, mainProcessService);
|
||||
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
services.set(IFileService, fileService);
|
||||
|
||||
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService));
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
// Configuration
|
||||
const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService));
|
||||
services.set(IConfigurationService, configurationService);
|
||||
|
||||
await configurationService.initialize();
|
||||
|
||||
// Storage
|
||||
const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService);
|
||||
services.set(IStorageService, storageService);
|
||||
|
||||
await storageService.initialize();
|
||||
this._register(toDisposable(() => storageService.flush()));
|
||||
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
// Request
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
|
||||
services.set(INativeHostService, nativeHostService);
|
||||
|
||||
// Download
|
||||
services.set(IDownloadService, new SyncDescriptor(DownloadService));
|
||||
|
||||
// Extension recommendations
|
||||
const activeWindowManager = this._register(new ActiveWindowManager(nativeHostService));
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter)));
|
||||
|
||||
// Logger
|
||||
const loggerService = this._register(new LoggerService(logService, fileService));
|
||||
services.set(ILoggerService, loggerService);
|
||||
|
||||
// Telemetry
|
||||
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
|
||||
|
||||
let telemetryAppender: ITelemetryAppender = NullAppender;
|
||||
let telemetryService: ITelemetryService;
|
||||
let telemetryAppender: ITelemetryAppender;
|
||||
if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
|
||||
telemetryAppender = new TelemetryLogAppender(accessor.get(ILoggerService), environmentService);
|
||||
telemetryAppender = new TelemetryLogAppender(loggerService, environmentService);
|
||||
|
||||
// Application Insights
|
||||
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
|
||||
const appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey);
|
||||
disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey);
|
||||
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
telemetryAppender = combinedAppender(appInsightsAppender, telemetryAppender);
|
||||
}
|
||||
const config: ITelemetryServiceConfig = {
|
||||
|
||||
telemetryService = new TelemetryService({
|
||||
appender: telemetryAppender,
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath),
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, this.configuration.machineId, product.msftInternalDomains, installSourcePath),
|
||||
sendErrorTelemetry: true,
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
};
|
||||
|
||||
telemetryService = new TelemetryService(config, configurationService);
|
||||
services.set(ITelemetryService, telemetryService);
|
||||
}, configurationService);
|
||||
} else {
|
||||
telemetryService = NullTelemetryService;
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
telemetryAppender = NullAppender;
|
||||
}
|
||||
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender));
|
||||
|
||||
this.server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender));
|
||||
services.set(ITelemetryService, telemetryService);
|
||||
|
||||
// Extension Management
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
|
||||
// Extension Gallery
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
// Extension Tips
|
||||
services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService));
|
||||
|
||||
// Localizations
|
||||
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
|
||||
// Diagnostics
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
// Settings Sync
|
||||
services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService));
|
||||
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(this.server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
|
||||
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
|
||||
services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService));
|
||||
services.set(IExtensionsStorageSyncService, new SyncDescriptor(ExtensionsStorageSyncService));
|
||||
|
@ -217,114 +254,86 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
|||
services.set(IUserDataAutoSyncEnablementService, new SyncDescriptor(UserDataAutoSyncEnablementService));
|
||||
services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
registerConfiguration();
|
||||
|
||||
const instantiationService2 = instantiationService.createChild(services);
|
||||
|
||||
instantiationService2.invokeFunction(accessor => {
|
||||
|
||||
const extensionManagementService = accessor.get(IExtensionManagementService);
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, () => null);
|
||||
server.registerChannel('extensions', channel);
|
||||
|
||||
const localizationsService = accessor.get(ILocalizationsService);
|
||||
const localizationsChannel = createChannelReceiver(localizationsService);
|
||||
server.registerChannel('localizations', localizationsChannel);
|
||||
|
||||
const diagnosticsService = accessor.get(IDiagnosticsService);
|
||||
const diagnosticsChannel = createChannelReceiver(diagnosticsService);
|
||||
server.registerChannel('diagnostics', diagnosticsChannel);
|
||||
|
||||
const extensionTipsService = accessor.get(IExtensionTipsService);
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
|
||||
server.registerChannel('extensionTipsService', extensionTipsChannel);
|
||||
|
||||
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
|
||||
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
const authTokenService = accessor.get(IUserDataSyncAccountService);
|
||||
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
|
||||
server.registerChannel('userDataSyncAccount', authTokenChannel);
|
||||
|
||||
const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService);
|
||||
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService);
|
||||
server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel);
|
||||
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(server, userDataSyncService, logService);
|
||||
server.registerChannel('userDataSync', userDataSyncChannel);
|
||||
|
||||
const userDataAutoSync = instantiationService2.createInstance(UserDataAutoSyncService);
|
||||
const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync);
|
||||
server.registerChannel('userDataAutoSync', userDataAutoSyncChannel);
|
||||
|
||||
// clean up deprecated extensions
|
||||
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();
|
||||
// update localizations cache
|
||||
(localizationsService as LocalizationsService).update();
|
||||
// cache clean ups
|
||||
disposables.add(combinedDisposable(
|
||||
new NodeCachedDataCleaner(initData.nodeCachedDataDir),
|
||||
instantiationService2.createInstance(LanguagePackCachedDataCleaner),
|
||||
instantiationService2.createInstance(StorageDataCleaner, initData.backupWorkspacesPath),
|
||||
instantiationService2.createInstance(LogsDataCleaner),
|
||||
userDataAutoSync
|
||||
));
|
||||
disposables.add(extensionManagementService as ExtensionManagementService);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupIPC(hook: string): Promise<Server> {
|
||||
function setup(retry: boolean): Promise<Server> {
|
||||
return serve(hook).then(null, err => {
|
||||
if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
// should retry, not windows and eaddrinuse
|
||||
|
||||
return connect(hook, '').then(
|
||||
client => {
|
||||
// we could connect to a running instance. this is not good, abort
|
||||
client.dispose();
|
||||
return Promise.reject(new Error('There is an instance already running.'));
|
||||
},
|
||||
err => {
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
// let's delete it, since we can't connect to it
|
||||
// and the retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(hook);
|
||||
} catch (e) {
|
||||
return Promise.reject(new Error('Error deleting the shared ipc hook.'));
|
||||
}
|
||||
|
||||
return setup(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
return new InstantiationService(services);
|
||||
}
|
||||
|
||||
return setup(true);
|
||||
private initChannels(accessor: ServicesAccessor): void {
|
||||
|
||||
// Extensions Management
|
||||
const extensionManagementService = accessor.get(IExtensionManagementService);
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, () => null);
|
||||
this.server.registerChannel('extensions', channel);
|
||||
|
||||
// Localizations
|
||||
const localizationsService = accessor.get(ILocalizationsService);
|
||||
const localizationsChannel = createChannelReceiver(localizationsService);
|
||||
this.server.registerChannel('localizations', localizationsChannel);
|
||||
|
||||
// Diagnostics
|
||||
const diagnosticsService = accessor.get(IDiagnosticsService);
|
||||
const diagnosticsChannel = createChannelReceiver(diagnosticsService);
|
||||
this.server.registerChannel('diagnostics', diagnosticsChannel);
|
||||
|
||||
// Extension Tips
|
||||
const extensionTipsService = accessor.get(IExtensionTipsService);
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
|
||||
this.server.registerChannel('extensionTipsService', extensionTipsChannel);
|
||||
|
||||
// Settings Sync
|
||||
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
|
||||
this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
const authTokenService = accessor.get(IUserDataSyncAccountService);
|
||||
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
|
||||
this.server.registerChannel('userDataSyncAccount', authTokenChannel);
|
||||
|
||||
const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService);
|
||||
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService);
|
||||
this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel);
|
||||
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(this.server, userDataSyncService, accessor.get(ILogService));
|
||||
this.server.registerChannel('userDataSync', userDataSyncChannel);
|
||||
|
||||
const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService));
|
||||
const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync);
|
||||
this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel);
|
||||
}
|
||||
|
||||
private registerErrorHandler(logService: ILogService): void {
|
||||
|
||||
// Listen on unhandled rejection events
|
||||
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
|
||||
onUnexpectedError(event.reason);
|
||||
|
||||
// Prevent the printing of this event to the console
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Install handler for unexpected errors
|
||||
setUnexpectedErrorHandler(error => {
|
||||
const message = toErrorMessage(error, true);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
logService.error(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handshake(configuration: ISharedProcessConfiguration): Promise<void> {
|
||||
export async function main(configuration: ISharedProcessConfiguration): Promise<void> {
|
||||
|
||||
// receive payload from electron-main to start things
|
||||
const data = await new Promise<ISharedProcessInitData>(c => {
|
||||
ipcRenderer.once('vscode:electron-main->shared-process=payload', (event: unknown, r: ISharedProcessInitData) => c(r));
|
||||
|
||||
// tell electron-main we are ready to receive payload
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=ready-for-payload');
|
||||
});
|
||||
|
||||
// await IPC connection and signal this back to electron-main
|
||||
const server = await setupIPC(data.sharedIPCHandle);
|
||||
// create shared process and signal back to main that we are
|
||||
// ready to accept message ports as client connections
|
||||
const sharedProcess = new SharedProcessMain(configuration);
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready');
|
||||
|
||||
// await initialization and signal this back to electron-main
|
||||
await main(server, data, configuration);
|
||||
await sharedProcess.open();
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=init-done');
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get _whenIpcReady(): Promise<void> {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent the window from dying
|
||||
const onClose = (e: ElectronEvent) => {
|
||||
private registerWindowListeners(): void {
|
||||
if (!this.window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
this.window.on('close', this.windowCloseListener);
|
||||
|
||||
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 => {
|
||||
|
||||
// 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();
|
||||
|
||||
// Release barrier
|
||||
this.whenSpawnedBarrier.open();
|
||||
}
|
||||
|
||||
async whenReady(): Promise<void> {
|
||||
await this.barrier.wait();
|
||||
await this._whenReady;
|
||||
}
|
||||
async connect(): Promise<MessagePortMain> {
|
||||
|
||||
async whenIpcReady(): Promise<void> {
|
||||
await this.barrier.wait();
|
||||
await this._whenIpcReady;
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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();
|
||||
}
|
||||
}
|
39
src/vs/platform/sharedProcess/node/sharedProcess.ts
Normal file
39
src/vs/platform/sharedProcess/node/sharedProcess.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
|
Loading…
Reference in a new issue