mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e5287287aa
Stacked on https://github.com/facebook/react/pull/28351, please review only the last commit. Top-level description of the approach: 1. Once user selects an element from the tree, frontend asks backend to return the inspected element, this is where we simulate an error happening in `render` function of the component and then we parse the error stack. As an improvement, we should probably migrate from custom implementation of error stack parser to `error-stack-parser` from npm. 2. When frontend receives the inspected element and this object is being propagated, we create a Promise for symbolicated source, which is then passed down to all components, which are using `source`. 3. These components use `use` hook for this promise and are wrapped in Suspense. Caching: 1. For browser extension, we cache Promises based on requested resource + key + column, also added use of `chrome.devtools.inspectedWindow.getResource` API. 2. For standalone case (RN), we cache based on requested resource url, we cache the content of it.
143 lines
4.2 KiB
JavaScript
143 lines
4.2 KiB
JavaScript
/* global chrome */
|
|
|
|
import {normalizeUrl} from 'react-devtools-shared/src/utils';
|
|
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,
|
|
},
|
|
});
|
|
};
|
|
|
|
// 1. Check if resource is available via chrome.devtools.inspectedWindow.getResources
|
|
// 2. Check if resource was loaded previously and available in network cache via chrome.devtools.network.getHAR
|
|
// 3. Fallback to fetching directly from the page context (from backend)
|
|
async function fetchFileWithCaching(url: string): Promise<string> {
|
|
if (__IS_CHROME__ || __IS_EDGE__) {
|
|
const resources = await new Promise(resolve =>
|
|
chrome.devtools.inspectedWindow.getResources(r => resolve(r)),
|
|
);
|
|
|
|
const normalizedReferenceURL = normalizeUrl(url);
|
|
const resource = resources.find(r => r.url === normalizedReferenceURL);
|
|
|
|
if (resource != null) {
|
|
const content = await new Promise(resolve =>
|
|
resource.getContent(fetchedContent => resolve(fetchedContent)),
|
|
);
|
|
|
|
if (content) {
|
|
return content;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|