Files
react-native/packages/debugger-shell/src/electron/MainInstanceEntryPoint.js
T
Moti Zilberman 33be0606c1 Always reload the frontend when launching, even in an existing window (#53407)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53407

Changelog: [Internal]

## Context

Upon receiving a launch command, the RNDT shell either:

1. Creates a new window and navigates to the requested frontend URL.
2. Brings an existing window to the foreground *with no further navigation*.

In the happy path, (2) is a pretty nice experience: it preserves all prior UI state in the frontend and leaves the user with an instantly responsive debugger - this can be quite a bit faster than (1) because of the overhead of loading and parsing source maps for example. However, this breaks down if the frontend is not in a usable state to begin with. This is, sadly, a frequent-enough occurrence that we must account for it: the CDP connection may have been lost, the frontend app itself might have failed to load the last time, etc.

Preserving everything that's nice about (2) while also making it fully reliable - incrementally bringing the frontend to the state specified by a new URL - would require delicate engineering across the shell and frontend codebases, which is an amount of complexity I would like to sidestep for now.

NOTE: The more complex solution **is 100% worth implementing in the long term,** as it has tangible benefits for the user, and matches Chrome best.

## This diff

Here we take a much cheaper approach than the one described above: the shell will *always* initiate navigation to the new frontend URL, regardless of whether it does so in a new window or a previously opened one. This will consistently bring the user to a state where the frontend is open and working (although it will reset any ephemeral UI state in the process, and typically take a noticeable amount of time to load).

Even with this simplified approach, the standalone shell still offers a better experience than launching in a browser (if only because it is zero-install and avoids the "dead tab spam" problem).

Reviewed By: huntie

Differential Revision: D80711185

fbshipit-source-id: 8f376ccf1717c48a1742c798da3171ac6d2f8af0
2025-08-22 04:03:08 -07:00

119 lines
2.9 KiB
JavaScript

/**
* 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.
*
* @flow strict-local
* @format
*/
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {BrowserWindow, app, shell, ipcMain} = require('electron') as any;
const path = require('path');
const util = require('util');
const windowMetadata = new WeakMap<
typeof BrowserWindow,
$ReadOnly<{
windowKey: string,
}>,
>();
function handleLaunchArgs(argv: string[]) {
const {
values: {frontendUrl, windowKey},
} = util.parseArgs({
options: {
frontendUrl: {
type: 'string',
},
windowKey: {
type: 'string',
},
},
args: argv,
});
// Find an existing window for this app and launch configuration.
let frontendWindow = BrowserWindow.getAllWindows().find(window => {
const metadata = windowMetadata.get(window);
if (!metadata) {
return false;
}
return metadata.windowKey === windowKey;
});
if (frontendWindow) {
// If the window is already visible, flash it.
if (frontendWindow.isVisible()) {
frontendWindow.flashFrame(true);
setTimeout(() => {
frontendWindow.flashFrame(false);
}, 1000);
}
} else {
// Create the browser window.
frontendWindow = new BrowserWindow({
width: 1200,
height: 600,
webPreferences: {
partition: 'persist:react-native-devtools',
preload: require.resolve('./preload.js'),
},
// Icon for Linux
icon: path.join(__dirname, 'resources', 'icon.png'),
});
}
// Open links in the default browser instead of in new Electron windows.
frontendWindow.webContents.setWindowOpenHandler(({url}) => {
shell.openExternal(url);
return {action: 'deny'};
});
// TODO: If the window contains a live, working frontend instance with a valid connection to the backend,
// we should avoid this reload and instead send the frontend a message to handle the launch arguments
// dynamically (e.g. update the launch ID for telemetry purposes, handle deeplinking to a specific CDT panel, etc).
frontendWindow.loadURL(frontendUrl);
windowMetadata.set(frontendWindow, {
windowKey,
});
if (process.platform === 'darwin') {
app.focus({
steal: true,
});
}
frontendWindow.focus();
}
app.whenReady().then(() => {
handleLaunchArgs(process.argv.slice(app.isPackaged ? 1 : 2));
app.on(
'second-instance',
(event, electronArgv, workingDirectory, additionalData) => {
handleLaunchArgs(additionalData.argv);
},
);
});
app.on('window-all-closed', function () {
app.quit();
});
ipcMain.on('bringToFront', (event, title) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
if (win) {
win.focus();
}
if (process.platform === 'darwin') {
app.focus({
steal: true,
});
}
});