From 0e7f122d9a6bdd9c6c798ef9488112bc0ac2371e Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Fri, 22 Sep 2023 19:29:11 +0100 Subject: [PATCH] refactor[devtools/extension]: refactored messaging logic across different parts of the extension --- .../dynamicallyInjectContentScripts.js | 15 +++ .../src/background/executeScript.js | 58 +++++++++ .../src/background/index.js | 84 ++++--------- .../src/background/injectProxy.js | 12 -- .../src/background/messageHandlers.js | 101 ++++++++++++++++ .../src/contentScripts/backendManager.js | 2 +- .../src/contentScripts/fileFetcher.js | 51 ++++++++ .../src/contentScripts/installHook.js | 7 +- .../src/contentScripts/prepareInjection.js | 89 ++------------ .../src/contentScripts/proxy.js | 20 +++- .../src/main/fetchFileWithCaching.js | 106 ++++++++++++++++ .../src/main/index.js | 113 +----------------- .../src/main/injectBackendManager.js | 21 +--- .../webpack.config.js | 1 + 14 files changed, 389 insertions(+), 291 deletions(-) create mode 100644 packages/react-devtools-extensions/src/background/executeScript.js delete mode 100644 packages/react-devtools-extensions/src/background/injectProxy.js create mode 100644 packages/react-devtools-extensions/src/background/messageHandlers.js create mode 100644 packages/react-devtools-extensions/src/contentScripts/fileFetcher.js create mode 100644 packages/react-devtools-extensions/src/main/fetchFileWithCaching.js diff --git a/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js b/packages/react-devtools-extensions/src/background/dynamicallyInjectContentScripts.js index 08db763bdbaa7..45fe394974004 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: [''], + 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: [''], + 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 0000000000000..af48726be028c --- /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 element is created, + // so we add the script to 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 35d57dc6dbcee..b25eb53033193 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 1f38ce416c556..0000000000000 --- 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 0000000000000..a02b231fc9317 --- /dev/null +++ b/packages/react-devtools-extensions/src/background/messageHandlers.js @@ -0,0 +1,101 @@ +/* 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); + } + } +} + +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 e9d2082828599..36a2ab19aa411 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 0000000000000..675a0d2ae86ae --- /dev/null +++ b/packages/react-devtools-extensions/src/contentScripts/fileFetcher.js @@ -0,0 +1,51 @@ +/* global chrome */ + +function fetchResource(url) { + const reject = value => { + console.log('fetch reject'); + chrome.runtime.sendMessage({ + source: 'react-devtools-fetch-resource-content-script', + payload: { + type: 'fetch-file-with-cache-error', + url, + value, + }, + }); + }; + + const resolve = value => { + console.log('fetch resolve'); + 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' + ) { + console.log('fetch start'); + 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 c02a66342ffc2..2d33cdd89036d 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 44bdb5d6df792..294969415b6b8 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,13 @@ 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, payload} = data; + lastSentDevToolsHookMessage = {source, 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; + chrome.runtime.sendMessage({source, payload}); } }); @@ -116,19 +54,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 18ff976daafdd..8ffeeffb2af53 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 0000000000000..637dd83e601cc --- /dev/null +++ b/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js @@ -0,0 +1,106 @@ +/* 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) { + debugLog( + `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`, + url, + ); + + 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); + } + }); + + return; + } + } + + 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 da5b211f715d6..555d35fe5d372 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 0b5c16aa157c1..e70cb95a9a5ca 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 558d2e93e9699..e6c3ca931bb51 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',