Remove unstable scheduler/tracing API (#20037)

This commit is contained in:
Brian Vaughn 2021-04-26 19:16:18 -04:00 committed by GitHub
parent 7212383945
commit fc33f12bde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 2710 additions and 11153 deletions

View File

@ -1,6 +1,6 @@
{
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
"buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,scheduler/unstable_no_dom,scheduler/tracing,react/jsx-runtime,react/jsx-dev-runtime",
"buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,scheduler/unstable_no_dom,react/jsx-runtime,react/jsx-dev-runtime",
"publishDirectory": {
"react": "build/node_modules/react",
"react-dom": "build/node_modules/react-dom",

View File

@ -1,83 +0,0 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%;">
<head>
<meta charset="utf-8">
<title>Test tracing UMD</title>
<style>
body {
font-family: sans-serif;
}
ol {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
}
li {
background-color: #F7F7F7;
border: solid #CCC 0.125rem;
margin-bottom: 0.5rem;
border-radius: 0.25rem;
padding: 0.5rem;
}
li:after {
content: attr(data-value);
margin-left: 0.25rem;
}
.correct {
border-color: #0C0;
border-style: solid;
background: #EFE;
}
.incorrect {
border-color: #F00;
border-style: dashed;
background-color: #FEE;
}
</style>
</head>
<body>
<h1>Test tracing UMD</h1>
<p>
This fixture tests that the new tracing API is accessible via UMD build using the UMD shim.
It does not exhaustively test API functionality, only that the forwarded methods can be called.
</p>
<p>
Before running the tests below, check the console to make sure there are no errors.
</p>
<h3>
Tests
<button id="run-test-button" onClick="runAllTests()">Run all tests</button>
</h3>
<ol>
<li id="checkSchedulerAPI" data-value="...">
<strong>Test scheduler API</strong>
</li>
<li id="checkSchedulerTracingAPI" data-value="...">
<strong>Test tracing API</strong>
</li>
<li id="checkSchedulerTracingSubscriptionsAPI" data-value="...">
<strong>Test tracing subscriptions API</strong>
</li>
<li id="checkEndToEndIntegration" data-value="...">
<strong>Test end-to-end integration</strong>
</li>
</ol>
<script>
if (window.location.search.includes('puppeteer=true')) {
// Collocated calls to performance.now() often yield different values in Puppeteer.
// This causes the Scheduler API test to fail.
// For the purposes of our automated release scripts,
// Coerce tests to use Date.now() instead to reduce the chances of a false positive.
window.performance = {now: Date.now};
}
</script>
<!-- Load the tracing API before react to test that it's lazily evaluated -->
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler-tracing.development.js"></script>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="./script.js"></script>
</body>
</html>

View File

@ -1,206 +0,0 @@
function runTest(listItem, callback) {
try {
callback();
listItem.className = 'correct';
listItem.setAttribute('data-value', 'All checks pass');
} catch (error) {
listItem.className = 'incorrect';
listItem.setAttribute('data-value', error);
}
}
function runAllTests() {
try {
checkSchedulerAPI();
} finally {
try {
checkSchedulerTracingAPI();
} finally {
try {
checkSchedulerTracingSubscriptionsAPI();
} finally {
checkEndToEndIntegration();
}
}
}
}
function checkSchedulerAPI() {
runTest(document.getElementById('checkSchedulerAPI'), () => {
if (
typeof Scheduler === 'undefined' ||
typeof Scheduler.unstable_now !== 'function' ||
typeof Scheduler.unstable_scheduleCallback !== 'function' ||
typeof Scheduler.unstable_cancelCallback !== 'function'
) {
throw 'API is not defined';
}
const abs = Math.abs(Scheduler.unstable_now() - performance.now());
if (typeof abs !== 'number' || Number.isNaN(abs) || abs > 5) {
throw 'API does not work';
}
// There is no real way to verify that the two APIs are connected.
});
}
function checkSchedulerTracingAPI() {
runTest(document.getElementById('checkSchedulerTracingAPI'), () => {
if (
typeof SchedulerTracing === 'undefined' ||
typeof SchedulerTracing.unstable_clear !== 'function' ||
typeof SchedulerTracing.unstable_getCurrent !== 'function' ||
typeof SchedulerTracing.unstable_getThreadID !== 'function' ||
typeof SchedulerTracing.unstable_trace !== 'function' ||
typeof SchedulerTracing.unstable_wrap !== 'function'
) {
throw 'API is not defined';
}
try {
let interactionsSet;
SchedulerTracing.unstable_trace('test', 123, () => {
interactionsSet = SchedulerTracing.unstable_getCurrent();
});
if (interactionsSet.size !== 1) {
throw null;
}
const interaction = Array.from(interactionsSet)[0];
if (interaction.name !== 'test' || interaction.timestamp !== 123) {
throw null;
}
} catch (error) {
throw 'API does not work';
}
const ForwardedSchedulerTracing =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing;
if (
SchedulerTracing.unstable_getThreadID() ===
ForwardedSchedulerTracing.unstable_getThreadID()
) {
throw 'API forwarding is broken';
}
});
}
function checkSchedulerTracingSubscriptionsAPI() {
runTest(
document.getElementById('checkSchedulerTracingSubscriptionsAPI'),
() => {
if (
typeof SchedulerTracing === 'undefined' ||
typeof SchedulerTracing.unstable_subscribe !== 'function' ||
typeof SchedulerTracing.unstable_unsubscribe !== 'function'
) {
throw 'API is not defined';
}
const onInteractionScheduledWorkCompletedCalls = [];
const onInteractionTracedCalls = [];
const onWorkCanceledCalls = [];
const onWorkScheduledCalls = [];
const onWorkStartedCalls = [];
const onWorkStoppedCalls = [];
const subscriber = {
onInteractionScheduledWorkCompleted: (...args) =>
onInteractionScheduledWorkCompletedCalls.push(args),
onInteractionTraced: (...args) => onInteractionTracedCalls.push(args),
onWorkCanceled: (...args) => onWorkCanceledCalls.push(args),
onWorkScheduled: (...args) => onWorkScheduledCalls.push(args),
onWorkStarted: (...args) => onWorkStartedCalls.push(args),
onWorkStopped: (...args) => onWorkStoppedCalls.push(args),
};
try {
SchedulerTracing.unstable_subscribe(subscriber);
SchedulerTracing.unstable_trace('foo', 123, () => {});
SchedulerTracing.unstable_unsubscribe(subscriber);
if (onInteractionTracedCalls.length !== 1) {
throw null;
}
const interaction = onInteractionTracedCalls[0][0];
if (interaction.name !== 'foo' || interaction.timestamp !== 123) {
throw null;
}
SchedulerTracing.unstable_trace('bar', 456, () => {});
if (onInteractionTracedCalls.length !== 1) {
throw null;
}
} catch (error) {
throw 'API does not forward methods';
}
const ForwardedSchedulerTracing =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.SchedulerTracing;
try {
ForwardedSchedulerTracing.unstable_subscribe(subscriber);
SchedulerTracing.unstable_trace('foo', 123, () => {});
ForwardedSchedulerTracing.unstable_trace('bar', 456, () => {});
SchedulerTracing.unstable_unsubscribe(subscriber);
if (onInteractionTracedCalls.length !== 3) {
throw null;
}
const interactionFoo = onInteractionTracedCalls[1][0];
const interactionBar = onInteractionTracedCalls[2][0];
if (
interactionFoo.name !== 'foo' ||
interactionFoo.timestamp !== 123 ||
interactionBar.name !== 'bar' ||
interactionBar.timestamp !== 456
) {
throw null;
}
ForwardedSchedulerTracing.unstable_trace('baz', 789, () => {});
if (onInteractionTracedCalls.length !== 3) {
throw null;
}
} catch (error) {
throw 'API forwarding is broken';
}
}
);
}
function checkEndToEndIntegration() {
runTest(document.getElementById('checkEndToEndIntegration'), () => {
try {
const onRenderCalls = [];
const onRender = (...args) => onRenderCalls.push(args);
const container = document.createElement('div');
SchedulerTracing.unstable_trace('render', 123, () => {
ReactDOM.render(
React.createElement(
React.Profiler,
{id: 'profiler', onRender},
React.createElement('div', null, 'hi')
),
container
);
});
if (container.textContent !== 'hi') {
throw null;
}
if (onRenderCalls.length !== 1) {
throw null;
}
const call = onRenderCalls[0];
if (call.length !== 7) {
throw null;
}
const interaction = Array.from(call[6])[0];
if (interaction.name !== 'render' || interaction.timestamp !== 123) {
throw null;
}
} catch (error) {
throw 'End to end integration is broken';
}
});
}

View File

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%;">
<head>
<meta charset="utf-8">
<title>Test tracing UMD</title>
<style>
body {
font-family: sans-serif;
}
</style>
</head>
<body>
<div id="root"></div>
<!-- Load the tracing API before react to test that it's lazily evaluated -->
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler-tracing.development.js"></script>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="./test.js"></script>
</body>
</html>

View File

@ -1,101 +0,0 @@
const {createElement, Component, Suspense} = React;
const {createRoot} = ReactDOM;
const {
unstable_subscribe: subscribe,
unstable_trace: trace,
unstable_wrap: wrap,
} = SchedulerTracing;
const createLogger = (backgroundColor, color, enabled) => (
message,
...args
) => {
if (enabled === false) return;
console.groupCollapsed(
`%c${message}`,
`background-color: ${backgroundColor}; color: ${color}; padding: 2px 4px;`,
...args
);
console.log(
new Error('stack').stack
.split('\n')
.slice(2)
.join('\n')
);
console.groupEnd();
};
window.log = {
app: createLogger('#37474f', '#fff'),
interaction: createLogger('#6a1b9a', '#fff'),
react: createLogger('#ff5722', '#fff'),
tracing: createLogger('#2962ff', '#fff'),
work: createLogger('#e1bee7', '#000'),
};
// Fake suspense
const resolvedValues = {};
const read = key => {
if (!resolvedValues[key]) {
log.app(`Suspending for "${key}" ...`);
throw new Promise(
wrap(resolve => {
setTimeout(
wrap(() => {
log.app(`Loaded "${key}" ...`);
resolvedValues[key] = true;
resolve(key);
}),
1000
);
})
);
}
return key;
};
const TestApp = () =>
createElement(
Suspense,
{fallback: createElement(PlaceholderText)},
createElement(SuspendingChild, {text: 'foo'}),
createElement(SuspendingChild, {text: 'bar'}),
createElement(SuspendingChild, {text: 'baz'})
);
const PlaceholderText = () => 'Loading ...';
const SuspendingChild = ({text}) => {
const resolvedValue = read(text);
return resolvedValue;
};
subscribe({
onInteractionScheduledWorkCompleted: interaction =>
log.interaction(
'onInteractionScheduledWorkCompleted',
JSON.stringify(interaction)
),
onInteractionTraced: interaction =>
log.interaction('onInteractionTraced', JSON.stringify(interaction)),
onWorkCanceled: interactions =>
log.work('onWorkCanceled', JSON.stringify(Array.from(interactions))),
onWorkScheduled: interactions =>
log.work('onWorkScheduled', JSON.stringify(Array.from(interactions))),
onWorkStarted: interactions =>
log.work('onWorkStarted', JSON.stringify(Array.from(interactions))),
onWorkStopped: interactions =>
log.work('onWorkStopped', JSON.stringify(Array.from(interactions))),
});
const element = document.getElementById('root');
trace('initial_render', performance.now(), () => {
const root = createRoot(element);
log.app('render()');
root.render(
createElement(TestApp),
wrap(() => {
log.app('committed');
})
);
});

View File

@ -39,7 +39,6 @@ Object {
3 => 1,
5 => 1,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 16,
@ -87,7 +86,6 @@ Object {
3 => 3,
4 => 2,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 15,
@ -124,7 +122,6 @@ Object {
5 => 3,
3 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 18,
@ -188,7 +185,6 @@ Object {
4 => 1,
5 => 1,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 12,
@ -254,7 +250,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 25,
@ -302,7 +297,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 35,
@ -341,7 +335,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 45,
@ -451,7 +444,6 @@ Object {
1,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 12,
@ -556,7 +548,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 25,
@ -625,7 +616,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 35,
@ -676,7 +666,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 45,
@ -693,8 +682,6 @@ Object {
],
"displayName": "Parent",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -950,7 +937,6 @@ Object {
1,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 11,
@ -1037,7 +1023,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 22,
@ -1142,7 +1127,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 35,
@ -1159,8 +1143,6 @@ Object {
],
"displayName": "Parent",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -1352,7 +1334,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 13,
@ -1397,7 +1378,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 34,
@ -1433,7 +1413,6 @@ Object {
2 => 10,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 44,
@ -1456,8 +1435,6 @@ Object {
4 => 1,
5 => 1,
},
"interactionCommits": Map {},
"interactions": Map {},
"operations": Array [
Array [
1,
@ -1619,7 +1596,6 @@ Object {
13 => 0,
14 => 1,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 24,
@ -1636,8 +1612,6 @@ Object {
],
"displayName": "Parent",
"initialTreeBaseDurations": Map {},
"interactionCommits": Map {},
"interactions": Map {},
"operations": Array [
Array [
1,
@ -1712,7 +1686,6 @@ Object {
"effectDuration": null,
"fiberActualDurations": Map {},
"fiberSelfDurations": Map {},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 34,
@ -1734,8 +1707,6 @@ Object {
8 => 0,
9 => 1,
},
"interactionCommits": Map {},
"interactions": Map {},
"operations": Array [
Array [
1,
@ -1890,7 +1861,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 13,
@ -1959,7 +1929,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 34,
@ -2010,7 +1979,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 44,
@ -2048,8 +2016,6 @@ Object {
1,
],
],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -2256,7 +2222,6 @@ Object {
1,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 24,
@ -2273,8 +2238,6 @@ Object {
],
"displayName": "Parent",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -2346,7 +2309,6 @@ Object {
"effectDuration": null,
"fiberActualDurations": Array [],
"fiberSelfDurations": Array [],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 34,
@ -2380,8 +2342,6 @@ Object {
1,
],
],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -2470,7 +2430,6 @@ Object {
1 => 0,
2 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 0,
@ -2487,8 +2446,6 @@ Object {
],
"displayName": "Suspense",
"initialTreeBaseDurations": Map {},
"interactionCommits": Map {},
"interactions": Map {},
"operations": Array [
Array [
1,
@ -2548,7 +2505,6 @@ Object {
2 => 0,
3 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 0,
@ -2589,7 +2545,6 @@ Object {
2 => 0,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2624,7 +2579,6 @@ Object {
"fiberSelfDurations": Map {
3 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2659,7 +2613,6 @@ Object {
"fiberSelfDurations": Map {
3 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2698,7 +2651,6 @@ Object {
2 => 0,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2762,7 +2714,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 0,
@ -2821,7 +2772,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2862,7 +2812,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2903,7 +2852,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2960,7 +2908,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -2977,8 +2924,6 @@ Object {
],
"displayName": "Component",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -3125,7 +3070,6 @@ Object {
6 => 0,
7 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 0,
@ -3206,7 +3150,6 @@ Object {
3 => 0,
2 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3283,7 +3226,6 @@ Object {
2 => 0,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3361,7 +3303,6 @@ Object {
2 => 0,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3438,7 +3379,6 @@ Object {
2 => 0,
1 => 0,
},
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3574,7 +3514,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 0,
@ -3703,7 +3642,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3834,7 +3772,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -3966,7 +3903,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -4097,7 +4033,6 @@ Object {
0,
],
],
"interactionIDs": Array [],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 0,
@ -4114,8 +4049,6 @@ Object {
],
"displayName": "LegacyContextProvider",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [],
"interactions": Array [],
"operations": Array [
Array [
1,
@ -4325,341 +4258,3 @@ Object {
"version": 5,
}
`;
exports[`ProfilingCache should report every traced interaction: Interactions 1`] = `
Array [
Object {
"__count": 1,
"id": 0,
"name": "mount: one child",
"timestamp": 0,
},
Object {
"__count": 0,
"id": 1,
"name": "update: two children",
"timestamp": 11,
},
]
`;
exports[`ProfilingCache should report every traced interaction: imported data 1`] = `
Object {
"dataForRoots": Array [
Object {
"commitData": Array [
Object {
"changeDescriptions": Array [
Array [
2,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
],
Array [
3,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
],
Array [
4,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
],
],
"duration": 11,
"effectDuration": null,
"fiberActualDurations": Array [
Array [
1,
11,
],
Array [
2,
11,
],
Array [
3,
0,
],
Array [
4,
1,
],
],
"fiberSelfDurations": Array [
Array [
1,
0,
],
Array [
2,
10,
],
Array [
3,
0,
],
Array [
4,
1,
],
],
"interactionIDs": Array [
0,
],
"passiveEffectDuration": null,
"priorityLevel": "Normal",
"timestamp": 11,
"updaters": Array [
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
"id": 1,
"key": null,
"type": 11,
},
],
},
Object {
"changeDescriptions": Array [
Array [
3,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [],
"state": null,
},
],
Array [
5,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": true,
"props": null,
"state": null,
},
],
Array [
2,
Object {
"context": null,
"didHooksChange": false,
"isFirstMount": false,
"props": Array [
"count",
],
"state": null,
},
],
],
"duration": 11,
"effectDuration": null,
"fiberActualDurations": Array [
Array [
3,
0,
],
Array [
5,
1,
],
Array [
2,
11,
],
Array [
1,
11,
],
],
"fiberSelfDurations": Array [
Array [
3,
0,
],
Array [
5,
1,
],
Array [
2,
10,
],
Array [
1,
0,
],
],
"interactionIDs": Array [
1,
],
"passiveEffectDuration": null,
"priorityLevel": "Immediate",
"timestamp": 22,
"updaters": Array [
Object {
"displayName": "Anonymous",
"hocDisplayNames": null,
"id": 1,
"key": null,
"type": 11,
},
],
},
],
"displayName": "Parent",
"initialTreeBaseDurations": Array [],
"interactionCommits": Array [
Array [
0,
Array [
0,
],
],
Array [
1,
Array [
1,
],
],
],
"interactions": Array [
Array [
0,
Object {
"__count": 1,
"id": 0,
"name": "mount: one child",
"timestamp": 0,
},
],
Array [
1,
Object {
"__count": 0,
"id": 1,
"name": "update: two children",
"timestamp": 11,
},
],
],
"operations": Array [
Array [
1,
1,
15,
6,
80,
97,
114,
101,
110,
116,
5,
67,
104,
105,
108,
100,
1,
48,
1,
1,
11,
1,
1,
4,
1,
11000,
1,
2,
5,
1,
0,
1,
0,
4,
2,
11000,
1,
3,
5,
2,
2,
2,
3,
4,
3,
0,
1,
4,
8,
2,
2,
2,
0,
4,
4,
1000,
],
Array [
1,
1,
8,
5,
67,
104,
105,
108,
100,
1,
49,
1,
5,
5,
2,
2,
1,
2,
4,
5,
1000,
4,
2,
12000,
3,
2,
3,
3,
5,
4,
4,
1,
12000,
],
],
"rootID": 1,
"snapshots": Array [],
},
],
"version": 5,
}
`;

View File

@ -255,48 +255,6 @@ Object {
}
`;
exports[`profiling charts interactions should contain valid data: Interactions 1`] = `
Object {
"interactions": Array [
Object {
"__count": 1,
"id": 0,
"name": "mount",
"timestamp": 0,
},
Object {
"__count": 0,
"id": 1,
"name": "update",
"timestamp": 15,
},
],
"lastInteractionTime": 25,
"maxCommitDuration": 15,
}
`;
exports[`profiling charts interactions should contain valid data: Interactions 2`] = `
Object {
"interactions": Array [
Object {
"__count": 1,
"id": 0,
"name": "mount",
"timestamp": 0,
},
Object {
"__count": 0,
"id": 1,
"name": "update",
"timestamp": 15,
},
],
"lastInteractionTime": 25,
"maxCommitDuration": 15,
}
`;
exports[`profiling charts ranked chart should contain valid data: 0: CommitTree 1`] = `
Object {
"nodes": Map {

View File

@ -16,7 +16,6 @@ describe('ProfilingCache', () => {
let React;
let ReactDOM;
let Scheduler;
let SchedulerTracing;
let TestRenderer: ReactTestRenderer;
let bridge: FrontendBridge;
let store: Store;
@ -35,7 +34,6 @@ describe('ProfilingCache', () => {
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
TestRenderer = utils.requireTestRenderer();
});
@ -622,79 +620,6 @@ describe('ProfilingCache', () => {
}
});
it('should report every traced interaction', () => {
const Parent = ({count}) => {
Scheduler.unstable_advanceTime(10);
const children = new Array(count)
.fill(true)
.map((_, index) => <Child key={index} duration={index} />);
return (
<React.Fragment>
{children}
<MemoizedChild duration={1} />
</React.Fragment>
);
};
const Child = ({duration}) => {
Scheduler.unstable_advanceTime(duration);
return null;
};
const MemoizedChild = React.memo(Child);
const container = document.createElement('div');
utils.act(() => store.profilerStore.startProfiling());
utils.act(() =>
SchedulerTracing.unstable_trace(
'mount: one child',
Scheduler.unstable_now(),
() => ReactDOM.render(<Parent count={1} />, container),
),
);
utils.act(() =>
SchedulerTracing.unstable_trace(
'update: two children',
Scheduler.unstable_now(),
() => ReactDOM.render(<Parent count={2} />, container),
),
);
utils.act(() => store.profilerStore.stopProfiling());
let interactions = null;
function Validator({previousInteractions, rootID}) {
interactions = store.profilerStore.profilingCache.getInteractionsChartData(
{
rootID,
},
).interactions;
if (previousInteractions != null) {
expect(interactions).toEqual(previousInteractions);
} else {
expect(interactions).toMatchSnapshot('Interactions');
}
return null;
}
const rootID = store.roots[0];
utils.act(() =>
TestRenderer.create(
<Validator previousInteractions={null} rootID={rootID} />,
),
);
expect(interactions).not.toBeNull();
utils.exportImportHelper(bridge, store);
utils.act(() =>
TestRenderer.create(
<Validator previousInteractions={interactions} rootID={rootID} />,
),
);
});
it('should handle unexpectedly shallow suspense trees', () => {
const container = document.createElement('div');

View File

@ -14,7 +14,6 @@ describe('profiling charts', () => {
let React;
let ReactDOM;
let Scheduler;
let SchedulerTracing;
let TestRenderer: TestRendererType;
let store: Store;
let utils;
@ -30,7 +29,6 @@ describe('profiling charts', () => {
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
TestRenderer = utils.requireTestRenderer();
});
@ -56,18 +54,8 @@ describe('profiling charts', () => {
const container = document.createElement('div');
utils.act(() => store.profilerStore.startProfiling());
utils.act(() =>
SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
ReactDOM.render(<Parent />, container),
),
);
utils.act(() =>
SchedulerTracing.unstable_trace(
'update',
Scheduler.unstable_now(),
() => ReactDOM.render(<Parent />, container),
),
);
utils.act(() => ReactDOM.render(<Parent />, container));
utils.act(() => ReactDOM.render(<Parent />, container));
utils.act(() => store.profilerStore.stopProfiling());
let renderFinished = false;
@ -132,18 +120,8 @@ describe('profiling charts', () => {
const container = document.createElement('div');
utils.act(() => store.profilerStore.startProfiling());
utils.act(() =>
SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
ReactDOM.render(<Parent />, container),
),
);
utils.act(() =>
SchedulerTracing.unstable_trace(
'update',
Scheduler.unstable_now(),
() => ReactDOM.render(<Parent />, container),
),
);
utils.act(() => ReactDOM.render(<Parent />, container));
utils.act(() => ReactDOM.render(<Parent />, container));
utils.act(() => store.profilerStore.stopProfiling());
let renderFinished = false;
@ -181,69 +159,4 @@ describe('profiling charts', () => {
}
});
});
describe('interactions', () => {
it('should contain valid data', () => {
const Parent = (_: {||}) => {
Scheduler.unstable_advanceTime(10);
return (
<React.Fragment>
<Child key="first" duration={3} />
<Child key="second" duration={2} />
<Child key="third" duration={0} />
</React.Fragment>
);
};
// Memoize children to verify that chart doesn't include in the update.
const Child = React.memo(function Child({duration}) {
Scheduler.unstable_advanceTime(duration);
return null;
});
const container = document.createElement('div');
utils.act(() => store.profilerStore.startProfiling());
utils.act(() =>
SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
ReactDOM.render(<Parent />, container),
),
);
utils.act(() =>
SchedulerTracing.unstable_trace(
'update',
Scheduler.unstable_now(),
() => ReactDOM.render(<Parent />, container),
),
);
utils.act(() => store.profilerStore.stopProfiling());
let renderFinished = false;
function Validator({commitIndex, rootID}) {
const chartData = store.profilerStore.profilingCache.getInteractionsChartData(
{
rootID,
},
);
expect(chartData).toMatchSnapshot('Interactions');
renderFinished = true;
return null;
}
const rootID = store.roots[0];
for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
renderFinished = false;
utils.act(() => {
TestRenderer.create(
<Validator commitIndex={commitIndex} rootID={rootID} />,
);
});
expect(renderFinished).toBe(true);
}
});
});
});

View File

@ -102,7 +102,6 @@ import type {
SerializedElement,
WorkTagMap,
} from './types';
import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types';
import type {
ComponentFilter,
ElementType,
@ -2136,6 +2135,22 @@ export function attach(
// We don't patch any methods so there is no cleanup.
}
function rootSupportsProfiling(root) {
if (root.memoizedInteractions != null) {
// v16 builds include this field for the scheduler/tracing API.
return true;
} else if (
root.current != null &&
root.current.hasOwnProperty('treeBaseDuration')
) {
// The scheduler/tracing API was removed in v17 though
// so we need to check a non-root Fiber.
return true;
} else {
return false;
}
}
function flushInitialOperations() {
const localPendingOperationsQueue = pendingOperationsQueue;
@ -2161,21 +2176,14 @@ export function attach(
currentRootID = getFiberID(getPrimaryFiber(root.current));
setRootPseudoKey(currentRootID, root.current);
// Checking root.memoizedInteractions handles multi-renderer edge-case-
// where some v16 renderers support profiling and others don't.
if (isProfiling && root.memoizedInteractions != null) {
// If profiling is active, store commit time and duration, and the current interactions.
// Handle multi-renderer edge-case where only some v16 renderers support profiling.
if (isProfiling && rootSupportsProfiling(root)) {
// If profiling is active, store commit time and duration.
// The frontend may request this information after profiling has stopped.
currentCommitProfilingMetadata = {
changeDescriptions: recordChangeDescriptions ? new Map() : null,
durations: [],
commitTime: getCurrentTime() - profilingStartTime,
interactions: Array.from(root.memoizedInteractions).map(
(interaction: Interaction) => ({
...interaction,
timestamp: interaction.timestamp - profilingStartTime,
}),
),
maxActualDuration: 0,
priorityLevel: null,
updaters: getUpdatersList(root),
@ -2205,8 +2213,7 @@ export function attach(
}
function handlePostCommitFiberRoot(root) {
const isProfilingSupported = root.memoizedInteractions != null;
if (isProfiling && isProfilingSupported) {
if (isProfiling && rootSupportsProfiling(root)) {
if (currentCommitProfilingMetadata !== null) {
const {effectDuration, passiveEffectDuration} = getEffectDurations(
root,
@ -2233,23 +2240,16 @@ export function attach(
traceUpdatesForNodes.clear();
}
// Checking root.memoizedInteractions handles multi-renderer edge-case-
// where some v16 renderers support profiling and others don't.
const isProfilingSupported = root.memoizedInteractions != null;
// Handle multi-renderer edge-case where only some v16 renderers support profiling.
const isProfilingSupported = rootSupportsProfiling(root);
if (isProfiling && isProfilingSupported) {
// If profiling is active, store commit time and duration, and the current interactions.
// If profiling is active, store commit time and duration.
// The frontend may request this information after profiling has stopped.
currentCommitProfilingMetadata = {
changeDescriptions: recordChangeDescriptions ? new Map() : null,
durations: [],
commitTime: getCurrentTime() - profilingStartTime,
interactions: Array.from(root.memoizedInteractions).map(
(interaction: Interaction) => ({
...interaction,
timestamp: interaction.timestamp - profilingStartTime,
}),
),
maxActualDuration: 0,
priorityLevel:
priorityLevel == null ? null : formatPriorityLevel(priorityLevel),
@ -3386,7 +3386,6 @@ export function attach(
commitTime: number,
durations: Array<number>,
effectDuration: number | null,
interactions: Array<Interaction>,
maxActualDuration: number,
passiveEffectDuration: number | null,
priorityLevel: string | null,
@ -3419,8 +3418,6 @@ export function attach(
(commitProfilingMetadata, rootID) => {
const commitData: Array<CommitDataBackend> = [];
const initialTreeBaseDurations: Array<[number, number]> = [];
const allInteractions: Map<number, Interaction> = new Map();
const interactionCommits: Map<number, Array<number>> = new Map();
const displayName =
(displayNamesByRootID !== null && displayNamesByRootID.get(rootID)) ||
@ -3444,7 +3441,6 @@ export function attach(
changeDescriptions,
durations,
effectDuration,
interactions,
maxActualDuration,
passiveEffectDuration,
priorityLevel,
@ -3452,23 +3448,6 @@ export function attach(
updaters,
} = commitProfilingData;
const interactionIDs: Array<number> = [];
interactions.forEach(interaction => {
if (!allInteractions.has(interaction.id)) {
allInteractions.set(interaction.id, interaction);
}
interactionIDs.push(interaction.id);
const commitIndices = interactionCommits.get(interaction.id);
if (commitIndices != null) {
commitIndices.push(commitIndex);
} else {
interactionCommits.set(interaction.id, [commitIndex]);
}
});
const fiberActualDurations: Array<[number, number]> = [];
const fiberSelfDurations: Array<[number, number]> = [];
for (let i = 0; i < durations.length; i += 3) {
@ -3486,7 +3465,6 @@ export function attach(
effectDuration,
fiberActualDurations,
fiberSelfDurations,
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp: commitTime,
@ -3498,8 +3476,6 @@ export function attach(
commitData,
displayName,
initialTreeBaseDurations,
interactionCommits: Array.from(interactionCommits.entries()),
interactions: Array.from(allInteractions.entries()),
rootID,
});
},

View File

@ -14,7 +14,6 @@ import type {
ComponentFilter,
ElementType,
} from 'react-devtools-shared/src/types';
import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types';
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
type BundleType =
@ -165,7 +164,6 @@ export type CommitDataBackend = {|
fiberActualDurations: Array<[number, number]>,
// Tuple of fiber ID and computed "self" duration
fiberSelfDurations: Array<[number, number]>,
interactionIDs: Array<number>,
// Only available in certain (newer) React builds,
passiveEffectDuration: number | null,
priorityLevel: string | null,
@ -178,9 +176,6 @@ export type ProfilingDataForRootBackend = {|
displayName: string,
// Tuple of Fiber ID and base duration
initialTreeBaseDurations: Array<[number, number]>,
// Tuple of Interaction ID and commit indices
interactionCommits: Array<[number, Array<number>]>,
interactions: Array<[number, Interaction]>,
rootID: number,
|};

View File

@ -16,10 +16,6 @@ import {
getChartData as getFlamegraphChartData,
invalidateChartData as invalidateFlamegraphChartData,
} from 'react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder';
import {
getChartData as getInteractionsChartData,
invalidateChartData as invalidateInteractionsChartData,
} from 'react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder';
import {
getChartData as getRankedChartData,
invalidateChartData as invalidateRankedChartData,
@ -27,7 +23,6 @@ import {
import type {CommitTree} from 'react-devtools-shared/src/devtools/views/Profiler/types';
import type {ChartData as FlamegraphChartData} from 'react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder';
import type {ChartData as InteractionsChartData} from 'react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder';
import type {ChartData as RankedChartData} from 'react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder';
export default class ProfilingCache {
@ -92,16 +87,6 @@ export default class ProfilingCache {
rootID,
});
getInteractionsChartData = ({
rootID,
}: {|
rootID: number,
|}): InteractionsChartData =>
getInteractionsChartData({
profilerStore: this._profilerStore,
rootID,
});
getRankedChartData = ({
commitIndex,
commitTree,
@ -123,7 +108,6 @@ export default class ProfilingCache {
invalidateCommitTrees();
invalidateFlamegraphChartData();
invalidateInteractionsChartData();
invalidateRankedChartData();
}
}

View File

@ -19,7 +19,6 @@ export type IconType =
| 'error'
| 'facebook'
| 'flame-chart'
| 'interactions'
| 'profiler'
| 'ranked-chart'
| 'search'
@ -59,9 +58,6 @@ export default function Icon({className = '', type}: Props) {
case 'flame-chart':
pathData = PATH_FLAME_CHART;
break;
case 'interactions':
pathData = PATH_INTERACTIONS;
break;
case 'profiler':
pathData = PATH_PROFILER;
break;
@ -138,14 +134,6 @@ const PATH_FLAME_CHART = `
13.3541667,19.4702042 C13.3541667,20.1226027 12.7851952,20.6514763 12.0833333,20.6514763 Z
`;
const PATH_INTERACTIONS = `
M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2
2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55
4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02
9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55
2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z
`;
const PATH_PROFILER = 'M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z';
const PATH_SEARCH = `

View File

@ -1,43 +0,0 @@
.Interaction,
.SelectedInteraction {
display: flex;
align-items: center;
padding: 0 0.25rem;
border-bottom: 1px solid var(--color-border);
}
.Interaction:hover {
background-color: var(--color-background-hover);
}
.SelectedInteraction {
background-color: var(--color-background-hover);
}
.Name {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.Timeline {
position: relative;
height: 100%;
}
.InteractionLine {
position: absolute;
height: 3px;
background-color: var(--color-commit-did-not-render-fill);
color: var(--color-commit-did-not-render-fill-text);
border-radius: 0.125rem;
}
.CommitBox {
position: absolute;
width: var(--interaction-commit-size);
height: var(--interaction-commit-size);
background-color: var(--color-commit-did-not-render-fill);
color: var(--color-commit-did-not-render-fill-text);
cursor: pointer;
}

View File

@ -1,105 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import {memo, useCallback} from 'react';
import {areEqual} from 'react-window';
import {getGradientColor} from './utils';
import styles from './InteractionListItem.css';
import type {ItemData} from './Interactions';
type Props = {
data: ItemData,
index: number,
style: Object,
...
};
function InteractionListItem({data: itemData, index, style}: Props) {
const {
chartData,
dataForRoot,
labelWidth,
scaleX,
selectedInteractionID,
selectCommitIndex,
selectInteraction,
selectTab,
} = itemData;
const {commitData, interactionCommits} = dataForRoot;
const {interactions, lastInteractionTime, maxCommitDuration} = chartData;
const interaction = interactions[index];
if (interaction == null) {
throw Error(`Could not find interaction #${index}`);
}
const handleClick = useCallback(() => {
selectInteraction(interaction.id);
}, [interaction, selectInteraction]);
const commits = interactionCommits.get(interaction.id) || [];
const startTime = interaction.timestamp;
const stopTime = lastInteractionTime;
const viewCommit = (commitIndex: number) => {
selectTab('flame-chart');
selectCommitIndex(commitIndex);
};
return (
<div
className={
selectedInteractionID === interaction.id
? styles.SelectedInteraction
: styles.Interaction
}
onClick={handleClick}
style={style}>
<div
className={styles.Name}
style={{maxWidth: labelWidth}}
title={interaction.name}>
{interaction.name}
</div>
<div
className={styles.InteractionLine}
style={{
left: labelWidth + scaleX(startTime, 0),
width: scaleX(stopTime - startTime, 0),
}}
/>
{commits.map(commitIndex => (
<div
className={styles.CommitBox}
key={commitIndex}
onClick={() => viewCommit(commitIndex)}
style={{
backgroundColor: getGradientColor(
Math.min(
1,
Math.max(
0,
commitData[commitIndex].duration / maxCommitDuration,
),
) || 0,
),
left: labelWidth + scaleX(commitData[commitIndex].timestamp, 0),
}}
/>
))}
</div>
);
}
export default memo<Props>(InteractionListItem, areEqual);

View File

@ -1,9 +0,0 @@
.Container {
width: 100%;
flex: 1;
padding: 0.5rem;
}
.FocusTarget:focus {
outline: none;
}

View File

@ -1,146 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import {useCallback, useContext, useMemo} from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import {FixedSizeList} from 'react-window';
import {ProfilerContext} from './ProfilerContext';
import InteractionListItem from './InteractionListItem';
import NoInteractions from './NoInteractions';
import {StoreContext} from '../context';
import {scale} from './utils';
import styles from './Interactions.css';
import type {ProfilingDataForRootFrontend} from './types';
import type {ChartData} from './InteractionsChartBuilder';
import type {TabID} from './ProfilerContext';
export type ItemData = {|
chartData: ChartData,
dataForRoot: ProfilingDataForRootFrontend,
labelWidth: number,
scaleX: (value: number, fallbackValue: number) => number,
selectedInteractionID: number | null,
selectCommitIndex: (id: number | null) => void,
selectInteraction: (id: number | null) => void,
selectTab: (id: TabID) => void,
|};
export default function InteractionsAutoSizer(_: {||}) {
return (
<div className={styles.Container}>
<AutoSizer>
{({height, width}) => <Interactions height={height} width={width} />}
</AutoSizer>
</div>
);
}
function Interactions({height, width}: {|height: number, width: number|}) {
const {
rootID,
selectedInteractionID,
selectInteraction,
selectCommitIndex,
selectTab,
} = useContext(ProfilerContext);
const {profilerStore} = useContext(StoreContext);
const {profilingCache} = profilerStore;
const dataForRoot = profilerStore.getDataForRoot(((rootID: any): number));
const chartData = profilingCache.getInteractionsChartData({
rootID: ((rootID: any): number),
});
const {interactions} = chartData;
const handleKeyDown = useCallback(
event => {
let index;
switch (event.key) {
case 'ArrowDown':
index = interactions.findIndex(
interaction => interaction.id === selectedInteractionID,
);
selectInteraction(Math.min(interactions.length - 1, index + 1));
event.stopPropagation();
break;
case 'ArrowUp':
index = interactions.findIndex(
interaction => interaction.id === selectedInteractionID,
);
selectInteraction(Math.max(0, index - 1));
event.stopPropagation();
break;
default:
break;
}
},
[interactions, selectedInteractionID, selectInteraction],
);
const itemData = useMemo<ItemData>(() => {
const interactionCommitSize = parseInt(
getComputedStyle((document.body: any)).getPropertyValue(
'--interaction-commit-size',
),
10,
);
const interactionLabelWidth = parseInt(
getComputedStyle((document.body: any)).getPropertyValue(
'--interaction-label-width',
),
10,
);
const labelWidth = Math.min(interactionLabelWidth, width / 5);
const timelineWidth = width - labelWidth - interactionCommitSize;
return {
chartData,
dataForRoot,
labelWidth,
scaleX: scale(0, chartData.lastInteractionTime, 0, timelineWidth),
selectedInteractionID,
selectCommitIndex,
selectInteraction,
selectTab,
};
}, [
chartData,
dataForRoot,
selectedInteractionID,
selectCommitIndex,
selectInteraction,
selectTab,
width,
]);
// If a commit contains no fibers with an actualDuration > 0,
// Display a fallback message.
if (interactions.length === 0) {
return <NoInteractions height={height} width={width} />;
}
return (
<div className={styles.FocusTarget} onKeyDown={handleKeyDown} tabIndex={0}>
<FixedSizeList
height={height}
itemCount={interactions.length}
itemData={itemData}
itemSize={30}
width={width}>
{InteractionListItem}
</FixedSizeList>
</div>
);
}

View File

@ -1,62 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
import type {Interaction} from './types';
export type ChartData = {|
interactions: Array<Interaction>,
lastInteractionTime: number,
maxCommitDuration: number,
|};
const cachedChartData: Map<number, ChartData> = new Map();
export function getChartData({
profilerStore,
rootID,
}: {|
profilerStore: ProfilerStore,
rootID: number,
|}): ChartData {
if (cachedChartData.has(rootID)) {
return ((cachedChartData.get(rootID): any): ChartData);
}
const dataForRoot = profilerStore.getDataForRoot(rootID);
if (dataForRoot == null) {
throw Error(`Could not find profiling data for root "${rootID}"`);
}
const {commitData, interactions} = dataForRoot;
const lastInteractionTime =
commitData.length > 0 ? commitData[commitData.length - 1].timestamp : 0;
let maxCommitDuration = 0;
commitData.forEach(commitDatum => {
maxCommitDuration = Math.max(maxCommitDuration, commitDatum.duration);
});
const chartData = {
interactions: Array.from(interactions.values()),
lastInteractionTime,
maxCommitDuration,
};
cachedChartData.set(rootID, chartData);
return chartData;
}
export function invalidateChartData(): void {
cachedChartData.clear();
}

View File

@ -1,16 +0,0 @@
.NoInteractions {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.Header {
font-size: var(--font-size-sans-large);
}
.Link {
color: var(--color-button);
}

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import styles from './NoInteractions.css';
export default function NoInteractions({
height,
width,
}: {|
height: number,
width: number,
|}) {
return (
<div className={styles.NoInteractions} style={{height, width}}>
<p className={styles.Header}>No interactions were recorded.</p>
<p>
<a
className={styles.Link}
href="https://reactjs.org/link/interaction-tracing"
rel="noopener noreferrer"
target="_blank">
Learn more about the interaction tracing API here.
</a>
</p>
</div>
);
}

View File

@ -15,14 +15,12 @@ import TabBar from '../TabBar';
import ClearProfilingDataButton from './ClearProfilingDataButton';
import CommitFlamegraph from './CommitFlamegraph';
import CommitRanked from './CommitRanked';
import Interactions from './Interactions';
import RootSelector from './RootSelector';
import RecordToggle from './RecordToggle';
import ReloadAndProfileButton from './ReloadAndProfileButton';
import ProfilingImportExportButtons from './ProfilingImportExportButtons';
import SnapshotSelector from './SnapshotSelector';
import SidebarCommitInfo from './SidebarCommitInfo';
import SidebarInteractions from './SidebarInteractions';
import SidebarSelectedFiberInfo from './SidebarSelectedFiberInfo';
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
@ -53,9 +51,6 @@ function Profiler(_: {||}) {
case 'ranked-chart':
view = <CommitRanked />;
break;
case 'interactions':
view = <Interactions />;
break;
default:
break;
}
@ -72,9 +67,6 @@ function Profiler(_: {||}) {
let sidebar = null;
if (!isProfiling && !isProcessingData && didRecordCommits) {
switch (selectedTabID) {
case 'interactions':
sidebar = <SidebarInteractions />;
break;
case 'flame-chart':
case 'ranked-chart':
// TRICKY
@ -148,12 +140,6 @@ const tabs = [
label: 'Ranked',
title: 'Ranked chart',
},
{
id: 'interactions',
icon: 'interactions',
label: 'Interactions',
title: 'Profiled interactions',
},
];
const NoProfilingData = () => (

View File

@ -19,7 +19,7 @@ import {StoreContext} from '../context';
import type {ProfilingDataFrontend} from './types';
export type TabID = 'flame-chart' | 'ranked-chart' | 'interactions';
export type TabID = 'flame-chart' | 'ranked-chart';
export type Context = {|
// Which tab is selected in the Profiler UI?
@ -64,10 +64,6 @@ export type Context = {|
selectedFiberID: number | null,
selectedFiberName: string | null,
selectFiber: (id: number | null, name: string | null) => void,
// Which interaction is currently selected in the Interactions graph?
selectedInteractionID: number | null,
selectInteraction: (id: number | null) => void,
|};
const ProfilerContext = createContext<Context>(((null: any): Context));
@ -216,9 +212,6 @@ function ProfilerContextController({children}: Props) {
null,
);
const [selectedTabID, selectTab] = useState<TabID>('flame-chart');
const [selectedInteractionID, selectInteraction] = useState<number | null>(
null,
);
if (isProfiling) {
batchedUpdates(() => {
@ -229,9 +222,6 @@ function ProfilerContextController({children}: Props) {
selectFiberID(null);
selectFiberName(null);
}
if (selectedInteractionID !== null) {
selectInteraction(null);
}
});
}
@ -262,9 +252,6 @@ function ProfilerContextController({children}: Props) {
selectedFiberID,
selectedFiberName,
selectFiber,
selectedInteractionID,
selectInteraction,
}),
[
selectedTabID,
@ -293,9 +280,6 @@ function ProfilerContextController({children}: Props) {
selectedFiberID,
selectedFiberName,
selectFiber,
selectedInteractionID,
selectInteraction,
],
);

View File

@ -23,33 +23,6 @@
margin: 0 0 0.5rem;
}
.NoInteractions {
color: var(--color-dim);
}
.Interactions {
margin: 0 0 0.5rem;
}
.NoInteractions,
.Interaction {
display: block;
width: 100%;
text-align: left;
background: none;
border: none;
padding: 0.25rem 0.5rem;
color: var(--color-text);
}
.Interaction:focus,
.Interaction:hover {
outline: none;
background-color: var(--color-background-hover);
}
.NoInteractions {
color: var(--color-dim);
}
.Label {
overflow: hidden;
text-overflow: ellipsis;

View File

@ -20,12 +20,7 @@ import styles from './SidebarCommitInfo.css';
export type Props = {||};
export default function SidebarCommitInfo(_: Props) {
const {
selectedCommitIndex,
rootID,
selectInteraction,
selectTab,
} = useContext(ProfilerContext);
const {selectedCommitIndex, rootID} = useContext(ProfilerContext);
const {profilerStore} = useContext(StoreContext);
@ -33,22 +28,15 @@ export default function SidebarCommitInfo(_: Props) {
return <div className={styles.NothingSelected}>Nothing selected</div>;
}
const {interactions} = profilerStore.getDataForRoot(rootID);
const {
duration,
effectDuration,
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp,
updaters,
} = profilerStore.getCommitData(rootID, selectedCommitIndex);
const viewInteraction = interactionID => {
selectTab('interactions');
selectInteraction(interactionID);
};
const hasCommitPhaseDurations =
effectDuration !== null || passiveEffectDuration !== null;
@ -120,29 +108,6 @@ export default function SidebarCommitInfo(_: Props) {
<Updaters commitTree={commitTree} updaters={updaters} />
</li>
)}
<li className={styles.Interactions}>
<label className={styles.Label}>Interactions</label>:
<div className={styles.InteractionList}>
{interactionIDs.length === 0 ? (
<div className={styles.NoInteractions}>(none)</div>
) : null}
{interactionIDs.map(interactionID => {
const interaction = interactions.get(interactionID);
if (interaction == null) {
throw Error(`Invalid interaction "${interactionID}"`);
}
return (
<button
key={interactionID}
className={styles.Interaction}
onClick={() => viewInteraction(interactionID)}>
{interaction.name}
</button>
);
})}
</div>
</li>
</ul>
</div>
</Fragment>

View File

@ -1,55 +0,0 @@
.Toolbar {
height: 2.25rem;
padding: 0 0.5rem;
flex: 0 0 auto;
display: flex;
align-items: center;
}
.Content {
padding: 0.5rem;
user-select: none;
border-top: 1px solid var(--color-border);
overflow: auto;
}
.Name {
font-size: var(--font-size-sans-large);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.NothingSelected {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--color-dim);
}
.Commits {
font-weight: bold;
}
.List {
list-style: none;
margin: 0;
padding: 0;
}
.ListItem {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.25rem 0.5rem;
}
.ListItem:hover {
background-color: var(--color-background-hover);
}
.CommitBox {
width: 20px;
height: 20px;
margin-right: 0.5rem;
}

View File

@ -1,100 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import {Fragment, useContext} from 'react';
import {ProfilerContext} from './ProfilerContext';
import {formatDuration, formatTime} from './utils';
import {StoreContext} from '../context';
import {getGradientColor} from './utils';
import styles from './SidebarInteractions.css';
export type Props = {||};
export default function SidebarInteractions(_: Props) {
const {
selectedInteractionID,
rootID,
selectCommitIndex,
selectTab,
} = useContext(ProfilerContext);
const {profilerStore} = useContext(StoreContext);
const {profilingCache} = profilerStore;
if (selectedInteractionID === null) {
return <div className={styles.NothingSelected}>Nothing selected</div>;
}
const {interactionCommits, interactions} = profilerStore.getDataForRoot(
((rootID: any): number),
);
const interaction = interactions.get(selectedInteractionID);
if (interaction == null) {
throw Error(
`Could not find interaction by selected interaction id "${selectedInteractionID}"`,
);
}
const {maxCommitDuration} = profilingCache.getInteractionsChartData({
rootID: ((rootID: any): number),
});
const viewCommit = (commitIndex: number) => {
selectTab('flame-chart');
selectCommitIndex(commitIndex);
};
const listItems: Array<React$Node> = [];
const commitIndices = interactionCommits.get(selectedInteractionID);
if (commitIndices != null) {
commitIndices.forEach(commitIndex => {
const {duration, timestamp} = profilerStore.getCommitData(
((rootID: any): number),
commitIndex,
);
listItems.push(
<li
key={commitIndex}
className={styles.ListItem}
onClick={() => viewCommit(commitIndex)}>
<div
className={styles.CommitBox}
style={{
backgroundColor: getGradientColor(
Math.min(1, Math.max(0, duration / maxCommitDuration)) || 0,
),
}}
/>
<div>
timestamp: {formatTime(timestamp)}s
<br />
duration: {formatDuration(duration)}ms
</div>
</li>,
);
});
}
return (
<Fragment>
<div className={styles.Toolbar}>
<div className={styles.Name} title={interaction.name}>
{interaction.name}
</div>
</div>
<div className={styles.Content}>
<div className={styles.Commits}>Commits:</div>
<ul className={styles.List}>{listItems}</ul>
</div>
</Fragment>
);
}

View File

@ -8,7 +8,5 @@
*/
export const barWidthThreshold = 2;
export const interactionCommitSize = 10;
export const interactionLabelWidth = 200;
export const maxBarWidth = 30;
export const minBarWidth = 5;

View File

@ -26,12 +26,6 @@ export type CommitTree = {|
rootID: number,
|};
export type Interaction = {|
id: number,
name: string,
timestamp: number,
|};
export type SnapshotNode = {|
id: number,
children: Array<number>,
@ -69,9 +63,6 @@ export type CommitDataFrontend = {|
// Fibers that did not render will not have entries in this Map.
fiberSelfDurations: Map<number, number>,
// Which interactions (IDs) were associated with this commit.
interactionIDs: Array<number>,
// How long was the passive commit phase?
// Note that not all builds of React expose this property.
passiveEffectDuration: number | null,
@ -98,12 +89,6 @@ export type ProfilingDataForRootFrontend = {|
// This info can be used along with commitOperations to reconstruct the tree for any commit.
initialTreeBaseDurations: Map<number, number>,
// All interactions recorded (for this root) during the current session.
interactionCommits: Map<number, Array<number>>,
// All interactions recorded (for this root) during the current session.
interactions: Map<number, Interaction>,
// List of tree mutation that occur during profiling.
// These mutations can be used along with initial snapshots to reconstruct the tree for any commit.
operations: Array<Array<number>>,
@ -131,7 +116,6 @@ export type CommitDataExport = {|
fiberActualDurations: Array<[number, number]>,
// Tuple of fiber ID and computed "self" duration
fiberSelfDurations: Array<[number, number]>,
interactionIDs: Array<number>,
passiveEffectDuration: number | null,
priorityLevel: string | null,
timestamp: number,
@ -143,9 +127,6 @@ export type ProfilingDataForRootExport = {|
displayName: string,
// Tuple of Fiber ID and base duration
initialTreeBaseDurations: Array<[number, number]>,
// Tuple of Interaction ID and commit indices
interactionCommits: Array<[number, Array<number>]>,
interactions: Array<[number, Interaction]>,
operations: Array<Array<number>>,
rootID: number,
snapshots: Array<[number, SnapshotNode]>,

View File

@ -43,14 +43,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
dataBackends.forEach(dataBackend => {
dataBackend.dataForRoots.forEach(
({
commitData,
displayName,
initialTreeBaseDurations,
interactionCommits,
interactions,
rootID,
}) => {
({commitData, displayName, initialTreeBaseDurations, rootID}) => {
const operations = operationsByRootID.get(rootID);
if (operations == null) {
throw Error(
@ -66,12 +59,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
}
// Do not filter empty commits from the profiler data!
// We used to do this, but it was error prone (see #18798).
// A commit may appear to be empty (no actual durations) because of component filters,
// but filtering these empty commits causes interaction commit indices to be off by N.
// This not only corrupts the resulting data, but also potentially causes runtime errors.
//
// For that matter, hiding "empty" commits might cause confusion too.
// Hiding "empty" commits might cause confusion too.
// A commit *did happen* even if none of the components the Profiler is showing were involved.
const convertedCommitData = commitData.map(
(commitDataBackend, commitIndex) => ({
@ -85,7 +73,6 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
commitDataBackend.fiberActualDurations,
),
fiberSelfDurations: new Map(commitDataBackend.fiberSelfDurations),
interactionIDs: commitDataBackend.interactionIDs,
passiveEffectDuration: commitDataBackend.passiveEffectDuration,
priorityLevel: commitDataBackend.priorityLevel,
timestamp: commitDataBackend.timestamp,
@ -113,8 +100,6 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
commitData: convertedCommitData,
displayName,
initialTreeBaseDurations: new Map(initialTreeBaseDurations),
interactionCommits: new Map(interactionCommits),
interactions: new Map(interactions),
operations,
rootID,
snapshots,
@ -144,8 +129,6 @@ export function prepareProfilingDataFrontendFromExport(
commitData,
displayName,
initialTreeBaseDurations,
interactionCommits,
interactions,
operations,
rootID,
snapshots,
@ -158,7 +141,6 @@ export function prepareProfilingDataFrontendFromExport(
effectDuration,
fiberActualDurations,
fiberSelfDurations,
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp,
@ -170,7 +152,6 @@ export function prepareProfilingDataFrontendFromExport(
effectDuration,
fiberActualDurations: new Map(fiberActualDurations),
fiberSelfDurations: new Map(fiberSelfDurations),
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp,
@ -179,8 +160,6 @@ export function prepareProfilingDataFrontendFromExport(
),
displayName,
initialTreeBaseDurations: new Map(initialTreeBaseDurations),
interactionCommits: new Map(interactionCommits),
interactions: new Map(interactions),
operations,
rootID,
snapshots: new Map(snapshots),
@ -201,8 +180,6 @@ export function prepareProfilingDataExport(
commitData,
displayName,
initialTreeBaseDurations,
interactionCommits,
interactions,
operations,
rootID,
snapshots,
@ -215,7 +192,6 @@ export function prepareProfilingDataExport(
effectDuration,
fiberActualDurations,
fiberSelfDurations,
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp,
@ -229,7 +205,6 @@ export function prepareProfilingDataExport(
effectDuration,
fiberActualDurations: Array.from(fiberActualDurations.entries()),
fiberSelfDurations: Array.from(fiberSelfDurations.entries()),
interactionIDs,
passiveEffectDuration,
priorityLevel,
timestamp,
@ -240,8 +215,6 @@ export function prepareProfilingDataExport(
initialTreeBaseDurations: Array.from(
initialTreeBaseDurations.entries(),
),
interactionCommits: Array.from(interactionCommits.entries()),
interactions: Array.from(interactions.entries()),
operations,
rootID,
snapshots: Array.from(snapshots.entries()),

View File

@ -211,8 +211,4 @@
Courier, monospace;
--font-family-sans: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica,
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
/* Constant values shared between JS and CSS */
--interaction-commit-size: 10px;
--interaction-label-width: 200px;
}

View File

@ -1,103 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import {
Fragment,
useCallback,
useLayoutEffect,
useEffect,
useState,
} from 'react';
import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
import {
unstable_trace as trace,
unstable_wrap as wrap,
} from 'scheduler/tracing';
function sleep(ms) {
const start = performance.now();
let now;
do {
now = performance.now();
} while (now - ms < start);
}
export default function InteractionTracing() {
const [count, setCount] = useState(0);
const [shouldCascade, setShouldCascade] = useState(false);
const handleUpdate = useCallback(() => {
trace('count', performance.now(), () => {
setTimeout(
wrap(() => {
setCount(count + 1);
}),
count * 100,
);
});
}, [count]);
const handleCascadingUpdate = useCallback(() => {
trace('cascade', performance.now(), () => {
setTimeout(
wrap(() => {
batchedUpdates(() => {
setCount(count + 1);
setShouldCascade(true);
});
}),
count * 100,
);
});
}, [count]);
const handleMultiple = useCallback(() => {
trace('first', performance.now(), () => {
trace('second', performance.now(), () => {
setTimeout(
wrap(() => {
setCount(count + 1);
}),
count * 100,
);
});
});
}, [count]);
useEffect(() => {
if (shouldCascade) {
setTimeout(
wrap(() => {
setShouldCascade(false);
}),
count * 100,
);
}
}, [count, shouldCascade]);
useLayoutEffect(() => {
sleep(150);
});
useEffect(() => {
sleep(300);
});
return (
<Fragment>
<h1>Interaction Tracing</h1>
<button onClick={handleUpdate}>Update ({count})</button>
<button onClick={handleCascadingUpdate}>
Cascading Update ({count}, {shouldCascade ? 'true' : 'false'})
</button>
<button onClick={handleMultiple}>Multiple</button>
</Fragment>
);
}

View File

@ -14,7 +14,6 @@ import ElementTypes from './ElementTypes';
import Hydration from './Hydration';
import InlineWarnings from './InlineWarnings';
import InspectableElements from './InspectableElements';
import InteractionTracing from './InteractionTracing';
import ReactNativeWeb from './ReactNativeWeb';
import ToDoList from './ToDoList';
import Toggle from './Toggle';
@ -48,7 +47,6 @@ function mountHelper(App) {
function mountTestApp() {
mountHelper(ToDoList);
mountHelper(InteractionTracing);
mountHelper(InspectableElements);
mountHelper(Hydration);
mountHelper(ElementTypes);

View File

@ -286,7 +286,6 @@ When profiling begins, the frontend takes a snapshot/copy of each root. This sna
When profiling begins, the backend records the base durations of each fiber currently in the tree. While profiling is in progress, the backend also stores some information about each commit, including:
* Commit time and duration
* Which elements were rendered during that commit
* Which interactions (if any) were part of the commit
* Which props and state changed (if enabled in profiler settings)
This information will eventually be required by the frontend in order to render its profiling graphs, but it will not be sent across the bridge until profiling has completed (to minimize the performance impact of profiling).

View File

@ -10,7 +10,6 @@
let React;
let ReactDOM;
let ReactTestUtils;
let SchedulerTracing;
let Scheduler;
let act;
let container;
@ -119,7 +118,6 @@ function runActTests(label, render, unmount, rerender) {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
SchedulerTracing = require('scheduler/tracing');
Scheduler = require('scheduler');
act = ReactTestUtils.act;
container = document.createElement('div');
@ -497,87 +495,6 @@ function runActTests(label, render, unmount, rerender) {
});
});
describe('interaction tracing', () => {
if (__DEV__) {
it('should correctly trace interactions for sync roots', () => {
let expectedInteraction;
const Component = jest.fn(() => {
expect(expectedInteraction).toBeDefined();
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expect(interactions).toContain(expectedInteraction);
return null;
});
act(() => {
SchedulerTracing.unstable_trace(
'mount traced inside act',
performance.now(),
() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
render(<Component />, container);
},
);
});
act(() => {
SchedulerTracing.unstable_trace(
'update traced inside act',
performance.now(),
() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
rerender(<Component />);
},
);
});
const secondContainer = document.createElement('div');
SchedulerTracing.unstable_trace(
'mount traced outside act',
performance.now(),
() => {
act(() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
render(<Component />, secondContainer);
});
},
);
SchedulerTracing.unstable_trace(
'update traced outside act',
performance.now(),
() => {
act(() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
rerender(<Component />);
});
},
);
expect(Component).toHaveBeenCalledTimes(
label === 'legacy mode' ? 4 : 8,
);
unmount(secondContainer);
});
}
});
describe('error propagation', () => {
it('propagates errors - sync', () => {
let err;

View File

@ -1684,84 +1684,4 @@ describe('ReactUpdates', () => {
expect(container.textContent).toBe('1000');
});
}
if (__DEV__) {
it('should properly trace interactions within batched updates', () => {
const SchedulerTracing = require('scheduler/tracing');
let expectedInteraction;
const container = document.createElement('div');
const Component = jest.fn(() => {
expect(expectedInteraction).toBeDefined();
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expect(interactions).toContain(expectedInteraction);
return null;
});
ReactDOM.unstable_batchedUpdates(() => {
SchedulerTracing.unstable_trace(
'mount traced inside a batched update',
1,
() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
ReactDOM.render(<Component />, container);
},
);
});
ReactDOM.unstable_batchedUpdates(() => {
SchedulerTracing.unstable_trace(
'update traced inside a batched update',
2,
() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
ReactDOM.render(<Component />, container);
},
);
});
const secondContainer = document.createElement('div');
SchedulerTracing.unstable_trace(
'mount traced outside a batched update',
3,
() => {
ReactDOM.unstable_batchedUpdates(() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
ReactDOM.render(<Component />, secondContainer);
});
},
);
SchedulerTracing.unstable_trace(
'update traced outside a batched update',
4,
() => {
ReactDOM.unstable_batchedUpdates(() => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
expectedInteraction = Array.from(interactions)[0];
ReactDOM.render(<Component />, container);
});
},
);
expect(Component).toHaveBeenCalledTimes(4);
});
}
});

View File

@ -78,7 +78,6 @@ import {
disableModulePatternComponents,
enableProfilerCommitHooks,
enableProfilerTimer,
enableSchedulerTracing,
enableSuspenseServerRenderer,
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
@ -200,7 +199,6 @@ import {
isSimpleFunctionComponent,
} from './ReactFiber.new';
import {
markSpawnedWork,
retryDehydratedSuspenseBoundary,
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
@ -211,7 +209,6 @@ import {
RetryAfterError,
NoContext,
} from './ReactFiberWorkLoop.new';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {setWorkInProgressVersion} from './ReactMutableSource.new';
import {
requestCacheFromPool,
@ -635,9 +632,6 @@ function updateOffscreenComponent(
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
if (enableSchedulerTracing) {
markSpawnedWork((OffscreenLane: Lane));
}
workInProgress.lanes = workInProgress.childLanes = laneToLanes(
OffscreenLane,
);
@ -1939,9 +1933,6 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
// RetryLane even if it's the one currently rendering since we're leaving
// it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
return fallbackFragment;
} else {
return mountSuspensePrimaryChildren(
@ -2397,17 +2388,11 @@ function mountDehydratedSuspenseComponent(
// time. This will mean that Suspense timeouts are slightly shifted to later than
// they should be.
// Schedule a normal pri update to render this content.
if (enableSchedulerTracing) {
markSpawnedWork(DefaultHydrationLane);
}
workInProgress.lanes = laneToLanes(DefaultHydrationLane);
} else {
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
workInProgress.lanes = laneToLanes(OffscreenLane);
if (enableSchedulerTracing) {
markSpawnedWork(OffscreenLane);
}
}
return null;
}
@ -2520,10 +2505,7 @@ function updateDehydratedSuspenseComponent(
// Leave the child in place. I.e. the dehydrated fragment.
workInProgress.child = current.child;
// Register a callback to retry this boundary once the server has sent the result.
let retry = retryDehydratedSuspenseBoundary.bind(null, current);
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
const retry = retryDehydratedSuspenseBoundary.bind(null, current);
registerSuspenseInstanceRetry(suspenseInstance, retry);
return null;
} else {

View File

@ -78,7 +78,6 @@ import {
disableModulePatternComponents,
enableProfilerCommitHooks,
enableProfilerTimer,
enableSchedulerTracing,
enableSuspenseServerRenderer,
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
@ -200,7 +199,6 @@ import {
isSimpleFunctionComponent,
} from './ReactFiber.old';
import {
markSpawnedWork,
retryDehydratedSuspenseBoundary,
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
@ -211,7 +209,6 @@ import {
RetryAfterError,
NoContext,
} from './ReactFiberWorkLoop.old';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {setWorkInProgressVersion} from './ReactMutableSource.old';
import {
requestCacheFromPool,
@ -635,9 +632,6 @@ function updateOffscreenComponent(
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
if (enableSchedulerTracing) {
markSpawnedWork((OffscreenLane: Lane));
}
workInProgress.lanes = workInProgress.childLanes = laneToLanes(
OffscreenLane,
);
@ -1939,9 +1933,6 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
// RetryLane even if it's the one currently rendering since we're leaving
// it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
return fallbackFragment;
} else {
return mountSuspensePrimaryChildren(
@ -2397,17 +2388,11 @@ function mountDehydratedSuspenseComponent(
// time. This will mean that Suspense timeouts are slightly shifted to later than
// they should be.
// Schedule a normal pri update to render this content.
if (enableSchedulerTracing) {
markSpawnedWork(DefaultHydrationLane);
}
workInProgress.lanes = laneToLanes(DefaultHydrationLane);
} else {
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
workInProgress.lanes = laneToLanes(OffscreenLane);
if (enableSchedulerTracing) {
markSpawnedWork(OffscreenLane);
}
}
return null;
}
@ -2520,10 +2505,7 @@ function updateDehydratedSuspenseComponent(
// Leave the child in place. I.e. the dehydrated fragment.
workInProgress.child = current.child;
// Register a callback to retry this boundary once the server has sent the result.
let retry = retryDehydratedSuspenseBoundary.bind(null, current);
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
const retry = retryDehydratedSuspenseBoundary.bind(null, current);
registerSuspenseInstanceRetry(suspenseInstance, retry);
return null;
} else {

View File

@ -25,9 +25,7 @@ import type {Wakeable} from 'shared/ReactTypes';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {HookFlags} from './ReactHookEffectTags';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {
enableSchedulerTracing,
enableProfilerTimer,
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
@ -641,17 +639,7 @@ export function commitPassiveEffectDurations(
}
if (typeof onPostCommit === 'function') {
if (enableSchedulerTracing) {
onPostCommit(
id,
phase,
passiveEffectDuration,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onPostCommit(id, phase, passiveEffectDuration, commitTime);
}
onPostCommit(id, phase, passiveEffectDuration, commitTime);
}
// Bubble times to the next nearest ancestor Profiler.
@ -919,46 +907,24 @@ function commitLayoutEffectOnFiber(
}
if (typeof onRender === 'function') {
if (enableSchedulerTracing) {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
if (enableSchedulerTracing) {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
@ -2105,13 +2071,8 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
}
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
if (!retryCache.has(wakeable)) {
if (enableSchedulerTracing) {
if (wakeable.__reactDoNotTraceInteractions !== true) {
retry = Schedule_tracing_wrap(retry);
}
}
retryCache.add(wakeable);
if (enableUpdaterTracking) {

View File

@ -25,9 +25,7 @@ import type {Wakeable} from 'shared/ReactTypes';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {HookFlags} from './ReactHookEffectTags';
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {
enableSchedulerTracing,
enableProfilerTimer,
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
@ -641,17 +639,7 @@ export function commitPassiveEffectDurations(
}
if (typeof onPostCommit === 'function') {
if (enableSchedulerTracing) {
onPostCommit(
id,
phase,
passiveEffectDuration,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onPostCommit(id, phase, passiveEffectDuration, commitTime);
}
onPostCommit(id, phase, passiveEffectDuration, commitTime);
}
// Bubble times to the next nearest ancestor Profiler.
@ -919,46 +907,24 @@ function commitLayoutEffectOnFiber(
}
if (typeof onRender === 'function') {
if (enableSchedulerTracing) {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
if (enableSchedulerTracing) {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
@ -2105,13 +2071,8 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
}
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
if (!retryCache.has(wakeable)) {
if (enableSchedulerTracing) {
if (wakeable.__reactDoNotTraceInteractions !== true) {
retry = Schedule_tracing_wrap(retry);
}
}
retryCache.add(wakeable);
if (enableUpdaterTracking) {

View File

@ -118,7 +118,6 @@ import {
getIsHydrating,
} from './ReactFiberHydrationContext.new';
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableSuspenseServerRenderer,
enableScopeAPI,
@ -127,7 +126,6 @@ import {
enableSuspenseLayoutEffectSemantics,
} from 'shared/ReactFeatureFlags';
import {
markSpawnedWork,
renderDidSuspend,
renderDidSuspendDelayIfPossible,
renderHasNotSuspendedYet,
@ -981,9 +979,6 @@ function completeWork(
'This is probably a bug in React.',
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
markSpawnedWork(OffscreenLane);
}
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
@ -1259,9 +1254,6 @@ function completeWork(
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
}
} else {
cutOffTailIfNeeded(renderState, false);
@ -1320,9 +1312,6 @@ function completeWork(
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
}
}
if (renderState.isBackwards) {

View File

@ -118,7 +118,6 @@ import {
getIsHydrating,
} from './ReactFiberHydrationContext.old';
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableSuspenseServerRenderer,
enableScopeAPI,
@ -127,7 +126,6 @@ import {
enableSuspenseLayoutEffectSemantics,
} from 'shared/ReactFeatureFlags';
import {
markSpawnedWork,
renderDidSuspend,
renderDidSuspendDelayIfPossible,
renderHasNotSuspendedYet,
@ -981,9 +979,6 @@ function completeWork(
'This is probably a bug in React.',
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
markSpawnedWork(OffscreenLane);
}
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
@ -1259,9 +1254,6 @@ function completeWork(
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
}
} else {
cutOffTailIfNeeded(renderState, false);
@ -1320,9 +1312,6 @@ function completeWork(
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
workInProgress.lanes = SomeRetryLane;
if (enableSchedulerTracing) {
markSpawnedWork(SomeRetryLane);
}
}
}
if (renderState.isBackwards) {

View File

@ -20,14 +20,12 @@ import {
createLaneMap,
} from './ReactFiberLane.new';
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableCache,
enableProfilerCommitHooks,
enableProfilerTimer,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.new';
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
@ -66,11 +64,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.mutableSourceEagerHydrationData = null;
}
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}

View File

@ -20,14 +20,12 @@ import {
createLaneMap,
} from './ReactFiberLane.old';
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableCache,
enableProfilerCommitHooks,
enableProfilerTimer,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.old';
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
@ -66,11 +64,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.mutableSourceEagerHydrationData = null;
}
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}

View File

@ -10,7 +10,6 @@
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane.new';
import type {Interaction} from 'scheduler/src/Tracing';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {StackCursor} from './ReactFiberStack.new';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
@ -24,7 +23,6 @@ import {
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableProfilerNestedUpdateScheduledHook,
enableSchedulerTracing,
warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
enableDebugTracing,
@ -83,8 +81,6 @@ import {
// The scheduler is imported here *only* to detect whether it's been mocked
import * as Scheduler from 'scheduler';
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
import {
resetAfterCommit,
scheduleTimeout,
@ -349,13 +345,6 @@ let rootWithNestedUpdates: FiberRoot | null = null;
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
let nestedPassiveUpdateCount: number = 0;
// Marks the need to reschedule pending interactions at these lanes
// during the commit phase. This enables them to be traced across components
// that spawn new work during render. E.g. hidden boundaries, suspended SSR
// hydration or SuspenseList.
// TODO: Can use a bitmask instead of an array
let spawnedWorkDuringRender: null | Array<Lane | Lanes> = null;
// If two updates are scheduled within the same event, we should treat their
// event times as simultaneous, even if the actual clock time has advanced
// between the first and second call.
@ -496,11 +485,7 @@ export function scheduleUpdateOnFiber(
if (current.tag === Profiler) {
const {id, onNestedUpdateScheduled} = current.memoizedProps;
if (typeof onNestedUpdateScheduled === 'function') {
if (enableSchedulerTracing) {
onNestedUpdateScheduled(id, root.memoizedInteractions);
} else {
onNestedUpdateScheduled(id);
}
onNestedUpdateScheduled(id);
}
}
current = current.return;
@ -543,16 +528,12 @@ export function scheduleUpdateOnFiber(
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
schedulePendingInteractions(root, lane);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
if (
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode
@ -569,7 +550,6 @@ export function scheduleUpdateOnFiber(
} else {
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
}
return root;
@ -1269,10 +1249,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
enqueueInterleavedUpdates();
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
}
@ -1357,21 +1333,6 @@ function popDispatcher(prevDispatcher) {
ReactCurrentDispatcher.current = prevDispatcher;
}
function pushInteractions(root) {
if (enableSchedulerTracing) {
const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
return prevInteractions;
}
return null;
}
function popInteractions(prevInteractions) {
if (enableSchedulerTracing) {
__interactionsRef.current = prevInteractions;
}
}
export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
@ -1454,11 +1415,8 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
}
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
@ -1478,9 +1436,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
}
} while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
@ -1546,11 +1501,8 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
resetRenderTimer();
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
@ -1570,9 +1522,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
}
} while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
@ -1874,7 +1823,6 @@ function commitRootImpl(root, renderPriorityLevel) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
@ -1947,9 +1895,6 @@ function commitRootImpl(root, renderPriorityLevel) {
// opportunity to paint.
requestPaint();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
// Reset the priority to the previous non-sync value.
@ -1980,22 +1925,7 @@ function commitRootImpl(root, renderPriorityLevel) {
remainingLanes = root.pendingLanes;
// Check if there's remaining work on this root
if (remainingLanes !== NoLanes) {
if (enableSchedulerTracing) {
if (spawnedWorkDuringRender !== null) {
const expirationTimes = spawnedWorkDuringRender;
spawnedWorkDuringRender = null;
for (let i = 0; i < expirationTimes.length; i++) {
scheduleInteractions(
root,
expirationTimes[i],
root.memoizedInteractions,
);
}
}
schedulePendingInteractions(root, remainingLanes);
}
} else {
if (remainingLanes === NoLanes) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
@ -2007,16 +1937,6 @@ function commitRootImpl(root, renderPriorityLevel) {
}
}
if (enableSchedulerTracing) {
if (!rootDidHavePassiveEffects) {
// If there are no passive effects, then we can complete the pending interactions.
// Otherwise, we'll wait until after the passive effects are flushed.
// Wait to do this until after remaining work has been scheduled,
// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
finishPendingInteractions(root, lanes);
}
}
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
@ -2177,7 +2097,6 @@ function flushPassiveEffectsImpl() {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
@ -2192,11 +2111,6 @@ function flushPassiveEffectsImpl() {
}
}
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
finishPendingInteractions(root, lanes);
}
if (__DEV__) {
isFlushingPassiveEffects = false;
}
@ -2271,7 +2185,6 @@ function captureCommitPhaseErrorOnRoot(
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, SyncLane);
}
}
@ -2318,7 +2231,6 @@ export function captureCommitPhaseError(
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, SyncLane);
}
return;
}
@ -2390,7 +2302,6 @@ export function pingSuspendedRoot(
}
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, pingedLanes);
}
function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
@ -2409,7 +2320,6 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
if (root !== null) {
markRootUpdated(root, retryLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, retryLane);
}
}
@ -3008,167 +2918,6 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
}
}
function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
// Interaction threads are unique per root and expiration time.
// NOTE: Intentionally unsound cast. All that matters is that it's a number
// and it represents a batch of work. Could make a helper function instead,
// but meh this is fine for now.
return (lane: any) * 1000 + root.interactionThreadID;
}
export function markSpawnedWork(lane: Lane | Lanes) {
if (!enableSchedulerTracing) {
return;
}
if (spawnedWorkDuringRender === null) {
spawnedWorkDuringRender = [lane];
} else {
spawnedWorkDuringRender.push(lane);
}
}
function scheduleInteractions(
root: FiberRoot,
lane: Lane | Lanes,
interactions: Set<Interaction>,
) {
if (!enableSchedulerTracing) {
return;
}
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap;
const pendingInteractions = pendingInteractionMap.get(lane);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}
pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(lane, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, lane);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
if (!enableSchedulerTracing) {
return;
}
scheduleInteractions(root, lane, __interactionsRef.current);
}
function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
// This is called when new work is started on a root.
if (!enableSchedulerTracing) {
return;
}
// Determine which interactions this batch of work currently includes, So that
// we can accurately attribute time spent working on it, And so that cascading
// work triggered during the render phase will be associated with it.
const interactions: Set<Interaction> = new Set();
root.pendingInteractionMap.forEach((scheduledInteractions, scheduledLane) => {
if (includesSomeLane(lanes, scheduledLane)) {
scheduledInteractions.forEach(interaction =>
interactions.add(interaction),
);
}
});
// Store the current set of interactions on the FiberRoot for a few reasons:
// We can re-use it in hot functions like performConcurrentWorkOnRoot()
// without having to recalculate it. We will also use it in commitWork() to
// pass to any Profiler onRender() hooks. This also provides DevTools with a
// way to access it when the onCommitRoot() hook is called.
root.memoizedInteractions = interactions;
if (interactions.size > 0) {
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, lanes);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
}
}
}
}
function finishPendingInteractions(root, committedLanes) {
if (!enableSchedulerTracing) {
return;
}
const remainingLanesAfterCommit = root.pendingLanes;
let subscriber;
try {
subscriber = __subscriberRef.current;
if (subscriber !== null && root.memoizedInteractions.size > 0) {
// FIXME: More than one lane can finish in a single commit.
const threadID = computeThreadID(root, committedLanes);
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
}
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
} finally {
// Clear completed interactions from the pending Map.
// Unless the render was suspended or cascading work was scheduled,
// In which case leave pending interactions until the subsequent render.
const pendingInteractionMap = root.pendingInteractionMap;
pendingInteractionMap.forEach((scheduledInteractions, lane) => {
// Only decrement the pending interaction count if we're done.
// If there's still work at the current priority,
// That indicates that we are waiting for suspense data.
if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
pendingInteractionMap.delete(lane);
scheduledInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
try {
subscriber.onInteractionScheduledWorkCompleted(interaction);
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
}
}
});
}
});
}
}
// `act` testing API
//
// TODO: This is mostly a copy-paste from the legacy `act`, which does not have

View File

@ -10,7 +10,6 @@
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane.old';
import type {Interaction} from 'scheduler/src/Tracing';
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
import type {StackCursor} from './ReactFiberStack.old';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
@ -24,7 +23,6 @@ import {
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableProfilerNestedUpdateScheduledHook,
enableSchedulerTracing,
warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
enableDebugTracing,
@ -83,8 +81,6 @@ import {
// The scheduler is imported here *only* to detect whether it's been mocked
import * as Scheduler from 'scheduler';
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
import {
resetAfterCommit,
scheduleTimeout,
@ -349,13 +345,6 @@ let rootWithNestedUpdates: FiberRoot | null = null;
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
let nestedPassiveUpdateCount: number = 0;
// Marks the need to reschedule pending interactions at these lanes
// during the commit phase. This enables them to be traced across components
// that spawn new work during render. E.g. hidden boundaries, suspended SSR
// hydration or SuspenseList.
// TODO: Can use a bitmask instead of an array
let spawnedWorkDuringRender: null | Array<Lane | Lanes> = null;
// If two updates are scheduled within the same event, we should treat their
// event times as simultaneous, even if the actual clock time has advanced
// between the first and second call.
@ -496,11 +485,7 @@ export function scheduleUpdateOnFiber(
if (current.tag === Profiler) {
const {id, onNestedUpdateScheduled} = current.memoizedProps;
if (typeof onNestedUpdateScheduled === 'function') {
if (enableSchedulerTracing) {
onNestedUpdateScheduled(id, root.memoizedInteractions);
} else {
onNestedUpdateScheduled(id);
}
onNestedUpdateScheduled(id);
}
}
current = current.return;
@ -543,16 +528,12 @@ export function scheduleUpdateOnFiber(
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
schedulePendingInteractions(root, lane);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
if (
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode
@ -569,7 +550,6 @@ export function scheduleUpdateOnFiber(
} else {
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
}
return root;
@ -1269,10 +1249,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
enqueueInterleavedUpdates();
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
}
@ -1357,21 +1333,6 @@ function popDispatcher(prevDispatcher) {
ReactCurrentDispatcher.current = prevDispatcher;
}
function pushInteractions(root) {
if (enableSchedulerTracing) {
const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
return prevInteractions;
}
return null;
}
function popInteractions(prevInteractions) {
if (enableSchedulerTracing) {
__interactionsRef.current = prevInteractions;
}
}
export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
@ -1454,11 +1415,8 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
}
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
@ -1478,9 +1436,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
}
} while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
@ -1546,11 +1501,8 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
resetRenderTimer();
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
@ -1570,9 +1522,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
}
} while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
@ -1874,7 +1823,6 @@ function commitRootImpl(root, renderPriorityLevel) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
@ -1947,9 +1895,6 @@ function commitRootImpl(root, renderPriorityLevel) {
// opportunity to paint.
requestPaint();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
// Reset the priority to the previous non-sync value.
@ -1980,22 +1925,7 @@ function commitRootImpl(root, renderPriorityLevel) {
remainingLanes = root.pendingLanes;
// Check if there's remaining work on this root
if (remainingLanes !== NoLanes) {
if (enableSchedulerTracing) {
if (spawnedWorkDuringRender !== null) {
const expirationTimes = spawnedWorkDuringRender;
spawnedWorkDuringRender = null;
for (let i = 0; i < expirationTimes.length; i++) {
scheduleInteractions(
root,
expirationTimes[i],
root.memoizedInteractions,
);
}
}
schedulePendingInteractions(root, remainingLanes);
}
} else {
if (remainingLanes === NoLanes) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
@ -2007,16 +1937,6 @@ function commitRootImpl(root, renderPriorityLevel) {
}
}
if (enableSchedulerTracing) {
if (!rootDidHavePassiveEffects) {
// If there are no passive effects, then we can complete the pending interactions.
// Otherwise, we'll wait until after the passive effects are flushed.
// Wait to do this until after remaining work has been scheduled,
// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
finishPendingInteractions(root, lanes);
}
}
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
@ -2177,7 +2097,6 @@ function flushPassiveEffectsImpl() {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
@ -2192,11 +2111,6 @@ function flushPassiveEffectsImpl() {
}
}
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
finishPendingInteractions(root, lanes);
}
if (__DEV__) {
isFlushingPassiveEffects = false;
}
@ -2271,7 +2185,6 @@ function captureCommitPhaseErrorOnRoot(
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, SyncLane);
}
}
@ -2318,7 +2231,6 @@ export function captureCommitPhaseError(
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, SyncLane);
}
return;
}
@ -2390,7 +2302,6 @@ export function pingSuspendedRoot(
}
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, pingedLanes);
}
function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
@ -2409,7 +2320,6 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
if (root !== null) {
markRootUpdated(root, retryLane, eventTime);
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, retryLane);
}
}
@ -3008,167 +2918,6 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
}
}
function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
// Interaction threads are unique per root and expiration time.
// NOTE: Intentionally unsound cast. All that matters is that it's a number
// and it represents a batch of work. Could make a helper function instead,
// but meh this is fine for now.
return (lane: any) * 1000 + root.interactionThreadID;
}
export function markSpawnedWork(lane: Lane | Lanes) {
if (!enableSchedulerTracing) {
return;
}
if (spawnedWorkDuringRender === null) {
spawnedWorkDuringRender = [lane];
} else {
spawnedWorkDuringRender.push(lane);
}
}
function scheduleInteractions(
root: FiberRoot,
lane: Lane | Lanes,
interactions: Set<Interaction>,
) {
if (!enableSchedulerTracing) {
return;
}
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap;
const pendingInteractions = pendingInteractionMap.get(lane);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}
pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(lane, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, lane);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
if (!enableSchedulerTracing) {
return;
}
scheduleInteractions(root, lane, __interactionsRef.current);
}
function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
// This is called when new work is started on a root.
if (!enableSchedulerTracing) {
return;
}
// Determine which interactions this batch of work currently includes, So that
// we can accurately attribute time spent working on it, And so that cascading
// work triggered during the render phase will be associated with it.
const interactions: Set<Interaction> = new Set();
root.pendingInteractionMap.forEach((scheduledInteractions, scheduledLane) => {
if (includesSomeLane(lanes, scheduledLane)) {
scheduledInteractions.forEach(interaction =>
interactions.add(interaction),
);
}
});
// Store the current set of interactions on the FiberRoot for a few reasons:
// We can re-use it in hot functions like performConcurrentWorkOnRoot()
// without having to recalculate it. We will also use it in commitWork() to
// pass to any Profiler onRender() hooks. This also provides DevTools with a
// way to access it when the onCommitRoot() hook is called.
root.memoizedInteractions = interactions;
if (interactions.size > 0) {
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, lanes);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
}
}
}
}
function finishPendingInteractions(root, committedLanes) {
if (!enableSchedulerTracing) {
return;
}
const remainingLanesAfterCommit = root.pendingLanes;
let subscriber;
try {
subscriber = __subscriberRef.current;
if (subscriber !== null && root.memoizedInteractions.size > 0) {
// FIXME: More than one lane can finish in a single commit.
const threadID = computeThreadID(root, committedLanes);
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
}
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
} finally {
// Clear completed interactions from the pending Map.
// Unless the render was suspended or cascading work was scheduled,
// In which case leave pending interactions until the subsequent render.
const pendingInteractionMap = root.pendingInteractionMap;
pendingInteractionMap.forEach((scheduledInteractions, lane) => {
// Only decrement the pending interaction count if we're done.
// If there's still work at the current priority,
// That indicates that we are waiting for suspense data.
if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
pendingInteractionMap.delete(lane);
scheduledInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
try {
subscriber.onInteractionScheduledWorkCompleted(interaction);
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediateSchedulerPriority, () => {
throw error;
});
}
}
});
}
});
}
}
// `act` testing API
//
// TODO: This is mostly a copy-paste from the legacy `act`, which does not have

View File

@ -24,7 +24,6 @@ import type {Lane, Lanes, LaneMap} from './ReactFiberLane.old';
import type {RootTag} from './ReactRootTags';
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
import type {Wakeable} from 'shared/ReactTypes';
import type {Interaction} from 'scheduler/src/Tracing';
import type {Cache} from './ReactFiberCacheComponent.old';
// Unwind Circular: moved from ReactFiberHooks.old
@ -240,16 +239,6 @@ type BaseFiberRootProperties = {|
pooledCacheLanes: Lanes,
|};
// The following attributes are only used by interaction tracing builds.
// They enable interactions to be associated with their async work,
// And expose interaction metadata to the React DevTools Profiler plugin.
// Note that these attributes are only defined when the enableSchedulerTracing flag is enabled.
type ProfilingOnlyFiberRootProperties = {|
interactionThreadID: number,
memoizedInteractions: Set<Interaction>,
pendingInteractionMap: Map<Lane | Lanes, Set<Interaction>>,
|};
// The following attributes are only used by DevTools and are only present in DEV builds.
// They enable DevTools Profiler UI to show which Fiber(s) scheduled a given commit.
type UpdaterTrackingOnlyFiberRootProperties = {|
@ -270,12 +259,9 @@ type SuspenseCallbackOnlyFiberRootProperties = {|
// Exported FiberRoot type includes all properties,
// To avoid requiring potentially error-prone :any casts throughout the project.
// Profiling properties are only safe to access in profiling builds (when enableSchedulerTracing is true).
// The types are defined separately within this file to ensure they stay in sync.
// (We don't have to use an inline :any cast when enableSchedulerTracing is disabled.)
export type FiberRoot = {
...BaseFiberRootProperties,
...ProfilingOnlyFiberRootProperties,
...SuspenseCallbackOnlyFiberRootProperties,
...UpdaterTrackingOnlyFiberRootProperties,
...

View File

@ -12,9 +12,6 @@
// because Rollup would use dynamic dispatch for CommonJS interop named imports.
// When we switch to ESM, we can delete this module.
import * as Scheduler from 'scheduler';
import {__interactionsRef} from 'scheduler/tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
export const scheduleCallback = Scheduler.unstable_scheduleCallback;
export const cancelCallback = Scheduler.unstable_cancelCallback;
@ -28,19 +25,4 @@ export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
export const NormalPriority = Scheduler.unstable_NormalPriority;
export const LowPriority = Scheduler.unstable_LowPriority;
export const IdlePriority = Scheduler.unstable_IdlePriority;
if (enableSchedulerTracing) {
// Provide explicit error message when production+profiling bundle of e.g.
// react-dom is used with production (non-profiling) bundle of
// scheduler/tracing
invariant(
__interactionsRef != null && __interactionsRef.current != null,
'It is not supported to run the profiling version of a renderer (for ' +
'example, `react-dom/profiling`) without also replacing the ' +
'`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
'bundler might have a setting for aliasing both modules. Learn more at ' +
'https://reactjs.org/link/profiling',
);
}
export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;

View File

@ -7,7 +7,5 @@
* @flow
*/
'use strict';
export * from './src/Tracing';
export * from './src/TracingSubscriptions';
// This placeholder is to support a legacy/regression test in ReactFresh-test.
// Without this mock, jest.mock('scheduler/tracing') throws.

View File

@ -18,7 +18,6 @@ let readText;
let resolveText;
let ReactNoop;
let Scheduler;
let SchedulerTracing;
let Suspense;
let useState;
let useReducer;
@ -43,7 +42,6 @@ describe('ReactHooksWithNoopRenderer', () => {
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
useState = React.useState;
useReducer = React.useReducer;
useEffect = React.useEffect;
@ -1797,71 +1795,6 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
});
// @gate enableSchedulerTracing
it('does not flush non-discrete passive effects when flushing sync (with tracing)', () => {
const onInteractionScheduledWorkCompleted = jest.fn();
const onWorkCanceled = jest.fn();
SchedulerTracing.unstable_subscribe({
onInteractionScheduledWorkCompleted,
onInteractionTraced: jest.fn(),
onWorkCanceled,
onWorkScheduled: jest.fn(),
onWorkStarted: jest.fn(),
onWorkStopped: jest.fn(),
});
let _updateCount;
function Counter(props) {
const [count, updateCount] = useState(0);
_updateCount = updateCount;
useEffect(() => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
tracingEvent,
]);
Scheduler.unstable_yieldValue(`Will set count to 1`);
updateCount(1);
}, []);
return <Text text={'Count: ' + count} />;
}
const tracingEvent = {id: 0, name: 'hello', timestamp: 0};
// we explicitly wait for missing act() warnings here since
// it's a lot harder to simulate this condition inside an act scope
expect(() => {
SchedulerTracing.unstable_trace(
tracingEvent.name,
tracingEvent.timestamp,
() => {
ReactNoop.render(<Counter count={0} />, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
},
);
expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
}).toErrorDev(['An update to Counter ran an effect']);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);
// A flush sync doesn't cause the passive effects to fire.
act(() => {
ReactNoop.flushSync(() => {
_updateCount(2);
});
});
expect(Scheduler).toHaveYielded([
'Will set count to 1',
'Count: 2',
'Count: 1',
]);
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenCalledTimes(0);
});
it(
'in legacy mode, useEffect is deferred and updates finish synchronously ' +
'(in a single batch)',

View File

@ -2,7 +2,6 @@ let React;
let ReactTestRenderer;
let ReactFeatureFlags;
let Scheduler;
let SchedulerTracing;
let ReactCache;
let Suspense;
let act;
@ -18,12 +17,10 @@ describe('ReactSuspense', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.enableSchedulerTracing = true;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
act = ReactTestRenderer.unstable_concurrentAct;
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
ReactCache = require('react-cache');
Suspense = React.Suspense;
@ -1271,76 +1268,6 @@ describe('ReactSuspense', () => {
]);
});
it('should call onInteractionScheduledWorkCompleted after suspending', () => {
const subscriber = {
onInteractionScheduledWorkCompleted: jest.fn(),
onInteractionTraced: jest.fn(),
onWorkCanceled: jest.fn(),
onWorkScheduled: jest.fn(),
onWorkStarted: jest.fn(),
onWorkStopped: jest.fn(),
};
SchedulerTracing.unstable_subscribe(subscriber);
SchedulerTracing.unstable_trace('test', performance.now(), () => {
function App() {
return (
<React.Suspense fallback={<Text text="Loading..." />}>
<AsyncText text="A" ms={1000} />
<AsyncText text="B" ms={2000} />
<AsyncText text="C" ms={3000} />
</React.Suspense>
);
}
const root = ReactTestRenderer.create(null);
root.update(<App />);
expect(Scheduler).toHaveYielded([
'Suspend! [A]',
'Suspend! [B]',
'Suspend! [C]',
'Loading...',
]);
// Resolve A
jest.advanceTimersByTime(1000);
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
expect(Scheduler).toFlushUntilNextPaint([
'A',
// The promises for B and C have now been thrown twice
'Suspend! [B]',
'Suspend! [C]',
]);
// Resolve B
jest.advanceTimersByTime(1000);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushUntilNextPaint([
// Even though the promise for B was thrown twice, we should only
// re-render once.
'B',
// The promise for C has now been thrown three times
'Suspend! [C]',
]);
// Resolve C
jest.advanceTimersByTime(1000);
expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
expect(Scheduler).toFlushAndYield([
// Even though the promise for C was thrown three times, we should only
// re-render once.
'C',
]);
});
expect(
subscriber.onInteractionScheduledWorkCompleted,
).toHaveBeenCalledTimes(1);
});
it('#14162', () => {
const {lazy} = React;

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
describe('ReactTracing', () => {
it('should error if profiling renderer and non-profiling scheduler/tracing bundles are combined', () => {
jest.resetModules();
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = false;
require('scheduler/tracing');
ReactFeatureFlags.enableSchedulerTracing = true;
expect(() => require('react-dom')).toThrow(
'Learn more at https://reactjs.org/link/profiling',
);
});
});

View File

@ -292,91 +292,6 @@ describe('updaters', () => {
done();
});
// @gate experimental
it('traces interaction through hidden subtree', async () => {
const {
FunctionComponent,
HostRoot,
} = require('react-reconciler/src/ReactWorkTags');
// Note: This is based on a similar component we use in www. We can delete once
// the extra div wrapper is no longer necessary.
function LegacyHiddenDiv({children, mode}) {
return (
<div hidden={mode === 'hidden'}>
<React.unstable_LegacyHidden
mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
{children}
</React.unstable_LegacyHidden>
</div>
);
}
const Child = () => {
const [didMount, setDidMount] = React.useState(false);
Scheduler.unstable_yieldValue('Child');
React.useEffect(() => {
if (didMount) {
Scheduler.unstable_yieldValue('Child:update');
} else {
Scheduler.unstable_yieldValue('Child:mount');
setDidMount(true);
}
}, [didMount]);
return <div />;
};
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
Scheduler.unstable_yieldValue('App:mount');
}, []);
return (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
);
};
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
await ReactTestUtils.act(async () => {
root.render(<App />);
});
// TODO: There are 4 commits here instead of 3
// because this update was scheduled at idle priority,
// and idle updates are slightly higher priority than offscreen work.
// So it takes two render passes to finish it.
// The onCommit hook is called even after the no-op bailout update.
expect(Scheduler).toHaveYielded([
'App',
'onCommitRoot',
'App:mount',
'Child',
'onCommitRoot',
'Child:mount',
'onCommitRoot',
'Child',
'onCommitRoot',
'Child:update',
]);
expect(allSchedulerTypes).toEqual([
// Initial render
[null],
// Offscreen update
[],
// Child passive effect
[Child],
// Offscreen update
[],
]);
expect(allSchedulerTags).toEqual([[HostRoot], [], [FunctionComponent], []]);
});
// @gate experimental
it('should cover error handling', async () => {
let triggerError = null;

View File

@ -25,7 +25,6 @@ function loadModules() {
jest.useFakeTimers();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.enableProfilerTimer = true;
React = require('react');

View File

@ -28,6 +28,7 @@
},
"devDependencies": {
"react-16-8": "npm:react@16.8.0",
"react-dom-16-8": "npm:react-dom@16.8.0"
"react-dom-16-8": "npm:react-dom@16.8.0",
"scheduler-0-13": "npm:scheduler@0.13.0"
}
}

View File

@ -3844,6 +3844,10 @@ describe('ReactFresh', () => {
// Redirect all React/ReactDOM requires to v16.8.0
// This version predates Fast Refresh support.
jest.mock('scheduler', () => jest.requireActual('scheduler-0-13'));
jest.mock('scheduler/tracing', () =>
jest.requireActual('scheduler-0-13/tracing'),
);
jest.mock('react', () => jest.requireActual('react-16-8'));
jest.mock('react-dom', () => jest.requireActual('react-dom-16-8'));

View File

@ -28,11 +28,6 @@ export type ElementConfig<C> = React$ElementConfig<C>;
export type ElementRef<C> = React$ElementRef<C>;
export type Config<Props, DefaultProps> = React$Config<Props, DefaultProps>;
export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
export type Interaction = {
name: string,
timestamp: number,
...
};
// Export all exports so that they're available in tests.
// We can't use export * from in Flow for some reason.

View File

@ -1,922 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactDOM;
let ReactDOMServer;
let ReactFeatureFlags;
let Scheduler;
let SchedulerTracing;
let TestUtils;
let act;
let onInteractionScheduledWorkCompleted;
let onInteractionTraced;
let onWorkCanceled;
let onWorkScheduled;
let onWorkStarted;
let onWorkStopped;
let IdleEventPriority;
let ContinuousEventPriority;
function loadModules() {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableProfilerTimer = true;
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
TestUtils = require('react-dom/test-utils');
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
ContinuousEventPriority = require('react-reconciler/constants')
.ContinuousEventPriority;
act = TestUtils.unstable_concurrentAct;
onInteractionScheduledWorkCompleted = jest.fn();
onInteractionTraced = jest.fn();
onWorkCanceled = jest.fn();
onWorkScheduled = jest.fn();
onWorkStarted = jest.fn();
onWorkStopped = jest.fn();
// Verify interaction subscriber methods are called as expected.
SchedulerTracing.unstable_subscribe({
onInteractionScheduledWorkCompleted,
onInteractionTraced,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
});
}
// Note: This is based on a similar component we use in www. We can delete once
// the extra div wrapper is no longer necessary.
function LegacyHiddenDiv({children, mode}) {
return (
<div hidden={mode === 'hidden'}>
<React.unstable_LegacyHidden
mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
{children}
</React.unstable_LegacyHidden>
</div>
);
}
describe('ReactDOMTracing', () => {
beforeEach(() => {
jest.resetModules();
loadModules();
});
describe('interaction tracing', () => {
describe('hidden', () => {
// @gate experimental
it('traces interaction through hidden subtree', () => {
const Child = () => {
const [didMount, setDidMount] = React.useState(false);
Scheduler.unstable_yieldValue('Child');
React.useEffect(() => {
if (didMount) {
Scheduler.unstable_yieldValue('Child:update');
} else {
Scheduler.unstable_yieldValue('Child:mount');
setDidMount(true);
}
}, [didMount]);
return <div />;
};
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
Scheduler.unstable_yieldValue('App:mount');
}, []);
return (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
);
};
let interaction;
const onRender = jest.fn();
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
act(() => {
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.unstable_startTransition(() => {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
});
} else {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
}
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(1);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
expect(Scheduler).toFlushAndYield(['Child', 'Child:update']);
});
});
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
expect(onRender).toHaveBeenCalledTimes(3);
} else {
// TODO: This is 4 instead of 3 because this update was scheduled at
// idle priority, and idle updates are slightly higher priority than
// offscreen work. So it takes two render passes to finish it. Profiler
// calls `onRender` for the first render even though everything
// bails out.
expect(onRender).toHaveBeenCalledTimes(4);
}
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
});
// @gate experimental
it('traces interaction through hidden subtree when there is other pending traced work', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
return <div />;
};
let wrapped = null;
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
wrapped = SchedulerTracing.unstable_wrap(() => {});
Scheduler.unstable_yieldValue('App:mount');
}, []);
return (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
);
};
let interaction;
const onRender = jest.fn();
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
act(() => {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(1);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
expect(wrapped).not.toBeNull();
expect(Scheduler).toFlushAndYield(['Child']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
});
});
wrapped();
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
// @gate experimental
it('traces interaction through hidden subtree that schedules more idle/never work', () => {
const Child = () => {
const [didMount, setDidMount] = React.useState(false);
Scheduler.unstable_yieldValue('Child');
React.useLayoutEffect(() => {
if (didMount) {
Scheduler.unstable_yieldValue('Child:update');
} else {
Scheduler.unstable_yieldValue('Child:mount');
ReactDOM.unstable_runWithPriority(IdleEventPriority, () =>
setDidMount(true),
);
}
}, [didMount]);
return <div />;
};
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
Scheduler.unstable_yieldValue('App:mount');
}, []);
return (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
);
};
let interaction;
const onRender = jest.fn();
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
act(() => {
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.unstable_startTransition(() => {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
});
} else {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
}
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(1);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
expect(Scheduler).toFlushAndYield(['Child', 'Child:update']);
});
});
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
// TODO: This is 4 instead of 3 because this update was scheduled at
// idle priority, and idle updates are slightly higher priority than
// offscreen work. So it takes two render passes to finish it. Profiler
// calls `onRender` for the first render even though everything
// bails out.
expect(onRender).toHaveBeenCalledTimes(4);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
});
// @gate experimental
it('does not continue interactions across pre-existing idle work', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
return <div />;
};
let update = null;
const WithHiddenWork = () => {
Scheduler.unstable_yieldValue('WithHiddenWork');
return (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
);
};
const Updater = () => {
Scheduler.unstable_yieldValue('Updater');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Updater:effect');
});
const setCount = React.useState(0)[1];
update = () => {
setCount(current => current + 1);
};
return <div />;
};
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
Scheduler.unstable_yieldValue('App:effect');
});
return (
<>
<WithHiddenWork />
<Updater />
</>
);
};
const onRender = jest.fn();
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
// Schedule some idle work without any interactions.
act(() => {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
expect(Scheduler).toFlushAndYieldThrough([
'App',
'WithHiddenWork',
'Updater',
'Updater:effect',
'App:effect',
]);
expect(update).not.toBeNull();
// Trace a higher-priority update.
let interaction = null;
SchedulerTracing.unstable_trace('update', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
update();
});
expect(interaction).not.toBeNull();
expect(onRender).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
// Ensure the traced interaction completes without being attributed to the pre-existing idle work.
expect(Scheduler).toFlushAndYieldThrough([
'Updater',
'Updater:effect',
]);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
// Complete low-priority work and ensure no lingering interaction.
expect(Scheduler).toFlushAndYield(['Child']);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(onRender).toHaveBeenCalledTimes(3);
expect(onRender).toHaveLastRenderedWithInteractions(new Set([]));
});
});
// @gate experimental
it('should properly trace interactions when there is work of interleaved priorities', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
return <div />;
};
let scheduleUpdate = null;
let scheduleUpdateWithHidden = null;
const MaybeHiddenWork = () => {
const [flag, setFlag] = React.useState(false);
scheduleUpdateWithHidden = () => setFlag(true);
Scheduler.unstable_yieldValue('MaybeHiddenWork');
React.useEffect(() => {
Scheduler.unstable_yieldValue('MaybeHiddenWork:effect');
});
return flag ? (
<LegacyHiddenDiv mode="hidden">
<Child />
</LegacyHiddenDiv>
) : null;
};
const Updater = () => {
Scheduler.unstable_yieldValue('Updater');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Updater:effect');
});
const setCount = React.useState(0)[1];
scheduleUpdate = () => setCount(current => current + 1);
return <div />;
};
const App = () => {
Scheduler.unstable_yieldValue('App');
React.useEffect(() => {
Scheduler.unstable_yieldValue('App:effect');
});
return (
<>
<MaybeHiddenWork />
<Updater />
</>
);
};
const onRender = jest.fn();
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
act(() => {
root.render(
<React.Profiler id="test" onRender={onRender}>
<App />
</React.Profiler>,
);
expect(Scheduler).toFlushAndYield([
'App',
'MaybeHiddenWork',
'Updater',
'MaybeHiddenWork:effect',
'Updater:effect',
'App:effect',
]);
expect(scheduleUpdate).not.toBeNull();
expect(scheduleUpdateWithHidden).not.toBeNull();
expect(onRender).toHaveBeenCalledTimes(1);
// schedule traced high-pri update and a (non-traced) low-pri update.
let interaction = null;
SchedulerTracing.unstable_trace('update', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
ReactDOM.unstable_runWithPriority(ContinuousEventPriority, () =>
scheduleUpdateWithHidden(),
);
});
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.unstable_startTransition(() => {
scheduleUpdate();
});
} else {
scheduleUpdate();
}
expect(interaction).not.toBeNull();
expect(onRender).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
// high-pri update should leave behind idle work and should not complete the interaction
expect(Scheduler).toFlushAndYieldThrough([
'MaybeHiddenWork',
'MaybeHiddenWork:effect',
]);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
// low-pri update should not have the interaction
expect(Scheduler).toFlushAndYieldThrough([
'Updater',
'Updater:effect',
]);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).toHaveBeenCalledTimes(3);
expect(onRender).toHaveLastRenderedWithInteractions(new Set([]));
// idle work should complete the interaction
expect(Scheduler).toFlushAndYield(['Child']);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
expect(onRender).toHaveBeenCalledTimes(4);
expect(onRender).toHaveLastRenderedWithInteractions(
new Set([interaction]),
);
});
});
// @gate experimental
it('should properly trace interactions through a multi-pass SuspenseList render', () => {
const SuspenseList = React.SuspenseList;
const Suspense = React.Suspense;
function Text({text}) {
Scheduler.unstable_yieldValue(text);
React.useEffect(() => {
Scheduler.unstable_yieldValue('Commit ' + text);
});
return <span>{text}</span>;
}
function App() {
return (
<SuspenseList revealOrder="forwards">
<Suspense fallback={<Text text="Loading A" />}>
<Text text="A" />
</Suspense>
<Suspense fallback={<Text text="Loading B" />}>
<Text text="B" />
</Suspense>
<Suspense fallback={<Text text="Loading C" />}>
<Text text="C" />
</Suspense>
</SuspenseList>
);
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
let interaction;
act(() => {
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
// This render is only CPU bound. Nothing suspends.
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.unstable_startTransition(() => {
root.render(<App />);
});
} else {
root.render(<App />);
}
});
expect(Scheduler).toFlushAndYieldThrough(['A']);
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
expect(Scheduler).toFlushAndYieldThrough(['B']);
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
// Time has now elapsed for so long that we're just going to give up
// rendering the rest of the content. So that we can at least show
// something.
expect(Scheduler).toFlushAndYieldThrough([
'Loading C',
'Commit A',
'Commit B',
'Commit Loading C',
]);
// Schedule an unrelated low priority update that shouldn't be included
// in the previous interaction. This is meant to ensure that we don't
// rely on the whole tree completing to cover up bugs.
ReactDOM.unstable_runWithPriority(IdleEventPriority, () => {
root.render(<App />);
});
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
// Then we do a second pass to commit the last item.
expect(Scheduler).toFlushAndYieldThrough(['C', 'Commit C']);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
});
});
describe('hydration', () => {
// @gate experimental
it('traces interaction across hydration', () => {
const ref = React.createRef();
function Child() {
return 'Hello';
}
function App() {
return (
<div>
<span ref={ref}>
<Child />
</span>
</div>
);
}
// Render the final HTML.
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
let interaction;
const root = ReactDOM.createRoot(container, {hydrate: true});
// Hydrate it.
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
root.render(<App />);
});
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).not.toBe(null);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
// @gate experimental
it('traces interaction across suspended hydration', async () => {
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
);
const ref = React.createRef();
function Child() {
if (suspend) {
throw promise;
} else {
return 'Hello';
}
}
function App() {
return (
<div>
<React.Suspense fallback="Loading...">
<span ref={ref}>
<Child />
</span>
</React.Suspense>
</div>
);
}
// Render the final HTML.
// Don't suspend on the server.
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
let interaction;
const root = ReactDOM.createRoot(container, {hydrate: true});
// Start hydrating but simulate blocking for suspense data.
suspend = true;
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
root.render(<App />);
});
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).toBe(null);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
// Resolving the promise should continue hydration
suspend = false;
resolve();
await promise;
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).not.toBe(null);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
// @gate experimental
it('traces interaction across suspended hydration from server', async () => {
// Copied from ReactDOMHostConfig.js
const SUSPENSE_START_DATA = '$';
const SUSPENSE_PENDING_START_DATA = '$?';
const ref = React.createRef();
function App() {
return (
<React.Suspense fallback="Loading...">
<span ref={ref}>Hello</span>
</React.Suspense>
);
}
const container = document.createElement('div');
// Render the final HTML.
const finalHTML = ReactDOMServer.renderToString(<App />);
// Replace the marker with a pending state.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
const escapedMarker = SUSPENSE_START_DATA.replace(
/[.*+\-?^${}()|[\]\\]/g,
'\\$&',
);
container.innerHTML = finalHTML.replace(
new RegExp(escapedMarker, 'g'),
SUSPENSE_PENDING_START_DATA,
);
let interaction;
const root = ReactDOM.createRoot(container, {hydrate: true});
// Start hydrating but simulate blocking for suspense data from the server.
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
root.render(<App />);
});
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).toBe(null);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
// Unblock rendering, pretend the content is injected by the server.
const startNode = container.childNodes[0];
expect(startNode).not.toBe(null);
expect(startNode.nodeType).toBe(Node.COMMENT_NODE);
startNode.textContent = SUSPENSE_START_DATA;
startNode._reactRetry();
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).not.toBe(null);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
// @gate experimental
it('traces interaction across client-rendered hydration', () => {
let suspend = false;
const promise = new Promise(() => {});
const ref = React.createRef();
function Child() {
if (suspend) {
throw promise;
} else {
return 'Hello';
}
}
function App() {
return (
<div>
<React.Suspense fallback="Loading...">
<span ref={ref}>
<Child />
</span>
</React.Suspense>
</div>
);
}
// Render the final HTML.
suspend = true;
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
let interaction;
const root = ReactDOM.createRoot(container, {hydrate: true});
// Hydrate without suspending to fill in the client-rendered content.
suspend = false;
SchedulerTracing.unstable_trace('initialization', 0, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
root.render(<App />);
});
expect(onWorkStopped).toHaveBeenCalledTimes(1);
// Advance time a bit so that we get into a new expiration bucket.
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ref.current).not.toBe(null);
// We should've had two commits that was traced.
// First one that hydrates the parent, and then one that hydrates
// the boundary at higher than Never priority.
expect(onWorkStopped).toHaveBeenCalledTimes(3);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactFeatureFlags;
let ReactDOM;
let SchedulerTracing;
let Scheduler;
function loadModules() {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableProfilerTimer = true;
ReactFeatureFlags.enableSchedulerTracing = true;
React = require('react');
SchedulerTracing = require('scheduler/tracing');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
}
describe('ProfilerDOM', () => {
let onInteractionScheduledWorkCompleted;
let onInteractionTraced;
beforeEach(() => {
loadModules();
onInteractionScheduledWorkCompleted = jest.fn();
onInteractionTraced = jest.fn();
// Verify interaction subscriber methods are called as expected.
SchedulerTracing.unstable_subscribe({
onInteractionScheduledWorkCompleted,
onInteractionTraced,
onWorkCanceled: () => {},
onWorkScheduled: () => {},
onWorkStarted: () => {},
onWorkStopped: () => {},
});
});
function Text(props) {
Scheduler.unstable_yieldValue(props.text);
return props.text;
}
// @gate experimental
it('should correctly trace interactions for async roots', async () => {
let resolve;
let thenable = {
then(res) {
resolve = () => {
thenable = null;
res();
};
},
};
function Async() {
if (thenable !== null) {
Scheduler.unstable_yieldValue('Suspend! [Async]');
throw thenable;
}
Scheduler.unstable_yieldValue('Async');
return 'Async';
}
const element = document.createElement('div');
const root = ReactDOM.createRoot(element);
let interaction;
let wrappedResolve;
SchedulerTracing.unstable_trace('initial_event', performance.now(), () => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions.size).toBe(1);
interaction = Array.from(interactions)[0];
root.render(
<React.Suspense fallback={<Text text="Loading..." />}>
<Async />
</React.Suspense>,
);
wrappedResolve = SchedulerTracing.unstable_wrap(() => resolve());
});
// Render, suspend, and commit fallback
expect(Scheduler).toFlushAndYield(['Suspend! [Async]', 'Loading...']);
expect(element.textContent).toEqual('Loading...');
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
// Ping React to try rendering again
wrappedResolve();
// Complete the tree without committing it
expect(Scheduler).toFlushAndYieldThrough(['Async']);
// Still showing the fallback
expect(element.textContent).toEqual('Loading...');
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(Scheduler).toFlushAndYield([]);
expect(element.textContent).toEqual('Async');
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
interaction,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
});
});

View File

@ -15,7 +15,6 @@ describe('ReactProfiler DevTools integration', () => {
let ReactFeatureFlags;
let ReactTestRenderer;
let Scheduler;
let SchedulerTracing;
let AdvanceTime;
let hook;
@ -31,9 +30,7 @@ describe('ReactProfiler DevTools integration', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableProfilerTimer = true;
ReactFeatureFlags.enableSchedulerTracing = true;
Scheduler = require('scheduler');
SchedulerTracing = require('scheduler/tracing');
React = require('react');
ReactTestRenderer = require('react-test-renderer');
@ -76,15 +73,7 @@ describe('ReactProfiler DevTools integration', () => {
// The time spent in App (above the Profiler) won't be included in the durations,
// But needs to be accounted for in the offset times.
expect(onRender).toHaveBeenCalledTimes(1);
expect(onRender).toHaveBeenCalledWith(
'Profiler',
'mount',
10,
10,
2,
12,
new Set(),
);
expect(onRender).toHaveBeenCalledWith('Profiler', 'mount', 10, 10, 2, 12);
onRender.mockClear();
// Measure unobservable timing required by the DevTools profiler.
@ -101,15 +90,7 @@ describe('ReactProfiler DevTools integration', () => {
// The time spent in App (above the Profiler) won't be included in the durations,
// But needs to be accounted for in the offset times.
expect(onRender).toHaveBeenCalledTimes(1);
expect(onRender).toHaveBeenCalledWith(
'Profiler',
'update',
6,
13,
14,
20,
new Set(),
);
expect(onRender).toHaveBeenCalledWith('Profiler', 'update', 6, 13, 14, 20);
// Measure unobservable timing required by the DevTools profiler.
// At this point, the base time should include both:
@ -157,27 +138,6 @@ describe('ReactProfiler DevTools integration', () => {
).toBe(7);
});
it('should store traced interactions on the HostNode so DevTools can access them', () => {
// Render without an interaction
const rendered = ReactTestRenderer.create(<div />);
const root = rendered.root._currentFiber().return;
expect(root.stateNode.memoizedInteractions).toContainNoInteractions();
Scheduler.unstable_advanceTime(10);
const eventTime = Scheduler.unstable_now();
// Render with an interaction
SchedulerTracing.unstable_trace('some event', eventTime, () => {
rendered.update(<div />);
});
expect(root.stateNode.memoizedInteractions).toMatchInteractions([
{name: 'some event', timestamp: eventTime},
]);
});
// @gate experimental || !enableSyncDefaultUpdates
it('regression test: #17159', () => {
function Text({text}) {

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should render children 1`] = `
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should render children 1`] = `
<div>
<span>
outside span
@ -14,11 +14,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTr
</div>
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support nested Profilers 1`] = `
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support nested Profilers 1`] = `
Array [
<div>
outer function component
@ -32,7 +32,7 @@ Array [
]
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should render children 1`] = `
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should render children 1`] = `
<div>
<span>
outside span
@ -46,75 +46,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTr
</div>
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support nested Profilers 1`] = `
Array [
<div>
outer function component
</div>,
<block>
inner class component
</block>,
<span>
inner span
</span>,
]
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should render children 1`] = `
<div>
<span>
outside span
</span>
<span>
inside span
</span>
<span>
function component
</span>
</div>
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support nested Profilers 1`] = `
Array [
<div>
outer function component
</div>,
<block>
inner class component
</block>,
<span>
inner span
</span>,
]
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should render children 1`] = `
<div>
<span>
outside span
</span>
<span>
inside span
</span>
<span>
function component
</span>
</div>
`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `<div />`;
exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support nested Profilers 1`] = `
exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support nested Profilers 1`] = `
Array [
<div>
outer function component

View File

@ -7,7 +7,6 @@
import assign from 'object-assign';
import * as Scheduler from 'scheduler';
import * as SchedulerTracing from 'scheduler/tracing';
import ReactCurrentDispatcher from '../ReactCurrentDispatcher';
import ReactCurrentOwner from '../ReactCurrentOwner';
import ReactDebugCurrentFrame from '../ReactDebugCurrentFrame';
@ -28,7 +27,6 @@ const ReactSharedInternals = {
// This re-export is only required for UMD bundles;
// CJS bundles use the shared NPM package.
Scheduler,
SchedulerTracing,
};
if (__DEV__) {

View File

@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/scheduler-tracing.profiling.min.js');
} else {
module.exports = require('./cjs/scheduler-tracing.development.js');
}

View File

@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/scheduler-tracing.production.min.js');
} else {
module.exports = require('./cjs/scheduler-tracing.development.js');
}

View File

@ -1,80 +0,0 @@
/**
* @license React
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
(function(global, factory) {
// eslint-disable-next-line no-unused-expressions
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory(require('react')))
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
? define(['react'], factory) // eslint-disable-line no-undef
: (global.SchedulerTracing = factory(global));
})(this, function(global) {
function unstable_clear() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply(
this,
arguments
);
}
function unstable_getCurrent() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply(
this,
arguments
);
}
function unstable_getThreadID() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply(
this,
arguments
);
}
function unstable_subscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply(
this,
arguments
);
}
function unstable_trace() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply(
this,
arguments
);
}
function unstable_unsubscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply(
this,
arguments
);
}
function unstable_wrap() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply(
this,
arguments
);
}
return Object.freeze({
unstable_clear: unstable_clear,
unstable_getCurrent: unstable_getCurrent,
unstable_getThreadID: unstable_getThreadID,
unstable_subscribe: unstable_subscribe,
unstable_trace: unstable_trace,
unstable_unsubscribe: unstable_unsubscribe,
unstable_wrap: unstable_wrap,
});
});

View File

@ -1,80 +0,0 @@
/**
* @license React
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
(function(global, factory) {
// eslint-disable-next-line no-unused-expressions
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory(require('react')))
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
? define(['react'], factory) // eslint-disable-line no-undef
: (global.SchedulerTracing = factory(global));
})(this, function(global) {
function unstable_clear() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply(
this,
arguments
);
}
function unstable_getCurrent() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply(
this,
arguments
);
}
function unstable_getThreadID() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply(
this,
arguments
);
}
function unstable_subscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply(
this,
arguments
);
}
function unstable_trace() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply(
this,
arguments
);
}
function unstable_unsubscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply(
this,
arguments
);
}
function unstable_wrap() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply(
this,
arguments
);
}
return Object.freeze({
unstable_clear: unstable_clear,
unstable_getCurrent: unstable_getCurrent,
unstable_getThreadID: unstable_getThreadID,
unstable_subscribe: unstable_subscribe,
unstable_trace: unstable_trace,
unstable_unsubscribe: unstable_unsubscribe,
unstable_wrap: unstable_wrap,
});
});

View File

@ -1,80 +0,0 @@
/**
* @license React
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
(function(global, factory) {
// eslint-disable-next-line no-unused-expressions
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory(require('react')))
: typeof define === 'function' && define.amd // eslint-disable-line no-undef
? define(['react'], factory) // eslint-disable-line no-undef
: (global.SchedulerTracing = factory(global));
})(this, function(global) {
function unstable_clear() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply(
this,
arguments
);
}
function unstable_getCurrent() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply(
this,
arguments
);
}
function unstable_getThreadID() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply(
this,
arguments
);
}
function unstable_subscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply(
this,
arguments
);
}
function unstable_trace() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply(
this,
arguments
);
}
function unstable_unsubscribe() {
// eslint-disable-next-line max-len
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply(
this,
arguments
);
}
function unstable_wrap() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply(
this,
arguments
);
}
return Object.freeze({
unstable_clear: unstable_clear,
unstable_getCurrent: unstable_getCurrent,
unstable_getThreadID: unstable_getThreadID,
unstable_subscribe: unstable_subscribe,
unstable_trace: unstable_trace,
unstable_unsubscribe: unstable_unsubscribe,
unstable_wrap: unstable_wrap,
});
});

View File

@ -25,8 +25,6 @@
"README.md",
"build-info.json",
"index.js",
"tracing.js",
"tracing-profiling.js",
"unstable_mock.js",
"unstable_no_dom.js",
"unstable_post_task.js",

View File

@ -1,264 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
export type Interaction = {|
__count: number,
id: number,
name: string,
timestamp: number,
|};
export type Subscriber = {
// A new interaction has been created via the trace() method.
onInteractionTraced: (interaction: Interaction) => void,
// All scheduled async work for an interaction has finished.
onInteractionScheduledWorkCompleted: (interaction: Interaction) => void,
// New async work has been scheduled for a set of interactions.
// When this work is later run, onWorkStarted/onWorkStopped will be called.
// A batch of async/yieldy work may be scheduled multiple times before completing.
// In that case, onWorkScheduled may be called more than once before onWorkStopped.
// Work is scheduled by a "thread" which is identified by a unique ID.
onWorkScheduled: (interactions: Set<Interaction>, threadID: number) => void,
// A batch of scheduled work has been canceled.
// Work is done by a "thread" which is identified by a unique ID.
onWorkCanceled: (interactions: Set<Interaction>, threadID: number) => void,
// A batch of work has started for a set of interactions.
// When this work is complete, onWorkStopped will be called.
// Work is not always completed synchronously; yielding may occur in between.
// A batch of async/yieldy work may also be re-started before completing.
// In that case, onWorkStarted may be called more than once before onWorkStopped.
// Work is done by a "thread" which is identified by a unique ID.
onWorkStarted: (interactions: Set<Interaction>, threadID: number) => void,
// A batch of work has completed for a set of interactions.
// Work is done by a "thread" which is identified by a unique ID.
onWorkStopped: (interactions: Set<Interaction>, threadID: number) => void,
...
};
export type InteractionsRef = {|current: Set<Interaction>|};
export type SubscriberRef = {|current: Subscriber | null|};
const DEFAULT_THREAD_ID = 0;
// Counters used to generate unique IDs.
let interactionIDCounter: number = 0;
let threadIDCounter: number = 0;
// Set of currently traced interactions.
// Interactions "stack"
// Meaning that newly traced interactions are appended to the previously active set.
// When an interaction goes out of scope, the previous set (if any) is restored.
let interactionsRef: InteractionsRef = (null: any);
// Listener(s) to notify when interactions begin and end.
let subscriberRef: SubscriberRef = (null: any);
if (enableSchedulerTracing) {
interactionsRef = {
current: new Set(),
};
subscriberRef = {
current: null,
};
}
export {interactionsRef as __interactionsRef, subscriberRef as __subscriberRef};
export function unstable_clear(callback: Function): any {
if (!enableSchedulerTracing) {
return callback();
}
const prevInteractions = interactionsRef.current;
interactionsRef.current = new Set();
try {
return callback();
} finally {
interactionsRef.current = prevInteractions;
}
}
export function unstable_getCurrent(): Set<Interaction> | null {
if (!enableSchedulerTracing) {
return null;
} else {
return interactionsRef.current;
}
}
export function unstable_getThreadID(): number {
return ++threadIDCounter;
}
export function unstable_trace(
name: string,
timestamp: number,
callback: Function,
threadID: number = DEFAULT_THREAD_ID,
): any {
if (!enableSchedulerTracing) {
return callback();
}
const interaction: Interaction = {
__count: 1,
id: interactionIDCounter++,
name,
timestamp,
};
const prevInteractions = interactionsRef.current;
// Traced interactions should stack/accumulate.
// To do that, clone the current interactions.
// The previous set will be restored upon completion.
const interactions = new Set(prevInteractions);
interactions.add(interaction);
interactionsRef.current = interactions;
const subscriber = subscriberRef.current;
let returnValue;
try {
if (subscriber !== null) {
subscriber.onInteractionTraced(interaction);
}
} finally {
try {
if (subscriber !== null) {
subscriber.onWorkStarted(interactions, threadID);
}
} finally {
try {
returnValue = callback();
} finally {
interactionsRef.current = prevInteractions;
try {
if (subscriber !== null) {
subscriber.onWorkStopped(interactions, threadID);
}
} finally {
interaction.__count--;
// If no async work was scheduled for this interaction,
// Notify subscribers that it's completed.
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
}
}
}
}
return returnValue;
}
export function unstable_wrap(
callback: Function,
threadID: number = DEFAULT_THREAD_ID,
): Function {
if (!enableSchedulerTracing) {
return callback;
}
const wrappedInteractions = interactionsRef.current;
let subscriber = subscriberRef.current;
if (subscriber !== null) {
subscriber.onWorkScheduled(wrappedInteractions, threadID);
}
// Update the pending async work count for the current interactions.
// Update after calling subscribers in case of error.
wrappedInteractions.forEach(interaction => {
interaction.__count++;
});
let hasRun = false;
function wrapped() {
const prevInteractions = interactionsRef.current;
interactionsRef.current = wrappedInteractions;
subscriber = subscriberRef.current;
try {
let returnValue;
try {
if (subscriber !== null) {
subscriber.onWorkStarted(wrappedInteractions, threadID);
}
} finally {
try {
returnValue = callback.apply(undefined, arguments);
} finally {
interactionsRef.current = prevInteractions;
if (subscriber !== null) {
subscriber.onWorkStopped(wrappedInteractions, threadID);
}
}
}
return returnValue;
} finally {
if (!hasRun) {
// We only expect a wrapped function to be executed once,
// But in the event that it's executed more than once
// Only decrement the outstanding interaction counts once.
hasRun = true;
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
});
}
}
}
wrapped.cancel = function cancel() {
subscriber = subscriberRef.current;
try {
if (subscriber !== null) {
subscriber.onWorkCanceled(wrappedInteractions, threadID);
}
} finally {
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
});
}
};
return wrapped;
}

View File

@ -1,171 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Interaction, Subscriber} from './Tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import {__subscriberRef} from './Tracing';
let subscribers: Set<Subscriber> = (null: any);
if (enableSchedulerTracing) {
subscribers = new Set();
}
export function unstable_subscribe(subscriber: Subscriber): void {
if (enableSchedulerTracing) {
subscribers.add(subscriber);
if (subscribers.size === 1) {
__subscriberRef.current = {
onInteractionScheduledWorkCompleted,
onInteractionTraced,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
};
}
}
}
export function unstable_unsubscribe(subscriber: Subscriber): void {
if (enableSchedulerTracing) {
subscribers.delete(subscriber);
if (subscribers.size === 0) {
__subscriberRef.current = null;
}
}
}
function onInteractionTraced(interaction: Interaction): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onInteractionTraced(interaction);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onInteractionScheduledWorkCompleted(interaction: Interaction): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onInteractionScheduledWorkCompleted(interaction);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkScheduled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkScheduled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkStarted(interactions: Set<Interaction>, threadID: number): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkStopped(interactions: Set<Interaction>, threadID: number): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkStopped(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkCanceled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkCanceled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}

View File

@ -37,30 +37,10 @@ describe('Scheduling UMD bundle', () => {
global.MessageChannel = undefined;
});
function filterPrivateKeys(name) {
// Be very careful adding things to this filter!
// It's easy to introduce bugs by doing it:
// https://github.com/facebook/react/issues/14904
switch (name) {
case '__interactionsRef':
case '__subscriberRef':
// Don't forward these. (TODO: why?)
return false;
default:
return true;
}
}
function validateForwardedAPIs(api, forwardedAPIs) {
const apiKeys = Object.keys(api)
.filter(filterPrivateKeys)
.sort();
const apiKeys = Object.keys(api).sort();
forwardedAPIs.forEach(forwardedAPI => {
expect(
Object.keys(forwardedAPI)
.filter(filterPrivateKeys)
.sort(),
).toEqual(apiKeys);
expect(Object.keys(forwardedAPI).sort()).toEqual(apiKeys);
});
}
@ -78,19 +58,4 @@ describe('Scheduling UMD bundle', () => {
secretAPI.Scheduler,
]);
});
it('should define the same tracing API', () => {
const api = require('../../tracing');
const umdAPIDev = require('../../npm/umd/scheduler-tracing.development');
const umdAPIProd = require('../../npm/umd/scheduler-tracing.production.min');
const umdAPIProfiling = require('../../npm/umd/scheduler-tracing.profiling.min');
const secretAPI = require('react/src/forks/ReactSharedInternals.umd')
.default;
validateForwardedAPIs(api, [
umdAPIDev,
umdAPIProd,
umdAPIProfiling,
secretAPI.SchedulerTracing,
]);
});
});

View File

@ -1,375 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
describe('Tracing', () => {
let SchedulerTracing;
let ReactFeatureFlags;
let advanceTimeBy;
let currentTime;
function loadModules({enableSchedulerTracing}) {
jest.resetModules();
jest.useFakeTimers();
currentTime = 0;
Date.now = jest.fn().mockImplementation(() => currentTime);
advanceTimeBy = amount => {
currentTime += amount;
};
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
SchedulerTracing = require('scheduler/tracing');
}
describe('enableSchedulerTracing enabled', () => {
beforeEach(() => loadModules({enableSchedulerTracing: true}));
it('should return the value of a traced function', () => {
expect(
SchedulerTracing.unstable_trace('arbitrary', currentTime, () => 123),
).toBe(123);
});
it('should return the value of a clear function', () => {
expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123);
});
it('should return the value of a wrapped function', () => {
let wrapped;
SchedulerTracing.unstable_trace('arbitrary', currentTime, () => {
wrapped = SchedulerTracing.unstable_wrap(() => 123);
});
expect(wrapped()).toBe(123);
});
it('should pass arguments through to a wrapped function', done => {
let wrapped;
SchedulerTracing.unstable_trace('arbitrary', currentTime, () => {
wrapped = SchedulerTracing.unstable_wrap((param1, param2) => {
expect(param1).toBe('foo');
expect(param2).toBe('bar');
done();
});
});
wrapped('foo', 'bar');
});
it('should return an empty set when outside of a traced event', () => {
expect(SchedulerTracing.unstable_getCurrent()).toContainNoInteractions();
});
it('should report the traced interaction from within the trace callback', done => {
advanceTimeBy(100);
SchedulerTracing.unstable_trace('some event', currentTime, () => {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'some event', timestamp: 100},
]);
done();
});
});
it('should report the traced interaction from within wrapped callbacks', done => {
let wrappedIndirection;
function indirection() {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'some event', timestamp: 100},
]);
done();
}
advanceTimeBy(100);
SchedulerTracing.unstable_trace('some event', currentTime, () => {
wrappedIndirection = SchedulerTracing.unstable_wrap(indirection);
});
advanceTimeBy(50);
wrappedIndirection();
});
it('should clear the interaction stack for traced callbacks', () => {
let innerTestReached = false;
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event'},
]);
SchedulerTracing.unstable_clear(() => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions(
[],
);
SchedulerTracing.unstable_trace('inner event', currentTime, () => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'inner event'},
]);
innerTestReached = true;
});
});
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event'},
]);
});
expect(innerTestReached).toBe(true);
});
it('should clear the interaction stack for wrapped callbacks', () => {
let innerTestReached = false;
let wrappedIndirection;
const indirection = jest.fn(() => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event'},
]);
SchedulerTracing.unstable_clear(() => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions(
[],
);
SchedulerTracing.unstable_trace('inner event', currentTime, () => {
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'inner event'},
]);
innerTestReached = true;
});
});
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event'},
]);
});
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
wrappedIndirection = SchedulerTracing.unstable_wrap(indirection);
});
wrappedIndirection();
expect(innerTestReached).toBe(true);
});
it('should support nested traced events', done => {
advanceTimeBy(100);
let innerIndirectionTraced = false;
let outerIndirectionTraced = false;
function innerIndirection() {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'outer event', timestamp: 100},
{name: 'inner event', timestamp: 150},
]);
innerIndirectionTraced = true;
}
function outerIndirection() {
const interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'outer event', timestamp: 100},
]);
outerIndirectionTraced = true;
}
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
// Verify the current traced event
let interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'outer event', timestamp: 100},
]);
advanceTimeBy(50);
const wrapperOuterIndirection = SchedulerTracing.unstable_wrap(
outerIndirection,
);
let wrapperInnerIndirection;
let innerEventTraced = false;
// Verify that a nested event is properly traced
SchedulerTracing.unstable_trace('inner event', currentTime, () => {
interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'outer event', timestamp: 100},
{name: 'inner event', timestamp: 150},
]);
// Verify that a wrapped outer callback is properly traced
wrapperOuterIndirection();
expect(outerIndirectionTraced).toBe(true);
wrapperInnerIndirection = SchedulerTracing.unstable_wrap(
innerIndirection,
);
innerEventTraced = true;
});
expect(innerEventTraced).toBe(true);
// Verify that the original event is restored
interactions = SchedulerTracing.unstable_getCurrent();
expect(interactions).toMatchInteractions([
{name: 'outer event', timestamp: 100},
]);
// Verify that a wrapped nested callback is properly traced
wrapperInnerIndirection();
expect(innerIndirectionTraced).toBe(true);
done();
});
});
describe('error handling', () => {
it('should reset state appropriately when an error occurs in a trace callback', done => {
advanceTimeBy(100);
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
expect(() => {
SchedulerTracing.unstable_trace('inner event', currentTime, () => {
throw Error('intentional');
});
}).toThrow();
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event', timestamp: 100},
]);
done();
});
});
it('should reset state appropriately when an error occurs in a wrapped callback', done => {
advanceTimeBy(100);
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
let wrappedCallback;
SchedulerTracing.unstable_trace('inner event', currentTime, () => {
wrappedCallback = SchedulerTracing.unstable_wrap(() => {
throw Error('intentional');
});
});
expect(wrappedCallback).toThrow();
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'outer event', timestamp: 100},
]);
done();
});
});
});
describe('advanced integration', () => {
it('should return a unique threadID per request', () => {
expect(SchedulerTracing.unstable_getThreadID()).not.toBe(
SchedulerTracing.unstable_getThreadID(),
);
});
it('should expose the current set of interactions to be externally manipulated', () => {
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
expect(SchedulerTracing.__interactionsRef.current).toBe(
SchedulerTracing.unstable_getCurrent(),
);
SchedulerTracing.__interactionsRef.current = new Set([
{name: 'override event'},
]);
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
{name: 'override event'},
]);
});
});
it('should expose a subscriber ref to be externally manipulated', () => {
SchedulerTracing.unstable_trace('outer event', currentTime, () => {
expect(SchedulerTracing.__subscriberRef).toEqual({
current: null,
});
});
});
});
});
describe('enableSchedulerTracing disabled', () => {
beforeEach(() => loadModules({enableSchedulerTracing: false}));
it('should return the value of a traced function', () => {
expect(
SchedulerTracing.unstable_trace('arbitrary', currentTime, () => 123),
).toBe(123);
});
it('should return the value of a wrapped function', () => {
let wrapped;
SchedulerTracing.unstable_trace('arbitrary', currentTime, () => {
wrapped = SchedulerTracing.unstable_wrap(() => 123);
});
expect(wrapped()).toBe(123);
});
it('should return null for traced interactions', () => {
expect(SchedulerTracing.unstable_getCurrent()).toBe(null);
});
it('should execute traced callbacks', done => {
SchedulerTracing.unstable_trace('some event', currentTime, () => {
expect(SchedulerTracing.unstable_getCurrent()).toBe(null);
done();
});
});
it('should return the value of a clear function', () => {
expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123);
});
it('should execute wrapped callbacks', done => {
const wrappedCallback = SchedulerTracing.unstable_wrap(() => {
expect(SchedulerTracing.unstable_getCurrent()).toBe(null);
done();
});
wrappedCallback();
});
describe('advanced integration', () => {
it('should not create unnecessary objects', () => {
expect(SchedulerTracing.__interactionsRef).toBe(null);
});
});
});
});

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
describe('Tracing', () => {
let SchedulerTracing;
beforeEach(() => {
jest.resetModules();
SchedulerTracing = require('scheduler/tracing');
});
it('should return the value of a traced function', () => {
expect(SchedulerTracing.unstable_trace('arbitrary', 0, () => 123)).toBe(
123,
);
});
it('should return the value of a wrapped function', () => {
let wrapped;
SchedulerTracing.unstable_trace('arbitrary', 0, () => {
wrapped = SchedulerTracing.unstable_wrap(() => 123);
});
expect(wrapped()).toBe(123);
});
it('should execute traced callbacks', done => {
SchedulerTracing.unstable_trace('some event', 0, () => {
done();
});
});
it('should return the value of a clear function', () => {
expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123);
});
it('should execute wrapped callbacks', done => {
const wrappedCallback = SchedulerTracing.unstable_wrap(() => {
done();
});
wrappedCallback();
});
});

View File

@ -1,621 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
describe('TracingSubscriptions', () => {
let SchedulerTracing;
let ReactFeatureFlags;
let currentTime;
let onInteractionScheduledWorkCompleted;
let onInteractionTraced;
let onWorkCanceled;
let onWorkScheduled;
let onWorkStarted;
let onWorkStopped;
let throwInOnInteractionScheduledWorkCompleted;
let throwInOnInteractionTraced;
let throwInOnWorkCanceled;
let throwInOnWorkScheduled;
let throwInOnWorkStarted;
let throwInOnWorkStopped;
let firstSubscriber;
let secondSubscriber;
const firstEvent = {id: 0, name: 'first', timestamp: 0};
const secondEvent = {id: 1, name: 'second', timestamp: 0};
const threadID = 123;
function loadModules({enableSchedulerTracing, autoSubscribe = true}) {
jest.resetModules();
jest.useFakeTimers();
currentTime = 0;
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
SchedulerTracing = require('scheduler/tracing');
throwInOnInteractionScheduledWorkCompleted = false;
throwInOnInteractionTraced = false;
throwInOnWorkCanceled = false;
throwInOnWorkScheduled = false;
throwInOnWorkStarted = false;
throwInOnWorkStopped = false;
onInteractionScheduledWorkCompleted = jest.fn(() => {
if (throwInOnInteractionScheduledWorkCompleted) {
throw Error('Expected error onInteractionScheduledWorkCompleted');
}
});
onInteractionTraced = jest.fn(() => {
if (throwInOnInteractionTraced) {
throw Error('Expected error onInteractionTraced');
}
});
onWorkCanceled = jest.fn(() => {
if (throwInOnWorkCanceled) {
throw Error('Expected error onWorkCanceled');
}
});
onWorkScheduled = jest.fn(() => {
if (throwInOnWorkScheduled) {
throw Error('Expected error onWorkScheduled');
}
});
onWorkStarted = jest.fn(() => {
if (throwInOnWorkStarted) {
throw Error('Expected error onWorkStarted');
}
});
onWorkStopped = jest.fn(() => {
if (throwInOnWorkStopped) {
throw Error('Expected error onWorkStopped');
}
});
firstSubscriber = {
onInteractionScheduledWorkCompleted,
onInteractionTraced,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
};
secondSubscriber = {
onInteractionScheduledWorkCompleted: jest.fn(),
onInteractionTraced: jest.fn(),
onWorkCanceled: jest.fn(),
onWorkScheduled: jest.fn(),
onWorkStarted: jest.fn(),
onWorkStopped: jest.fn(),
};
if (autoSubscribe) {
SchedulerTracing.unstable_subscribe(firstSubscriber);
SchedulerTracing.unstable_subscribe(secondSubscriber);
}
}
describe('enabled', () => {
beforeEach(() => loadModules({enableSchedulerTracing: true}));
it('should lazily subscribe to tracing and unsubscribe again if there are no external subscribers', () => {
loadModules({enableSchedulerTracing: true, autoSubscribe: false});
expect(SchedulerTracing.__subscriberRef.current).toBe(null);
SchedulerTracing.unstable_subscribe(firstSubscriber);
expect(SchedulerTracing.__subscriberRef.current).toBeDefined();
SchedulerTracing.unstable_subscribe(secondSubscriber);
expect(SchedulerTracing.__subscriberRef.current).toBeDefined();
SchedulerTracing.unstable_unsubscribe(secondSubscriber);
expect(SchedulerTracing.__subscriberRef.current).toBeDefined();
SchedulerTracing.unstable_unsubscribe(firstSubscriber);
expect(SchedulerTracing.__subscriberRef.current).toBe(null);
});
describe('error handling', () => {
it('should cover onInteractionTraced/onWorkStarted within', done => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
const mock = jest.fn();
// It should call the callback before re-throwing
throwInOnInteractionTraced = true;
expect(() =>
SchedulerTracing.unstable_trace(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onInteractionTraced');
throwInOnInteractionTraced = false;
expect(mock).toHaveBeenCalledTimes(1);
throwInOnWorkStarted = true;
expect(() =>
SchedulerTracing.unstable_trace(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onWorkStarted');
expect(mock).toHaveBeenCalledTimes(2);
// It should restore the previous/outer interactions
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onInteractionTraced).toHaveBeenCalledTimes(3);
expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(3);
done();
});
});
it('should cover onWorkStopped within trace', done => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
let innerInteraction;
const mock = jest.fn(() => {
innerInteraction = Array.from(
SchedulerTracing.unstable_getCurrent(),
)[1];
});
throwInOnWorkStopped = true;
expect(() =>
SchedulerTracing.unstable_trace(
secondEvent.name,
currentTime,
mock,
),
).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous/outer interactions
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(innerInteraction.__count).toBe(0);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover onInteractionScheduledWorkCompleted within trace', done => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
const mock = jest.fn();
throwInOnInteractionScheduledWorkCompleted = true;
expect(() =>
SchedulerTracing.unstable_trace(
secondEvent.name,
currentTime,
mock,
),
).toThrow('Expected error onInteractionScheduledWorkCompleted');
throwInOnInteractionScheduledWorkCompleted = false;
// It should restore the previous/outer interactions
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should call other subscribers despite the earlier error
expect(
secondSubscriber.onInteractionScheduledWorkCompleted,
).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover the callback within trace', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
expect(() => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
throw Error('Expected error callback');
});
}).toThrow('Expected error callback');
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
done();
});
it('should cover onWorkScheduled within wrap', done => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
const interaction = Array.from(
SchedulerTracing.unstable_getCurrent(),
)[0];
const beforeCount = interaction.__count;
throwInOnWorkScheduled = true;
expect(() => SchedulerTracing.unstable_wrap(() => {})).toThrow(
'Expected error onWorkScheduled',
);
// It should not update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(beforeCount);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkScheduled).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover onWorkStarted within wrap', () => {
const mock = jest.fn();
let interaction, wrapped;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
wrapped = SchedulerTracing.unstable_wrap(mock);
});
expect(interaction.__count).toBe(1);
throwInOnWorkStarted = true;
expect(wrapped).toThrow('Expected error onWorkStarted');
// It should call the callback before re-throwing
expect(mock).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(2);
});
it('should cover onWorkStopped within wrap', done => {
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
const outerInteraction = Array.from(
SchedulerTracing.unstable_getCurrent(),
)[0];
expect(outerInteraction.__count).toBe(1);
let wrapped;
let innerInteraction;
SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => {
innerInteraction = Array.from(
SchedulerTracing.unstable_getCurrent(),
)[1];
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(1);
wrapped = SchedulerTracing.unstable_wrap(jest.fn());
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(2);
});
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(1);
throwInOnWorkStopped = true;
expect(wrapped).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous interactions
expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([
outerInteraction,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(0);
expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(2);
done();
});
});
it('should cover the callback within wrap', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
let wrapped;
let interaction;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
wrapped = SchedulerTracing.unstable_wrap(() => {
throw Error('Expected error wrap');
});
});
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(wrapped).toThrow('Expected error wrap');
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork([interaction]);
done();
});
it('should cover onWorkCanceled within wrap', () => {
let interaction, wrapped;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
wrapped = SchedulerTracing.unstable_wrap(jest.fn());
});
expect(interaction.__count).toBe(1);
throwInOnWorkCanceled = true;
expect(wrapped.cancel).toThrow('Expected error onWorkCanceled');
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkCanceled).toHaveBeenCalledTimes(1);
});
});
it('calls lifecycle methods for trace', () => {
expect(onInteractionTraced).not.toHaveBeenCalled();
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
SchedulerTracing.unstable_trace(
firstEvent.name,
currentTime,
() => {
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
SchedulerTracing.unstable_trace(
secondEvent.name,
currentTime,
() => {
expect(onInteractionTraced).toHaveBeenCalledTimes(2);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
secondEvent,
);
expect(
onInteractionScheduledWorkCompleted,
).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkScheduled).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
});
it('calls lifecycle methods for wrap', () => {
const unwrapped = jest.fn();
let wrapped;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => {
expect(onInteractionTraced).toHaveBeenCalledTimes(2);
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
secondEvent,
);
wrapped = SchedulerTracing.unstable_wrap(unwrapped, threadID);
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkScheduled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
});
});
expect(onInteractionTraced).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrapped();
expect(unwrapped).toHaveBeenCalled();
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(3);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).toHaveBeenCalledTimes(3);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(
onInteractionScheduledWorkCompleted.mock.calls[0][0],
).toMatchInteraction(firstEvent);
expect(
onInteractionScheduledWorkCompleted.mock.calls[1][0],
).toMatchInteraction(secondEvent);
});
it('should call the correct interaction subscriber methods when a wrapped callback is canceled', () => {
const fnOne = jest.fn();
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
wrappedOne = SchedulerTracing.unstable_wrap(fnOne, threadID);
SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => {
wrappedTwo = SchedulerTracing.unstable_wrap(fnTwo, threadID);
});
});
expect(onInteractionTraced).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
wrappedTwo.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
wrappedOne.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(2);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(fnOne).not.toHaveBeenCalled();
expect(fnTwo).not.toHaveBeenCalled();
});
it('should not end an interaction twice if wrap is used to schedule follow up work within another wrap', () => {
const fnOne = jest.fn(() => {
wrappedTwo = SchedulerTracing.unstable_wrap(fnTwo, threadID);
});
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
wrappedOne = SchedulerTracing.unstable_wrap(fnOne, threadID);
});
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedOne();
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedTwo();
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
});
it('should not decrement the interaction count twice if a wrapped function is run twice', () => {
const unwrappedOne = jest.fn();
const unwrappedTwo = jest.fn();
let wrappedOne, wrappedTwo;
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {
wrappedOne = SchedulerTracing.unstable_wrap(unwrappedOne, threadID);
wrappedTwo = SchedulerTracing.unstable_wrap(unwrappedTwo, threadID);
});
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedOne();
expect(unwrappedOne).toHaveBeenCalledTimes(1);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedOne();
expect(unwrappedOne).toHaveBeenCalledTimes(2);
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedTwo();
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
});
it('should unsubscribe', () => {
SchedulerTracing.unstable_unsubscribe(firstSubscriber);
SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {});
expect(onInteractionTraced).not.toHaveBeenCalled();
});
});
describe('disabled', () => {
beforeEach(() => loadModules({enableSchedulerTracing: false}));
// TODO
});
});

View File

@ -51,9 +51,6 @@ export const enableProfilerNestedUpdatePhase = false;
// This callback accepts the component type (class instance or function) the update is scheduled for.
export const enableProfilerNestedUpdateScheduledHook = false;
// Trace which interactions trigger each commit.
export const enableSchedulerTracing = __PROFILE__;
// Track which Fiber(s) schedule render work.
export const enableUpdaterTracking = __PROFILE__;

View File

@ -155,8 +155,6 @@ export type MutableSource<Source: $NonMaybeType<mixed>> = {|
// This doesn't require a value to be passed to either handler.
export interface Wakeable {
then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable;
// Special flag to opt out of tracing interactions across a Suspense boundary.
__reactDoNotTraceInteractions?: boolean;
}
// The subset of a Promise that React APIs rely on. This resolves a value.

View File

@ -17,7 +17,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = __PROFILE__;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;

View File

@ -19,7 +19,6 @@ export const enableProfilerTimer = false;
export const enableProfilerCommitHooks = false;
export const enableProfilerNestedUpdatePhase = false;
export const enableProfilerNestedUpdateScheduledHook = false;
export const enableSchedulerTracing = false;
export const enableUpdaterTracking = false;
export const enableSuspenseServerRenderer = true;
export const enableSelectiveHydration = true;

View File

@ -55,7 +55,6 @@ export const enableSchedulingProfiler =
// Note: we'll want to remove this when we to userland implementation.
// For now, we'll turn it on for everyone because it's *already* on for everyone in practice.
// At least this will let us stop shipping <Profiler> implementation to all users.
export const enableSchedulerTracing = true;
export const enableSchedulerDebugging = true;
export const warnAboutDeprecatedLifecycles = true;

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
const ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
const {
__interactionsRef,
__subscriberRef,
unstable_clear,
unstable_getCurrent,
unstable_getThreadID,
unstable_subscribe,
unstable_trace,
unstable_unsubscribe,
unstable_wrap,
} = ReactInternals.SchedulerTracing;
export {
__interactionsRef,
__subscriberRef,
unstable_clear,
unstable_getCurrent,
unstable_getThreadID,
unstable_subscribe,
unstable_trace,
unstable_unsubscribe,
unstable_wrap,
};

View File

@ -1,101 +0,0 @@
'use strict';
const jestDiff = require('jest-diff').default;
function toContainNoInteractions(actualSet) {
return {
message: () =>
this.isNot
? `Expected interactions but there were none.`
: `Expected no interactions but there were ${actualSet.size}.`,
pass: actualSet.size === 0,
};
}
function toHaveBeenLastNotifiedOfInteraction(
mockFunction,
expectedInteraction
) {
const calls = mockFunction.mock.calls;
if (calls.length === 0) {
return {
message: () => 'Mock function was not called',
pass: false,
};
}
const [actualInteraction] = calls[calls.length - 1];
return toMatchInteraction(actualInteraction, expectedInteraction);
}
function toHaveBeenLastNotifiedOfWork(
mockFunction,
expectedInteractions,
expectedThreadID = undefined
) {
const calls = mockFunction.mock.calls;
if (calls.length === 0) {
return {
message: () => 'Mock function was not called',
pass: false,
};
}
const [actualInteractions, actualThreadID] = calls[calls.length - 1];
if (expectedThreadID !== undefined) {
if (expectedThreadID !== actualThreadID) {
return {
message: () => jestDiff(expectedThreadID + '', actualThreadID + ''),
pass: false,
};
}
}
return toMatchInteractions(actualInteractions, expectedInteractions);
}
function toMatchInteraction(actual, expected) {
let attribute;
for (attribute in expected) {
if (actual[attribute] !== expected[attribute]) {
return {
message: () => jestDiff(expected, actual),
pass: false,
};
}
}
return {pass: true};
}
function toMatchInteractions(actualSetOrArray, expectedSetOrArray) {
const actualArray = Array.from(actualSetOrArray);
const expectedArray = Array.from(expectedSetOrArray);
if (actualArray.length !== expectedArray.length) {
return {
message: () =>
`Expected ${expectedArray.length} interactions but there were ${actualArray.length}`,
pass: false,
};
}
for (let i = 0; i < actualArray.length; i++) {
const result = toMatchInteraction(actualArray[i], expectedArray[i]);
if (result.pass === false) {
return result;
}
}
return {pass: true};
}
module.exports = {
toContainNoInteractions,
toHaveBeenLastNotifiedOfInteraction,
toHaveBeenLastNotifiedOfWork,
toMatchInteraction,
toMatchInteractions,
};

View File

@ -1,72 +0,0 @@
'use strict';
const jestDiff = require('jest-diff').default;
function toHaveLastRenderedWithNoInteractions(onRenderMockFn) {
const calls = onRenderMockFn.mock.calls;
if (calls.length === 0) {
return {
message: () => 'Mock onRender function was not called',
pass: false,
};
}
}
function toHaveLastRenderedWithInteractions(
onRenderMockFn,
expectedInteractions
) {
const calls = onRenderMockFn.mock.calls;
if (calls.length === 0) {
return {
message: () => 'Mock onRender function was not called',
pass: false,
};
}
const lastCall = calls[calls.length - 1];
const actualInteractions = lastCall[6];
return toMatchInteractions(actualInteractions, expectedInteractions);
}
function toMatchInteraction(actual, expected) {
let attribute;
for (attribute in expected) {
if (actual[attribute] !== expected[attribute]) {
return {
message: () => jestDiff(expected, actual),
pass: false,
};
}
}
return {pass: true};
}
function toMatchInteractions(actualSetOrArray, expectedSetOrArray) {
const actualArray = Array.from(actualSetOrArray);
const expectedArray = Array.from(expectedSetOrArray);
if (actualArray.length !== expectedArray.length) {
return {
message: () =>
`Expected ${expectedArray.length} interactions but there were ${actualArray.length}`,
pass: false,
};
}
for (let i = 0; i < actualArray.length; i++) {
const result = toMatchInteraction(actualArray[i], expectedArray[i]);
if (result.pass === false) {
return result;
}
}
return {pass: true};
}
module.exports = {
toHaveLastRenderedWithInteractions,
toHaveLastRenderedWithNoInteractions,
};

View File

@ -45,8 +45,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
}
expect.extend({
...require('./matchers/interactionTracingMatchers'),
...require('./matchers/profilerMatchers'),
...require('./matchers/toWarnDev'),
...require('./matchers/reactTestMatchers'),
});

View File

@ -46,8 +46,6 @@ global.spyOnProd = function(...args) {
};
expect.extend({
...require('../matchers/interactionTracingMatchers'),
...require('../matchers/profilerMatchers'),
...require('../matchers/toWarnDev'),
...require('../matchers/reactTestMatchers'),
});

View File

@ -1,58 +0,0 @@
#!/usr/bin/env node
'use strict';
const {join} = require('path');
const puppeteer = require('puppeteer');
const theme = require('../theme');
const {logPromise} = require('../utils');
const validate = async ({cwd}) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(
'file://' + join(cwd, 'fixtures/tracing/index.html?puppeteer=true')
);
try {
return await page.evaluate(() => {
const button = document.getElementById('run-test-button');
button.click();
const items = document.querySelectorAll('[data-value]');
if (items.length === 0) {
return 'No results were found.';
}
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.getAttribute('data-value') !== 'All checks pass') {
return `Unexpected result, "${item.getAttribute('data-value')}"`;
}
}
return null;
});
} finally {
await browser.close();
}
};
const run = async ({cwd}) => {
const errorMessage = await logPromise(
validate({cwd}),
'Verifying "scheduler/tracing" fixture'
);
if (errorMessage) {
console.error(
theme.error('✗'),
'Verifying "scheduler/tracing" fixture\n ',
theme.error(errorMessage)
);
process.exit(1);
}
};
module.exports = run;

View File

@ -805,24 +805,6 @@ const bundles = [
global: 'ReactFreshRuntime',
externals: [],
},
{
bundleTypes: [
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
RN_FB_DEV,
RN_FB_PROD,
RN_FB_PROFILING,
],
moduleType: ISOMORPHIC,
entry: 'scheduler/tracing',
global: 'SchedulerTracing',
externals: [],
},
];
// Based on deep-freeze by substack (public domain)

View File

@ -171,25 +171,6 @@ const forks = Object.freeze({
}
},
'scheduler/tracing': (bundleType, entry, dependencies) => {
switch (bundleType) {
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
if (dependencies.indexOf('react') === -1) {
// It's only safe to use this fork for modules that depend on React,
// because they read the re-exported API from the SECRET_INTERNALS object.
return null;
}
// Optimization: for UMDs, use the API that is already a part of the React
// package instead of requiring it to be loaded via a separate <script> tag
return 'shared/forks/SchedulerTracing.umd.js';
default:
// For other bundles, use the shared NPM package.
return null;
}
},
'scheduler/src/SchedulerFeatureFlags': (bundleType, entry, dependencies) => {
if (
bundleType === FB_WWW_DEV ||

View File

@ -15,7 +15,6 @@ const importSideEffects = Object.freeze({
'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'scheduler/tracing': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
react: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'react-dom/server': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'react/jsx-dev-runtime': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
@ -31,7 +30,6 @@ const knownGlobals = Object.freeze({
'react-dom/server': 'ReactDOMServer',
'react-interactions/events/tap': 'ReactEventsTap',
scheduler: 'Scheduler',
'scheduler/tracing': 'SchedulerTracing',
'scheduler/unstable_mock': 'SchedulerMock',
});

View File

@ -12319,6 +12319,14 @@ saxes@^5.0.0:
dependencies:
xmlchars "^2.2.0"
"scheduler-0-13@npm:scheduler@0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.0.tgz#e701f62e1b3e78d2bbb264046d4e7260f12184dd"
integrity sha512-w7aJnV30jc7OsiZQNPVmBc+HooZuvQZIZIShKutC3tnMFMkcwVN9CZRRSSNw03OnSCKmEkK8usmwcw6dqBaLzw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.11.0:
version "0.11.3"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.3.tgz#b5769b90cf8b1464f3f3cfcafe8e3cd7555a2d6b"