Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
7398334948 [compiler] Infer render helpers for additional validation
We currently assume that any functions passes as props may be event handlers or effect functions, and thus don't check for side effects such as mutating globals. However, if a prop is a function that returns JSX that is a sure sign that it's actually a render helper and not an event handler or effect function. So we now emit a `Render` effect for any prop that is a JSX-returning function, triggering all of our render validation.

This required a small fix to InferTypes: we weren't correctly populating the `return` type of function types during unification. I also improved the printing of types so we can see the inferred return types.
2025-08-06 23:18:14 -07:00
42 changed files with 438 additions and 1073 deletions

View File

@@ -9,7 +9,6 @@ import {
import * as t from '@babel/types';
import * as TypeErrors from './TypeErrors';
import {assertExhaustive} from '../Utils/utils';
import {FlowType} from './FlowTypes';
export const DEBUG = false;
@@ -197,6 +196,8 @@ export function makeVariableId(id: number): VariableId {
return id as VariableId;
}
import {inspect} from 'util';
import {FlowType} from './FlowTypes';
export function printConcrete<T>(
type: ConcreteType<T>,
printType: (_: T) => string,
@@ -240,7 +241,7 @@ export function printConcrete<T>(
case 'Generic':
return `T${type.id}`;
case 'Object': {
const name = `Object [${[...type.members.keys()].map(key => JSON.stringify(key)).join(', ')}]`;
const name = `Object ${inspect([...type.members.keys()])}`;
return `${name}`;
}
case 'Tuple': {

View File

@@ -892,7 +892,8 @@ export function printType(type: Type): string {
if (type.kind === 'Object' && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
} else if (type.kind === 'Function' && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
const returnType = printType(type.return);
return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? `: ${returnType}` : ''}`;
} else {
return `:T${type.kind}`;
}

View File

@@ -26,6 +26,7 @@ import {
InstructionKind,
InstructionValue,
isArrayType,
isJsxType,
isMapType,
isPrimitiveType,
isRefOrRefValue,
@@ -1841,6 +1842,23 @@ function computeSignatureForInstruction(
});
}
}
for (const prop of value.props) {
if (
prop.kind === 'JsxAttribute' &&
prop.place.identifier.type.kind === 'Function' &&
(isJsxType(prop.place.identifier.type.return) ||
(prop.place.identifier.type.return.kind === 'Phi' &&
prop.place.identifier.type.return.operands.some(operand =>
isJsxType(operand),
)))
) {
// Any props which return jsx are assumed to be called during render
effects.push({
kind: 'Render',
place: prop.place,
});
}
}
}
break;
}

View File

@@ -1810,70 +1810,41 @@ function codegenInstructionValue(
case 'MethodCall': {
const isHook =
getHookKind(cx.env, instrValue.property.identifier) != null;
/**
* We need to check if the property was memoized. If it has, we should reconstruct the
* MemberExpression.
*/
let memberExpr: t.Expression;
const tmp = cx.temp.get(instrValue.property.identifier.declarationId);
if (tmp != null && tmp.type === 'Identifier') {
/**
* We can't reconstruct the MemberExpression from just the identifier, so we work around
* this by allowing an Identifier here.
*/
memberExpr = tmp;
} else if (tmp != null) {
memberExpr = convertValueToExpression(tmp);
} else {
memberExpr = codegenPlaceToExpression(cx, instrValue.property);
}
// Reconstruct the MemberExpression if we previously saw an Identifier.
if (memberExpr.type === 'Identifier') {
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env,
memberExpr,
args,
instrValue.loc,
isHook,
);
} else {
CompilerError.invariant(
t.isMemberExpression(memberExpr) ||
t.isOptionalMemberExpression(memberExpr),
{
reason:
'[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. ' +
`Got a \`${memberExpr.type}\``,
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
},
);
CompilerError.invariant(
t.isNodesEquivalent(
memberExpr.object,
codegenPlaceToExpression(cx, instrValue.receiver),
),
{
reason:
'[Codegen] Internal error: Forget should always generate MethodCall::property ' +
'as a MemberExpression of MethodCall::receiver',
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
},
);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env,
memberExpr,
args,
instrValue.loc,
isHook,
);
}
const memberExpr = codegenPlaceToExpression(cx, instrValue.property);
CompilerError.invariant(
t.isMemberExpression(memberExpr) ||
t.isOptionalMemberExpression(memberExpr),
{
reason:
'[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. ' +
`Got a \`${memberExpr.type}\``,
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
},
);
CompilerError.invariant(
t.isNodesEquivalent(
memberExpr.object,
codegenPlaceToExpression(cx, instrValue.receiver),
),
{
reason:
'[Codegen] Internal error: Forget should always generate MethodCall::property ' +
'as a MemberExpression of MethodCall::receiver',
description: null,
loc: memberExpr.loc ?? null,
suggestions: null,
},
);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env,
memberExpr,
args,
instrValue.loc,
isHook,
);
break;
}
case 'NewExpression': {

View File

@@ -777,6 +777,15 @@ class Unifier {
return {kind: 'Phi', operands: type.operands.map(o => this.get(o))};
}
if (type.kind === 'Function') {
return {
kind: 'Function',
isConstructor: type.isConstructor,
shapeId: type.shapeId,
return: this.get(type.return),
};
}
return type;
}
}

View File

@@ -1,69 +0,0 @@
## Input
```javascript
import {PrimitiveBox} from 'shared-runtime';
function Component({value, realmax}) {
const box = new PrimitiveBox(value);
const maxValue = Math.max(box.get(), realmax);
// ^^^^^^^^^ should not be separated into static call
return <div>{maxValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42, realmax: 100}],
isComponent: true,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { PrimitiveBox } from "shared-runtime";
function Component(t0) {
const $ = _c(6);
const { value, realmax } = t0;
let t1;
let t2;
let t3;
if ($[0] !== value) {
const box = new PrimitiveBox(value);
t1 = Math;
t2 = t1.max;
t3 = box.get();
$[0] = value;
$[1] = t1;
$[2] = t2;
$[3] = t3;
} else {
t1 = $[1];
t2 = $[2];
t3 = $[3];
}
const maxValue = t2(t3, realmax);
let t4;
if ($[4] !== maxValue) {
t4 = <div>{maxValue}</div>;
$[4] = maxValue;
$[5] = t4;
} else {
t4 = $[5];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42, realmax: 100 }],
isComponent: true,
};
```
### Eval output
(kind: ok) <div>100</div>

View File

@@ -1,14 +0,0 @@
import {PrimitiveBox} from 'shared-runtime';
function Component({value, realmax}) {
const box = new PrimitiveBox(value);
const maxValue = Math.max(box.get(), realmax);
// ^^^^^^^^^ should not be separated into static call
return <div>{maxValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42, realmax: 100}],
isComponent: true,
};

View File

@@ -1,85 +0,0 @@
## Input
```javascript
function foo() {
return {
bar() {
return 3.14;
},
};
}
const YearsAndMonthsSince = () => {
const diff = foo();
const months = Math.floor(diff.bar());
return <>{months}</>;
};
export const FIXTURE_ENTRYPOINT = {
fn: YearsAndMonthsSince,
params: [],
isComponent: true,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function foo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
bar() {
return 3.14;
},
};
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
const YearsAndMonthsSince = () => {
const $ = _c(4);
let t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const diff = foo();
t0 = Math;
t1 = t0.floor;
t2 = diff.bar();
$[0] = t0;
$[1] = t1;
$[2] = t2;
} else {
t0 = $[0];
t1 = $[1];
t2 = $[2];
}
const months = t1(t2);
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <>{months}</>;
$[3] = t3;
} else {
t3 = $[3];
}
return t3;
};
export const FIXTURE_ENTRYPOINT = {
fn: YearsAndMonthsSince,
params: [],
isComponent: true,
};
```
### Eval output
(kind: ok) 3

View File

@@ -1,19 +0,0 @@
function foo() {
return {
bar() {
return 3.14;
},
};
}
const YearsAndMonthsSince = () => {
const diff = foo();
const months = Math.floor(diff.bar());
return <>{months}</>;
};
export const FIXTURE_ENTRYPOINT = {
fn: YearsAndMonthsSince,
params: [],
isComponent: true,
};

View File

@@ -0,0 +1,31 @@
## Input
```javascript
const YearsAndMonthsSince = () => {
const diff = foo();
const months = Math.floor(diff.bar());
return <>{months}</>;
};
```
## Error
```
Found 1 error:
Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier`
error.bug-invariant-codegen-methodcall.ts:3:17
1 | const YearsAndMonthsSince = () => {
2 | const diff = foo();
> 3 | const months = Math.floor(diff.bar());
| ^^^^^^^^^^ [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier`
4 | return <>{months}</>;
5 | };
6 |
```

View File

@@ -0,0 +1,5 @@
const YearsAndMonthsSince = () => {
const diff = foo();
const months = Math.floor(diff.bar());
return <>{months}</>;
};

View File

@@ -0,0 +1,44 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-phi-return-prop.ts:12:4
10 | // called during render, and thus it's unsafe to mutate globals or call
11 | // other impure code.
> 12 | global.property = true;
| ^^^^^^ value cannot be modified
13 | return <Item item={item} value={rand} />;
14 | };
15 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -0,0 +1,16 @@
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -0,0 +1,40 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-prop.ts:8:4
6 | // called during render, and thus it's unsafe to mutate globals or call
7 | // other impure code.
> 8 | global.property = true;
| ^^^^^^ value cannot be modified
9 | return <Item item={item} value={rand} />;
10 | };
11 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -0,0 +1,12 @@
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -0,0 +1,39 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
const other = [0, 1];
function Component({}) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(2, items.push(5), ...other);
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Error
```
Found 1 error:
Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier`
error.todo-nested-method-calls-lower-property-load-into-temporary.ts:6:14
4 | function Component({}) {
5 | const items = makeArray(0, 1, 2, null, 4, false, 6);
> 6 | const max = Math.max(2, items.push(5), ...other);
| ^^^^^^^^ [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier`
7 | return max;
8 | }
9 |
```

View File

@@ -1,62 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
const other = [0, 1];
function Component({}) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(2, items.push(5), ...other);
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
const other = [0, 1];
function Component(t0) {
const $ = _c(4);
let t1;
let t2;
let t3;
let t4;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
t1 = Math;
t2 = t1.max;
t3 = 2;
t4 = items.push(5);
$[0] = t1;
$[1] = t2;
$[2] = t3;
$[3] = t4;
} else {
t1 = $[0];
t2 = $[1];
t3 = $[2];
t4 = $[3];
}
const max = t2(t3, t4, ...other);
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) 8

View File

@@ -196,29 +196,6 @@ export function makeSharedRuntimeTypeProvider({
],
},
},
PrimitiveBox: {
kind: 'object',
properties: {
get: {
kind: 'function',
positionalParams: [],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Primitive'},
returnValueKind: ValueKindEnum.Primitive,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [
{kind: 'CreateFrom', from: '@value', into: '@return'},
],
},
},
},
},
},
};
} else if (moduleName === 'ReactCompilerTest') {

View File

@@ -421,22 +421,4 @@ export function typedMutate(x: any, v: any = null): void {
x.property = v;
}
type PrimitiveValue =
| number
| string
| boolean
| symbol
| null
| undefined
| bigint;
export class PrimitiveBox<T extends PrimitiveValue> {
value: T;
constructor(value: T) {
this.value = value;
}
get(): T {
return this.value;
}
}
export default typedLog;

View File

@@ -1969,44 +1969,6 @@ 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,
@@ -2196,37 +2158,41 @@ function parseModelString(
// This should not compile to eval() because then it has local scope access.
const code = value.slice(2);
try {
// 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);
}
// eslint-disable-next-line no-eval
return (0, eval)(code);
} catch (x) {
// 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);
// 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 = JSON.parse(
code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
);
// $FlowFixMe[cannot-write]
Object.defineProperty(fn, 'name', {value: name});
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);
if (idx !== -1) {
const name = code.slice(6, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[
name
];
}
}
} catch (_) {
fn = function () {};
return function () {};
}
return fn;
}
// Fallthrough
}

View File

@@ -3239,8 +3239,6 @@ describe('ReactFlight', () => {
}
Object.defineProperty(MyClass.prototype, 'y', {enumerable: true});
Object.defineProperty(MyClass, 'name', {value: 'MyClassName'});
function ServerComponent() {
console.log('hi', {
prop: 123,
@@ -3343,7 +3341,6 @@ 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);

View File

@@ -862,7 +862,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "render()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
@@ -904,7 +903,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
@@ -945,7 +943,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,

View File

@@ -4818,7 +4818,6 @@ export function attach(
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
id: instance.id,
key: fiber.key,
env: null,
type: getElementTypeForFiber(fiber),
};
} else {
@@ -4827,7 +4826,6 @@ 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,
};
}
@@ -5453,8 +5451,6 @@ export function attach(
// List of owners
owners,
env: null,
rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,
@@ -5558,8 +5554,6 @@ export function attach(
// List of owners
owners,
env: componentInfo.env == null ? null : componentInfo.env,
rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,

View File

@@ -795,7 +795,6 @@ export function attach(
displayName: getData(owner).displayName || 'Unknown',
id: getID(owner),
key: element.key,
env: null,
type: getElementType(owner),
});
if (owner._currentElement) {
@@ -858,8 +857,6 @@ export function attach(
// List of owners
owners,
env: null,
rootType: null,
rendererPackageName: null,
rendererVersion: null,

View File

@@ -256,7 +256,6 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
env: null | string,
type: ElementType,
};
@@ -302,10 +301,6 @@ 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,

View File

@@ -255,7 +255,6 @@ export function convertInspectedElementBackendToFrontend(
id,
type,
owners,
env,
source,
context,
hooks,
@@ -300,7 +299,6 @@ export function convertInspectedElementBackendToFrontend(
owners === null
? null
: owners.map(backendToFrontendSerializedElementMapper),
env,
context: hydrateHelper(context),
hooks: hydrateHelper(hooks),
props: hydrateHelper(props),

View File

@@ -16,21 +16,18 @@ 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) &&
environmentName == null
(hocDisplayNames == null || hocDisplayNames.length === 0)
) {
return null;
}
@@ -39,8 +36,6 @@ 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>
)}

View File

@@ -150,28 +150,13 @@ function SuspendedByRow({
</Button>
{isOpen && (
<div className={styles.CollapsableContent}>
{showIOStack && (
<StackTraceView
stack={ioInfo.stack}
environmentName={
ioOwner !== null && ioOwner.env === ioInfo.env
? null
: ioInfo.env
}
/>
)}
{showIOStack && <StackTraceView stack={ioInfo.stack} />}
{(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}
@@ -183,25 +168,12 @@ function SuspendedByRow({
<>
<div className={styles.SmallHeader}>awaited at:</div>
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
<StackTraceView
stack={asyncInfo.stack}
environmentName={
asyncOwner !== null && asyncOwner.env === asyncInfo.env
? null
: asyncInfo.env
}
/>
<StackTraceView stack={asyncInfo.stack} />
)}
{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}

View File

@@ -174,9 +174,6 @@ 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)}

View File

@@ -20,7 +20,6 @@ import styles from './OwnerView.css';
type OwnerViewProps = {
displayName: string,
hocDisplayNames: Array<string> | null,
environmentName: string | null,
compiledWithForget: boolean,
id: number,
isInStore: boolean,
@@ -28,7 +27,6 @@ type OwnerViewProps = {
export default function OwnerView({
displayName,
environmentName,
hocDisplayNames,
compiledWithForget,
id,
@@ -67,7 +65,6 @@ export default function OwnerView({
<ElementBadges
hocDisplayNames={hocDisplayNames}
compiledWithForget={compiledWithForget}
environmentName={environmentName}
/>
</span>
</Button>

View File

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

View File

@@ -12,8 +12,6 @@ import {use, useContext} from 'react';
import useOpenResource from '../useOpenResource';
import ElementBadges from './ElementBadges';
import styles from './StackTraceView.css';
import type {
@@ -30,13 +28,9 @@ import formatLocationForDisplay from './formatLocationForDisplay';
type CallSiteViewProps = {
callSite: ReactCallSite,
environmentName: null | string,
};
export function CallSiteView({
callSite,
environmentName,
}: CallSiteViewProps): React.Node {
export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
@@ -70,33 +64,19 @@ export function CallSiteView({
title={url + ':' + line}>
{formatLocationForDisplay(url, line, column)}
</span>
<ElementBadges environmentName={environmentName} />
</div>
);
}
type Props = {
stack: ReactStackTrace,
environmentName: null | string,
};
export default function StackTraceView({
stack,
environmentName,
}: Props): React.Node {
export default function StackTraceView({stack}: Props): React.Node {
return (
<div className={styles.StackTraceView}>
{stack.map((callSite, index) => (
<CallSiteView
key={index}
callSite={callSite}
environmentName={
// Badge last row
// TODO: If we start ignore listing the last row, we should badge the last
// non-ignored row.
index === stack.length - 1 ? environmentName : null
}
/>
<CallSiteView key={index} callSite={callSite} />
))}
</div>
);

View File

@@ -954,7 +954,7 @@ function TreeContextController({
Array<number>,
Map<number, number>,
]) => {
dispatch({
transitionDispatch({
type: 'HANDLE_STORE_MUTATION',
payload: [addedElementIDs, removedElementIDs],
});
@@ -965,7 +965,7 @@ function TreeContextController({
// At the moment, we can treat this as a mutation.
// We don't know which Elements were newly added/removed, but that should be okay in this case.
// It would only impact the search state, which is unlikely to exist yet at this point.
dispatch({
transitionDispatch({
type: 'HANDLE_STORE_MUTATION',
payload: [[], new Map()],
});

View File

@@ -208,7 +208,6 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
env: null | string,
hocDisplayNames: Array<string> | null,
compiledWithForget: boolean,
type: ElementType,
@@ -266,9 +265,6 @@ 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,
// Location of component in source code.
source: ReactFunctionLocation | null,

View File

@@ -158,7 +158,6 @@ import {
DefaultHydrationLane,
SomeRetryLane,
includesSomeLane,
includesOnlyRetries,
laneToLanes,
removeLanes,
mergeLanes,
@@ -270,7 +269,6 @@ import {
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
markSkippedUpdateLanes,
markRenderDerivedCause,
getWorkInProgressRoot,
peekDeferredLane,
} from './ReactFiberWorkLoop';
@@ -948,13 +946,6 @@ function updateDehydratedActivityComponent(
// but after we've already committed once.
warnIfHydrating();
if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
if (
// TODO: Factoring is a little weird, since we check this right below, too.
!didReceiveUpdate
@@ -1141,16 +1132,6 @@ function updateActivityComponent(
children: nextChildren,
};
if (
includesSomeLane(renderLanes, (OffscreenLane: Lane)) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
const primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentChild,
offscreenChildProps,
@@ -2534,17 +2515,6 @@ function updateSuspenseComponent(
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
} else {
if (
prevState !== null &&
includesOnlyRetries(renderLanes) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Retry lanes and we're entering the primary content then it's possible
// that the only reason we rendered was because we left this boundary to be warmed up but
// nothing else scheduled an update. If so, use it as the cause of the render.
markRenderDerivedCause(workInProgress);
}
pushPrimaryTreeSuspenseHandler(workInProgress);
const nextPrimaryChildren = nextProps.children;
@@ -2903,13 +2873,6 @@ function updateDehydratedSuspenseComponent(
// but after we've already committed once.
warnIfHydrating();
if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
if (isSuspenseInstanceFallback(suspenseInstance)) {
// This boundary is in a permanent fallback state. In this case, we'll never
// get an update and we'll never be able to hydrate the final content. Let's just try the

View File

@@ -122,10 +122,7 @@ import {
markStateUpdateScheduled,
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook';
import {
startUpdateTimerByLane,
startHostActionTimer,
} from './ReactProfilerTimer';
import {startUpdateTimerByLane} from './ReactProfilerTimer';
import {createCache} from './ReactFiberCacheComponent';
import {
createUpdate as createLegacyQueueUpdate,
@@ -3242,8 +3239,6 @@ export function startHostTransition<F>(
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
> = stateHook.queue;
startHostActionTimer(formFiber);
startTransition(
formFiber,
queue,

View File

@@ -609,7 +609,6 @@ export function logBlockingStart(
eventType: null | string,
eventIsRepeat: boolean,
isSpawnedUpdate: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask, // DEV-only
@@ -659,13 +658,11 @@ export function logBlockingStart(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
isPingedUpdate
? 'Promise Resolved'
: isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -675,13 +672,11 @@ export function logBlockingStart(
);
} else {
console.timeStamp(
isPingedUpdate
? 'Promise Resolved'
: isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -699,7 +694,6 @@ export function logTransitionStart(
eventTime: number,
eventType: null | string,
eventIsRepeat: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
debugTask: null | ConsoleTask, // DEV-only
): void {
@@ -769,11 +763,7 @@ export function logTransitionStart(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -783,11 +773,7 @@ export function logTransitionStart(
);
} else {
console.timeStamp(
isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -803,43 +789,23 @@ export function logRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prepared'
: includesOnlyHydrationLanes(lanes)
? 'Hydrated'
: 'Render';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
includesOnlyOffscreenLanes(lanes)
? 'Prepared'
: includesOnlyHydrationLanes(lanes)
? 'Hydrated'
: 'Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -847,43 +813,23 @@ export function logInterruptedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prewarm'
: includesOnlyHydrationLanes(lanes)
? 'Interrupted Hydration'
: 'Interrupted Render';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
includesOnlyOffscreenLanes(lanes)
? 'Prewarm'
: includesOnlyHydrationLanes(lanes)
? 'Interrupted Hydration'
: 'Interrupted Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -891,38 +837,19 @@ export function logSuspendedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -930,39 +857,20 @@ export function logSuspendedWithDelayPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
// This means the render was suspended and cannot commit until it gets unblocked.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -972,12 +880,8 @@ export function logRecoveredRenderPhase(
lanes: Lanes,
recoverableErrors: Array<CapturedValue<mixed>>,
hydrationFailed: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < recoverableErrors.length; i++) {
@@ -993,7 +897,7 @@ export function logRecoveredRenderPhase(
String(error);
properties.push(['Recoverable Error', message]);
}
const options = {
performance.measure('Recovered', {
start: startTime,
end: endTime,
detail: {
@@ -1007,15 +911,7 @@ export function logRecoveredRenderPhase(
properties,
},
},
};
if (debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
performance.measure.bind(performance, 'Recovered', options),
);
} else {
performance.measure('Recovered', options);
}
});
} else {
console.timeStamp(
'Recovered',
@@ -1033,144 +929,68 @@ export function logErroredRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
export function logInconsistentRender(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
console.timeStamp(
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
export function logSuspenseThrottlePhase(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
// This was inside a throttled Suspense boundary commit.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
export function logSuspendedCommitPhase(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
// This means the commit was suspended on CSS or images.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
// TODO: Include the exact reason and URLs of what resources suspended.
// TODO: This might also be Suspended while waiting on a View Transition.
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
@@ -1179,12 +999,8 @@ export function logCommitErrored(
endTime: number,
errors: Array<CapturedValue<mixed>>,
passive: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < errors.length; i++) {
@@ -1200,7 +1016,7 @@ export function logCommitErrored(
String(error);
properties.push(['Error', message]);
}
const options = {
performance.measure('Errored', {
start: startTime,
end: endTime,
detail: {
@@ -1214,15 +1030,7 @@ export function logCommitErrored(
properties,
},
},
};
if (debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
performance.measure.bind(performance, 'Errored', options),
);
} else {
performance.measure('Errored', options);
}
});
} else {
console.timeStamp(
'Errored',
@@ -1240,39 +1048,20 @@ export function logCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, false, debugTask);
logCommitErrored(startTime, endTime, errors, false);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
console.timeStamp(
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}
@@ -1280,35 +1069,16 @@ export function logPaintYieldPhase(
startTime: number,
endTime: number,
delayedUntilPaint: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
@@ -1316,38 +1086,19 @@ export function logPassiveCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, true, debugTask);
logCommitErrored(startTime, endTime, errors, true);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
console.timeStamp(
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}

View File

@@ -346,7 +346,6 @@ export function createHydrationContainer(
update.callback =
callback !== undefined && callback !== null ? callback : null;
enqueueUpdate(current, update, lane);
startUpdateTimerByLane(lane, 'hydrateRoot()');
scheduleInitialHydrationOnRoot(root, lane);
return root;

View File

@@ -266,16 +266,15 @@ import {
blockingClampTime,
blockingUpdateTime,
blockingUpdateTask,
blockingUpdateType,
blockingEventTime,
blockingEventType,
blockingEventIsRepeat,
blockingSpawnedUpdate,
blockingSuspendedTime,
transitionClampTime,
transitionStartTime,
transitionUpdateTime,
transitionUpdateTask,
transitionUpdateType,
transitionEventTime,
transitionEventType,
transitionEventIsRepeat,
@@ -302,8 +301,6 @@ import {
startPingTimerByLanes,
recordEffectError,
resetCommitErrors,
PINGED_UPDATE,
SPAWNED_UPDATE,
} from './ReactProfilerTimer';
// DEV stuff
@@ -485,9 +482,6 @@ export function getWorkInProgressTransitions(): null | Array<Transition> {
return workInProgressTransitions;
}
// The first setState call that eventually caused the current render.
let workInProgressUpdateTask: null | ConsoleTask = null;
let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
let currentEndTime: number | null = null;
@@ -1110,11 +1104,7 @@ export function performWorkOnRoot(
) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logInconsistentRender(
renderStartTime,
renderEndTime,
workInProgressUpdateTask,
);
logInconsistentRender(renderStartTime, renderEndTime);
finalizeRender(lanes, renderEndTime);
}
// A store was mutated in an interleaved event. Render again,
@@ -1140,12 +1130,7 @@ export function performWorkOnRoot(
if (errorRetryLanes !== NoLanes) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
}
lanes = errorRetryLanes;
@@ -1176,12 +1161,7 @@ export function performWorkOnRoot(
if (exitStatus === RootFatalErrored) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
}
prepareFreshStack(root, NoLanes);
@@ -1322,12 +1302,7 @@ function finishConcurrentRender(
// until we receive more data.
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logSuspendedRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logSuspendedRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
trackSuspendedTime(lanes, renderEndTime);
}
@@ -1892,22 +1867,18 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
} else {
logInterruptedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
}
workInProgressUpdateTask = null;
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
workInProgressUpdateTask = blockingUpdateTask;
const clampedUpdateTime =
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
? blockingClampTime
@@ -1927,7 +1898,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
? clampedUpdateTime
: renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
logBlockingStart(
@@ -1935,8 +1905,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clampedEventTime,
blockingEventType,
blockingEventIsRepeat,
blockingUpdateType === SPAWNED_UPDATE,
blockingUpdateType === PINGED_UPDATE,
blockingSpawnedUpdate,
renderStartTime,
lanes,
blockingUpdateTask,
@@ -1944,7 +1913,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clearBlockingTimers();
}
if (includesTransitionLane(lanes)) {
workInProgressUpdateTask = transitionUpdateTask;
const clampedStartTime =
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
? transitionClampTime
@@ -1968,7 +1936,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
? clampedUpdateTime
: renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
logTransitionStart(
@@ -1977,7 +1944,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clampedEventTime,
transitionEventType,
transitionEventIsRepeat,
transitionUpdateType === PINGED_UPDATE,
renderStartTime,
transitionUpdateTask,
);
@@ -2261,21 +2227,6 @@ function popAsyncDispatcher(prevAsyncDispatcher: any) {
ReactSharedInternals.A = prevAsyncDispatcher;
}
export function markRenderDerivedCause(fiber: Fiber): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (__DEV__) {
if (workInProgressUpdateTask === null) {
// If we don't have a cause associated with this render, it's likely because some
// other render left work behind on this Fiber. The real cause is this Fiber itself.
// We use its debugTask as the cause for this render. This might not be the only
// one when multiple siblings are rendered but they ideally shouldn't be.
workInProgressUpdateTask =
fiber._debugTask == null ? null : fiber._debugTask;
}
}
}
}
export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
@@ -3288,7 +3239,6 @@ function commitRoot(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
} else if (recoverableErrors !== null) {
const hydrationFailed =
@@ -3302,15 +3252,9 @@ function commitRoot(
lanes,
recoverableErrors,
hydrationFailed,
workInProgressUpdateTask,
);
} else {
logRenderPhase(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
logRenderPhase(completedRenderStartTime, completedRenderEndTime, lanes);
}
}
@@ -3481,17 +3425,9 @@ function commitRoot(
recordCommitTime();
if (enableComponentPerformanceTrack) {
if (suspendedCommitReason === SUSPENDED_COMMIT) {
logSuspendedCommitPhase(
completedRenderEndTime,
commitStartTime,
workInProgressUpdateTask,
);
logSuspendedCommitPhase(completedRenderEndTime, commitStartTime);
} else if (suspendedCommitReason === THROTTLED_COMMIT) {
logSuspenseThrottlePhase(
completedRenderEndTime,
commitStartTime,
workInProgressUpdateTask,
);
logSuspenseThrottlePhase(completedRenderEndTime, commitStartTime);
}
}
}
@@ -3736,7 +3672,6 @@ function flushSpawnedWork(): void {
: commitStartTime,
commitEndTime,
commitErrors,
workInProgressUpdateTask,
);
}
@@ -4212,7 +4147,6 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
commitEndTime,
passiveEffectStartTime,
!!wasDelayedCommit,
workInProgressUpdateTask,
);
}
@@ -4248,7 +4182,6 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
passiveEffectStartTime,
passiveEffectsEndTime,
commitErrors,
workInProgressUpdateTask,
);
finalizeRender(lanes, passiveEffectsEndTime);
}

View File

@@ -48,11 +48,6 @@ const createTask =
console.createTask
: (name: string) => null;
export const REGULAR_UPDATE: UpdateType = 0;
export const SPAWNED_UPDATE: UpdateType = 1;
export const PINGED_UPDATE: UpdateType = 2;
export opaque type UpdateType = 0 | 1 | 2;
export let renderStartTime: number = -0;
export let commitStartTime: number = -0;
export let commitEndTime: number = -0;
@@ -67,16 +62,15 @@ export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
export let blockingClampTime: number = -0;
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
export let blockingUpdateType: UpdateType = 0;
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
export let blockingEventType: null | string = null; // Event type of the first setState.
export let blockingEventIsRepeat: boolean = false;
export let blockingSpawnedUpdate: boolean = false;
export let blockingSuspendedTime: number = -1.1;
// TODO: This should really be one per Transition lane.
export let transitionClampTime: number = -0;
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
export let transitionUpdateType: UpdateType = 0;
export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
export let transitionEventType: null | string = null; // Event type of the first transition.
@@ -103,7 +97,7 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
blockingUpdateTime = now();
blockingUpdateTask = createTask(method);
if (isAlreadyRendering()) {
blockingUpdateType = SPAWNED_UPDATE;
blockingSpawnedUpdate = true;
}
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
@@ -116,7 +110,7 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
// If this is a second update in the same event, we treat it as a spawned update.
// This might be a microtask spawned from useEffect, multiple flushSync or
// a setState in a microtask spawned after the first setState. Regardless it's bad.
blockingUpdateType = SPAWNED_UPDATE;
blockingSpawnedUpdate = true;
}
blockingEventTime = newEventTime;
blockingEventType = newEventType;
@@ -141,54 +135,6 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
}
}
export function startHostActionTimer(fiber: Fiber): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
// This schedules an update on both the blocking lane for the pending state and on the
// transition lane for the action update. Using the debug task from the host fiber.
if (blockingUpdateTime < 0) {
blockingUpdateTime = now();
blockingUpdateTask =
__DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
if (isAlreadyRendering()) {
blockingUpdateType = SPAWNED_UPDATE;
}
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
if (
newEventTime !== blockingEventTime ||
newEventType !== blockingEventType
) {
blockingEventIsRepeat = false;
} else if (newEventType !== null) {
// If this is a second update in the same event, we treat it as a spawned update.
// This might be a microtask spawned from useEffect, multiple flushSync or
// a setState in a microtask spawned after the first setState. Regardless it's bad.
blockingUpdateType = SPAWNED_UPDATE;
}
blockingEventTime = newEventTime;
blockingEventType = newEventType;
}
if (transitionUpdateTime < 0) {
transitionUpdateTime = now();
transitionUpdateTask =
__DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
if (transitionStartTime < 0) {
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
if (
newEventTime !== transitionEventTime ||
newEventType !== transitionEventType
) {
transitionEventIsRepeat = false;
}
transitionEventTime = newEventTime;
transitionEventType = newEventType;
}
}
}
export function startPingTimerByLanes(lanes: Lanes): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
@@ -199,14 +145,10 @@ export function startPingTimerByLanes(lanes: Lanes): void {
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
if (blockingUpdateTime < 0) {
blockingClampTime = blockingUpdateTime = now();
blockingUpdateTask = createTask('Promise Resolved');
blockingUpdateType = PINGED_UPDATE;
}
} else if (includesTransitionLane(lanes)) {
if (transitionUpdateTime < 0) {
transitionClampTime = transitionUpdateTime = now();
transitionUpdateTask = createTask('Promise Resolved');
transitionUpdateType = PINGED_UPDATE;
}
}
}
@@ -224,9 +166,10 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
export function clearBlockingTimers(): void {
blockingUpdateTime = -1.1;
blockingUpdateType = 0;
blockingUpdateTask = null;
blockingSuspendedTime = -1.1;
blockingEventIsRepeat = true;
blockingSpawnedUpdate = false;
}
export function startAsyncTransitionTimer(): void {
@@ -253,6 +196,20 @@ export function hasScheduledTransitionWork(): boolean {
return transitionUpdateTime > -1;
}
// We use this marker to indicate that we have scheduled a render to be performed
// but it's not an explicit state update.
const ACTION_STATE_MARKER = -0.5;
export function startActionStateUpdate(): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
if (transitionUpdateTime < 0) {
transitionUpdateTime = ACTION_STATE_MARKER;
transitionUpdateTask = null;
}
}
export function clearAsyncTransitionTimer(): void {
transitionStartTime = -1.1;
}
@@ -260,7 +217,7 @@ export function clearAsyncTransitionTimer(): void {
export function clearTransitionTimers(): void {
transitionStartTime = -1.1;
transitionUpdateTime = -1.1;
transitionUpdateType = 0;
transitionUpdateTask = null;
transitionSuspendedTime = -1.1;
transitionEventIsRepeat = true;
}

View File

@@ -4848,18 +4848,9 @@ function renderDebugModel(
return existingReference;
}
// $FlowFixMe[method-unbinding]
const functionBody: string = Function.prototype.toString.call(value);
const name = value.name;
const serializedValue = serializeEval(
typeof name === 'string'
? 'Object.defineProperty(' +
functionBody +
',"name",{value:' +
JSON.stringify(name) +
'})'
: '(' + functionBody + ')',
// $FlowFixMe[method-unbinding]
'(' + Function.prototype.toString.call(value) + ')',
);
request.pendingDebugChunks++;
const id = request.nextChunkId++;