Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 64 additions & 30 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,44 @@ function createModel(response: Response, model: any): any {
return model;
}

const mightHaveStaticConstructor = /\bclass\b.*\bstatic\b/;

function getInferredFunctionApproximate(code: string): () => void {
let slicedCode;
if (code.startsWith('Object.defineProperty(')) {
slicedCode = code.slice('Object.defineProperty('.length);
} else if (code.startsWith('(')) {
slicedCode = code.slice(1);
} else {
slicedCode = code;
}
if (slicedCode.startsWith('async function')) {
const idx = slicedCode.indexOf('(', 14);
if (idx !== -1) {
const name = slicedCode.slice(14, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':async function(){}})')[
name
];
}
} else if (slicedCode.startsWith('function')) {
const idx = slicedCode.indexOf('(', 8);
if (idx !== -1) {
const name = slicedCode.slice(8, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':function(){}})')[name];
}
} else if (slicedCode.startsWith('class')) {
const idx = slicedCode.indexOf('{', 5);
if (idx !== -1) {
const name = slicedCode.slice(5, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[name];
}
}
return function () {};
}

function parseModelString(
response: Response,
parentObject: Object,
Expand Down Expand Up @@ -2158,41 +2196,37 @@ function parseModelString(
// This should not compile to eval() because then it has local scope access.
const code = value.slice(2);
try {
// eslint-disable-next-line no-eval
return (0, eval)(code);
// If this might be a class constructor with a static initializer or
// static constructor then don't eval it. It might cause unexpected
// side-effects. Instead, fallback to parsing out the function type
// and name.
if (!mightHaveStaticConstructor.test(code)) {
// eslint-disable-next-line no-eval
return (0, eval)(code);
}
} catch (x) {
// We currently use this to express functions so we fail parsing it,
// let's just return a blank function as a place holder.
if (code.startsWith('(async function')) {
const idx = code.indexOf('(', 15);
if (idx !== -1) {
const name = code.slice(15, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)(
'({' + JSON.stringify(name) + ':async function(){}})',
)[name];
}
} else if (code.startsWith('(function')) {
const idx = code.indexOf('(', 9);
if (idx !== -1) {
const name = code.slice(9, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)(
'({' + JSON.stringify(name) + ':function(){}})',
)[name];
}
} else if (code.startsWith('(class')) {
const idx = code.indexOf('{', 6);
// Fallthrough to fallback case.
}
// We currently use this to express functions so we fail parsing it,
// let's just return a blank function as a place holder.
let fn;
try {
fn = getInferredFunctionApproximate(code);
if (code.startsWith('Object.defineProperty(')) {
const DESCRIPTOR = ',"name",{value:"';
const idx = code.lastIndexOf(DESCRIPTOR);
if (idx !== -1) {
const name = code.slice(6, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[
name
];
const name = JSON.parse(
code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
);
// $FlowFixMe[cannot-write]
Object.defineProperty(fn, 'name', {value: name});
}
}
return function () {};
} catch (_) {
fn = function () {};
}
return fn;
}
// Fallthrough
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3239,6 +3239,8 @@ describe('ReactFlight', () => {
}
Object.defineProperty(MyClass.prototype, 'y', {enumerable: true});

Object.defineProperty(MyClass, 'name', {value: 'MyClassName'});

function ServerComponent() {
console.log('hi', {
prop: 123,
Expand Down Expand Up @@ -3341,6 +3343,7 @@ describe('ReactFlight', () => {
const instance = mockConsoleLog.mock.calls[0][1].instance;
expect(typeof Class).toBe('function');
expect(Class.prototype.constructor).toBe(Class);
expect(Class.name).toBe('MyClassName');
expect(instance instanceof Class).toBe(true);
expect(Object.getPrototypeOf(instance)).toBe(Class.prototype);
expect(instance.x).toBe(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "render()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
Expand Down Expand Up @@ -903,6 +904,7 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
Expand Down Expand Up @@ -943,6 +945,7 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4818,6 +4818,7 @@ export function attach(
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
id: instance.id,
key: fiber.key,
env: null,
type: getElementTypeForFiber(fiber),
};
} else {
Expand All @@ -4826,6 +4827,7 @@ export function attach(
displayName: componentInfo.name || 'Anonymous',
id: instance.id,
key: componentInfo.key == null ? null : componentInfo.key,
env: componentInfo.env == null ? null : componentInfo.env,
type: ElementTypeVirtual,
};
}
Expand Down Expand Up @@ -5451,6 +5453,8 @@ export function attach(
// List of owners
owners,

env: null,

rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,
Expand Down Expand Up @@ -5554,6 +5558,8 @@ export function attach(
// List of owners
owners,

env: componentInfo.env == null ? null : componentInfo.env,

rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,
Expand Down
3 changes: 3 additions & 0 deletions packages/react-devtools-shared/src/backend/legacy/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ export function attach(
displayName: getData(owner).displayName || 'Unknown',
id: getID(owner),
key: element.key,
env: null,
type: getElementType(owner),
});
if (owner._currentElement) {
Expand Down Expand Up @@ -857,6 +858,8 @@ export function attach(
// List of owners
owners,

env: null,

rootType: null,
rendererPackageName: null,
rendererVersion: null,
Expand Down
5 changes: 5 additions & 0 deletions packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
env: null | string,
type: ElementType,
};

Expand Down Expand Up @@ -301,6 +302,10 @@ export type InspectedElement = {

// List of owners
owners: Array<SerializedElement> | null,

// Environment name that this component executed in or null for the client
env: string | null,

source: ReactFunctionLocation | null,

type: ElementType,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-devtools-shared/src/backendAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export function convertInspectedElementBackendToFrontend(
id,
type,
owners,
env,
source,
context,
hooks,
Expand Down Expand Up @@ -299,6 +300,7 @@ export function convertInspectedElementBackendToFrontend(
owners === null
? null
: owners.map(backendToFrontendSerializedElementMapper),
env,
context: hydrateHelper(context),
hooks: hydrateHelper(hooks),
props: hydrateHelper(props),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ import styles from './ElementBadges.css';

type Props = {
hocDisplayNames: Array<string> | null,
environmentName: string | null,
compiledWithForget: boolean,
className?: string,
};

export default function ElementBadges({
compiledWithForget,
environmentName,
hocDisplayNames,
className = '',
}: Props): React.Node {
if (
!compiledWithForget &&
(hocDisplayNames == null || hocDisplayNames.length === 0)
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
environmentName == null
) {
return null;
}
Expand All @@ -36,6 +39,8 @@ export default function ElementBadges({
<div className={`${styles.Root} ${className}`}>
{compiledWithForget && <ForgetBadge indexable={false} />}

{environmentName != null ? <Badge>{environmentName}</Badge> : null}

{hocDisplayNames != null && hocDisplayNames.length > 0 && (
<Badge>{hocDisplayNames[0]}</Badge>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,28 @@ function SuspendedByRow({
</Button>
{isOpen && (
<div className={styles.CollapsableContent}>
{showIOStack && <StackTraceView stack={ioInfo.stack} />}
{showIOStack && (
<StackTraceView
stack={ioInfo.stack}
environmentName={
ioOwner !== null && ioOwner.env === ioInfo.env
? null
: ioInfo.env
}
/>
)}
{(showIOStack || !showAwaitStack) &&
ioOwner !== null &&
ioOwner.id !== inspectedElement.id ? (
<OwnerView
key={ioOwner.id}
displayName={ioOwner.displayName || 'Anonymous'}
environmentName={
ioOwner.env === inspectedElement.env &&
ioOwner.env === ioInfo.env
? null
: ioOwner.env
}
hocDisplayNames={ioOwner.hocDisplayNames}
compiledWithForget={ioOwner.compiledWithForget}
id={ioOwner.id}
Expand All @@ -168,12 +183,25 @@ function SuspendedByRow({
<>
<div className={styles.SmallHeader}>awaited at:</div>
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
<StackTraceView stack={asyncInfo.stack} />
<StackTraceView
stack={asyncInfo.stack}
environmentName={
asyncOwner !== null && asyncOwner.env === asyncInfo.env
? null
: asyncInfo.env
}
/>
)}
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
<OwnerView
key={asyncOwner.id}
displayName={asyncOwner.displayName || 'Anonymous'}
environmentName={
asyncOwner.env === inspectedElement.env &&
asyncOwner.env === asyncInfo.env
? null
: asyncOwner.env
}
hocDisplayNames={asyncOwner.hocDisplayNames}
compiledWithForget={asyncOwner.compiledWithForget}
id={asyncOwner.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ export default function InspectedElementView({
key={owner.id}
displayName={owner.displayName || 'Anonymous'}
hocDisplayNames={owner.hocDisplayNames}
environmentName={
inspectedElement.env === owner.env ? null : owner.env
}
compiledWithForget={owner.compiledWithForget}
id={owner.id}
isInStore={store.containsElement(owner.id)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import styles from './OwnerView.css';
type OwnerViewProps = {
displayName: string,
hocDisplayNames: Array<string> | null,
environmentName: string | null,
compiledWithForget: boolean,
id: number,
isInStore: boolean,
};

export default function OwnerView({
displayName,
environmentName,
hocDisplayNames,
compiledWithForget,
id,
Expand Down Expand Up @@ -65,6 +67,7 @@ export default function OwnerView({
<ElementBadges
hocDisplayNames={hocDisplayNames}
compiledWithForget={compiledWithForget}
environmentName={environmentName}
/>
</span>
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) {

<ElementBadges
hocDisplayNames={owner.hocDisplayNames}
environmentName={owner.env}
compiledWithForget={owner.compiledWithForget}
className={styles.BadgesBlock}
/>
Expand Down Expand Up @@ -268,6 +269,7 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) {

<ElementBadges
hocDisplayNames={hocDisplayNames}
environmentName={owner.env}
compiledWithForget={compiledWithForget}
className={styles.BadgesBlock}
/>
Expand Down
Loading
Loading