mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[DevTools] upgrade to Manifest V3 (#25145)
## Summary resolves #24522 To upgrade to Manifest V3, one of the biggest issue is that we are no longer allowed to add a script element with code in textContent so that it would run synchronously. It's necessary for us because we need to inject a global hook for react reconciler to detect whether devtools exist. To do that, we'll leverage a new API `chrome.scripting.registerContentScripts` in V3. Particularly, we rely on the "world" option (added in Chrome v102 [commit](https://chromium.googlesource.com/chromium/src/+/e5ad3451c17b21341b0b9019b074801c44c92c9f)) to run it in the "main world" on the page. This PR also renames a few content script files so that it's easier to tell them apart from other extension scripts and understand the purpose of each of them. Manifest V3 is not yet ready for Firefox, so we need to keep some code for compatibility. ## How did you test this change? `yarn build:chrome && yarn test:chrome` `yarn build:edge && yarn test:edge` `yarn build:firefox && yarn test:firefox`
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Chrome Developer Tools.",
|
||||
"version": "4.26.1",
|
||||
"version_name": "4.26.1",
|
||||
"minimum_chrome_version": "60",
|
||||
"minimum_chrome_version": "88",
|
||||
"icons": {
|
||||
"16": "icons/16-production.png",
|
||||
"32": "icons/32-production.png",
|
||||
"48": "icons/48-production.png",
|
||||
"128": "icons/128-production.png"
|
||||
},
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_icon": {
|
||||
"16": "icons/16-disabled.png",
|
||||
"32": "icons/32-disabled.png",
|
||||
@@ -21,23 +21,34 @@
|
||||
"default_popup": "popups/disabled.html"
|
||||
},
|
||||
"devtools_page": "main.html",
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
{
|
||||
"resources": [
|
||||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/proxy.js",
|
||||
"build/renderer.js",
|
||||
"build/installHook.js"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"extension_ids": []
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
"build/background.js"
|
||||
],
|
||||
"persistent": false
|
||||
"service_worker": "build/background.js"
|
||||
},
|
||||
"permissions": [
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
"storage",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
@@ -45,7 +56,7 @@
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"build/injectGlobalHook.js"
|
||||
"build/prepareInjection.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
|
||||
"version": "4.26.1",
|
||||
"version_name": "4.26.1",
|
||||
"minimum_chrome_version": "60",
|
||||
"minimum_chrome_version": "88",
|
||||
"icons": {
|
||||
"16": "icons/16-production.png",
|
||||
"32": "icons/32-production.png",
|
||||
"48": "icons/48-production.png",
|
||||
"128": "icons/128-production.png"
|
||||
},
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_icon": {
|
||||
"16": "icons/16-disabled.png",
|
||||
"32": "icons/32-disabled.png",
|
||||
@@ -21,23 +21,34 @@
|
||||
"default_popup": "popups/disabled.html"
|
||||
},
|
||||
"devtools_page": "main.html",
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
{
|
||||
"resources": [
|
||||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/proxy.js",
|
||||
"build/renderer.js",
|
||||
"build/installHook.js"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"extension_ids": []
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
"build/background.js"
|
||||
],
|
||||
"persistent": false
|
||||
"service_worker": "build/background.js"
|
||||
},
|
||||
"permissions": [
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
"storage",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
@@ -45,7 +56,7 @@
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"build/injectGlobalHook.js"
|
||||
"build/prepareInjection.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
"build/proxy.js",
|
||||
"build/renderer.js",
|
||||
"build/installHook.js"
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
@@ -50,7 +52,7 @@
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"build/injectGlobalHook.js"
|
||||
"build/prepareInjection.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
|
||||
+43
-18
@@ -2,9 +2,33 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {IS_FIREFOX} from './utils';
|
||||
|
||||
const ports = {};
|
||||
|
||||
const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
|
||||
if (!IS_FIREFOX) {
|
||||
// Manifest V3 method of injecting content scripts (not yet supported in Firefox)
|
||||
// Note: the "world" option in registerContentScripts is only available in Chrome v102+
|
||||
// It's critical since it allows us to directly run scripts on the "main" world on the page
|
||||
// "document_start" allows it to run before the page's scripts
|
||||
// so the hook can be detected by react reconciler
|
||||
chrome.scripting.registerContentScripts([
|
||||
{
|
||||
id: 'hook',
|
||||
matches: ['<all_urls>'],
|
||||
js: ['build/installHook.js'],
|
||||
runAt: 'document_start',
|
||||
world: chrome.scripting.ExecutionWorld.MAIN,
|
||||
},
|
||||
{
|
||||
id: 'renderer',
|
||||
matches: ['<all_urls>'],
|
||||
js: ['build/renderer.js'],
|
||||
runAt: 'document_start',
|
||||
world: chrome.scripting.ExecutionWorld.MAIN,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
chrome.runtime.onConnect.addListener(function(port) {
|
||||
let tab = null;
|
||||
@@ -12,7 +36,7 @@ chrome.runtime.onConnect.addListener(function(port) {
|
||||
if (isNumeric(port.name)) {
|
||||
tab = port.name;
|
||||
name = 'devtools';
|
||||
installContentScript(+port.name);
|
||||
installProxy(+port.name);
|
||||
} else {
|
||||
tab = port.sender.tab.id;
|
||||
name = 'content-script';
|
||||
@@ -35,12 +59,15 @@ function isNumeric(str: string): boolean {
|
||||
return +str + '' === str;
|
||||
}
|
||||
|
||||
function installContentScript(tabId: number) {
|
||||
chrome.tabs.executeScript(
|
||||
tabId,
|
||||
{file: '/build/contentScript.js'},
|
||||
function() {},
|
||||
);
|
||||
function installProxy(tabId: number) {
|
||||
if (IS_FIREFOX) {
|
||||
chrome.tabs.executeScript(tabId, {file: '/build/proxy.js'}, function() {});
|
||||
} else {
|
||||
chrome.scripting.executeScript({
|
||||
target: {tabId: tabId},
|
||||
files: ['/build/proxy.js'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function doublePipe(one, two) {
|
||||
@@ -63,18 +90,19 @@ function doublePipe(one, two) {
|
||||
}
|
||||
|
||||
function setIconAndPopup(reactBuildType, tabId) {
|
||||
chrome.browserAction.setIcon({
|
||||
const action = IS_FIREFOX ? chrome.browserAction : chrome.action;
|
||||
action.setIcon({
|
||||
tabId: tabId,
|
||||
path: {
|
||||
'16': 'icons/16-' + reactBuildType + '.png',
|
||||
'32': 'icons/32-' + reactBuildType + '.png',
|
||||
'48': 'icons/48-' + reactBuildType + '.png',
|
||||
'128': 'icons/128-' + reactBuildType + '.png',
|
||||
'16': chrome.runtime.getURL(`icons/16-${reactBuildType}.png`),
|
||||
'32': chrome.runtime.getURL(`icons/32-${reactBuildType}.png`),
|
||||
'48': chrome.runtime.getURL(`icons/48-${reactBuildType}.png`),
|
||||
'128': chrome.runtime.getURL(`icons/128-${reactBuildType}.png`),
|
||||
},
|
||||
});
|
||||
chrome.browserAction.setPopup({
|
||||
action.setPopup({
|
||||
tabId: tabId,
|
||||
popup: 'popups/' + reactBuildType + '.html',
|
||||
popup: chrome.runtime.getURL(`popups/${reactBuildType}.html`),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,9 +151,6 @@ chrome.runtime.onMessage.addListener((request, sender) => {
|
||||
// This is sent from the hook content script.
|
||||
// It tells us a renderer has attached.
|
||||
if (request.hasDetectedReact) {
|
||||
// We use browserAction instead of pageAction because this lets us
|
||||
// display a custom default popup when React is *not* detected.
|
||||
// It is specified in the manifest.
|
||||
setIconAndPopup(request.reactBuildType, id);
|
||||
} else {
|
||||
switch (request.payload?.type) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import {installHook} from 'react-devtools-shared/src/hook';
|
||||
|
||||
// avoid double execution
|
||||
if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
|
||||
installHook(window);
|
||||
|
||||
// detect react
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function({
|
||||
reactBuildType,
|
||||
}) {
|
||||
window.postMessage(
|
||||
{
|
||||
source: 'react-devtools-detector',
|
||||
reactBuildType,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
});
|
||||
|
||||
// save native values
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
|
||||
}
|
||||
+32
-42
@@ -1,11 +1,19 @@
|
||||
/* global chrome */
|
||||
|
||||
import nullthrows from 'nullthrows';
|
||||
import {installHook} from 'react-devtools-shared/src/hook';
|
||||
import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants';
|
||||
import {sessionStorageGetItem} from 'react-devtools-shared/src/storage';
|
||||
import {IS_FIREFOX} from '../utils';
|
||||
|
||||
function injectScriptSync(src) {
|
||||
let code = '';
|
||||
const request = new XMLHttpRequest();
|
||||
request.addEventListener('load', function() {
|
||||
code = this.responseText;
|
||||
});
|
||||
request.open('GET', src, false);
|
||||
request.send();
|
||||
|
||||
function injectCode(code) {
|
||||
const script = document.createElement('script');
|
||||
script.textContent = code;
|
||||
|
||||
@@ -15,6 +23,15 @@ function injectCode(code) {
|
||||
nullthrows(script.parentNode).removeChild(script);
|
||||
}
|
||||
|
||||
function injectScriptAsync(src) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = function() {
|
||||
script.remove();
|
||||
};
|
||||
nullthrows(document.documentElement).appendChild(script);
|
||||
}
|
||||
|
||||
let lastDetectionResult;
|
||||
|
||||
// We want to detect when a renderer attaches, and notify the "background page"
|
||||
@@ -77,10 +94,9 @@ window.addEventListener('message', function onMessage({data, source}) {
|
||||
}
|
||||
break;
|
||||
case 'react-devtools-inject-backend':
|
||||
const script = document.createElement('script');
|
||||
script.src = chrome.runtime.getURL('build/react_devtools_backend.js');
|
||||
document.documentElement.appendChild(script);
|
||||
script.parentNode.removeChild(script);
|
||||
injectScriptAsync(
|
||||
chrome.runtime.getURL('build/react_devtools_backend.js'),
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -96,38 +112,18 @@ window.addEventListener('pageshow', function({target}) {
|
||||
chrome.runtime.sendMessage(lastDetectionResult);
|
||||
});
|
||||
|
||||
const detectReact = `
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function({reactBuildType}) {
|
||||
window.postMessage({
|
||||
source: 'react-devtools-detector',
|
||||
reactBuildType,
|
||||
}, '*');
|
||||
});
|
||||
`;
|
||||
const saveNativeValues = `
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
|
||||
`;
|
||||
// We create a "sync" script tag to page to inject the global hook on Manifest V2 extensions.
|
||||
// To comply with the new security policy in V3, we use chrome.scripting.registerContentScripts instead (see background.js).
|
||||
// However, the new API only works for Chrome v102+.
|
||||
// We insert a "async" script tag as a fallback for older versions.
|
||||
// It has known issues if JS on the page is faster than the extension.
|
||||
// Users will see a notice in components tab when that happens (see <Tree>).
|
||||
// For Firefox, V3 is not ready, so sync injection is still the best approach.
|
||||
const injectScript = IS_FIREFOX ? injectScriptSync : injectScriptAsync;
|
||||
|
||||
// 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') {
|
||||
const rendererURL = chrome.runtime.getURL('build/renderer.js');
|
||||
let rendererCode;
|
||||
|
||||
// We need to inject in time to catch the initial mount.
|
||||
// This means we need to synchronously read the renderer code itself,
|
||||
// and synchronously inject it into the page.
|
||||
// There are very few ways to actually do this.
|
||||
// This seems to be the best approach.
|
||||
const request = new XMLHttpRequest();
|
||||
request.addEventListener('load', function() {
|
||||
rendererCode = this.responseText;
|
||||
});
|
||||
request.open('GET', rendererURL, false);
|
||||
request.send();
|
||||
injectCode(rendererCode);
|
||||
injectScript(chrome.runtime.getURL('build/renderer.js'));
|
||||
}
|
||||
|
||||
// Inject a __REACT_DEVTOOLS_GLOBAL_HOOK__ global for React to interact with.
|
||||
@@ -138,13 +134,7 @@ if (sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
switch (document.contentType) {
|
||||
case 'text/html':
|
||||
case 'application/xhtml+xml': {
|
||||
injectCode(
|
||||
';(' +
|
||||
installHook.toString() +
|
||||
'(window))' +
|
||||
saveNativeValues +
|
||||
detectReact,
|
||||
);
|
||||
injectScript(chrome.runtime.getURL('build/installHook.js'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* In order to support reload-and-profile functionality, the renderer needs to be injected before any other scripts.
|
||||
* Since it is a complex file (with imports) we can't just toString() it like we do with the hook itself,
|
||||
* So this entry point (one of the web_accessible_resources) provides a way to eagerly inject it.
|
||||
* The hook will look for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach an injected renderer early.
|
||||
* The normal case (not a reload-and-profile) will not make use of this entry point though.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {attach} from 'react-devtools-shared/src/backend/renderer';
|
||||
import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants';
|
||||
import {sessionStorageGetItem} from 'react-devtools-shared/src/storage';
|
||||
|
||||
if (
|
||||
sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true' &&
|
||||
!window.hasOwnProperty('__REACT_DEVTOOLS_ATTACH__')
|
||||
) {
|
||||
Object.defineProperty(
|
||||
window,
|
||||
'__REACT_DEVTOOLS_ATTACH__',
|
||||
({
|
||||
enumerable: false,
|
||||
// This property needs to be configurable to allow third-party integrations
|
||||
// to attach their own renderer. Note that using third-party integrations
|
||||
// is not officially supported. Use at your own risk.
|
||||
configurable: true,
|
||||
get() {
|
||||
return attach;
|
||||
},
|
||||
}: Object),
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* In order to support reload-and-profile functionality, the renderer needs to be injected before any other scripts.
|
||||
* Since it is a complex file (with imports) we can't just toString() it like we do with the hook itself,
|
||||
* So this entry point (one of the web_accessible_resources) provides a way to eagerly inject it.
|
||||
* The hook will look for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach an injected renderer early.
|
||||
* The normal case (not a reload-and-profile) will not make use of this entry point though.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {attach} from 'react-devtools-shared/src/backend/renderer';
|
||||
|
||||
Object.defineProperty(
|
||||
window,
|
||||
'__REACT_DEVTOOLS_ATTACH__',
|
||||
({
|
||||
enumerable: false,
|
||||
// This property needs to be configurable to allow third-party integrations
|
||||
// to attach their own renderer. Note that using third-party integrations
|
||||
// is not officially supported. Use at your own risk.
|
||||
configurable: true,
|
||||
get() {
|
||||
return attach;
|
||||
},
|
||||
}: Object),
|
||||
);
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
|
||||
import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools';
|
||||
|
||||
const IS_EDGE = navigator.userAgent.indexOf('Edg') >= 0;
|
||||
const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
|
||||
const IS_CHROME = IS_EDGE === false && IS_FIREFOX === false;
|
||||
export const IS_EDGE = navigator.userAgent.indexOf('Edg') >= 0;
|
||||
export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
|
||||
export const IS_CHROME = IS_EDGE === false && IS_FIREFOX === false;
|
||||
|
||||
export type BrowserName = 'Chrome' | 'Firefox' | 'Edge';
|
||||
|
||||
|
||||
@@ -51,11 +51,12 @@ module.exports = {
|
||||
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
|
||||
entry: {
|
||||
background: './src/background.js',
|
||||
contentScript: './src/contentScript.js',
|
||||
injectGlobalHook: './src/injectGlobalHook.js',
|
||||
main: './src/main.js',
|
||||
panel: './src/panel.js',
|
||||
renderer: './src/renderer.js',
|
||||
proxy: './src/contentScripts/proxy.js',
|
||||
prepareInjection: './src/contentScripts/prepareInjection.js',
|
||||
renderer: './src/contentScripts/renderer.js',
|
||||
installHook: './src/contentScripts/installHook.js',
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/build',
|
||||
|
||||
@@ -79,3 +79,22 @@
|
||||
.WarningIcon {
|
||||
color: var(--color-console-warning-icon);
|
||||
}
|
||||
|
||||
.ZeroElementsNotice {
|
||||
padding-left: 1em;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.5s forwards;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.Link {
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
@@ -344,6 +344,22 @@ export default function Tree(props: Props): React.Node {
|
||||
clearErrorsAndWarningsAPI({bridge, store});
|
||||
};
|
||||
|
||||
const zeroElementsNotice = (
|
||||
<div className={styles.ZeroElementsNotice}>
|
||||
<p>Loading React Element Tree...</p>
|
||||
<p>
|
||||
If this seems stuck, please follow the{' '}
|
||||
<a
|
||||
className={styles.Link}
|
||||
href="https://github.com/facebook/react/tree/main/packages/react-devtools#the-issue-with-chrome-v101-and-earlier-versions"
|
||||
target="_blank">
|
||||
troubleshooting instructions
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<TreeFocusedContext.Provider value={treeFocused}>
|
||||
<div className={styles.Tree} ref={treeRef}>
|
||||
@@ -398,32 +414,36 @@ export default function Tree(props: Props): React.Node {
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={styles.AutoSizerWrapper}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyPress={handleKeyPress}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={focusTargetRef}
|
||||
tabIndex={0}>
|
||||
<AutoSizer>
|
||||
{({height, width}) => (
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
height={height}
|
||||
innerElementType={InnerElementType}
|
||||
itemCount={numElements}
|
||||
itemData={itemData}
|
||||
itemKey={itemKey}
|
||||
itemSize={lineHeight}
|
||||
ref={listCallbackRef}
|
||||
width={width}>
|
||||
{Element}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
{numElements === 0 ? (
|
||||
zeroElementsNotice
|
||||
) : (
|
||||
<div
|
||||
className={styles.AutoSizerWrapper}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyPress={handleKeyPress}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={focusTargetRef}
|
||||
tabIndex={0}>
|
||||
<AutoSizer>
|
||||
{({height, width}) => (
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
height={height}
|
||||
innerElementType={InnerElementType}
|
||||
itemCount={numElements}
|
||||
itemData={itemData}
|
||||
itemKey={itemKey}
|
||||
itemSize={lineHeight}
|
||||
ref={listCallbackRef}
|
||||
width={width}>
|
||||
{Element}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TreeFocusedContext.Provider>
|
||||
);
|
||||
|
||||
@@ -105,8 +105,13 @@ Or you could develop with a local HTTP server like [`serve`](https://www.npmjs.c
|
||||
|
||||
**If your app is inside an iframe, a Chrome extension, React Native, or in another unusual environment**, try [the standalone version instead](https://github.com/facebook/react/tree/main/packages/react-devtools). Chrome apps are currently not inspectable.
|
||||
|
||||
**If your Components tab is empty, refer to "Chrome v101 and earlier" section below**, please read the "the issue with Chrome v101 and earlier versions" part below.
|
||||
|
||||
**If you still have issues** please [report them](https://github.com/facebook/react/issues/new?labels=Component:%20Developer%20Tools). Don't forget to specify your OS, browser version, extension version, and the exact instructions to reproduce the issue with a screenshot.
|
||||
|
||||
### The Issue with Chrome v101 and earlier
|
||||
As we migrate to a Chrome Extension Manifest V3, we start to use a new method to hook the DevTools with the inspected page. This new method is more secure, but relies on a new API that's only supported in Chrome v102+. For Chrome v101 or earlier, we use a fallback method, which can cause malfunctions (e.g. empty React component tab) if the JS resources on your page is loaded from cache. Please upgrade to Chrome v102+ to avoid this issue.
|
||||
|
||||
## Local development
|
||||
The standalone DevTools app can be built and tested from source following the instructions below.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user