Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: capture Electron IPC messages for opensumi devtools #1583

Merged
merged 10 commits into from
Sep 26, 2022
42 changes: 24 additions & 18 deletions packages/addons/src/browser/chrome-devtools.contribution.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Autowired } from '@opensumi/di';
import { ClientAppContribution } from '@opensumi/ide-core-browser';
import { AppConfig, ClientAppContribution } from '@opensumi/ide-core-browser';
import { Domain } from '@opensumi/ide-core-common/lib/di-helper';

import { ConnectionRTTBrowserServiceToken, ConnectionRTTBrowserService } from './connection-rtt-service';
Expand All @@ -15,6 +15,9 @@ enum DevtoolsCommand {

@Domain(ClientAppContribution)
export class ChromeDevtoolsContribution implements ClientAppContribution {
@Autowired(AppConfig)
private readonly appConfig: AppConfig;

@Autowired(ConnectionRTTBrowserServiceToken)
protected readonly rttService: ConnectionRTTBrowserService;

Expand All @@ -23,25 +26,28 @@ export class ChromeDevtoolsContribution implements ClientAppContribution {
static INTERVAL = 1000;

initialize() {
// receive notification from opensumi devtools by custom event
window.addEventListener(DevtoolsEvent.Latency, (event) => {
const { command } = event.detail;
if (command === DevtoolsCommand.Start) {
// only runs when devtools supoprt is enabled
if (this.appConfig.devtools) {
// receive notification from opensumi devtools by custom event
window.addEventListener(DevtoolsEvent.Latency, (event) => {
const { command } = event.detail;
if (command === DevtoolsCommand.Start) {
if (!this.interval) {
this.startRTTInterval();
}
} else if (command === DevtoolsCommand.Stop) {
if (this.interval) {
global.clearInterval(this.interval);
this.interval = undefined;
}
}
});

// if opensumi devtools has started capturing before this contribution point is registered
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.captureRPC) {
if (!this.interval) {
this.startRTTInterval();
}
} else if (command === DevtoolsCommand.Stop) {
if (this.interval) {
global.clearInterval(this.interval);
this.interval = undefined;
}
}
});

// if opensumi devtools has started capturing before this contribution point is registered
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.capture) {
if (!this.interval) {
this.startRTTInterval();
}
}
}
Expand All @@ -52,7 +58,7 @@ export class ChromeDevtoolsContribution implements ClientAppContribution {
await this.rttService.measure();
const rtt = Date.now() - start;
// "if" below is to prevent setting latency after stoping capturing
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.capture) {
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.captureRPC) {
window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.latency = rtt;
}
}, ChromeDevtoolsContribution.INTERVAL);
Expand Down
4 changes: 2 additions & 2 deletions packages/connection/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export function parse(input: string, reviver?: (this: any, key: string, value: a
}

export function getCapturer() {
if (typeof window !== 'undefined' && window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.capture) {
return window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.capture;
if (typeof window !== 'undefined' && window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.captureRPC) {
return window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.captureRPC;
}
return;
}
13 changes: 9 additions & 4 deletions packages/core-browser/src/bootstrap/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ export class ClientApp implements IClientApp, IDisposable {
stateService: ClientAppStateService;

constructor(opts: IClientAppOpts) {
// set a global so the opensumi devtools can identify that
// the current page is powered by opensumi core
window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__ = {};

const {
modules,
contributions,
Expand All @@ -161,8 +157,10 @@ export class ClientApp implements IClientApp, IDisposable {
editorBackgroundImage,
defaultPreferences,
allowSetDocumentTitleFollowWorkspaceDir = true,
devtools = false, // if not set, disable devtools support as default
...restOpts // rest part 为 AppConfig
} = opts;

this.initEarlyPreference(opts.workspaceDir || '');
setLanguageId(getPreferenceLanguageId(defaultPreferences));
this.injector = opts.injector || new Injector();
Expand All @@ -188,8 +186,15 @@ export class ClientApp implements IClientApp, IDisposable {
layoutConfig: opts.layoutConfig as LayoutConfig,
editorBackgroundImage: opts.editorBackgroundImage || editorBackgroundImage,
allowSetDocumentTitleFollowWorkspaceDir,
devtools,
};

if (devtools) {
// set a global so the opensumi devtools can identify that
// the current page is powered by opensumi core
window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__ = {};
}

if (this.config.isElectronRenderer && electronEnv.metadata?.extensionDevelopmentHost) {
this.config.extensionDevelopmentHost = electronEnv.metadata.extensionDevelopmentHost;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/core-browser/src/react-providers/config-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ export interface AppConfig {
* 这将影响 Terminal 与 Extension 模块与子进程的连接方式
*/
isRemote?: boolean;
/**
* 是否开启对 OpenSumi DevTools 的支持
* 默认值为 false
*/
devtools?: boolean;
}

export const ConfigContext = React.createContext<AppConfig>({
Expand Down
59 changes: 58 additions & 1 deletion packages/core-electron-main/browser-preload/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,60 @@ const os = require('os');

const { ipcRenderer } = require('electron');

const initForDevtools = () => {
const getCapturer = () => {
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.captureIPC) {
return window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.captureIPC;
}
return;
};

const capture = (message) => {
const capturer = getCapturer();
if (capturer !== undefined) {
capturer(message);
}
};

// ipcRenderer.on
const originalIpcRendererOn = ipcRenderer.on;
ipcRenderer.on = (channel, handler) => {
const proxyHandler = (event, ...args) => {
if (channel !== 'main->browser') {
tyn1998 marked this conversation as resolved.
Show resolved Hide resolved
capture({ ipcMethod: 'ipcRenderer.on', channel, args });
}
handler(event, ...args);
};
return originalIpcRendererOn.call(ipcRenderer, channel, proxyHandler);
};

// ipcRenderer.send
const originalIpcRendererSend = ipcRenderer.send;
ipcRenderer.send = (channel, ...args) => {
capture({ ipcMethod: 'ipcRenderer.send', channel, args });
return originalIpcRendererSend.call(ipcRenderer, channel, ...args);
};

// ipcRenderer.sendSync
const originalIpcRendererSendSync = ipcRenderer.sendSync;
ipcRenderer.sendSync = (channel, ...args) => {
capture({ ipcMethod: 'ipcRenderer.sendSync', channel, args });
return originalIpcRendererSendSync.call(ipcRenderer, channel, ...args);
};

// ipcRenderer.invoke
const originalIpcRendererInvoke = ipcRenderer.invoke;
ipcRenderer.invoke = (channel, ...args) => {
capture({ ipcMethod: 'ipcRenderer.invoke', channel, args });
return originalIpcRendererInvoke.call(ipcRenderer, channel, ...args);
};

// receive messages that transfered from main process and capture them
ipcRenderer.on('main->browser', (event, message) => {
capture(message);
});
};

const electronEnv = {};

const urlParams = new URLSearchParams(decodeURIComponent(window.location.search));
Expand Down Expand Up @@ -32,7 +86,10 @@ electronEnv.currentWebContentsId = webContentsId;
electronEnv.onigWasmPath = require.resolve('vscode-oniguruma/release/onig.wasm');

const metaData = JSON.parse(ipcRenderer.sendSync('window-metadata', electronEnv.currentWindowId));

// if devtools support enabled
if (metaData.devtools) {
initForDevtools();
}
electronEnv.metadata = metaData;
process.env = Object.assign({}, process.env, metaData.env, { WORKSPACE_DIR: metaData.workspace });

Expand Down
5 changes: 5 additions & 0 deletions packages/core-electron-main/src/bootstrap/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export class ElectronMainApp {
this.onBeforeReadyContribution();
this.registerMainApis();
this.registerURLHandlers();

// if not set, disable devtools support as default
if (this.config.devtools === undefined) {
tyn1998 marked this conversation as resolved.
Show resolved Hide resolved
this.config.devtools = false;
}
}

async init() {
Expand Down
33 changes: 33 additions & 0 deletions packages/core-electron-main/src/bootstrap/devtools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ipcMain, BrowserWindow } from 'electron';

export const initForDevtools = (mainWindow: BrowserWindow) => {
// `mainWindow.webContents.send` is used to transfer messages to main window
// wherer ipcRenderer.on('main->browser', xxx) is set to receive the messages
// then put them to window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.IPCMessages

// ipcMain.on
const originalIpcMainOn = ipcMain.on;
ipcMain.on = (channel: any, handler: any) => {
const proxyHandler = (event: any, ...args: any) => {
mainWindow.webContents.send('main->browser', { ipcMethod: 'ipcMain.on', channel, args });
handler(event, ...args);
// TODO event.returnValue会作为ipcRenderer.sendSync的response, 可捕获
tyn1998 marked this conversation as resolved.
Show resolved Hide resolved
};
return originalIpcMainOn.call(ipcMain, channel, proxyHandler);
};

// ipcMain.handle
const originalIpcMainHandle = ipcMain.handle;
ipcMain.handle = (channel: any, handler: any) => {
const proxyHandler = (event: any, ...args: any) => {
mainWindow.webContents.send('main->browser', { ipcMethod: 'ipcMain.handle', channel, args });
handler(event, ...args);
// TODO ipcMain.handle的返回值会作为ipcRenderer.invoke的response, 可捕获
};
return originalIpcMainHandle.call(ipcMain, channel, proxyHandler);
};

// TODO BrowserWindow.webContents.send
// 这是main进程向renderer进程发送消息的方式, 但BrowserWindow可以是任何实例,
// 所以我不知道怎么处理. 如果可以实现, 注意判断channel是否为'main->browser'
tyn1998 marked this conversation as resolved.
Show resolved Hide resolved
};
6 changes: 6 additions & 0 deletions packages/core-electron-main/src/bootstrap/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ export interface ElectronAppConfig {
* 如有外部 injector,优先使用外部
*/
injector?: Injector;

/**
* 是否开启对 OpenSumi DevTools 的支持
* 默认值为 false
*/
devtools?: boolean;
}

export const ElectronAppConfig = Symbol('ElectronAppConfig');
Expand Down
8 changes: 8 additions & 0 deletions packages/core-electron-main/src/bootstrap/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@opensumi/ide-core-common';
import { normalizedIpcHandlerPathAsync } from '@opensumi/ide-core-common/lib/utils/ipc';

import { initForDevtools } from './devtools';
import { ElectronAppConfig, ICodeWindow, ICodeWindowOptions } from './types';

const DEFAULT_WINDOW_HEIGHT = 700;
Expand Down Expand Up @@ -94,6 +95,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
...this.appConfig.overrideBrowserOptions,
...options,
});

if (this.appConfig.devtools) {
// initialize for OpenSumi DevTools
initForDevtools(this.browser);
}

if (options) {
if (options.extensionDir) {
this.extensionDir = options.extensionDir;
Expand Down Expand Up @@ -131,6 +138,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
workerHostEntry: this.appConfig.extensionWorkerEntry,
extensionDevelopmentHost: this.appConfig.extensionDevelopmentHost,
appPath: app.getAppPath(),
devtools: this.appConfig.devtools,
});
}
};
Expand Down
1 change: 1 addition & 0 deletions packages/startup/entry/web/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ renderApp({
bottom: '@opensumi/ide-terminal-next',
right: '',
},
devtools: true, // 开启 core-browser 对 OpenSumi DevTools 的支持,默认为关闭
});
1 change: 1 addition & 0 deletions tools/electron/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ renderApp({
},
modules: [...CommonBrowserModules, ElectronBasicModule],
layoutConfig: customLayoutConfig,
devtools: true, // 开启 core-browser 对 OpenSumi DevTools 的支持,默认为关闭
});
1 change: 1 addition & 0 deletions tools/electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const electronApp = new ElectronMainApp({
extensionDir: getExtensionDir(),
extensionCandidate: [],
overrideWebPreferences: {},
devtools: true, // 开启 core-electron-main 对 OpenSumi DevTools 的支持,默认为关闭
});

electronApp.init().then(() => {
Expand Down