From 2e14b944a5aa4835b43d0a1a7a6d5f23eff2fd44 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Mon, 13 Dec 2021 10:01:20 -0500
Subject: [PATCH 1/3] DevTools: Support mulitple DevTools instances per page

This is being done so that we can embed DevTools within the new React (beta) docs.

The primary changes here are to 'react-devtools-inline/backend':
* Add a new 'createBridge' API
* Add an option to the 'activate' method to support passing in the custom bridge object.

To verify these changes, this commit also updates the test shell to add a new entry-point for multiple DevTools.
---
 packages/react-devtools-inline/README.md      | 39 ++++++----
 packages/react-devtools-inline/src/backend.js | 68 ++++++++++++------
 .../src/backend/agent.js                      |  4 ++
 .../{index.html => app.html}                  |  2 +-
 packages/react-devtools-shell/multi.html      | 57 +++++++++++++++
 packages/react-devtools-shell/now.json        |  5 --
 packages/react-devtools-shell/package.json    |  6 +-
 .../src/{ => app}/devtools.js                 |  2 +-
 .../src/multi/devtools.js                     | 71 +++++++++++++++++++
 .../react-devtools-shell/src/multi/left.js    | 19 +++++
 .../react-devtools-shell/src/multi/right.js   | 33 +++++++++
 .../react-devtools-shell/webpack.config.js    |  7 +-
 packages/react-devtools/package.json          |  9 +--
 13 files changed, 268 insertions(+), 54 deletions(-)
 rename packages/react-devtools-shell/{index.html => app.html} (97%)
 create mode 100644 packages/react-devtools-shell/multi.html
 delete mode 100644 packages/react-devtools-shell/now.json
 rename packages/react-devtools-shell/src/{ => app}/devtools.js (98%)
 create mode 100644 packages/react-devtools-shell/src/multi/devtools.js
 create mode 100644 packages/react-devtools-shell/src/multi/left.js
 create mode 100644 packages/react-devtools-shell/src/multi/right.js

diff --git a/packages/react-devtools-inline/README.md b/packages/react-devtools-inline/README.md
index b7b3f8373adc9..f05e9868ce334 100644
--- a/packages/react-devtools-inline/README.md
+++ b/packages/react-devtools-inline/README.md
@@ -177,32 +177,47 @@ Below is an example of an advanced integration with a website like [Replay.io](h
 
 ```js
 import {
-  createBridge,
+  activate as activateBackend,
+  createBridge as createBackendBridge,
+  initialize as initializeBackend,
+} from 'react-devtools-inline/backend';
+import {
+  createBridge as createFrontendBridge,
   createStore,
   initialize as createDevTools,
-} from "react-devtools-inline/frontend";
+} from 'react-devtools-inline/frontend';
 
-// Custom Wall implementation enables serializing data
-// using an API other than window.postMessage()
+// DevTools uses "message" events and window.postMessage() by default,
+// but we can override this behavior by creating a custom "Wall" object.
 // For example...
 const wall = {
-  emit() {},
+  _listeners: [],
   listen(listener) {
-    wall._listener = listener;
+    wall._listeners.push(listener);
   },
-  async send(event, payload) {
-    const response = await fetch(...).json();
-    wall._listener(response);
+  send(event, payload) {
+    wall._listeners.forEach(listener => listener({event, payload}));
   },
 };
 
-// Create a Bridge and Store that use the custom Wall.
+// Initialize the DevTools backend before importing React (or any other packages that might import React).
+initializeBackend(contentWindow);
+
+// Prepare DevTools for rendering.
+// To use the custom Wall we've created, we need to also create our own "Bridge" and "Store" objects.
 const bridge = createBridge(target, wall);
 const store = createStore(bridge);
 const DevTools = createDevTools(target, { bridge, store });
 
-// Render DevTools with it.
-<DevTools {...otherProps} />;
+// You can render DevTools now:
+const root = createRoot(container);
+root.render(<DevTools {...otherProps} />);
+
+// Lastly, let the DevTools backend know that the frontend is ready.
+// To use the custom Wall we've created, we need to also pass in the "Bridge".
+activateBackend(contentWindow, {
+  bridge: createBackendBridge(contentWindow, wall),
+});
 ```
 
 ## Local development
diff --git a/packages/react-devtools-inline/src/backend.js b/packages/react-devtools-inline/src/backend.js
index a6466d82709fc..3bbb58304bb5b 100644
--- a/packages/react-devtools-inline/src/backend.js
+++ b/packages/react-devtools-inline/src/backend.js
@@ -10,7 +10,10 @@ import {
   MESSAGE_TYPE_SAVED_PREFERENCES,
 } from './constants';
 
-function startActivation(contentWindow: window) {
+import type {BackendBridge} from 'react-devtools-shared/src/bridge';
+import type {Wall} from 'react-devtools-shared/src/types';
+
+function startActivation(contentWindow: window, bridge: BackendBridge) {
   const {parent} = contentWindow;
 
   const onMessage = ({data}) => {
@@ -48,7 +51,7 @@ function startActivation(contentWindow: window) {
           window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = hideConsoleLogsInStrictMode;
         }
 
-        finishActivation(contentWindow);
+        finishActivation(contentWindow, bridge);
         break;
       default:
         break;
@@ -61,27 +64,11 @@ function startActivation(contentWindow: window) {
   // because they are stored in localStorage within the context of the extension (on the frontend).
   // Instead it relies on the extension to pass preferences through.
   // Because we might be in a sandboxed iframe, we have to ask for them by way of postMessage().
+  // TODO WHAT HUH
   parent.postMessage({type: MESSAGE_TYPE_GET_SAVED_PREFERENCES}, '*');
 }
 
-function finishActivation(contentWindow: window) {
-  const {parent} = contentWindow;
-
-  const bridge = new Bridge({
-    listen(fn) {
-      const onMessage = event => {
-        fn(event.data);
-      };
-      contentWindow.addEventListener('message', onMessage);
-      return () => {
-        contentWindow.removeEventListener('message', onMessage);
-      };
-    },
-    send(event: string, payload: any, transferable?: Array<any>) {
-      parent.postMessage({event, payload}, '*', transferable);
-    },
-  });
-
+function finishActivation(contentWindow: window, bridge: BackendBridge) {
   const agent = new Agent(bridge);
 
   const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -100,8 +87,45 @@ function finishActivation(contentWindow: window) {
   }
 }
 
-export function activate(contentWindow: window): void {
-  startActivation(contentWindow);
+export function activate(
+  contentWindow: window,
+  {
+    bridge,
+  }: {|
+    bridge?: BackendBridge,
+  |} = {},
+): void {
+  if (bridge == null) {
+    bridge = createBridge(contentWindow);
+  }
+
+  startActivation(contentWindow, bridge);
+}
+
+export function createBridge(
+  contentWindow: window,
+  wall?: Wall,
+): BackendBridge {
+  const {parent} = contentWindow;
+
+  if (wall == null) {
+    wall = {
+      listen(fn) {
+        const onMessage = ({data}) => {
+          fn(data);
+        };
+        window.addEventListener('message', onMessage);
+        return () => {
+          window.removeEventListener('message', onMessage);
+        };
+      },
+      send(event: string, payload: any, transferable?: Array<any>) {
+        parent.postMessage({event, payload}, '*', transferable);
+      },
+    };
+  }
+
+  return (new Bridge(wall): BackendBridge);
 }
 
 export function initialize(contentWindow: window): void {
diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js
index fdd46bf9c88f0..4b433a47e615b 100644
--- a/packages/react-devtools-shared/src/backend/agent.js
+++ b/packages/react-devtools-shared/src/backend/agent.js
@@ -225,6 +225,9 @@ export default class Agent extends EventEmitter<{|
       bridge.send('profilingStatus', true);
     }
 
+    // Send the Bridge protocol after initialization in case the frontend has already requested it.
+    this._bridge.send('bridgeProtocol', currentBridgeProtocol);
+
     // Notify the frontend if the backend supports the Storage API (e.g. localStorage).
     // If not, features like reload-and-profile will not work correctly and must be disabled.
     let isBackendStorageAPISupported = false;
@@ -320,6 +323,7 @@ export default class Agent extends EventEmitter<{|
   }
 
   getBridgeProtocol = () => {
+    console.log('[agent] getBridgeProtocol -> bridge.send("bridgeProtocol")');
     this._bridge.send('bridgeProtocol', currentBridgeProtocol);
   };
 
diff --git a/packages/react-devtools-shell/index.html b/packages/react-devtools-shell/app.html
similarity index 97%
rename from packages/react-devtools-shell/index.html
rename to packages/react-devtools-shell/app.html
index 410dc5bdc2abd..7bd183891a99b 100644
--- a/packages/react-devtools-shell/index.html
+++ b/packages/react-devtools-shell/app.html
@@ -64,6 +64,6 @@
     <!-- This script installs the hook, injects the backend, and renders the DevTools UI -->
     <!-- In DEV mode, this file is served by the Webpack dev server -->
     <!-- For production builds, it's built by Webpack and uploaded from the local file system -->
-    <script src="dist/devtools.js"></script>
+    <script src="dist/app-devtools.js"></script>
   </body>
 </html>
\ No newline at end of file
diff --git a/packages/react-devtools-shell/multi.html b/packages/react-devtools-shell/multi.html
new file mode 100644
index 0000000000000..57b25c3760767
--- /dev/null
+++ b/packages/react-devtools-shell/multi.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf8">
+    <title>React DevTools</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <style>
+      * {
+        box-sizing: border-box;
+      }
+      body {
+        display: flex;
+        flex-direction: row;
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        margin: 0;
+        padding: 0;
+        font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
+          sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+      .column {
+        display: flex;
+        flex-direction: column;
+        flex: 1 1 50%;
+      }
+      .column:first-of-type {
+        border-right: 1px solid #3d424a;
+      }
+      .iframe {
+        height: 50%;
+        flex: 0 0 50%;
+        border: none;
+      }
+      .devtools {
+        height: 50%;
+        flex: 0 0 50%;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="column left-column">
+      <iframe id="iframe-left" class="iframe"></iframe>
+      <div id="devtools-left" class="devtools"></div>
+    </div>
+    <div class="column">
+      <iframe id="iframe-right" class="iframe"></iframe>
+      <div id="devtools-right" class="devtools"></div>
+    </div>
+
+    <script src="dist/multi-devtools.js"></script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/packages/react-devtools-shell/now.json b/packages/react-devtools-shell/now.json
deleted file mode 100644
index 7c0805c3b1656..0000000000000
--- a/packages/react-devtools-shell/now.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "react-devtools-experimental",
-  "alias": ["react-devtools-experimental"],
-  "files": ["index.html", "dist"]
-}
diff --git a/packages/react-devtools-shell/package.json b/packages/react-devtools-shell/package.json
index 0d995bdfb07fc..776085ec06183 100644
--- a/packages/react-devtools-shell/package.json
+++ b/packages/react-devtools-shell/package.json
@@ -3,9 +3,9 @@
   "name": "react-devtools-shell",
   "version": "0.0.0",
   "scripts": {
-    "build": "cross-env NODE_ENV=development cross-env TARGET=remote webpack --config webpack.config.js",
-    "deploy": "yarn run build && now deploy && now alias react-devtools-experimental",
-    "start": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open"
+    "start": "yarn start:app",
+    "start:app": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open-page app.html",
+    "start:multi": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open-page multi.html"
   },
   "dependencies": {
     "immutable": "^4.0.0-rc.12",
diff --git a/packages/react-devtools-shell/src/devtools.js b/packages/react-devtools-shell/src/app/devtools.js
similarity index 98%
rename from packages/react-devtools-shell/src/devtools.js
rename to packages/react-devtools-shell/src/app/devtools.js
index 7b4171851c1be..a4e21c81d772f 100644
--- a/packages/react-devtools-shell/src/devtools.js
+++ b/packages/react-devtools-shell/src/app/devtools.js
@@ -60,7 +60,7 @@ function hookNamesModuleLoaderFunction() {
   return import('react-devtools-inline/hookNames');
 }
 
-inject('dist/app.js', () => {
+inject('dist/app-index.js', () => {
   initDevTools({
     connect(cb) {
       const root = createRoot(container);
diff --git a/packages/react-devtools-shell/src/multi/devtools.js b/packages/react-devtools-shell/src/multi/devtools.js
new file mode 100644
index 0000000000000..6b1313fa7a1d5
--- /dev/null
+++ b/packages/react-devtools-shell/src/multi/devtools.js
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import {createRoot} from 'react-dom';
+import {
+  activate as activateBackend,
+  createBridge as createBackendBridge,
+  initialize as initializeBackend,
+} from 'react-devtools-inline/backend';
+import {
+  createBridge as createFrontendBridge,
+  createStore,
+  initialize as createDevTools,
+} from 'react-devtools-inline/frontend';
+import {__DEBUG__} from 'react-devtools-shared/src/constants';
+
+function inject(contentDocument, sourcePath, callback) {
+  const script = contentDocument.createElement('script');
+  script.onload = callback;
+  script.src = sourcePath;
+
+  ((contentDocument.body: any): HTMLBodyElement).appendChild(script);
+}
+
+function init(appIframe, devtoolsContainer, appSource) {
+  const {contentDocument, contentWindow} = appIframe;
+
+  // Wire each DevTools instance directly to its app.
+  // By default, DevTools dispatches "message" events on the window,
+  // but this means that only one instance of DevTools can live on a page.
+  const wall = {
+    _listeners: [],
+    listen(listener) {
+      if (__DEBUG__) {
+        console.log('[Shell] Wall.listen()');
+      }
+
+      wall._listeners.push(listener);
+    },
+    send(event, payload) {
+      if (__DEBUG__) {
+        console.log('[Shell] Wall.send()', {event, payload});
+      }
+
+      wall._listeners.forEach(listener => listener({event, payload}));
+    },
+  };
+
+  const backendBridge = createBackendBridge(contentWindow, wall);
+
+  initializeBackend(contentWindow);
+
+  const frontendBridge = createFrontendBridge(contentWindow, wall);
+  const store = createStore(frontendBridge);
+  const DevTools = createDevTools(contentWindow, {
+    bridge: frontendBridge,
+    store,
+  });
+
+  inject(contentDocument, appSource, () => {
+    createRoot(devtoolsContainer).render(<DevTools />);
+  });
+
+  activateBackend(contentWindow, {bridge: backendBridge});
+}
+
+const appIframeLeft = document.getElementById('iframe-left');
+const appIframeRight = document.getElementById('iframe-right');
+const devtoolsContainerLeft = document.getElementById('devtools-left');
+const devtoolsContainerRight = document.getElementById('devtools-right');
+
+init(appIframeLeft, devtoolsContainerLeft, 'dist/multi-left.js');
+init(appIframeRight, devtoolsContainerRight, 'dist/multi-right.js');
diff --git a/packages/react-devtools-shell/src/multi/left.js b/packages/react-devtools-shell/src/multi/left.js
new file mode 100644
index 0000000000000..aaf380f39a6da
--- /dev/null
+++ b/packages/react-devtools-shell/src/multi/left.js
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import {useState} from 'react';
+import {createRoot} from 'react-dom';
+
+function createContainer() {
+  const container = document.createElement('div');
+
+  ((document.body: any): HTMLBodyElement).appendChild(container);
+
+  return container;
+}
+
+function StatefulCounter() {
+  const [count, setCount] = useState(0);
+  const handleClick = () => setCount(count + 1);
+  return <button onClick={handleClick}>Count {count}</button>;
+}
+
+createRoot(createContainer()).render(<StatefulCounter />);
diff --git a/packages/react-devtools-shell/src/multi/right.js b/packages/react-devtools-shell/src/multi/right.js
new file mode 100644
index 0000000000000..f4d08632e75f0
--- /dev/null
+++ b/packages/react-devtools-shell/src/multi/right.js
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import {useLayoutEffect, useRef, useState} from 'react';
+import {render} from 'react-dom';
+
+function createContainer() {
+  const container = document.createElement('div');
+
+  ((document.body: any): HTMLBodyElement).appendChild(container);
+
+  return container;
+}
+
+function EffectWithState() {
+  const [didMount, setDidMount] = useState(0);
+
+  const renderCountRef = useRef(0);
+  renderCountRef.current++;
+
+  useLayoutEffect(() => {
+    if (!didMount) {
+      setDidMount(true);
+    }
+  }, [didMount]);
+
+  return (
+    <ul>
+      <li>Rendered {renderCountRef.current} times</li>
+      {didMount && <li>Mounted!</li>}
+    </ul>
+  );
+}
+
+render(<EffectWithState />, createContainer());
diff --git a/packages/react-devtools-shell/webpack.config.js b/packages/react-devtools-shell/webpack.config.js
index fb3df7240fb3e..4f690bb0e3c2d 100644
--- a/packages/react-devtools-shell/webpack.config.js
+++ b/packages/react-devtools-shell/webpack.config.js
@@ -42,8 +42,11 @@ const config = {
   mode: __DEV__ ? 'development' : 'production',
   devtool: __DEV__ ? 'cheap-source-map' : 'source-map',
   entry: {
-    app: './src/app/index.js',
-    devtools: './src/devtools.js',
+    'app-index': './src/app/index.js',
+    'app-devtools': './src/app/devtools.js',
+    'multi-left': './src/multi/left.js',
+    'multi-devtools': './src/multi/devtools.js',
+    'multi-right': './src/multi/right.js',
   },
   node: {
     // source-maps package has a dependency on 'fs'
diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json
index ca66824859ca4..d36aeff05f584 100644
--- a/packages/react-devtools/package.json
+++ b/packages/react-devtools/package.json
@@ -11,14 +11,7 @@
   "bin": {
     "react-devtools": "./bin.js"
   },
-  "files": [
-    "bin.js",
-    "build-info.json",
-    "app.html",
-    "app.js",
-    "index.js",
-    "icons"
-  ],
+  "files": [],
   "scripts": {
     "start": "node bin.js"
   },

From c888188b51112c23858107d20edcc77f4398cf44 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Tue, 14 Dec 2021 08:46:44 -0500
Subject: [PATCH 2/3] Replaced direct calls to window.postMessage() with
 Bridge.send()

---
 packages/react-devtools-inline/README.md      |  2 +-
 packages/react-devtools-inline/src/backend.js | 87 ++++++++-----------
 .../react-devtools-inline/src/constants.js    |  6 --
 .../react-devtools-inline/src/frontend.js     | 65 ++++++--------
 packages/react-devtools-shared/src/bridge.js  | 14 ++-
 5 files changed, 77 insertions(+), 97 deletions(-)
 delete mode 100644 packages/react-devtools-inline/src/constants.js

diff --git a/packages/react-devtools-inline/README.md b/packages/react-devtools-inline/README.md
index f05e9868ce334..dff2ce2a06261 100644
--- a/packages/react-devtools-inline/README.md
+++ b/packages/react-devtools-inline/README.md
@@ -56,7 +56,7 @@ const iframe = document.getElementById(frameID);
 const contentWindow = iframe.contentWindow;
 
 // This returns a React component that can be rendered into your app.
-// <DevTools {...props} />
+// e.g. render(<DevTools {...props} />);
 const DevTools = initialize(contentWindow);
 ```
 
diff --git a/packages/react-devtools-inline/src/backend.js b/packages/react-devtools-inline/src/backend.js
index 3bbb58304bb5b..c0c87871df117 100644
--- a/packages/react-devtools-inline/src/backend.js
+++ b/packages/react-devtools-inline/src/backend.js
@@ -5,67 +5,54 @@ import Bridge from 'react-devtools-shared/src/bridge';
 import {initBackend} from 'react-devtools-shared/src/backend';
 import {installHook} from 'react-devtools-shared/src/hook';
 import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
-import {
-  MESSAGE_TYPE_GET_SAVED_PREFERENCES,
-  MESSAGE_TYPE_SAVED_PREFERENCES,
-} from './constants';
 
 import type {BackendBridge} from 'react-devtools-shared/src/bridge';
 import type {Wall} from 'react-devtools-shared/src/types';
 
 function startActivation(contentWindow: window, bridge: BackendBridge) {
-  const {parent} = contentWindow;
-
-  const onMessage = ({data}) => {
-    switch (data.type) {
-      case MESSAGE_TYPE_SAVED_PREFERENCES:
-        // This is the only message we're listening for,
-        // so it's safe to cleanup after we've received it.
-        contentWindow.removeEventListener('message', onMessage);
-
-        const {
-          appendComponentStack,
-          breakOnConsoleErrors,
-          componentFilters,
-          showInlineWarningsAndErrors,
-          hideConsoleLogsInStrictMode,
-        } = data;
-
-        contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
-        contentWindow.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
-        contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
-        contentWindow.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
-        contentWindow.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = hideConsoleLogsInStrictMode;
-
-        // TRICKY
-        // The backend entry point may be required in the context of an iframe or the parent window.
-        // If it's required within the parent window, store the saved values on it as well,
-        // since the injected renderer interface will read from window.
-        // Technically we don't need to store them on the contentWindow in this case,
-        // but it doesn't really hurt anything to store them there too.
-        if (contentWindow !== window) {
-          window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
-          window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
-          window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
-          window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
-          window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = hideConsoleLogsInStrictMode;
-        }
-
-        finishActivation(contentWindow, bridge);
-        break;
-      default:
-        break;
+  const onSavedPreferences = data => {
+    // This is the only message we're listening for,
+    // so it's safe to cleanup after we've received it.
+    bridge.removeListener('savedPreferences', onSavedPreferences);
+
+    const {
+      appendComponentStack,
+      breakOnConsoleErrors,
+      componentFilters,
+      showInlineWarningsAndErrors,
+      hideConsoleLogsInStrictMode,
+    } = data;
+
+    contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
+    contentWindow.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
+    contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
+    contentWindow.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
+    contentWindow.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = hideConsoleLogsInStrictMode;
+
+    // TRICKY
+    // The backend entry point may be required in the context of an iframe or the parent window.
+    // If it's required within the parent window, store the saved values on it as well,
+    // since the injected renderer interface will read from window.
+    // Technically we don't need to store them on the contentWindow in this case,
+    // but it doesn't really hurt anything to store them there too.
+    if (contentWindow !== window) {
+      window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
+      window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
+      window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
+      window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
+      window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = hideConsoleLogsInStrictMode;
     }
+
+    finishActivation(contentWindow, bridge);
   };
 
-  contentWindow.addEventListener('message', onMessage);
+  bridge.addListener('savedPreferences', onSavedPreferences);
 
   // The backend may be unable to read saved preferences directly,
   // because they are stored in localStorage within the context of the extension (on the frontend).
   // Instead it relies on the extension to pass preferences through.
   // Because we might be in a sandboxed iframe, we have to ask for them by way of postMessage().
-  // TODO WHAT HUH
-  parent.postMessage({type: MESSAGE_TYPE_GET_SAVED_PREFERENCES}, '*');
+  bridge.send('getSavedPreferences');
 }
 
 function finishActivation(contentWindow: window, bridge: BackendBridge) {
@@ -114,9 +101,9 @@ export function createBridge(
         const onMessage = ({data}) => {
           fn(data);
         };
-        window.addEventListener('message', onMessage);
+        contentWindow.addEventListener('message', onMessage);
         return () => {
-          window.removeEventListener('message', onMessage);
+          contentWindow.removeEventListener('message', onMessage);
         };
       },
       send(event: string, payload: any, transferable?: Array<any>) {
diff --git a/packages/react-devtools-inline/src/constants.js b/packages/react-devtools-inline/src/constants.js
deleted file mode 100644
index cfa443cc04806..0000000000000
--- a/packages/react-devtools-inline/src/constants.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/** @flow */
-
-export const MESSAGE_TYPE_GET_SAVED_PREFERENCES =
-  'React::DevTools::getSavedPreferences';
-export const MESSAGE_TYPE_SAVED_PREFERENCES =
-  'React::DevTools::savedPreferences';
diff --git a/packages/react-devtools-inline/src/frontend.js b/packages/react-devtools-inline/src/frontend.js
index 065bb1238715e..0e90c884667a6 100644
--- a/packages/react-devtools-inline/src/frontend.js
+++ b/packages/react-devtools-inline/src/frontend.js
@@ -12,10 +12,6 @@ import {
   getShowInlineWarningsAndErrors,
   getHideConsoleLogsInStrictMode,
 } from 'react-devtools-shared/src/utils';
-import {
-  MESSAGE_TYPE_GET_SAVED_PREFERENCES,
-  MESSAGE_TYPE_SAVED_PREFERENCES,
-} from './constants';
 
 import type {Wall} from 'react-devtools-shared/src/types';
 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
@@ -68,49 +64,40 @@ export function initialize(
     store?: Store,
   |} = {},
 ): React.AbstractComponent<Props, mixed> {
-  const onGetSavedPreferencesMessage = ({data, source}) => {
-    if (source === 'react-devtools-content-script') {
-      // Ignore messages from the DevTools browser extension.
-    }
-
-    switch (data.type) {
-      case MESSAGE_TYPE_GET_SAVED_PREFERENCES:
-        // This is the only message we're listening for,
-        // so it's safe to cleanup after we've received it.
-        window.removeEventListener('message', onGetSavedPreferencesMessage);
-
-        // The renderer interface can't read saved preferences directly,
-        // because they are stored in localStorage within the context of the extension.
-        // Instead it relies on the extension to pass them through.
-        contentWindow.postMessage(
-          {
-            type: MESSAGE_TYPE_SAVED_PREFERENCES,
-            appendComponentStack: getAppendComponentStack(),
-            breakOnConsoleErrors: getBreakOnConsoleErrors(),
-            componentFilters: getSavedComponentFilters(),
-            showInlineWarningsAndErrors: getShowInlineWarningsAndErrors(),
-            hideConsoleLogsInStrictMode: getHideConsoleLogsInStrictMode(),
-          },
-          '*',
-        );
-        break;
-      default:
-        break;
-    }
-  };
-
-  window.addEventListener('message', onGetSavedPreferencesMessage);
-
   if (bridge == null) {
     bridge = createBridge(contentWindow);
   }
 
+  // Type refinement.
+  const frontendBridge = ((bridge: any): FrontendBridge);
+
   if (store == null) {
-    store = createStore(bridge);
+    store = createStore(frontendBridge);
   }
 
+  const onGetSavedPreferences = () => {
+    // This is the only message we're listening for,
+    // so it's safe to cleanup after we've received it.
+    frontendBridge.removeListener('getSavedPreferences', onGetSavedPreferences);
+
+    const data = {
+      appendComponentStack: getAppendComponentStack(),
+      breakOnConsoleErrors: getBreakOnConsoleErrors(),
+      componentFilters: getSavedComponentFilters(),
+      showInlineWarningsAndErrors: getShowInlineWarningsAndErrors(),
+      hideConsoleLogsInStrictMode: getHideConsoleLogsInStrictMode(),
+    };
+
+    // The renderer interface can't read saved preferences directly,
+    // because they are stored in localStorage within the context of the extension.
+    // Instead it relies on the extension to pass them through.
+    frontendBridge.send('savedPreferences', data);
+  };
+
+  frontendBridge.addListener('getSavedPreferences', onGetSavedPreferences);
+
   const ForwardRef = forwardRef<Props, mixed>((props, ref) => (
-    <DevTools ref={ref} bridge={bridge} store={store} {...props} />
+    <DevTools ref={ref} bridge={frontendBridge} store={store} {...props} />
   ));
   ForwardRef.displayName = 'DevTools';
 
diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js
index 68891e8c18d4b..cdeec7aebfc4b 100644
--- a/packages/react-devtools-shared/src/bridge.js
+++ b/packages/react-devtools-shared/src/bridge.js
@@ -176,10 +176,19 @@ type UpdateConsolePatchSettingsParams = {|
   browserTheme: BrowserTheme,
 |};
 
+type SavedPreferencesParams = {|
+  appendComponentStack: boolean,
+  breakOnConsoleErrors: boolean,
+  componentFilters: Array<ComponentFilter>,
+  showInlineWarningsAndErrors: boolean,
+  hideConsoleLogsInStrictMode: boolean,
+|};
+
 export type BackendEvents = {|
   bridgeProtocol: [BridgeProtocol],
   extensionBackendInitialized: [],
   fastRefreshScheduled: [],
+  getSavedPreferences: [],
   inspectedElement: [InspectedElementPayload],
   isBackendStorageAPISupported: [boolean],
   isSynchronousXHRSupported: [boolean],
@@ -223,6 +232,7 @@ type FrontendEvents = {|
   profilingData: [ProfilingDataBackend],
   reloadAndProfile: [boolean],
   renamePath: [RenamePath],
+  savedPreferences: [SavedPreferencesParams],
   selectFiber: [number],
   setTraceUpdatesEnabled: [boolean],
   shutdown: [],
@@ -277,7 +287,9 @@ class Bridge<
 
     this._wallUnlisten =
       wall.listen((message: Message) => {
-        (this: any).emit(message.event, message.payload);
+        if (message && message.event) {
+          (this: any).emit(message.event, message.payload);
+        }
       }) || null;
 
     // Temporarily support older standalone front-ends sending commands to newer embedded backends.

From 292cc1f2cbc45667132d3117112fcb49eff64316 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Tue, 14 Dec 2021 08:47:46 -0500
Subject: [PATCH 3/3] Removed an accidental console.log

---
 packages/react-devtools-shared/src/backend/agent.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js
index 4b433a47e615b..f31e3a6c38a7b 100644
--- a/packages/react-devtools-shared/src/backend/agent.js
+++ b/packages/react-devtools-shared/src/backend/agent.js
@@ -323,7 +323,6 @@ export default class Agent extends EventEmitter<{|
   }
 
   getBridgeProtocol = () => {
-    console.log('[agent] getBridgeProtocol -> bridge.send("bridgeProtocol")');
     this._bridge.send('bridgeProtocol', currentBridgeProtocol);
   };