From 2c094c766d9ec60daa39e7bb864f398509f4ea89 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 9 Sep 2021 19:22:00 -0400 Subject: [PATCH] Replaced network.onRequestFinished() caching with network.getHAR() I'm not sure this is a net improvement though, as it seems the cached harLog events will often return 'null' content from entry.getContent(). Based on my testing, it seems like the only way around this is to eagerly fetch the content- by calling getContent() from network.onRequestFinished()- which seems to defeat the entire purpose of using the getHAR() method. --- .../react-devtools-extensions/src/main.js | 143 ++++++++++-------- 1 file changed, 77 insertions(+), 66 deletions(-) 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(); }