Repository: facebook/react
Pull request: Reattachment of the splash page event listeners (#22558)

## Summary

Prevents the splash page from becoming unresponsive after the disconnection of a client (issue #22558). A `onDisconnect` callback was added to the -core package and is used to re-attach the event listener to the HTML elements in the splash page.

## How did you test this change?

Connected and disconnected multiple times a client to the standalone DevTools and the listeners are now attached and working. Was able to copy both script tags and go to the Profiler tab.

Changed files:
filename: packages/react-devtools-core/src/standalone.js
patch: @@ -42,11 +42,13 @@ import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Co
 installHook(window);
 
 export type StatusListener = (message: string) => void;
+export type OnDisconnectedCallback = () => void;
 
 let node: HTMLElement = ((null: any): HTMLElement);
 let nodeWaitingToConnectHTML: string = '';
 let projectRoots: Array<string> = [];
 let statusListener: StatusListener = (message: string) => {};
+let disconnectedCallback: OnDisconnectedCallback = () => {};
 
 // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration.
 function hookNamesModuleLoaderFunction() {
@@ -73,6 +75,11 @@ function setStatusListener(value: StatusListener) {
   return DevtoolsUI;
 }
 
+function setDisconnectedCallback(value: OnDisconnectedCallback) {
+  disconnectedCallback = value;
+  return DevtoolsUI;
+}
+
 let bridge: FrontendBridge | null = null;
 let store: Store | null = null;
 let root = null;
@@ -153,6 +160,8 @@ function onDisconnected() {
   safeUnmount();
 
   node.innerHTML = nodeWaitingToConnectHTML;
+
+  disconnectedCallback();
 }
 
 function onError({code, message}) {
@@ -389,6 +398,7 @@ const DevtoolsUI = {
   setContentDOMNode,
   setProjectRoots,
   setStatusListener,
+  setDisconnectedCallback,
   startServer,
   openProfiler,
 };

filename: packages/react-devtools/app.html
patch: @@ -182,11 +182,9 @@
       const server = defaultPort ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
       const serverIp = defaultPort ? `${protocol}://${localIp}` : `${protocol}://${localIp}:${port}`;
       const $ = document.querySelector.bind(document);
-      const $promptDiv = $("#box-content-prompt");
-      const $confirmationDiv = $("#box-content-confirmation");
-
+      
       let timeoutID;
-
+      
       function selectAllAndCopy(event) {
         const element = event.target;
         if (window.getSelection) {
@@ -196,7 +194,9 @@
           selection.removeAllRanges();
           selection.addRange(range);
           clipboard.writeText(event.target.textContent);
-
+          
+          const $promptDiv = $("#box-content-prompt");
+          const $confirmationDiv = $("#box-content-confirmation");
           $promptDiv.classList.add('hidden');
           $confirmationDiv.classList.remove('hidden');
 
@@ -217,24 +217,29 @@
           .openProfiler();
       }
 
-      const link = $('#rn-help-link');
-      link.addEventListener('click', event => {
-        event.preventDefault();
-        require('electron').shell.openExternal(link.href);
-      });
-
-      const $localhost = $("#localhost");
-      $localhost.innerText = `<script src="${server}"></` + 'script>';
-      $localhost.addEventListener('click', selectAllAndCopy);
-      $localhost.addEventListener('focus', selectAllAndCopy);
-
-      const $byIp = $("#byip");
-      $byIp.innerText = `<script src="${serverIp}"></` + 'script>';
-      $byIp.addEventListener('click', selectAllAndCopy);
-      $byIp.addEventListener('focus', selectAllAndCopy);
+      function attachListeners() {
+        const link = $('#rn-help-link');
+        link.addEventListener('click', event => {
+          event.preventDefault();
+          require('electron').shell.openExternal(link.href);
+        });
+  
+        const $localhost = $("#localhost");
+        $localhost.innerText = `<script src="${server}"></` + 'script>';
+        $localhost.addEventListener('click', selectAllAndCopy);
+        $localhost.addEventListener('focus', selectAllAndCopy);
+  
+        const $byIp = $("#byip");
+        $byIp.innerText = `<script src="${serverIp}"></` + 'script>';
+        $byIp.addEventListener('click', selectAllAndCopy);
+        $byIp.addEventListener('focus', selectAllAndCopy);
+  
+        const $profiler = $("#profiler");
+        $profiler.addEventListener('click', openProfiler);
+      };
 
-      const $profiler = $("#profiler");
-      $profiler.addEventListener('click', openProfiler);
+      // Initially attach the listeners
+      attachListeners();
 
       let devtools;
       try {
@@ -248,6 +253,7 @@
       window.devtools = devtools;
       window.server = devtools
         .setContentDOMNode(document.getElementById("container"))
+        .setDisconnectedCallback(attachListeners)
         .setStatusListener(function(status) {
           const element = document.getElementById("loading-status");
           if (element) {