Upgrade electron version

Summary:
CHANGELOG: Upgrade electron to 18.2.0.

In Electron 18.2.0 we no longer have access to `remote`. Instead, we are recommended to implement IPC communications. We re-implement `remote` methods used before as IPC commands. To support type-safe execution of the commands, we create electron IPC clients on both sides: the main process and renderer process. We also move the main menu creation to the main process and track its usage via sending IPC messages to the renderer process where the logging happens.

Reviewed By: mweststrate

Differential Revision: D36593625

fbshipit-source-id: 6dcf531461ef2edceb9cac372a650f84f3370953
This commit is contained in:
Andrey Goncharov
2022-05-31 06:52:14 -07:00
committed by Facebook GitHub Bot
parent 2fe7e73175
commit 92cdb81096
10 changed files with 2279 additions and 401 deletions

View File

@@ -15,27 +15,28 @@ import {
_LoggerContext,
} from 'flipper-plugin';
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
import {
ipcRenderer,
remote,
SaveDialogReturnValue,
clipboard,
shell,
} from 'electron';
import {ipcRenderer, SaveDialogReturnValue, clipboard, shell} from 'electron';
import fs from 'fs';
import {setupMenuBar} from './setupMenuBar';
import {setupMenuBarTracking} from './setupMenuBar';
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
import type {Icon, RenderHost} from 'flipper-ui-core';
import {getLocalIconUrl} from '../utils/icons';
import {getCPUUsage} from 'process';
import {ElectronIpcClientRenderer} from '../electronIpc';
export function initializeElectron(
export async function initializeElectron(
flipperServer: FlipperServer,
flipperServerConfig: FlipperServerConfig,
electronIpcClient: ElectronIpcClientRenderer,
) {
const execPath = process.execPath || remote.process.execPath;
const electronProcess = await electronIpcClient.send('getProcess');
const electronTheme = await electronIpcClient.send('getNativeTheme');
const execPath = process.execPath || electronProcess.execPath;
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
setupMenuBarTracking(electronIpcClient);
function restart(update: boolean = false) {
if (isProduction) {
if (update) {
@@ -44,11 +45,11 @@ export function initializeElectron(
.splice(0, 1)
.filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
};
remote.app.relaunch(options);
electronIpcClient.send('relaunch', options);
} else {
remote.app.relaunch();
electronIpcClient.send('relaunch');
}
remote.app.exit();
electronIpcClient.send('exit');
} else {
// Relaunching the process with the standard way doesn't work in dev mode.
// So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
@@ -59,7 +60,7 @@ export function initializeElectron(
}
FlipperRenderHostInstance = {
processId: remote.process.pid,
processId: electronProcess.pid,
isProduction,
readTextFromClipboard() {
return clipboard.readText();
@@ -68,10 +69,11 @@ export function initializeElectron(
clipboard.writeText(text);
},
async showSaveDialog(options) {
return (await remote.dialog.showSaveDialog(options))?.filePath;
return (await electronIpcClient.send('showSaveDialog', options))
?.filePath;
},
async showOpenDialog({filter, defaultPath}) {
const result = await remote.dialog.showOpenDialog({
const result = await electronIpcClient.send('showOpenDialog', {
defaultPath,
properties: ['openFile'],
filters: filter ? [filter] : undefined,
@@ -79,8 +81,8 @@ export function initializeElectron(
return result.filePaths?.[0];
},
showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
return remote.dialog
.showOpenDialog({
return electronIpcClient
.send('showOpenDialog', {
properties: ['openDirectory'],
defaultPath,
})
@@ -104,7 +106,7 @@ export function initializeElectron(
encoding = 'utf-8',
multi,
} = {}) => {
let {filePaths} = await remote.dialog.showOpenDialog({
let {filePaths} = await electronIpcClient.send('showOpenDialog', {
defaultPath,
properties: [
'openFile',
@@ -138,7 +140,7 @@ export function initializeElectron(
return multi ? descriptors : descriptors[0];
}) as RenderHost['importFile'],
async exportFile(data, {defaultPath, encoding = 'utf-8'} = {}) {
const {filePath} = await remote.dialog.showSaveDialog({
const {filePath} = await electronIpcClient.send('showSaveDialog', {
defaultPath,
});
@@ -153,7 +155,8 @@ export function initializeElectron(
shell.openExternal(url);
},
hasFocus() {
return remote.getCurrentWindow().isFocused();
// eslint-disable-next-line node/no-sync
return electronIpcClient.sendSync('getCurrentWindowState').isFocused;
},
onIpcEvent(event, callback) {
ipcRenderer.on(event, (_ev, ...args: any[]) => {
@@ -164,7 +167,7 @@ export function initializeElectron(
ipcRenderer.send(event, ...args);
},
shouldUseDarkColors() {
return remote.nativeTheme.shouldUseDarkColors;
return electronTheme.shouldUseDarkColors;
},
restartFlipper(update: boolean = false) {
restart(update);
@@ -203,8 +206,6 @@ export function initializeElectron(
return getCPUUsage().percentCPUUsage;
},
} as RenderHost;
setupMenuBar();
}
function getDefaultPluginsIndex() {

View File

@@ -7,232 +7,16 @@
* @format
*/
// Deliberate use of remote in this context.
/* eslint-disable no-restricted-properties, no-restricted-imports */
import electron, {MenuItemConstructorOptions, webFrame} from 'electron';
import {getLogger} from 'flipper-common';
import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin';
import {ElectronIpcClientRenderer} from '../electronIpc';
export function setupMenuBar() {
const template = getTemplate(electron.remote.app);
// create actual menu instance
const applicationMenu = electron.remote.Menu.buildFromTemplate(template);
// update menubar
electron.remote.Menu.setApplicationMenu(applicationMenu);
}
function trackMenuItems(menu: string, items: MenuItemConstructorOptions[]) {
items.forEach((item) => {
if (item.label && item.click) {
item.click = _wrapInteractionHandler(
item.click,
'MenuItem',
'onClick',
'flipper:menu:' + menu,
item.label,
);
export function setupMenuBarTracking(
electronIpcClient: ElectronIpcClientRenderer,
) {
electronIpcClient.on('menuItemAction', ({action, menu, label}) => {
if (action === 'click') {
getLogger().track('usage', 'menuItemClick', {menu, label});
}
});
}
function getTemplate(app: electron.App): Array<MenuItemConstructorOptions> {
const viewMenu: MenuItemConstructorOptions[] = [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
try {
getLogger().track('usage', 'reload');
} catch (e) {
// Ignore track failures (which can happen if we try to reload from a broken state)
console.warn('Could not track reload', e);
}
window.location.reload();
},
},
{
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F';
} else {
return 'F11';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Actual Size',
accelerator: (function () {
return 'CmdOrCtrl+0';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(1);
},
},
{
label: 'Zoom In',
accelerator: (function () {
return 'CmdOrCtrl+=';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.25);
},
},
{
label: 'Zoom Out',
accelerator: (function () {
return 'CmdOrCtrl+-';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.25);
},
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
} else {
return 'Ctrl+Shift+I';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
// @ts-ignore: https://github.com/electron/electron/issues/7832
focusedWindow.toggleDevTools();
}
},
},
];
trackMenuItems('view', viewMenu);
const template: MenuItemConstructorOptions[] = [
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo',
},
{
type: 'separator',
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectAll',
},
],
},
{
label: 'View',
submenu: viewMenu,
},
{
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close',
},
],
},
];
if (process.platform === 'darwin') {
const name = app.name;
template.unshift({
label: name,
submenu: [
{
label: 'About ' + name,
role: 'about',
},
{
type: 'separator',
},
{
label: 'Services',
role: 'services',
submenu: [],
},
{
type: 'separator',
},
{
label: 'Hide ' + name,
accelerator: 'Command+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
role: 'hideOthers',
},
{
label: 'Show All',
role: 'unhide',
},
{
type: 'separator',
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function () {
app.quit();
},
},
],
});
const windowMenu = template.find(function (m) {
return m.role === 'window';
});
if (windowMenu) {
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
{
type: 'separator',
},
{
label: 'Bring All to Front',
role: 'front',
},
);
}
}
return template;
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
// eslint-disable-next-line no-restricted-imports
import type {
NativeTheme,
SaveDialogReturnValue,
SaveDialogOptions,
OpenDialogOptions,
OpenDialogReturnValue,
} from 'electron';
import {ipcRenderer} from 'electron/renderer';
export interface ElectronIpcAsyncCommands {
getPath: (path: 'app' | 'home' | 'temp' | 'desktop') => Promise<string>;
getProcess: () => Promise<{execPath: string; pid: number}>;
relaunch: (options?: {args: string[]}) => Promise<void>;
showSaveDialog: (
options: SaveDialogOptions,
) => Promise<SaveDialogReturnValue>;
showOpenDialog: (
options: OpenDialogOptions,
) => Promise<OpenDialogReturnValue>;
getNativeTheme: () => Promise<
Pick<
NativeTheme,
| 'inForcedColorsMode'
| 'shouldUseDarkColors'
| 'shouldUseHighContrastColors'
| 'shouldUseInvertedColorScheme'
| 'themeSource'
>
>;
quit: () => Promise<void>;
exit: () => Promise<void>;
getApp: () => Promise<{
name: string;
}>;
}
export interface ElectronIpcSyncCommands {
getCurrentWindowState: () => {
isFocused: boolean;
};
}
export interface ElectronIpcEvents {
menuItemAction: {
menu: 'view' | 'root';
label: string;
action: 'click';
};
}
export class ElectronIpcClientRenderer {
on<T extends keyof ElectronIpcEvents>(
eventName: T,
cb: (data: ElectronIpcEvents[T]) => void,
) {
ipcRenderer.on(eventName, (_event, data) => cb(data));
}
send<T extends keyof ElectronIpcAsyncCommands>(
eventName: T,
...args: Parameters<ElectronIpcAsyncCommands[T]>
): ReturnType<ElectronIpcAsyncCommands[T]> extends Promise<any>
? ReturnType<ElectronIpcAsyncCommands[T]>
: never {
return ipcRenderer.invoke(eventName, ...args) as any;
}
sendSync<T extends keyof ElectronIpcSyncCommands>(
eventName: T,
...args: Parameters<ElectronIpcSyncCommands[T]>
): ReturnType<ElectronIpcSyncCommands[T]> {
// eslint-disable-next-line node/no-sync
return ipcRenderer.sendSync(eventName, ...args);
}
}

View File

@@ -14,8 +14,6 @@ import {
_setGlobalInteractionReporter,
_LoggerContext,
} from 'flipper-plugin';
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
import {remote} from 'electron';
import {createFlipperServer, FlipperServerState} from 'flipper-frontend-core';
import {
FlipperServerImpl,
@@ -39,16 +37,18 @@ import constants from './fb-stubs/constants';
import {initializeElectron} from './electron/initializeElectron';
import path from 'path';
import fs from 'fs-extra';
import {ElectronIpcClientRenderer} from './electronIpc';
enableMapSet();
async function getEmbeddedFlipperServer(
logger: Logger,
electronIpcClient: ElectronIpcClientRenderer,
): Promise<FlipperServer> {
const app = remote.app;
const execPath = process.execPath || remote.process.execPath;
const appPath = app.getAppPath();
const staticPath = getStaticDir();
const execPath =
process.execPath || (await electronIpcClient.send('getProcess')).execPath;
const appPath = await electronIpcClient.send('getPath', 'app');
const staticPath = getStaticDir(appPath);
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
const env = process.env;
const environmentInfo = await getEnvironmentInfo(
@@ -84,11 +84,11 @@ async function getEmbeddedFlipperServer(
gatekeepers: getGatekeepers(environmentInfo.os.unixname),
paths: {
appPath,
homePath: app.getPath('home'),
homePath: await electronIpcClient.send('getPath', 'home'),
execPath,
staticPath,
tempPath: app.getPath('temp'),
desktopPath: app.getPath('desktop'),
tempPath: await electronIpcClient.send('getPath', 'temp'),
desktopPath: await electronIpcClient.send('getPath', 'desktop'),
},
launcherSettings: await loadLauncherSettings(),
processConfig: loadProcessConfig(env),
@@ -116,10 +116,19 @@ async function start() {
const logger = createDelegatedLogger();
setLoggerInstance(logger);
const flipperServer: FlipperServer = await getEmbeddedFlipperServer(logger);
const electronIpcClient = new ElectronIpcClientRenderer();
const flipperServer: FlipperServer = await getEmbeddedFlipperServer(
logger,
electronIpcClient,
);
const flipperServerConfig = await flipperServer.exec('get-config');
initializeElectron(flipperServer, flipperServerConfig);
await initializeElectron(
flipperServer,
flipperServerConfig,
electronIpcClient,
);
setProcessState(flipperServerConfig.settings);
@@ -142,7 +151,7 @@ start().catch((e) => {
'Failed to start Flipper desktop: ' + e;
});
function getStaticDir() {
function getStaticDir(appPath: string) {
let _staticPath = path.resolve(__dirname, '..', '..', 'static');
// fs.existSync used here, as fs-extra doesn't resovle properly in the app.asar
/* eslint-disable node/no-sync*/
@@ -150,8 +159,8 @@ function getStaticDir() {
// True in unit tests
return _staticPath;
}
if (remote && fs.existsSync(remote.app.getAppPath())) {
_staticPath = path.join(remote.app.getAppPath());
if (fs.existsSync(appPath)) {
_staticPath = path.join(appPath);
}
if (!fs.existsSync(_staticPath)) {
throw new Error('Static path does not exist: ' + _staticPath);

View File

@@ -68,12 +68,13 @@
"description": "Mobile development tool",
"devDependencies": {
"@babel/eslint-parser": "^7.17.0",
"@jest-runner/electron": "^3.0.1",
"@types/jest": "^26.0.24",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"babel-eslint": "^10.1.0",
"cross-env": "^7.0.3",
"electron": "11.2.3",
"electron": "18.2.0",
"eslint": "^7.32.0",
"eslint-config-fbjs": "^3.1.1",
"eslint-config-prettier": "^8.5.0",
@@ -113,25 +114,25 @@
"privileged": true,
"productName": "Flipper",
"resolutions": {
"@jest-runner/electron/electron": "18.2.0",
"adbkit-logcat": "^2.0.1",
"node-forge": "^1.0.6",
"minimist": "1.2.6"
"minimist": "1.2.6",
"node-forge": "^1.0.6"
},
"scripts": {
"build": "cross-env NODE_ENV=production ./ts-node scripts/build-release.tsx $@",
"build-plugin": "./ts-node scripts/build-plugin.tsx",
"build:dev": "cross-env NODE_ENV=development ./ts-node scripts/build-release.tsx $@",
"build:eslint": "cd eslint-plugin-flipper && yarn build",
"build:flipper-server": "yarn build:tsc && ./ts-node scripts/build-flipper-server-release.tsx",
"build:themes": "lessc --js themes/light.less static/themes/light.css && lessc --js themes/dark.less static/themes/dark.css",
"build:tsc": "tsc -b tsc-root/tsconfig.json && ./ts-node ./scripts/compute-package-checksum.tsx -d ./babel-transformer -o ./lib/checksum.txt",
"build:flipper-server": "yarn build:tsc && ./ts-node scripts/build-flipper-server-release.tsx",
"bump-versions": "./ts-node scripts/bump-versions.tsx",
"bundle-all-plugins": "./ts-node scripts/bundle-all-plugins.tsx",
"predev-server": "yarn build:tsc",
"dev-server": "cross-env NODE_ENV=development ./ts-node scripts/start-dev-server.tsx",
"preflipper-server": "yarn build:tsc",
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server-dev.tsx",
"docs": "cd ../website && yarn start",
"fix": "eslint . --fix --ext .js,.ts,.tsx",
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server-dev.tsx",
"lint": "yarn lint:eslint && yarn lint:tsc && yarn tsc-plugins",
"lint:eslint": "eslint . --ext .js,.ts,.tsx",
"lint:tsc": "tsc && tsc -p tsc-root/tsconfig.json --noemit",
@@ -139,10 +140,13 @@
"open-dist": "open ../dist/mac/Flipper.app --args --launcher=false --inspect=9229",
"postinstall": "patch-package && yarn --cwd plugins install --mutex network:30331 && yarn tsc -b pkg-lib/tsconfig.json && ./ts-node scripts/generate-plugin-entry-points.tsx && yarn build:tsc && yarn build:themes",
"prebuild": "yarn build:tsc && yarn rm-dist && yarn build:themes",
"predev-server": "yarn build:tsc",
"preflipper-server": "yarn build:tsc",
"preinstall": "node scripts/prepare-watchman-config.js && yarn config set ignore-engines",
"prelint:eslint": "yarn build:eslint",
"pretest": "yarn build:tsc",
"publish-packages": "./ts-node scripts/publish-packages.tsx",
"get-electron-cache-directory": "./ts-node scripts/get-electron-cache-directory.tsx",
"reset": "yarn rm-dist && yarn rm-temp && yarn rm-metro-cache && yarn cache clean && yarn rm-bundle && yarn rm-modules",
"resolve-plugin-dir": "./ts-node scripts/resolve-plugin-dir.tsx",
"rm-bundle": "rimraf static/main.bundle.* **/dist/bundle.* **/lib **/*.tsbuildinfo",
@@ -154,7 +158,6 @@
"start": "yarn dev-server --inspect=9229",
"start:break": "yarn dev-server --inspect-brk=9229",
"start:no-bundled-plugins": "yarn start --no-bundled-plugins",
"docs": "cd ../website && yarn start",
"test": "cross-env TZ=Pacific/Pohnpei jest",
"test:debug": "yarn build:tsc && cross-env TZ=Pacific/Pohnpei node --inspect node_modules/.bin/jest --runInBand",
"tsc-plugins": "./ts-node scripts/tsc-plugins.tsx",

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import yargs from 'yargs';
import * as crypto from 'crypto';
import * as path from 'path';
import * as url from 'url';
const argv = yargs
.usage('$0 [args]')
.options({
electronVersion: {
key: 'electronVersion',
alias: 'v',
type: 'string',
demandOption: true,
},
})
.help().argv;
// https://github.com/electron/get/blob/1671db2120142d7850260b098db72b0ef5ee988c/src/Cache.ts#L23
const getCacheDirectory = (downloadUrl: string): string => {
const parsedDownloadUrl = url.parse(downloadUrl);
const {search, hash, pathname, ...rest} = parsedDownloadUrl;
const strippedUrl = url.format({
...rest,
pathname: path.dirname(pathname || 'electron'),
});
return crypto.createHash('sha256').update(strippedUrl).digest('hex');
};
const getUrl = ({electronVersion}: {electronVersion: string}) => {
// It is going to be stripped to https://github.com/electron/electron/releases/download/v${electronVersion}, so it is going to be the same for all platforms
const url = `https://github.com/electron/electron/releases/download/v${electronVersion}/electron-v${electronVersion}-linux-x64.zip`;
return getCacheDirectory(url);
};
const folderName = getUrl(argv);
console.log(folderName);

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
// eslint-disable-next-line no-restricted-imports
import electron, {ipcMain, BrowserWindow} from 'electron';
// eslint-disable-next-line flipper/no-relative-imports-across-packages
import type {
ElectronIpcAsyncCommands,
ElectronIpcEvents,
ElectronIpcSyncCommands,
} from '../app/src/electronIpc';
export class ElectronIpcClientMain {
constructor(private readonly window: BrowserWindow) {
this.handleAll();
}
private handleAll() {
for (const command of Object.keys(this.commandsAsync)) {
ipcMain.handle(command, (_event, params) =>
this.commandsAsync[command as keyof ElectronIpcAsyncCommands](params),
);
}
// eslint-disable-next-line node/no-sync
for (const command of Object.keys(this.commandsSync)) {
ipcMain.on(command, (event) => {
event.returnValue =
// eslint-disable-next-line node/no-sync
this.commandsSync[command as keyof ElectronIpcSyncCommands]();
});
}
}
send<T extends keyof ElectronIpcEvents>(
eventName: T,
data: ElectronIpcEvents[T],
) {
this.window.webContents.send(eventName, data);
}
private readonly commandsAsync: ElectronIpcAsyncCommands = {
exit: async () => electron.app.exit(),
quit: async () => electron.app.quit(),
getPath: async (pathName) => {
switch (pathName) {
case 'app': {
return electron.app.getAppPath();
}
default: {
return electron.app.getPath(pathName);
}
}
},
getProcess: async () => {
const {pid, execPath} = process;
return {pid, execPath};
},
relaunch: async (options) => electron.app.relaunch(options),
showSaveDialog: (options) => electron.dialog.showSaveDialog(options),
showOpenDialog: (options) => electron.dialog.showOpenDialog(options),
getNativeTheme: async () => {
const {
themeSource,
shouldUseDarkColors,
shouldUseHighContrastColors,
shouldUseInvertedColorScheme,
inForcedColorsMode,
} = electron.nativeTheme;
return {
themeSource,
shouldUseDarkColors,
shouldUseHighContrastColors,
shouldUseInvertedColorScheme,
inForcedColorsMode,
};
},
getApp: async () => {
const {name} = electron.app;
return {name};
},
};
private readonly commandsSync: ElectronIpcSyncCommands = {
getCurrentWindowState: () => {
const isFocused = this.window.isFocused();
return {isFocused};
},
};
}

View File

@@ -32,6 +32,8 @@ import delegateToLauncher from './launcher';
import yargs from 'yargs';
import {promisify} from 'util';
import process from 'process';
import {setupMenuBar} from './setupMenuBar';
import {ElectronIpcClientMain} from './electronIpcMain';
const VERSION: string = (global as any).__VERSION__;
@@ -347,14 +349,12 @@ function createWindow(config: Config) {
? path.join(__dirname, 'icons/app_64x64.png')
: undefined,
webPreferences: {
enableRemoteModule: true,
backgroundThrottling: false,
webSecurity: false,
scrollBounce: true,
experimentalFeatures: true,
nodeIntegration: true,
webviewTag: true,
nativeWindowOpen: true,
contextIsolation: false,
},
});
@@ -410,6 +410,9 @@ function createWindow(config: Config) {
slashes: true,
});
win.loadURL(entryUrl);
const electronIpcClient = new ElectronIpcClientMain(win);
setupMenuBar(electronIpcClient);
}
function processConfig(config: Config) {

View File

@@ -0,0 +1,238 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
// eslint-disable-next-line no-restricted-imports
import electron, {MenuItemConstructorOptions, webFrame} from 'electron';
import {ElectronIpcClientMain} from './electronIpcMain';
export function setupMenuBar(electronIpcClient: ElectronIpcClientMain) {
function trackMenuItems(
menu: 'view' | 'root',
items: MenuItemConstructorOptions[],
) {
items.forEach((item) => {
if (item.label && item.click) {
electronIpcClient.send('menuItemAction', {
menu,
label: item.label,
action: 'click',
});
}
});
}
const template = getTemplate(trackMenuItems);
// create actual menu instance
const applicationMenu = electron.Menu.buildFromTemplate(template);
// update menubar
electron.Menu.setApplicationMenu(applicationMenu);
}
function getTemplate(
trackMenuItems: (
menu: 'view' | 'root',
items: MenuItemConstructorOptions[],
) => void,
): Array<MenuItemConstructorOptions> {
const viewMenu: MenuItemConstructorOptions[] = [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
window.location.reload();
},
},
{
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F';
} else {
return 'F11';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: 'Actual Size',
accelerator: (function () {
return 'CmdOrCtrl+0';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(1);
},
},
{
label: 'Zoom In',
accelerator: (function () {
return 'CmdOrCtrl+=';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.25);
},
},
{
label: 'Zoom Out',
accelerator: (function () {
return 'CmdOrCtrl+-';
})(),
click: function (_, _focusedWindow: electron.BrowserWindow | undefined) {
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.25);
},
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I';
} else {
return 'Ctrl+Shift+I';
}
})(),
click: function (_, focusedWindow: electron.BrowserWindow | undefined) {
if (focusedWindow) {
// @ts-ignore: https://github.com/electron/electron/issues/7832
focusedWindow.toggleDevTools();
}
},
},
];
trackMenuItems('view', viewMenu);
const template: MenuItemConstructorOptions[] = [
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo',
},
{
type: 'separator',
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectAll',
},
],
},
{
label: 'View',
submenu: viewMenu,
},
{
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close',
},
],
},
];
if (process.platform === 'darwin') {
const name = electron.app.name;
template.unshift({
label: name,
submenu: [
{
label: 'About ' + name,
role: 'about',
},
{
type: 'separator',
},
{
label: 'Services',
role: 'services',
submenu: [],
},
{
type: 'separator',
},
{
label: 'Hide ' + name,
accelerator: 'Command+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
role: 'hideOthers',
},
{
label: 'Show All',
role: 'unhide',
},
{
type: 'separator',
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function () {
electron.app.quit();
},
},
],
});
const windowMenu = template.find(function (m) {
return m.role === 'window';
});
if (windowMenu) {
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
{
type: 'separator',
},
{
label: 'Bring All to Front',
role: 'front',
},
);
}
}
trackMenuItems('root', template);
return template;
}

File diff suppressed because it is too large Load Diff