Skip to content

Commit f65ac7b

Browse files
authored
[DevTools] Make function inspection instant (#30786)
I noticed that there is a delay due to the inspection being split into one part that gets the attribute and another eval that does the inspection. This is a bit hacky and uses temporary global names that are leaky. The timeout was presumably to ensure that the first step had fully propagated but it's slow. As we've learned, it can be throttled, and it isn't a guarantee either way. Instead, we can just consolidate these into a single operation that by-passes the bridge and goes straight to the renderer interface from the eval. I did the same for the viewElementSource helper even though that's not currently in use since #28471 but I think we probably should return to that technique when it's available since it's more reliable than the throw - at least in Chrome. I'm not sure about the status of React Native here. In Firefox, inspecting a function with source maps doesn't seem to work. It doesn't jump to original code.
1 parent 1b74782 commit f65ac7b

6 files changed

Lines changed: 90 additions & 64 deletions

File tree

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

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
setBrowserSelectionFromReact,
2222
setReactSelectionFromBrowser,
2323
} from './elementSelection';
24+
import {viewAttributeSource} from './sourceSelection';
25+
2426
import {startReactPolling} from './reactPolling';
2527
import cloneStyleTags from './cloneStyleTags';
2628
import fetchFileWithCaching from './fetchFileWithCaching';
@@ -113,19 +115,7 @@ function createBridgeAndStore() {
113115
const viewAttributeSourceFunction = (id, path) => {
114116
const rendererID = store.getRendererIDForElement(id);
115117
if (rendererID != null) {
116-
// Ask the renderer interface to find the specified attribute,
117-
// and store it as a global variable on the window.
118-
bridge.send('viewAttributeSource', {id, path, rendererID});
119-
120-
setTimeout(() => {
121-
// Ask Chrome to display the location of the attribute,
122-
// assuming the renderer found a match.
123-
chrome.devtools.inspectedWindow.eval(`
124-
if (window.$attribute != null) {
125-
inspect(window.$attribute);
126-
}
127-
`);
128-
}, 100);
118+
viewAttributeSource(rendererID, id, path);
129119
}
130120
};
131121

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/* global chrome */
2+
3+
export function viewAttributeSource(rendererID, elementID, path) {
4+
chrome.devtools.inspectedWindow.eval(
5+
'{' + // The outer block is important because it means we can declare local variables.
6+
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
7+
JSON.stringify(rendererID) +
8+
');' +
9+
'if (renderer) {' +
10+
' const value = renderer.getElementAttributeByPath(' +
11+
JSON.stringify(elementID) +
12+
',' +
13+
JSON.stringify(path) +
14+
');' +
15+
' if (value) {' +
16+
' inspect(value);' +
17+
' true;' +
18+
' } else {' +
19+
' false;' +
20+
' }' +
21+
'} else {' +
22+
' false;' +
23+
'}' +
24+
'}',
25+
(didInspect, evalError) => {
26+
if (evalError) {
27+
console.error(evalError);
28+
}
29+
},
30+
);
31+
}
32+
33+
export function viewElementSource(rendererID, elementID) {
34+
chrome.devtools.inspectedWindow.eval(
35+
'{' + // The outer block is important because it means we can declare local variables.
36+
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
37+
JSON.stringify(rendererID) +
38+
');' +
39+
'if (renderer) {' +
40+
' const value = renderer.getElementSourceFunctionById(' +
41+
JSON.stringify(elementID) +
42+
');' +
43+
' if (value) {' +
44+
' inspect(value);' +
45+
' true;' +
46+
' } else {' +
47+
' false;' +
48+
' }' +
49+
'} else {' +
50+
' false;' +
51+
'}' +
52+
'}',
53+
(didInspect, evalError) => {
54+
if (evalError) {
55+
console.error(evalError);
56+
}
57+
},
58+
);
59+
}

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,6 @@ export default class Agent extends EventEmitter<{
220220
this.updateConsolePatchSettings,
221221
);
222222
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
223-
bridge.addListener('viewAttributeSource', this.viewAttributeSource);
224-
bridge.addListener('viewElementSource', this.viewElementSource);
225223

226224
// Temporarily support older standalone front-ends sending commands to newer embedded backends.
227225
// We do this because React Native embeds the React DevTools backend,
@@ -816,24 +814,6 @@ export default class Agent extends EventEmitter<{
816814
}
817815
};
818816

819-
viewAttributeSource: CopyElementParams => void = ({id, path, rendererID}) => {
820-
const renderer = this._rendererInterfaces[rendererID];
821-
if (renderer == null) {
822-
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
823-
} else {
824-
renderer.prepareViewAttributeSource(id, path);
825-
}
826-
};
827-
828-
viewElementSource: ElementAndRendererID => void = ({id, rendererID}) => {
829-
const renderer = this._rendererInterfaces[rendererID];
830-
if (renderer == null) {
831-
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
832-
} else {
833-
renderer.prepareViewElementSource(id);
834-
}
835-
};
836-
837817
onTraceUpdates: (nodes: Set<HostInstance>) => void = nodes => {
838818
this.emit('traceUpdates', nodes);
839819
};

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3874,27 +3874,28 @@ export function attach(
38743874

38753875
// END copied code
38763876

3877-
function prepareViewAttributeSource(
3877+
function getElementAttributeByPath(
38783878
id: number,
38793879
path: Array<string | number>,
3880-
): void {
3880+
): mixed {
38813881
if (isMostRecentlyInspectedElement(id)) {
3882-
window.$attribute = getInObject(
3882+
return getInObject(
38833883
((mostRecentlyInspectedElement: any): InspectedElement),
38843884
path,
38853885
);
38863886
}
3887+
return undefined;
38873888
}
38883889

3889-
function prepareViewElementSource(id: number): void {
3890+
function getElementSourceFunctionById(id: number): null | Function {
38903891
const devtoolsInstance = idToDevToolsInstanceMap.get(id);
38913892
if (devtoolsInstance === undefined) {
38923893
console.warn(`Could not find DevToolsInstance with id "${id}"`);
3893-
return;
3894+
return null;
38943895
}
38953896
if (devtoolsInstance.kind !== FIBER_INSTANCE) {
38963897
// TODO: Handle VirtualInstance.
3897-
return;
3898+
return null;
38983899
}
38993900
const fiber = devtoolsInstance.data;
39003901

@@ -3906,21 +3907,16 @@ export function attach(
39063907
case IncompleteFunctionComponent:
39073908
case IndeterminateComponent:
39083909
case FunctionComponent:
3909-
global.$type = type;
3910-
break;
3910+
return type;
39113911
case ForwardRef:
3912-
global.$type = type.render;
3913-
break;
3912+
return type.render;
39143913
case MemoComponent:
39153914
case SimpleMemoComponent:
3916-
global.$type =
3917-
elementType != null && elementType.type != null
3918-
? elementType.type
3919-
: type;
3920-
break;
3915+
return elementType != null && elementType.type != null
3916+
? elementType.type
3917+
: type;
39213918
default:
3922-
global.$type = null;
3923-
break;
3919+
return null;
39243920
}
39253921
}
39263922

@@ -5727,8 +5723,8 @@ export function attach(
57275723
inspectElement,
57285724
logElementToConsole,
57295725
patchConsoleForStrictMode,
5730-
prepareViewAttributeSource,
5731-
prepareViewElementSource,
5726+
getElementAttributeByPath,
5727+
getElementSourceFunctionById,
57325728
overrideError,
57335729
overrideSuspense,
57345730
overrideValueAtPath,

packages/react-devtools-shared/src/backend/legacy/renderer.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -907,30 +907,31 @@ export function attach(
907907
}
908908
}
909909

910-
function prepareViewAttributeSource(
910+
function getElementAttributeByPath(
911911
id: number,
912912
path: Array<string | number>,
913-
): void {
913+
): mixed {
914914
const inspectedElement = inspectElementRaw(id);
915915
if (inspectedElement !== null) {
916-
window.$attribute = getInObject(inspectedElement, path);
916+
return getInObject(inspectedElement, path);
917917
}
918+
return undefined;
918919
}
919920

920-
function prepareViewElementSource(id: number): void {
921+
function getElementSourceFunctionById(id: number): null | Function {
921922
const internalInstance = idToInternalInstanceMap.get(id);
922923
if (internalInstance == null) {
923924
console.warn(`Could not find instance with id "${id}"`);
924-
return;
925+
return null;
925926
}
926927

927928
const element = internalInstance._currentElement;
928929
if (element == null) {
929930
console.warn(`Could not find element with id "${id}"`);
930-
return;
931+
return null;
931932
}
932933

933-
global.$type = element.type;
934+
return element.type;
934935
}
935936

936937
function deletePath(
@@ -1141,8 +1142,8 @@ export function attach(
11411142
overrideValueAtPath,
11421143
renamePath,
11431144
patchConsoleForStrictMode,
1144-
prepareViewAttributeSource,
1145-
prepareViewElementSource,
1145+
getElementAttributeByPath,
1146+
getElementSourceFunctionById,
11461147
renderer,
11471148
setTraceUpdatesEnabled,
11481149
setTrackedPath,

packages/react-devtools-shared/src/backend/types.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,11 @@ export type RendererInterface = {
394394
value: any,
395395
) => void,
396396
patchConsoleForStrictMode: () => void,
397-
prepareViewAttributeSource: (
397+
getElementAttributeByPath: (
398398
id: number,
399399
path: Array<string | number>,
400-
) => void,
401-
prepareViewElementSource: (id: number) => void,
400+
) => mixed,
401+
getElementSourceFunctionById: (id: number) => null | Function,
402402
renamePath: (
403403
type: Type,
404404
id: number,

0 commit comments

Comments
 (0)