Skip to content

Commit 90bd228

Browse files
author
Brian Vaughn
committedSep 10, 2021
Replaced network.onRequestFinished() caching with network.getHAR()
This avoids the need for us to redundantly (pre) cache JavaScript content. In the event that the HAR log doesn't contain a match, we'll fall back to fetching from the Network (and hoping for a cache hit from that layer).
1 parent 0fd195f commit 90bd228

File tree

1 file changed

+116
-65
lines changed
  • packages/react-devtools-extensions/src

1 file changed

+116
-65
lines changed
 

‎packages/react-devtools-extensions/src/main.js

+116-65
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,13 @@ import {
1919
localStorageSetItem,
2020
} from 'react-devtools-shared/src/storage';
2121
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
22+
import {__DEBUG__} from 'react-devtools-shared/src/constants';
2223

2324
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
2425
'React::DevTools::supportsProfiling';
2526

2627
const isChrome = getBrowserName() === 'Chrome';
2728

28-
const cachedNetworkEvents = new Map();
29-
30-
// Cache JavaScript resources as the page loads them.
31-
// This helps avoid unnecessary duplicate requests when hook names are parsed.
32-
// Responses with a Vary: 'Origin' might not match future requests.
33-
// This lets us avoid a possible (expensive) cache miss.
34-
// For more info see: github.com/facebook/react/pull/22198
35-
chrome.devtools.network.onRequestFinished.addListener(
36-
function onRequestFinished(event) {
37-
if (event.request.method === 'GET') {
38-
switch (event.response.content.mimeType) {
39-
case 'application/javascript':
40-
case 'application/x-javascript':
41-
case 'text/javascript':
42-
cachedNetworkEvents.set(event.request.url, event);
43-
break;
44-
}
45-
}
46-
},
47-
);
48-
4929
let panelCreated = false;
5030

5131
// The renderer interface can't read saved component filters directly,
@@ -233,56 +213,113 @@ function createPanelIfReactLoaded() {
233213
}
234214
};
235215

216+
let debugIDCounter = 0;
217+
236218
// For some reason in Firefox, chrome.runtime.sendMessage() from a content script
237219
// never reaches the chrome.runtime.onMessage event listener.
238220
let fetchFileWithCaching = null;
239221
if (isChrome) {
240-
// Fetching files from the extension won't make use of the network cache
241-
// for resources that have already been loaded by the page.
242-
// This helper function allows the extension to request files to be fetched
243-
// by the content script (running in the page) to increase the likelihood of a cache hit.
244-
fetchFileWithCaching = url => {
245-
const event = cachedNetworkEvents.get(url);
246-
if (event != null) {
247-
// If this resource has already been cached locally,
248-
// skip the network queue (which might not be a cache hit anyway)
249-
// and just use the cached response.
250-
return new Promise(resolve => {
251-
event.getContent(content => resolve(content));
252-
});
222+
const fetchFromNetworkCache = (url, resolve, reject) => {
223+
// Debug ID allows us to avoid re-logging (potentially long) URL strings below,
224+
// while also still associating (potentially) interleaved logs with the original request.
225+
let debugID = null;
226+
227+
if (__DEBUG__) {
228+
debugID = debugIDCounter++;
229+
console.log(`[main] fetchFromNetworkCache(${debugID})`, url);
253230
}
254231

255-
// If DevTools was opened after the page started loading,
256-
// we may have missed some requests.
257-
// So fall back to a fetch() and hope we get a cached response.
258-
259-
return new Promise((resolve, reject) => {
260-
function onPortMessage({payload, source}) {
261-
if (source === 'react-devtools-content-script') {
262-
switch (payload?.type) {
263-
case 'fetch-file-with-cache-complete':
264-
chrome.runtime.onMessage.removeListener(onPortMessage);
265-
resolve(payload.value);
266-
break;
267-
case 'fetch-file-with-cache-error':
268-
chrome.runtime.onMessage.removeListener(onPortMessage);
269-
reject(payload.value);
270-
break;
232+
chrome.devtools.network.getHAR(harLog => {
233+
for (let i = 0; i < harLog.entries.length; i++) {
234+
const entry = harLog.entries[i];
235+
if (url === entry.request.url) {
236+
if (__DEBUG__) {
237+
console.log(
238+
`[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`,
239+
url,
240+
);
271241
}
242+
243+
entry.getContent(content => {
244+
if (content) {
245+
if (__DEBUG__) {
246+
console.log(
247+
`[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
248+
);
249+
}
250+
251+
resolve(content);
252+
} else {
253+
if (__DEBUG__) {
254+
console.log(
255+
`[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`,
256+
content,
257+
);
258+
}
259+
260+
// Edge case where getContent() returned null; fall back to fetch.
261+
fetchFromPage(url, resolve);
262+
}
263+
});
264+
265+
return;
272266
}
273267
}
274268

275-
chrome.runtime.onMessage.addListener(onPortMessage);
269+
if (__DEBUG__) {
270+
console.log(
271+
`[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
272+
);
273+
}
276274

277-
chrome.devtools.inspectedWindow.eval(`
278-
window.postMessage({
279-
source: 'react-devtools-extension',
280-
payload: {
281-
type: 'fetch-file-with-cache',
282-
url: "${url}",
283-
},
284-
});
285-
`);
275+
// No matching URL found; fall back to fetch.
276+
fetchFromPage(url, resolve);
277+
});
278+
};
279+
280+
const fetchFromPage = (url, resolve, reject) => {
281+
if (__DEBUG__) {
282+
console.log('[main] fetchFromPage()', url);
283+
}
284+
285+
function onPortMessage({payload, source}) {
286+
if (source === 'react-devtools-content-script') {
287+
switch (payload?.type) {
288+
case 'fetch-file-with-cache-complete':
289+
chrome.runtime.onMessage.removeListener(onPortMessage);
290+
resolve(payload.value);
291+
break;
292+
case 'fetch-file-with-cache-error':
293+
chrome.runtime.onMessage.removeListener(onPortMessage);
294+
reject(payload.value);
295+
break;
296+
}
297+
}
298+
}
299+
300+
chrome.runtime.onMessage.addListener(onPortMessage);
301+
302+
chrome.devtools.inspectedWindow.eval(`
303+
window.postMessage({
304+
source: 'react-devtools-extension',
305+
payload: {
306+
type: 'fetch-file-with-cache',
307+
url: "${url}",
308+
},
309+
});
310+
`);
311+
};
312+
313+
// Fetching files from the extension won't make use of the network cache
314+
// for resources that have already been loaded by the page.
315+
// This helper function allows the extension to request files to be fetched
316+
// by the content script (running in the page) to increase the likelihood of a cache hit.
317+
fetchFileWithCaching = url => {
318+
return new Promise((resolve, reject) => {
319+
// Try fetching from the Network cache first.
320+
// If DevTools was opened after the page started loading, we may have missed some requests.
321+
// So fall back to a fetch() from the page and hope we get a cached response that way.
322+
fetchFromNetworkCache(url, resolve, reject);
286323
});
287324
};
288325
}
@@ -441,9 +478,6 @@ function createPanelIfReactLoaded() {
441478

442479
// Re-initialize DevTools panel when a new page is loaded.
443480
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
444-
// Clear cached requests when a new page is opened.
445-
cachedNetworkEvents.clear();
446-
447481
// Re-initialize saved filters on navigation,
448482
// since global values stored on window get reset in this case.
449483
syncSavedPreferences();
@@ -460,8 +494,7 @@ function createPanelIfReactLoaded() {
460494

461495
// Load (or reload) the DevTools extension when the user navigates to a new page.
462496
function checkPageForReact() {
463-
// Clear cached requests when a new page is opened.
464-
cachedNetworkEvents.clear();
497+
cachedNetworkRequests.clear();
465498

466499
syncSavedPreferences();
467500
createPanelIfReactLoaded();
@@ -472,7 +505,25 @@ chrome.devtools.network.onNavigated.addListener(checkPageForReact);
472505
// Check to see if React has loaded once per second in case React is added
473506
// after page load
474507
const loadCheckInterval = setInterval(function() {
508+
cachedNetworkRequests.clear();
509+
475510
createPanelIfReactLoaded();
476511
}, 1000);
477512

478513
createPanelIfReactLoaded();
514+
515+
const cachedNetworkRequests = new Map();
516+
517+
chrome.devtools.network.onRequestFinished.addListener(
518+
function onRequestFinished(request) {
519+
if (request.request.method === 'GET') {
520+
switch (request.response.content.mimeType) {
521+
case 'application/javascript':
522+
case 'application/x-javascript':
523+
case 'text/javascript':
524+
cachedNetworkRequests.set(request.request.url, request);
525+
break;
526+
}
527+
}
528+
},
529+
);

0 commit comments

Comments
 (0)