Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inspect stack not returning all ancestors #693

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
118 changes: 118 additions & 0 deletions packages/vscode-extension/lib/inspector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const { Dimensions, findNodeHandle } = require("react-native");

function requireGetInspectorDataForViewAtPoint() {
return require("react-native/Libraries/Inspector/getInspectorDataForViewAtPoint");
km1chno marked this conversation as resolved.
Show resolved Hide resolved
}

function getInspectorDataForInstance(node) {
const renderers = Array.from(window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.values());
if (!renderers) {
return {};
}
for (const renderer of renderers) {
if (renderer.rendererConfig?.getInspectorDataForInstance) {
const data = renderer.rendererConfig.getInspectorDataForInstance(node);
return data ?? {};
}
}
return {};
};

function createStackElement(
frame, name, source
) {
return {
componentName: name,
source: {
fileName: source.fileName,
line0Based: source.lineNumber - 1,
column0Based: source.columnNumber - 1,
},
frame,
};
};

// Returns an array of promises which resolve to elements of the components hierarchy stack.
function traverseComponentsTreeUp(startNode) {
const stackPromises = [];
let node = startNode;

// Optimization: we break after reaching fiber node corresponding to OffscreenComponent (with tag 22).
// https://github.com/facebook/react/blob/c3570b158d087eb4e3ee5748c4bd9360045c8a26/packages/react-reconciler/src/ReactWorkTags.js#L62
while (node && node.tag !== 22) {
const data = getInspectorDataForInstance(node);

data.hierarchy?.length && stackPromises.push(new Promise((resolve, reject) => {
const item = data.hierarchy[data.hierarchy.length - 1];
const inspectorData = item.getInspectorData((arg) => findNodeHandle(arg));
km1chno marked this conversation as resolved.
Show resolved Hide resolved

try {
inspectorData.measure((_x, _y, viewWidth, viewHeight, pageX, pageY) => {
const stackElementFrame = {
x: pageX,
y: pageY,
width: viewWidth,
height: viewHeight
};

stackElement = (inspectorData.source) ?
createStackElement(stackElementFrame, item.name, inspectorData.source) : undefined;

resolve(stackElement);
});
} catch (e) {
reject(e);
}
}));

node = node.return;
}

return stackPromises;
};

export function getInspectorDataForCoordinates(mainContainerRef, x, y, requestStack, callback) {
const getInspectorDataForViewAtPoint = requireGetInspectorDataForViewAtPoint();
const { width: screenWidth, height: screenHeight } = Dimensions.get("screen");

function scaleFrame(frame) {
return {
x: frame.x / screenWidth,
y: frame.y / screenHeight,
width: frame.width / screenWidth,
height: frame.height / screenHeight
};
};

getInspectorDataForViewAtPoint(
mainContainerRef.current,
x * screenWidth,
y * screenHeight,
(viewData) => {
const frame = viewData.frame;
const scaledFrame = scaleFrame({
x: frame.left,
y: frame.top,
width: frame.width,
height: frame.height
});

if (!requestStack) {
callback({ frame: scaledFrame });
}

Promise.all(traverseComponentsTreeUp(viewData.closestInstance, []))
.then((stack) => stack.filter(Boolean))
.then((stack) =>
stack.map((stackElement) => ({
...stackElement,
frame: scaleFrame(stackElement.frame)
}))
).then((scaledStack) =>
callback({
frame: scaledFrame,
stack: scaledStack
}));
}
);
};
79 changes: 13 additions & 66 deletions packages/vscode-extension/lib/wrapper.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"use no memo";

import { getInspectorDataForCoordinates } from "./inspector";
const { useContext, useState, useEffect, useRef, useCallback } = require("react");
const {
LogBox,
AppRegistry,
RootTagContext,
View,
Dimensions,
Linking,
findNodeHandle,
} = require("react-native");
const { storybookPreview } = require("./storybook_helper");

Expand All @@ -26,9 +25,6 @@ const InternalImports = {
};

const RNInternals = {
get getInspectorDataForViewAtPoint() {
return require("react-native/Libraries/Inspector/getInspectorDataForViewAtPoint");
},
get SceneTracker() {
return require("react-native/Libraries/Utilities/SceneTracker");
},
Expand Down Expand Up @@ -179,69 +175,20 @@ export function AppWrapper({ children, initialProps, ..._rest }) {
devtoolsAgent,
"RNIDE_inspect",
(payload) => {
const getInspectorDataForViewAtPoint = RNInternals.getInspectorDataForViewAtPoint;
const { width, height } = Dimensions.get("screen");

getInspectorDataForViewAtPoint(
mainContainerRef.current,
payload.x * width,
payload.y * height,
(viewData) => {
const frame = viewData.frame;
const scaledFrame = {
x: frame.left / width,
y: frame.top / height,
width: frame.width / width,
height: frame.height / height,
};
let stackPromise = Promise.resolve(undefined);
if (payload.requestStack) {
stackPromise = Promise.all(
viewData.hierarchy.reverse().map((item) => {
const inspectorData = item.getInspectorData((arg) => findNodeHandle(arg));
const framePromise = new Promise((resolve, reject) => {
try {
inspectorData.measure((_x, _y, viewWidth, viewHeight, pageX, pageY) => {
resolve({
x: pageX / width,
y: pageY / height,
width: viewWidth / width,
height: viewHeight / height,
});
});
} catch (e) {
reject(e);
}
});
const { id, x, y, requestStack } = payload;

return framePromise
.catch(() => undefined)
.then((frame) => {
return inspectorData.source
? {
componentName: item.name,
source: {
fileName: inspectorData.source.fileName,
line0Based: inspectorData.source.lineNumber - 1,
column0Based: inspectorData.source.columnNumber - 1,
},
frame,
}
: undefined;
});
})
).then((stack) => stack?.filter(Boolean));
}
stackPromise.then((stack) => {
devtoolsAgent._bridge.send("RNIDE_inspectData", {
id: payload.id,
frame: scaledFrame,
stack: stack,
});
getInspectorDataForCoordinates(
mainContainerRef,
x,
y,
requestStack,
(inspectorData) => {
devtoolsAgent._bridge.send("RNIDE_inspectData", {
id,
...inspectorData
});
}
);
},
});
},
[mainContainerRef]
);

Expand Down