diff --git a/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js b/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js
index 08db763bdbaa..45fe39497400 100644
--- a/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js
+++ b/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js
@@ -13,6 +13,13 @@ const contentScriptsToInject = IS_FIREFOX
         persistAcrossSessions: true,
         runAt: 'document_end',
       },
+      {
+        id: '@react-devtools/file-fetcher',
+        js: ['build/fileFetcher.js'],
+        matches: ['<all_urls>'],
+        persistAcrossSessions: true,
+        runAt: 'document_end',
+      },
     ]
   : [
       {
@@ -23,6 +30,14 @@ const contentScriptsToInject = IS_FIREFOX
         runAt: 'document_end',
         world: chrome.scripting.ExecutionWorld.ISOLATED,
       },
+      {
+        id: '@react-devtools/file-fetcher',
+        js: ['build/fileFetcher.js'],
+        matches: ['<all_urls>'],
+        persistAcrossSessions: true,
+        runAt: 'document_end',
+        world: chrome.scripting.ExecutionWorld.ISOLATED,
+      },
       {
         id: '@react-devtools/hook',
         js: ['build/installHook.js'],
diff --git a/packages/react-devtools-extensions/src/background/executeScript.js b/packages/react-devtools-extensions/src/background/executeScript.js
new file mode 100644
index 000000000000..af48726be028
--- /dev/null
+++ b/packages/react-devtools-extensions/src/background/executeScript.js
@@ -0,0 +1,58 @@
+/* global chrome */
+
+import {IS_FIREFOX} from '../utils';
+
+// Firefox doesn't support ExecutionWorld.MAIN yet
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
+function executeScriptForFirefoxInMainWorld({target, files}) {
+  return chrome.scripting.executeScript({
+    target,
+    func: fileNames => {
+      function injectScriptSync(src) {
+        let code = '';
+        const request = new XMLHttpRequest();
+        request.addEventListener('load', function () {
+          code = this.responseText;
+        });
+        request.open('GET', src, false);
+        request.send();
+
+        const script = document.createElement('script');
+        script.textContent = code;
+
+        // This script runs before the <head> element is created,
+        // so we add the script to <html> instead.
+        if (document.documentElement) {
+          document.documentElement.appendChild(script);
+        }
+
+        if (script.parentNode) {
+          script.parentNode.removeChild(script);
+        }
+      }
+
+      fileNames.forEach(file => injectScriptSync(chrome.runtime.getURL(file)));
+    },
+    args: [files],
+  });
+}
+
+export function executeScriptInIsolatedWorld({target, files}) {
+  return chrome.scripting.executeScript({
+    target,
+    files,
+    world: chrome.scripting.ExecutionWorld.ISOLATED,
+  });
+}
+
+export function executeScriptInMainWorld({target, files}) {
+  if (IS_FIREFOX) {
+    return executeScriptForFirefoxInMainWorld({target, files});
+  }
+
+  return chrome.scripting.executeScript({
+    target,
+    files,
+    world: chrome.scripting.ExecutionWorld.MAIN,
+  });
+}
diff --git a/packages/react-devtools-extensions/src/background/index.js b/packages/react-devtools-extensions/src/background/index.js
index 35d57dc6dbce..b25eb5303319 100644
--- a/packages/react-devtools-extensions/src/background/index.js
+++ b/packages/react-devtools-extensions/src/background/index.js
@@ -2,11 +2,15 @@
 
 'use strict';
 
-import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from '../utils';
-
 import './dynamicallyInjectContentScripts';
 import './tabsManager';
-import setExtensionIconAndPopup from './setExtensionIconAndPopup';
+
+import {
+  handleDevToolsPageMessage,
+  handleBackendManagerMessage,
+  handleReactDevToolsHookMessage,
+  handleFetchResourceContentScriptMessage,
+} from './messageHandlers';
 
 /*
   {
@@ -173,67 +177,21 @@ function connectExtensionAndProxyPorts(extensionPort, proxyPort, tabId) {
 }
 
 chrome.runtime.onMessage.addListener((message, sender) => {
-  const tab = sender.tab;
-  // sender.tab.id from content script points to the tab that injected the content script
-  if (tab) {
-    const id = tab.id;
-    // This is sent from the hook content script.
-    // It tells us a renderer has attached.
-    if (message.hasDetectedReact) {
-      setExtensionIconAndPopup(message.reactBuildType, id);
-    } else {
-      const extensionPort = ports[id]?.extension;
-
-      switch (message.payload?.type) {
-        case 'fetch-file-with-cache-complete':
-        case 'fetch-file-with-cache-error':
-          // Forward the result of fetch-in-page requests back to the extension.
-          extensionPort?.postMessage(message);
-          break;
-        // This is sent from the backend manager running on a page
-        case 'react-devtools-required-backends':
-          const backendsToDownload = [];
-          message.payload.versions.forEach(version => {
-            if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
-              if (!IS_FIREFOX) {
-                // equivalent logic for Firefox is in prepareInjection.js
-                chrome.scripting.executeScript({
-                  target: {tabId: id},
-                  files: [`/build/react_devtools_backend_${version}.js`],
-                  world: chrome.scripting.ExecutionWorld.MAIN,
-                });
-              }
-            } else {
-              backendsToDownload.push(version);
-            }
-          });
-
-          // Request the necessary backends in the extension DevTools UI
-          // TODO: handle this message in index.js to build the UI
-          extensionPort?.postMessage({
-            payload: {
-              type: 'react-devtools-additional-backends',
-              versions: backendsToDownload,
-            },
-          });
-          break;
-      }
+  switch (message?.source) {
+    case 'devtools-page': {
+      handleDevToolsPageMessage(message);
+      break;
     }
-  }
-
-  // This is sent from the devtools page when it is ready for injecting the backend
-  if (message?.payload?.type === 'react-devtools-inject-backend-manager') {
-    // sender.tab.id from devtools page may not exist, or point to the undocked devtools window
-    // so we use the payload to get the tab id
-    const tabId = message.payload.tabId;
-
-    if (tabId && !IS_FIREFOX) {
-      // equivalent logic for Firefox is in prepareInjection.js
-      chrome.scripting.executeScript({
-        target: {tabId},
-        files: ['/build/backendManager.js'],
-        world: chrome.scripting.ExecutionWorld.MAIN,
-      });
+    case 'react-devtools-fetch-resource-content-script': {
+      handleFetchResourceContentScriptMessage(message);
+      break;
+    }
+    case 'react-devtools-backend-manager': {
+      handleBackendManagerMessage(message, sender);
+      break;
+    }
+    case 'react-devtools-hook': {
+      handleReactDevToolsHookMessage(message, sender);
     }
   }
 });
diff --git a/packages/react-devtools-extensions/src/background/injectProxy.js b/packages/react-devtools-extensions/src/background/injectProxy.js
deleted file mode 100644
index 1f38ce416c55..000000000000
--- a/packages/react-devtools-extensions/src/background/injectProxy.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* global chrome */
-
-// We keep this logic in background, because Firefox doesn't allow using these APIs
-// from extension page script
-function injectProxy(tabId: number) {
-  chrome.scripting.executeScript({
-    target: {tabId},
-    files: ['/build/proxy.js'],
-  });
-}
-
-export default injectProxy;
diff --git a/packages/react-devtools-extensions/src/background/messageHandlers.js b/packages/react-devtools-extensions/src/background/messageHandlers.js
new file mode 100644
index 000000000000..5afcd6aadcc0
--- /dev/null
+++ b/packages/react-devtools-extensions/src/background/messageHandlers.js
@@ -0,0 +1,103 @@
+/* global chrome */
+
+import setExtensionIconAndPopup from './setExtensionIconAndPopup';
+import {executeScriptInMainWorld} from './executeScript';
+
+import {EXTENSION_CONTAINED_VERSIONS} from '../utils';
+
+export function handleReactDevToolsHookMessage(message, sender) {
+  const {payload} = message;
+
+  switch (payload?.type) {
+    case 'react-renderer-attached': {
+      setExtensionIconAndPopup(payload.reactBuildType, sender.tab.id);
+
+      break;
+    }
+  }
+}
+
+export function handleBackendManagerMessage(message, sender) {
+  const {payload} = message;
+
+  switch (payload?.type) {
+    case 'require-backends': {
+      payload.versions.forEach(version => {
+        if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
+          executeScriptInMainWorld({
+            target: {tabId: sender.tab.id},
+            files: [`/build/react_devtools_backend_${version}.js`],
+          });
+        }
+      });
+
+      break;
+    }
+  }
+}
+
+export function handleDevToolsPageMessage(message) {
+  const {payload} = message;
+
+  switch (payload?.type) {
+    // Proxy this message from DevTools page to content script via chrome.tabs.sendMessage
+    case 'fetch-file-with-cache': {
+      const {
+        payload: {tabId, url},
+      } = message;
+
+      if (!tabId) {
+        throw new Error("Couldn't fetch file sources: tabId not specified");
+      }
+
+      if (!url) {
+        throw new Error("Couldn't fetch file sources: url not specified");
+      }
+
+      chrome.tabs.sendMessage(tabId, {
+        source: 'devtools-page',
+        payload: {
+          type: 'fetch-file-with-cache',
+          url,
+        },
+      });
+
+      break;
+    }
+
+    case 'inject-backend-manager': {
+      const {
+        payload: {tabId},
+      } = message;
+
+      if (!tabId) {
+        throw new Error("Couldn't inject backend manager: tabId not specified");
+      }
+
+      executeScriptInMainWorld({
+        target: {tabId},
+        files: ['/build/backendManager.js'],
+      });
+
+      break;
+    }
+  }
+}
+
+export function handleFetchResourceContentScriptMessage(message) {
+  const {payload} = message;
+
+  switch (payload?.type) {
+    case 'fetch-file-with-cache-complete':
+    case 'fetch-file-with-cache-error':
+      // Forward the result of fetch-in-page requests back to the DevTools page.
+      // We switch the source here because of inconsistency between Firefox and Chrome
+      // In Chromium this message will be propagated from content script to DevTools page
+      // For Firefox, only background script will get this message, so we need to forward it to DevTools page
+      chrome.runtime.sendMessage({
+        source: 'react-devtools-background',
+        payload,
+      });
+      break;
+  }
+}
diff --git a/packages/react-devtools-extensions/src/contentScripts/backendManager.js b/packages/react-devtools-extensions/src/contentScripts/backendManager.js
index e9d208282859..36a2ab19aa41 100644
--- a/packages/react-devtools-extensions/src/contentScripts/backendManager.js
+++ b/packages/react-devtools-extensions/src/contentScripts/backendManager.js
@@ -170,7 +170,7 @@ function updateRequiredBackends() {
     {
       source: 'react-devtools-backend-manager',
       payload: {
-        type: 'react-devtools-required-backends',
+        type: 'require-backends',
         versions: Array.from(requiredBackends),
       },
     },
diff --git a/packages/react-devtools-extensions/src/contentScripts/fileFetcher.js b/packages/react-devtools-extensions/src/contentScripts/fileFetcher.js
new file mode 100644
index 000000000000..198b4f68cd30
--- /dev/null
+++ b/packages/react-devtools-extensions/src/contentScripts/fileFetcher.js
@@ -0,0 +1,48 @@
+/* global chrome */
+
+function fetchResource(url) {
+  const reject = value => {
+    chrome.runtime.sendMessage({
+      source: 'react-devtools-fetch-resource-content-script',
+      payload: {
+        type: 'fetch-file-with-cache-error',
+        url,
+        value,
+      },
+    });
+  };
+
+  const resolve = value => {
+    chrome.runtime.sendMessage({
+      source: 'react-devtools-fetch-resource-content-script',
+      payload: {
+        type: 'fetch-file-with-cache-complete',
+        url,
+        value,
+      },
+    });
+  };
+
+  fetch(url, {cache: 'force-cache'}).then(
+    response => {
+      if (response.ok) {
+        response
+          .text()
+          .then(text => resolve(text))
+          .catch(error => reject(null));
+      } else {
+        reject(null);
+      }
+    },
+    error => reject(null),
+  );
+}
+
+chrome.runtime.onMessage.addListener(message => {
+  if (
+    message?.source === 'devtools-page' &&
+    message?.payload?.type === 'fetch-file-with-cache'
+  ) {
+    fetchResource(message.payload.url);
+  }
+});
diff --git a/packages/react-devtools-extensions/src/contentScripts/installHook.js b/packages/react-devtools-extensions/src/contentScripts/installHook.js
index c02a66342ffc..2d33cdd89036 100644
--- a/packages/react-devtools-extensions/src/contentScripts/installHook.js
+++ b/packages/react-devtools-extensions/src/contentScripts/installHook.js
@@ -10,8 +10,11 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
     function ({reactBuildType}) {
       window.postMessage(
         {
-          source: 'react-devtools-detector',
-          reactBuildType,
+          source: 'react-devtools-hook',
+          payload: {
+            type: 'react-renderer-attached',
+            reactBuildType,
+          },
         },
         '*',
       );
diff --git a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js
index 44bdb5d6df79..8f7b6405cb0a 100644
--- a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js
+++ b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js
@@ -1,9 +1,7 @@
 /* global chrome */
 
 import nullthrows from 'nullthrows';
-import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants';
-import {sessionStorageGetItem} from 'react-devtools-shared/src/storage';
-import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from '../utils';
+import {IS_FIREFOX} from '../utils';
 
 // We run scripts on the page via the service worker (background/index.js) for
 // Manifest V3 extensions (Chrome & Edge).
@@ -29,7 +27,7 @@ function injectScriptSync(src) {
   nullthrows(script.parentNode).removeChild(script);
 }
 
-let lastDetectionResult;
+let lastSentDevToolsHookMessage;
 
 // We want to detect when a renderer attaches, and notify the "background page"
 // (which is shared between tabs and can highlight the React icon).
@@ -41,73 +39,14 @@ window.addEventListener('message', function onMessage({data, source}) {
   if (source !== window || !data) {
     return;
   }
-  switch (data.source) {
-    case 'react-devtools-detector':
-      lastDetectionResult = {
-        hasDetectedReact: true,
-        reactBuildType: data.reactBuildType,
-      };
-      chrome.runtime.sendMessage(lastDetectionResult);
-      break;
-    case 'react-devtools-extension':
-      if (data.payload?.type === 'fetch-file-with-cache') {
-        const url = data.payload.url;
-
-        const reject = value => {
-          chrome.runtime.sendMessage({
-            source: 'react-devtools-content-script',
-            payload: {
-              type: 'fetch-file-with-cache-error',
-              url,
-              value,
-            },
-          });
-        };
 
-        const resolve = value => {
-          chrome.runtime.sendMessage({
-            source: 'react-devtools-content-script',
-            payload: {
-              type: 'fetch-file-with-cache-complete',
-              url,
-              value,
-            },
-          });
-        };
+  // We keep this logic here and not in `proxy.js`, because proxy content script is injected later at `document_end`
+  if (data.source === 'react-devtools-hook') {
+    const {source: messageSource, payload} = data;
+    const message = {source: messageSource, payload};
 
-        fetch(url, {cache: 'force-cache'}).then(
-          response => {
-            if (response.ok) {
-              response
-                .text()
-                .then(text => resolve(text))
-                .catch(error => reject(null));
-            } else {
-              reject(null);
-            }
-          },
-          error => reject(null),
-        );
-      }
-      break;
-    case 'react-devtools-inject-backend-manager':
-      if (IS_FIREFOX) {
-        injectScriptSync(chrome.runtime.getURL('build/backendManager.js'));
-      }
-      break;
-    case 'react-devtools-backend-manager':
-      if (IS_FIREFOX) {
-        data.payload?.versions?.forEach(version => {
-          if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
-            injectScriptSync(
-              chrome.runtime.getURL(
-                `/build/react_devtools_backend_${version}.js`,
-              ),
-            );
-          }
-        });
-      }
-      break;
+    lastSentDevToolsHookMessage = message;
+    chrome.runtime.sendMessage(message);
   }
 });
 
@@ -116,19 +55,16 @@ window.addEventListener('message', function onMessage({data, source}) {
 // replay the last detection result if the content script is active and the
 // document has been hidden and shown again.
 window.addEventListener('pageshow', function ({target}) {
-  if (!lastDetectionResult || target !== window.document) {
+  if (!lastSentDevToolsHookMessage || target !== window.document) {
     return;
   }
-  chrome.runtime.sendMessage(lastDetectionResult);
+
+  chrome.runtime.sendMessage(lastSentDevToolsHookMessage);
 });
 
 if (IS_FIREFOX) {
-  // If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
-  if (
-    sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true'
-  ) {
-    injectScriptSync(chrome.runtime.getURL('build/renderer.js'));
-  }
+  injectScriptSync(chrome.runtime.getURL('build/renderer.js'));
+
   // Inject a __REACT_DEVTOOLS_GLOBAL_HOOK__ global for React to interact with.
   // Only do this for HTML documents though, to avoid e.g. breaking syntax highlighting for XML docs.
   switch (document.contentType) {
diff --git a/packages/react-devtools-extensions/src/contentScripts/proxy.js b/packages/react-devtools-extensions/src/contentScripts/proxy.js
index 18ff976daafd..8ffeeffb2af5 100644
--- a/packages/react-devtools-extensions/src/contentScripts/proxy.js
+++ b/packages/react-devtools-extensions/src/contentScripts/proxy.js
@@ -56,19 +56,29 @@ function handleMessageFromDevtools(message) {
 }
 
 function handleMessageFromPage(event) {
-  if (event.source === window && event.data) {
+  if (event.source !== window || !event.data) {
+    return;
+  }
+
+  switch (event.data.source) {
     // This is a message from a bridge (initialized by a devtools backend)
-    if (event.data.source === 'react-devtools-bridge') {
+    case 'react-devtools-bridge': {
       backendInitialized = true;
 
       port.postMessage(event.data.payload);
+      break;
     }
 
-    // This is a message from the backend manager
-    if (event.data.source === 'react-devtools-backend-manager') {
+    // This is a message from the backend manager, which runs in ExecutionWorld.MAIN
+    // and can't use `chrome.runtime.sendMessage`
+    case 'react-devtools-backend-manager': {
+      const {source, payload} = event.data;
+
       chrome.runtime.sendMessage({
-        payload: event.data.payload,
+        source,
+        payload,
       });
+      break;
     }
   }
 }
diff --git a/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js b/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js
new file mode 100644
index 000000000000..6aee0969a322
--- /dev/null
+++ b/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js
@@ -0,0 +1,123 @@
+/* global chrome */
+
+import {__DEBUG__} from 'react-devtools-shared/src/constants';
+
+let debugIDCounter = 0;
+
+const debugLog = (...args) => {
+  if (__DEBUG__) {
+    console.log(...args);
+  }
+};
+
+const fetchFromNetworkCache = (url, resolve, reject) => {
+  // Debug ID allows us to avoid re-logging (potentially long) URL strings below,
+  // while also still associating (potentially) interleaved logs with the original request.
+  let debugID = null;
+
+  if (__DEBUG__) {
+    debugID = debugIDCounter++;
+    debugLog(`[main] fetchFromNetworkCache(${debugID})`, url);
+  }
+
+  chrome.devtools.network.getHAR(harLog => {
+    for (let i = 0; i < harLog.entries.length; i++) {
+      const entry = harLog.entries[i];
+      if (url !== entry.request.url) {
+        continue;
+      }
+
+      debugLog(
+        `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`,
+        url,
+      );
+
+      if (entry.getContent != null) {
+        entry.getContent(content => {
+          if (content) {
+            debugLog(
+              `[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
+            );
+
+            resolve(content);
+          } else {
+            debugLog(
+              `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`,
+              content,
+            );
+
+            // Edge case where getContent() returned null; fall back to fetch.
+            fetchFromPage(url, resolve, reject);
+          }
+        });
+      } else {
+        const content = entry.response.content.text;
+
+        if (content != null) {
+          debugLog(
+            `[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
+          );
+          resolve(content);
+        } else {
+          debugLog(
+            `[main] fetchFromNetworkCache(${debugID}) Invalid content returned from entry.response.content`,
+            content,
+          );
+          fetchFromPage(url, resolve, reject);
+        }
+      }
+    }
+
+    debugLog(
+      `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
+    );
+
+    // No matching URL found; fall back to fetch.
+    fetchFromPage(url, resolve, reject);
+  });
+};
+
+const fetchFromPage = async (url, resolve, reject) => {
+  debugLog('[main] fetchFromPage()', url);
+
+  function onPortMessage({payload, source}) {
+    if (source === 'react-devtools-background') {
+      switch (payload?.type) {
+        case 'fetch-file-with-cache-complete':
+          chrome.runtime.onMessage.removeListener(onPortMessage);
+          resolve(payload.value);
+          break;
+        case 'fetch-file-with-cache-error':
+          chrome.runtime.onMessage.removeListener(onPortMessage);
+          reject(payload.value);
+          break;
+      }
+    }
+  }
+
+  chrome.runtime.onMessage.addListener(onPortMessage);
+
+  chrome.runtime.sendMessage({
+    source: 'devtools-page',
+    payload: {
+      type: 'fetch-file-with-cache',
+      tabId: chrome.devtools.inspectedWindow.tabId,
+      url,
+    },
+  });
+};
+
+// Fetching files from the extension won't make use of the network cache
+// for resources that have already been loaded by the page.
+// This helper function allows the extension to request files to be fetched
+// by the content script (running in the page) to increase the likelihood of a cache hit.
+const fetchFileWithCaching = url => {
+  return new Promise((resolve, reject) => {
+    // Try fetching from the Network cache first.
+    // If DevTools was opened after the page started loading, we may have missed some requests.
+    // So fall back to a fetch() from the page and hope we get a cached response that way.
+    fetchFromNetworkCache(url, resolve, reject);
+  });
+};
+
+export default fetchFileWithCaching;
diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js
index da5b211f715d..555d35fe5d37 100644
--- a/packages/react-devtools-extensions/src/main/index.js
+++ b/packages/react-devtools-extensions/src/main/index.js
@@ -12,7 +12,6 @@ import {
 } from 'react-devtools-shared/src/storage';
 import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
 import {
-  __DEBUG__,
   LOCAL_STORAGE_SUPPORTS_PROFILING_KEY,
   LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
 } from 'react-devtools-shared/src/constants';
@@ -24,6 +23,7 @@ import {
 } from './elementSelection';
 import {startReactPolling} from './reactPolling';
 import cloneStyleTags from './cloneStyleTags';
+import fetchFileWithCaching from './fetchFileWithCaching';
 import injectBackendManager from './injectBackendManager';
 import syncSavedPreferences from './syncSavedPreferences';
 import registerEventsLogger from './registerEventsLogger';
@@ -158,117 +158,6 @@ function createBridgeAndStore() {
     }
   };
 
-  let debugIDCounter = 0;
-
-  // For some reason in Firefox, chrome.runtime.sendMessage() from a content script
-  // never reaches the chrome.runtime.onMessage event listener.
-  let fetchFileWithCaching = null;
-  if (IS_CHROME) {
-    const fetchFromNetworkCache = (url, resolve, reject) => {
-      // Debug ID allows us to avoid re-logging (potentially long) URL strings below,
-      // while also still associating (potentially) interleaved logs with the original request.
-      let debugID = null;
-
-      if (__DEBUG__) {
-        debugID = debugIDCounter++;
-        console.log(`[main] fetchFromNetworkCache(${debugID})`, url);
-      }
-
-      chrome.devtools.network.getHAR(harLog => {
-        for (let i = 0; i < harLog.entries.length; i++) {
-          const entry = harLog.entries[i];
-          if (url === entry.request.url) {
-            if (__DEBUG__) {
-              console.log(
-                `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`,
-                url,
-              );
-            }
-
-            entry.getContent(content => {
-              if (content) {
-                if (__DEBUG__) {
-                  console.log(
-                    `[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
-                  );
-                }
-
-                resolve(content);
-              } else {
-                if (__DEBUG__) {
-                  console.log(
-                    `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`,
-                    content,
-                  );
-                }
-
-                // Edge case where getContent() returned null; fall back to fetch.
-                fetchFromPage(url, resolve, reject);
-              }
-            });
-
-            return;
-          }
-        }
-
-        if (__DEBUG__) {
-          console.log(
-            `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
-          );
-        }
-
-        // No matching URL found; fall back to fetch.
-        fetchFromPage(url, resolve, reject);
-      });
-    };
-
-    const fetchFromPage = (url, resolve, reject) => {
-      if (__DEBUG__) {
-        console.log('[main] fetchFromPage()', url);
-      }
-
-      function onPortMessage({payload, source}) {
-        if (source === 'react-devtools-content-script') {
-          switch (payload?.type) {
-            case 'fetch-file-with-cache-complete':
-              chrome.runtime.onMessage.removeListener(onPortMessage);
-              resolve(payload.value);
-              break;
-            case 'fetch-file-with-cache-error':
-              chrome.runtime.onMessage.removeListener(onPortMessage);
-              reject(payload.value);
-              break;
-          }
-        }
-      }
-
-      chrome.runtime.onMessage.addListener(onPortMessage);
-
-      chrome.devtools.inspectedWindow.eval(`
-              window.postMessage({
-                source: 'react-devtools-extension',
-                payload: {
-                  type: 'fetch-file-with-cache',
-                  url: "${url}",
-                },
-              });
-            `);
-    };
-
-    // Fetching files from the extension won't make use of the network cache
-    // for resources that have already been loaded by the page.
-    // This helper function allows the extension to request files to be fetched
-    // by the content script (running in the page) to increase the likelihood of a cache hit.
-    fetchFileWithCaching = url => {
-      return new Promise((resolve, reject) => {
-        // Try fetching from the Network cache first.
-        // If DevTools was opened after the page started loading, we may have missed some requests.
-        // So fall back to a fetch() from the page and hope we get a cached response that way.
-        fetchFromNetworkCache(url, resolve, reject);
-      });
-    };
-  }
-
   // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration.
   const hookNamesModuleLoaderFunction = () =>
     import(
diff --git a/packages/react-devtools-extensions/src/main/injectBackendManager.js b/packages/react-devtools-extensions/src/main/injectBackendManager.js
index 0b5c16aa157c..e70cb95a9a5c 100644
--- a/packages/react-devtools-extensions/src/main/injectBackendManager.js
+++ b/packages/react-devtools-extensions/src/main/injectBackendManager.js
@@ -1,27 +1,10 @@
 /* global chrome */
 
-import {IS_FIREFOX} from '../utils';
-
 function injectBackendManager(tabId) {
-  if (IS_FIREFOX) {
-    // Firefox does not support executing script in ExecutionWorld.MAIN from content script.
-    // see prepareInjection.js
-    chrome.devtools.inspectedWindow.eval(
-      `window.postMessage({ source: 'react-devtools-inject-backend-manager' }, '*');`,
-      function (response, evalError) {
-        if (evalError) {
-          console.error(evalError);
-        }
-      },
-    );
-
-    return;
-  }
-
   chrome.runtime.sendMessage({
-    source: 'react-devtools-main',
+    source: 'devtools-page',
     payload: {
-      type: 'react-devtools-inject-backend-manager',
+      type: 'inject-backend-manager',
       tabId,
     },
   });
diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js
index 558d2e93e969..e6c3ca931bb5 100644
--- a/packages/react-devtools-extensions/webpack.config.js
+++ b/packages/react-devtools-extensions/webpack.config.js
@@ -57,6 +57,7 @@ module.exports = {
   entry: {
     background: './src/background/index.js',
     backendManager: './src/contentScripts/backendManager.js',
+    fileFetcher: './src/contentScripts/fileFetcher.js',
     main: './src/main/index.js',
     panel: './src/panel.js',
     proxy: './src/contentScripts/proxy.js',
