diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index 30318c1249e8a..63349d2920743 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -19,33 +19,13 @@ import { localStorageSetItem, } from 'react-devtools-shared/src/storage'; import DevTools from 'react-devtools-shared/src/devtools/views/DevTools'; +import {__DEBUG__} from 'react-devtools-shared/src/constants'; const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY = 'React::DevTools::supportsProfiling'; const isChrome = getBrowserName() === 'Chrome'; -const cachedNetworkEvents = new Map(); - -// Cache JavaScript resources as the page loads them. -// This helps avoid unnecessary duplicate requests when hook names are parsed. -// Responses with a Vary: 'Origin' might not match future requests. -// This lets us avoid a possible (expensive) cache miss. -// For more info see: github.com/facebook/react/pull/22198 -chrome.devtools.network.onRequestFinished.addListener( - function onRequestFinished(event) { - if (event.request.method === 'GET') { - switch (event.response.content.mimeType) { - case 'application/javascript': - case 'application/x-javascript': - case 'text/javascript': - cachedNetworkEvents.set(event.request.url, event); - break; - } - } - }, -); - let panelCreated = false; // The renderer interface can't read saved component filters directly, @@ -237,52 +217,89 @@ function createPanelIfReactLoaded() { // never reaches the chrome.runtime.onMessage event listener. let fetchFileWithCaching = null; if (isChrome) { - // 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 => { - const event = cachedNetworkEvents.get(url); - if (event != null) { - // If this resource has already been cached locally, - // skip the network queue (which might not be a cache hit anyway) - // and just use the cached response. - return new Promise(resolve => { - event.getContent(content => resolve(content)); - }); + const fetchFromNetworkCache = (url, resolve, reject) => { + if (__DEBUG__) { + console.log('[main] fetchFromNetworkCache()', url); } - // If DevTools was opened after the page started loading, - // we may have missed some requests. - // So fall back to a fetch() and hope we get a cached response. + chrome.devtools.network.getHAR(harLog => { + for (let i = 0; i < harLog.entries.length; i++) { + const entry = harLog.entries[i]; + if (url === entry.request.url) { + entry.getContent(content => { + if (content) { + resolve(content); + } else { + if (__DEBUG__) { + console.log( + '[main] fetchFromNetworkCache() Invalid content returned by getContent():', + content, + ); + } + + // Edge case where getContent() returned null; fall back to fetch. + fetchFromPage(url, resolve); + } + }); + + return; + } - return new Promise((resolve, reject) => { - 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; - } + if (__DEBUG__) { + console.log( + '[main] fetchFromNetworkCache() No cached request found in getHAR()', + ); } + + // No matching URL found; fall back to fetch. + fetchFromPage(url, resolve); } + }); + }; - chrome.runtime.onMessage.addListener(onPortMessage); + const fetchFromPage = (url, resolve, reject) => { + if (__DEBUG__) { + console.log('[main] fetchFromPage()', url); + } - chrome.devtools.inspectedWindow.eval(` - window.postMessage({ - source: 'react-devtools-extension', - payload: { - type: 'fetch-file-with-cache', - url: "${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); }); }; } @@ -441,9 +458,6 @@ function createPanelIfReactLoaded() { // Re-initialize DevTools panel when a new page is loaded. chrome.devtools.network.onNavigated.addListener(function onNavigated() { - // Clear cached requests when a new page is opened. - cachedNetworkEvents.clear(); - // Re-initialize saved filters on navigation, // since global values stored on window get reset in this case. syncSavedPreferences(); @@ -460,9 +474,6 @@ function createPanelIfReactLoaded() { // Load (or reload) the DevTools extension when the user navigates to a new page. function checkPageForReact() { - // Clear cached requests when a new page is opened. - cachedNetworkEvents.clear(); - syncSavedPreferences(); createPanelIfReactLoaded(); }