/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/'use strict';
import {normalizeCodeLocInfo} from './utils';
describe('Timeline profiler', () => {
let React;
let ReactDOM;
let ReactDOMClient;
let Scheduler;
let utils;
let assertLog;
let waitFor;
describe('User Timing API', () => {
let clearedMarks;
let featureDetectionMarkName = null;
let marks;
let setPerformanceMock;
function createUserTimingPolyfill() {
featureDetectionMarkName = null;
clearedMarks = [];
marks = [];
// Remove file-system specific bits or version-specific bits of information from the module range marks.
function filterMarkData(markName) {
if (markName.startsWith('--react-internal-module-start')) {
return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
} else if (markName.startsWith('--react-internal-module-stop')) {
return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
} else if (markName.startsWith('--react-version')) {
return '--react-version-<filtered-version>';
} else {
return markName;
}}// This is not a true polyfill, but it gives us enough to capture marks.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
return {
clearMarks(markName) {
markName = filterMarkData(markName);
clearedMarks.push(markName);
marks = marks.filter(mark => mark !== markName);
},mark(markName, markOptions) {
markName = filterMarkData(markName);
if (featureDetectionMarkName === null) {
featureDetectionMarkName = markName;
}marks.push(markName);
if (markOptions != null) {
// This is triggers the feature detection.
markOptions.startTime++;
}},};}function clearPendingMarks() {
clearedMarks.splice(0);
}beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
setPerformanceMock =
require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
setPerformanceMock(createUserTimingPolyfill());
const store = global.store;
// Start profiling so that data will actually be recorded.
utils.act(() => store.profilerStore.startProfiling());
global.IS_REACT_ACT_ENVIRONMENT = true;
});afterEach(() => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);
setPerformanceMock(null);
});describe('getLanesFromTransportDecimalBitmask', () => {
let getLanesFromTransportDecimalBitmask;
beforeEach(() => {
getLanesFromTransportDecimalBitmask =
require('react-devtools-timeline/src/import-worker/preprocessData').getLanesFromTransportDecimalBitmask;
});// @reactVersion >= 18.0
it('should return array of lane numbers from bitmask string', () => {
expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
1, 4, 6, 7, 10,
]); // 2 + 16 + 64 + 128 + 1024
expect(
getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
).toEqual([30]);
expect(
getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
).toEqual(Array.from(Array(31).keys()));
});// @reactVersion >= 18.0
it('should return empty array if laneBitmaskString is not a bitmask', () => {
expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
});// @reactVersion >= 18.0
it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
const REACT_TOTAL_NUM_LANES =
require('react-devtools-timeline/src/constants').REACT_TOTAL_NUM_LANES;
// Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
expect(REACT_TOTAL_NUM_LANES).toBe(31);
expect(
getLanesFromTransportDecimalBitmask(
'4294967297', // 2^32 + 1
),).toEqual([0]);
});});describe('preprocessData', () => {
let preprocessData;
beforeEach(() => {
preprocessData =
require('react-devtools-timeline/src/import-worker/preprocessData').default;
});// These should be dynamic to mimic a real profile,
// but reprooducible between test runs.
let pid = 0;
let tid = 0;
let startTime = 0;
function createUserTimingEntry(data) {
return {
pid: ++pid,
tid: ++tid,
ts: ++startTime,
...data,
};}function createProfilerVersionEntry() {
const SCHEDULING_PROFILER_VERSION =
require('react-devtools-timeline/src/constants').SCHEDULING_PROFILER_VERSION;
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
});}function createReactVersionEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--react-version-<filtered-version>',
});}function createLaneLabelsEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen',
});}function createNativeEventEntry(type, duration) {
return createUserTimingEntry({
cat: 'devtools.timeline',
name: 'EventDispatch',
args: {data: {type}},
dur: duration,
tdur: duration,
});}function creactCpuProfilerSample() {
return createUserTimingEntry({
args: {data: {startTime: ++startTime}},
cat: 'disabled-by-default-v8.cpu_profiler',
id: '0x1',
name: 'Profile',
ph: 'P',
});}function createBoilerplateEntries() {
return [
createProfilerVersionEntry(),
createReactVersionEntry(),
createLaneLabelsEntry(),
];}function createUserTimingData(sampleMarks) {
const cpuProfilerSample = creactCpuProfilerSample();
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},});const userTimingData = [cpuProfilerSample, randomSample];
sampleMarks.forEach(markName => {
userTimingData.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,
ph: 'R',
});});return userTimingData;
}beforeEach(() => {
tid = 0;
pid = 0;
startTime = 0;
});// @reactVersion >= 18.0
it('should throw given an empty timeline', async () => {
await expect(async () => preprocessData([])).rejects.toThrow();
});// @reactVersion >= 18.0
it('should throw given a timeline with no Profile event', async () => {
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},});await expect(async () =>
preprocessData([randomSample]),
).rejects.toThrow();
});// @reactVersion >= 18.0
it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
await expect(
async () => await preprocessData([cpuProfilerSample]),
).rejects.toThrow(
'Please provide profiling data from an React application',
);});// @reactVersion >= 18.0
it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const scheduleRenderSample = createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
});const samples = [cpuProfilerSample, scheduleRenderSample];
await expect(async () => await preprocessData(samples)).rejects.toThrow(
'This version of profiling data is not supported',
);});// @reactVersion >= 18.0
it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},});const data = await preprocessData([
...createBoilerplateEntries(),
cpuProfilerSample,
randomSample,
]);expect(data).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {},"componentMeasures": [],"duration": 0.005,"flamechart": [],"internalModuleSourceToRanges": Map {},"laneToLabelMap": Map {0 => "Sync",1 => "InputContinuousHydration",2 => "InputContinuous",3 => "DefaultHydration",4 => "Default",5 => "TransitionHydration",6 => "Transition",7 => "Transition",8 => "Transition",9 => "Transition",10 => "Transition",11 => "Transition",12 => "Transition",13 => "Transition",14 => "Transition",15 => "Transition",16 => "Transition",17 => "Transition",18 => "Transition",19 => "Transition",20 => "Transition",21 => "Transition",22 => "Retry",23 => "Retry",24 => "Retry",25 => "Retry",26 => "Retry",27 => "SelectiveHydration",28 => "IdleHydration",29 => "Idle",30 => "Offscreen",},"laneToReactMeasureMap": Map {0 => [],1 => [],2 => [],3 => [],4 => [],5 => [],6 => [],7 => [],8 => [],9 => [],10 => [],11 => [],12 => [],13 => [],14 => [],15 => [],16 => [],17 => [],18 => [],19 => [],20 => [],21 => [],22 => [],23 => [],24 => [],25 => [],26 => [],27 => [],28 => [],29 => [],30 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [],"snapshotHeight": 0,"snapshots": [],"startTime": 1,"suspenseEvents": [],"thrownErrors": [],}`);
});// @reactVersion >= 18.0
it('should process legacy data format (before lane labels were added)', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
// Data below is hard-coded based on an older profile sample.
// Should be fine since this is explicitly a legacy-format test.
const data = await preprocessData([
...createBoilerplateEntries(),
cpuProfilerSample,
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-start-512',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-stop',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-start-512',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-start-512',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-stop',
}),createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-stop',
}),]);expect(data).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {0 => [{"batchUID": 0,"depth": 0,"duration": 0.005,"lanes": "0b0000000000000000000000000001001","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.001,"lanes": "0b0000000000000000000000000001001","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000001001","timestamp": 0.008,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000001001","timestamp": 0.009,"type": "layout-effects",},],},"componentMeasures": [],"duration": 0.011,"flamechart": [],"internalModuleSourceToRanges": Map {},"laneToLabelMap": Map {0 => "Sync",1 => "InputContinuousHydration",2 => "InputContinuous",3 => "DefaultHydration",4 => "Default",5 => "TransitionHydration",6 => "Transition",7 => "Transition",8 => "Transition",9 => "Transition",10 => "Transition",11 => "Transition",12 => "Transition",13 => "Transition",14 => "Transition",15 => "Transition",16 => "Transition",17 => "Transition",18 => "Transition",19 => "Transition",20 => "Transition",21 => "Transition",22 => "Retry",23 => "Retry",24 => "Retry",25 => "Retry",26 => "Retry",27 => "SelectiveHydration",28 => "IdleHydration",29 => "Idle",30 => "Offscreen",},"laneToReactMeasureMap": Map {0 => [],1 => [],2 => [],3 => [],4 => [],5 => [],6 => [],7 => [],8 => [],9 => [{"batchUID": 0,"depth": 0,"duration": 0.005,"lanes": "0b0000000000000000000000000001001","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.001,"lanes": "0b0000000000000000000000000001001","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000001001","timestamp": 0.008,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000001001","timestamp": 0.009,"type": "layout-effects",},],10 => [],11 => [],12 => [],13 => [],14 => [],15 => [],16 => [],17 => [],18 => [],19 => [],20 => [],21 => [],22 => [],23 => [],24 => [],25 => [],26 => [],27 => [],28 => [],29 => [],30 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [{"lanes": "0b0000000000000000000000000001001","timestamp": 0.005,"type": "schedule-render","warning": null,},],"snapshotHeight": 0,"snapshots": [],"startTime": 1,"suspenseEvents": [],"thrownErrors": [],}`);
});it('should process a sample legacy render sequence', async () => {
utils.legacyRender(<div />, document.createElement('div'));
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);expect(data).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {0 => [{"batchUID": 0,"depth": 0,"duration": 0.01,"lanes": "0b0000000000000000000000000000001","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.001,"lanes": "0b0000000000000000000000000000001","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000001","timestamp": 0.008,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000001","timestamp": 0.014,"type": "layout-effects",},],},"componentMeasures": [],"duration": 0.016,"flamechart": [],"internalModuleSourceToRanges": Map {undefined => [[{"columnNumber": 0,"functionName": "filtered","lineNumber": 0,"source": " at filtered (<anonymous>:0:0)",},{"columnNumber": 1,"functionName": "filtered","lineNumber": 1,"source": " at filtered (<anonymous>:1:1)",},],],},"laneToLabelMap": Map {0 => "Sync",1 => "InputContinuousHydration",2 => "InputContinuous",3 => "DefaultHydration",4 => "Default",5 => "TransitionHydration",6 => "Transition",7 => "Transition",8 => "Transition",9 => "Transition",10 => "Transition",11 => "Transition",12 => "Transition",13 => "Transition",14 => "Transition",15 => "Transition",16 => "Transition",17 => "Transition",18 => "Transition",19 => "Transition",20 => "Transition",21 => "Transition",22 => "Retry",23 => "Retry",24 => "Retry",25 => "Retry",26 => "Retry",27 => "SelectiveHydration",28 => "IdleHydration",29 => "Idle",30 => "Offscreen",},"laneToReactMeasureMap": Map {0 => [],1 => [{"batchUID": 0,"depth": 0,"duration": 0.01,"lanes": "0b0000000000000000000000000000001","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.001,"lanes": "0b0000000000000000000000000000001","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000001","timestamp": 0.008,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000001","timestamp": 0.014,"type": "layout-effects",},],2 => [],3 => [],4 => [],5 => [],6 => [],7 => [],8 => [],9 => [],10 => [],11 => [],12 => [],13 => [],14 => [],15 => [],16 => [],17 => [],18 => [],19 => [],20 => [],21 => [],22 => [],23 => [],24 => [],25 => [],26 => [],27 => [],28 => [],29 => [],30 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [{"lanes": "0b0000000000000000000000000000001","timestamp": 0.005,"type": "schedule-render","warning": null,},],"snapshotHeight": 0,"snapshots": [],"startTime": 4,"suspenseEvents": [],"thrownErrors": [],}`);
});it('should process a sample createRoot render sequence', async () => {
function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}});return true;
}const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() => root.render(<App />));
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);expect(data).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {0 => [{"batchUID": 0,"depth": 0,"duration": 0.012,"lanes": "0b0000000000000000000000000000101","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000101","timestamp": 0.01,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000101","timestamp": 0.016,"type": "layout-effects",},{"batchUID": 0,"depth": 0,"duration": 0.004,"lanes": "0b0000000000000000000000000000101","timestamp": 0.019,"type": "passive-effects",},],1 => [{"batchUID": 1,"depth": 0,"duration": 0.012,"lanes": "0b0000000000000000000000000000101","timestamp": 0.024,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.024,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000101","timestamp": 0.028,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000101","timestamp": 0.034,"type": "layout-effects",},{"batchUID": 1,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.037,"type": "passive-effects",},],},"componentMeasures": [{"componentName": "App","duration": 0.001,"timestamp": 0.007,"type": "render","warning": null,},{"componentName": "App","duration": 0.002,"timestamp": 0.02,"type": "passive-effect-mount","warning": null,},{"componentName": "App","duration": 0.001,"timestamp": 0.025,"type": "render","warning": null,},{"componentName": "App","duration": 0.001,"timestamp": 0.038,"type": "passive-effect-mount","warning": null,},],"duration": 0.04,"flamechart": [],"internalModuleSourceToRanges": Map {undefined => [[{"columnNumber": 0,"functionName": "filtered","lineNumber": 0,"source": " at filtered (<anonymous>:0:0)",},{"columnNumber": 1,"functionName": "filtered","lineNumber": 1,"source": " at filtered (<anonymous>:1:1)",},],],},"laneToLabelMap": Map {0 => "Sync",1 => "InputContinuousHydration",2 => "InputContinuous",3 => "DefaultHydration",4 => "Default",5 => "TransitionHydration",6 => "Transition",7 => "Transition",8 => "Transition",9 => "Transition",10 => "Transition",11 => "Transition",12 => "Transition",13 => "Transition",14 => "Transition",15 => "Transition",16 => "Transition",17 => "Transition",18 => "Transition",19 => "Transition",20 => "Transition",21 => "Transition",22 => "Retry",23 => "Retry",24 => "Retry",25 => "Retry",26 => "Retry",27 => "SelectiveHydration",28 => "IdleHydration",29 => "Idle",30 => "Offscreen",},"laneToReactMeasureMap": Map {0 => [],1 => [],2 => [],3 => [],4 => [],5 => [{"batchUID": 0,"depth": 0,"duration": 0.012,"lanes": "0b0000000000000000000000000000101","timestamp": 0.006,"type": "render-idle",},{"batchUID": 0,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.006,"type": "render",},{"batchUID": 0,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000101","timestamp": 0.01,"type": "commit",},{"batchUID": 0,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000101","timestamp": 0.016,"type": "layout-effects",},{"batchUID": 0,"depth": 0,"duration": 0.004,"lanes": "0b0000000000000000000000000000101","timestamp": 0.019,"type": "passive-effects",},{"batchUID": 1,"depth": 0,"duration": 0.012,"lanes": "0b0000000000000000000000000000101","timestamp": 0.024,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.024,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0.008,"lanes": "0b0000000000000000000000000000101","timestamp": 0.028,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0.001,"lanes": "0b0000000000000000000000000000101","timestamp": 0.034,"type": "layout-effects",},{"batchUID": 1,"depth": 0,"duration": 0.003,"lanes": "0b0000000000000000000000000000101","timestamp": 0.037,"type": "passive-effects",},],6 => [],7 => [],8 => [],9 => [],10 => [],11 => [],12 => [],13 => [],14 => [],15 => [],16 => [],17 => [],18 => [],19 => [],20 => [],21 => [],22 => [],23 => [],24 => [],25 => [],26 => [],27 => [],28 => [],29 => [],30 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [{"lanes": "0b0000000000000000000000000000101","timestamp": 0.005,"type": "schedule-render","warning": null,},{"componentName": "App","lanes": "0b0000000000000000000000000000101","timestamp": 0.021,"type": "schedule-state-update","warning": null,},],"snapshotHeight": 0,"snapshots": [],"startTime": 4,"suspenseEvents": [],"thrownErrors": [],}`);
});// @reactVersion >= 18.0
it('should error if events and measures are incomplete', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);
const invalidMarks = clearedMarks.filter(
mark => !mark.includes('render-stop'),
);const invalidUserTimingData = createUserTimingData(invalidMarks);
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);expect(error).toHaveBeenCalled();
});// @reactVersion >= 18.0
it('should error if work is completed without being started', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);
const invalidMarks = clearedMarks.filter(
mark => !mark.includes('render-start'),
);const invalidUserTimingData = createUserTimingData(invalidMarks);
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);expect(error).toHaveBeenCalled();
});// @reactVersion >= 18.0
it('should populate other user timing marks', async () => {
const userTimingData = createUserTimingData([]);
userTimingData.push(
createUserTimingEntry({
args: {},cat: 'blink.user_timing',
id: '0xcdf75f7c',
name: 'VCWithoutImage: root',
ph: 'n',
scope: 'blink.user_timing',
}),);userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--a-mark-that-looks-like-one-of-ours',
ph: 'R',
}),);userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: 'Some other mark',
ph: 'R',
}),);const data = await preprocessData([
...createBoilerplateEntries(),
...userTimingData,
]);expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
[{"name": "VCWithoutImage: root","timestamp": 0.003,},{"name": "--a-mark-that-looks-like-one-of-ours","timestamp": 0.004,},{"name": "Some other mark","timestamp": 0.005,},]`);
});// @reactVersion >= 18.0
it('should include a suspended resource "displayName" if one is set', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});promise.displayName = 'Testing displayName';
}throw promise;
}function Component() {
const value = readValue(123);
return value;
}const testMarks = [creactCpuProfilerSample()];
const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component />
</React.Suspense>,
),);testMarks.push(...createUserTimingData(clearedMarks));
let data;await utils.actAsync(async () => {
data = await preprocessData(testMarks);});expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
});describe('warnings', () => {describe('long event handlers', () => {// @reactVersion >= 18.0
it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {function App() {return null;}const testMarks = [
creactCpuProfilerSample(),...createBoilerplateEntries(),createNativeEventEntry('click', 5),];
clearPendingMarks();utils.legacyRender(<App />, document.createElement('div'));
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});// @reactVersion >= 18.0
it('should not warn about long events if the cause was non-React JavaScript', async () => {function App() {return null;}const testMarks = [
creactCpuProfilerSample(),...createBoilerplateEntries(),createNativeEventEntry('click', 25000),];
startTime += 2000;
clearPendingMarks();utils.legacyRender(<App />, document.createElement('div'));
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});// @reactVersion >= 18.0
it('should warn when React scedules a long (sync) update inside of an event', async () => {function App() {return null;}const testMarks = [
creactCpuProfilerSample(),...createBoilerplateEntries(),createNativeEventEntry('click', 25000),];
clearPendingMarks();utils.legacyRender(<App />, document.createElement('div'));
clearedMarks.forEach(markName => {
if (markName === '--render-stop') {// Fake a long running renderstartTime += 20000;
}testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,ph: 'R',});});const data = await preprocessData(testMarks);const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toMatchInlineSnapshot(
`"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
);});// @reactVersion >= 18.2
it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {function Yield({id, value}) {Scheduler.log(`${id}:${value}`);
return null;}const testMarks = [
creactCpuProfilerSample(),...createBoilerplateEntries(),];
// Advance the clock by some arbitrary amount.
startTime += 50000;
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);// Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
global.IS_REACT_ACT_ENVIRONMENT = false;
React.startTransition(() => {
// Start rendering an async update (but don't finish).
root.render(
<><Yield id="A" value={1} />
<Yield id="B" value={1} />
</>,);});await waitFor(['A:1']);
testMarks.push(...createUserTimingData(clearedMarks));
clearPendingMarks();// Advance the clock some more to make the pending React update seem long.
startTime += 20000;
// Fake a long "click" event in the middle// and schedule a sync update that will also flush the previous work.
testMarks.push(createNativeEventEntry('click', 25000));
ReactDOM.flushSync(() => {
root.render(
<><Yield id="A" value={2} />
<Yield id="B" value={2} />
</>,);});assertLog(['A:2', 'B:2']);
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});});describe('nested updates', () => {// @reactVersion >= 18.2
it('should not warn about short nested (state) updates during layout effects', async () => {function Component() {const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
React.useLayoutEffect(() => {
setDidMount(true);}, []);
return didMount;}const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog(['Component mount', 'Component update']);
const data = await preprocessData([
...createBoilerplateEntries(),...createUserTimingData(clearedMarks),]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-state-update',);expect(event.warning).toBe(null);
});// @reactVersion >= 18.2
it('should not warn about short (forced) updates during layout effects', async () => {class Component extends React.Component {
_didMount: boolean = false;componentDidMount() {this._didMount = true;
this.forceUpdate();
}render() {Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);return null;}}const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog(['Component mount', 'Component update']);
const data = await preprocessData([
...createBoilerplateEntries(),...createUserTimingData(clearedMarks),]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-force-update',);expect(event.warning).toBe(null);
});// This is temporarily disabled because the warning doesn't work// with useDeferredValueit.skip('should warn about long nested (state) updates during layout effects', async () => {
function Component() {const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
// Fake a long renderstartTime += 20000;
React.useLayoutEffect(() => {
setDidMount(true);}, []);
return didMount;}const cpuProfilerSample = creactCpuProfilerSample();const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog(['Component mount', 'Component update']);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {// Fake a long running renderstartTime += 20000;
}testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,ph: 'R',});});const data = await preprocessData([
cpuProfilerSample,...createBoilerplateEntries(),...testMarks,]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-state-update',);expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
);});// This is temporarily disabled because the warning doesn't work// with useDeferredValueit.skip('should warn about long nested (forced) updates during layout effects', async () => {
class Component extends React.Component {
_didMount: boolean = false;componentDidMount() {this._didMount = true;
this.forceUpdate();
}render() {Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);return null;}}const cpuProfilerSample = creactCpuProfilerSample();const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog(['Component mount', 'Component update']);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {// Fake a long running renderstartTime += 20000;
}testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,ph: 'R',});});const data = await preprocessData([
cpuProfilerSample,...createBoilerplateEntries(),...testMarks,]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-force-update',);expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
);});// @reactVersion >= 18.2
it('should not warn about transition updates scheduled during commit phase', async () => {function Component() {const [value, setValue] = React.useState(0);
// eslint-disable-next-line no-unused-varsconst [isPending, startTransition] = React.useTransition();
Scheduler.log(`Component rendered with value ${value}`);
// Fake a long renderif (value !== 0) {Scheduler.log('Long render');
startTime += 20000;
}React.useLayoutEffect(() => {
startTransition(() => {setValue(1);});}, []);
return value;}const cpuProfilerSample = creactCpuProfilerSample();const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog([
'Component rendered with value 0','Component rendered with value 0','Component rendered with value 1','Long render',]);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {// Fake a long running renderstartTime += 20000;
}testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,ph: 'R',});});const data = await preprocessData([
cpuProfilerSample,...createBoilerplateEntries(),...testMarks,]);
data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});});// This is temporarily disabled because the warning doesn't work// with useDeferredValueit.skip('should not warn about deferred value updates scheduled during commit phase', async () => {
function Component() {const [value, setValue] = React.useState(0);
const deferredValue = React.useDeferredValue(value);
Scheduler.log(
`Component rendered with value ${value} and deferredValue ${deferredValue}`,
);// Fake a long renderif (deferredValue !== 0) {Scheduler.log('Long render');
startTime += 20000;
}React.useLayoutEffect(() => {
setValue(1);}, []);
return value + deferredValue;
}const cpuProfilerSample = creactCpuProfilerSample();const root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() => {
root.render(<Component />);
});assertLog([
'Component rendered with value 0 and deferredValue 0','Component rendered with value 1 and deferredValue 0','Component rendered with value 1 and deferredValue 1','Long render',]);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {// Fake a long running renderstartTime += 20000;
}testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},cat: 'blink.user_timing',
name: markName,ph: 'R',});});const data = await preprocessData([
cpuProfilerSample,...createBoilerplateEntries(),...testMarks,]);
data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});});});describe('errors thrown while rendering', () => {// @reactVersion >= 18.0
it('shoult parse Errors thrown during render', async () => {jest.spyOn(console, 'error');
class ErrorBoundary extends React.Component {
state = {error: null};componentDidCatch(error) {this.setState({error});
}render() {if (this.state.error) {
return null;}return this.props.children;
}}function ExampleThatThrows() {throw Error('Expected error');}const testMarks = [creactCpuProfilerSample()];
// Mount and commit the appconst root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() =>
root.render(
<ErrorBoundary><ExampleThatThrows /></ErrorBoundary>,),);testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);expect(data.thrownErrors).toHaveLength(2);
expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
'"Expected error"',);});});describe('suspend during an update', () => {// This also tests an edge case where a component suspends while profiling// before the first commit is logged (so the lane-to-labels map will not yet exist).
// @reactVersion >= 18.2
it('should warn about suspending during an update', async () => {let promise = null;let resolvedValue = null;function readValue(value) {if (resolvedValue !== null) {return resolvedValue;} else if (promise === null) {promise = Promise.resolve(true).then(() => {
resolvedValue = value;});}throw promise;}function Component({shouldSuspend}) {Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {readValue(123);}return null;}// Mount and commit the appconst root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} /></React.Suspense>,
),);const testMarks = [creactCpuProfilerSample()];
// Start profiling and suspend during a render.
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} /></React.Suspense>,
),);testMarks.push(...createUserTimingData(clearedMarks));
let data;await utils.actAsync(async () => {
data = await preprocessData(testMarks);});expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
`"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
);});// @reactVersion >= 18.2
it('should not warn about suspending during an transition', async () => {let promise = null;let resolvedValue = null;function readValue(value) {if (resolvedValue !== null) {return resolvedValue;} else if (promise === null) {promise = Promise.resolve(true).then(() => {
resolvedValue = value;});}throw promise;}function Component({shouldSuspend}) {Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {readValue(123);}return null;}// Mount and commit the appconst root = ReactDOMClient.createRoot(
document.createElement('div'),
);utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} /></React.Suspense>,
),);const testMarks = [creactCpuProfilerSample()];
// Start profiling and suspend during a render.
await utils.actAsync(async () =>
React.startTransition(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} /></React.Suspense>,
),),);testMarks.push(...createUserTimingData(clearedMarks));
let data;await utils.actAsync(async () => {
data = await preprocessData(testMarks);});expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toBe(null);
});});});// TODO: Add test for snapshot base64 parsing
// TODO: Add test for flamechart parsing
});});// Note the in-memory tests vary slightly (e.g. timestamp values, lane numbers) from the above tests.
// That's okay; the important thing is the lane-to-label matches the subsequent events/measures.
describe('DevTools hook (in memory)', () => {
let store;
beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
store = global.store;
// Start profiling so that data will actually be recorded.
utils.act(() => store.profilerStore.startProfiling());
global.IS_REACT_ACT_ENVIRONMENT = true;
});it('should process a sample legacy render sequence', async () => {
utils.legacyRender(<div />, document.createElement('div'));
utils.act(() => store.profilerStore.stopProfiling());
const data = store.profilerStore.profilingData?.timelineData;
expect(data).toHaveLength(1);
const timelineData = data[0];
expect(timelineData).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {1 => [{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "layout-effects",},],},"componentMeasures": [],"duration": 20,"flamechart": [],"internalModuleSourceToRanges": Map {},"laneToLabelMap": Map {1 => "SyncHydrationLane",2 => "Sync",4 => "InputContinuousHydration",8 => "InputContinuous",16 => "DefaultHydration",32 => "Default",64 => "TransitionHydration",128 => "Transition",256 => "Transition",512 => "Transition",1024 => "Transition",2048 => "Transition",4096 => "Transition",8192 => "Transition",16384 => "Transition",32768 => "Transition",65536 => "Transition",131072 => "Transition",262144 => "Transition",524288 => "Transition",1048576 => "Transition",2097152 => "Transition",4194304 => "Retry",8388608 => "Retry",16777216 => "Retry",33554432 => "Retry",67108864 => "SelectiveHydration",134217728 => "IdleHydration",268435456 => "Idle",536870912 => "Offscreen",1073741824 => "Deferred",},"laneToReactMeasureMap": Map {1 => [],2 => [{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "layout-effects",},],4 => [],8 => [],16 => [],32 => [],64 => [],128 => [],256 => [],512 => [],1024 => [],2048 => [],4096 => [],8192 => [],16384 => [],32768 => [],65536 => [],131072 => [],262144 => [],524288 => [],1048576 => [],2097152 => [],4194304 => [],8388608 => [],16777216 => [],33554432 => [],67108864 => [],134217728 => [],268435456 => [],536870912 => [],1073741824 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [{"lanes": "0b0000000000000000000000000000010","timestamp": 10,"type": "schedule-render","warning": null,},],"snapshotHeight": 0,"snapshots": [],"startTime": -10,"suspenseEvents": [],"thrownErrors": [],}`);
});it('should process a sample createRoot render sequence', async () => {
function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}});return true;
}const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() => root.render(<App />));
utils.act(() => store.profilerStore.stopProfiling());
const data = store.profilerStore.profilingData?.timelineData;
expect(data).toHaveLength(1);
const timelineData = data[0];
// normalize the location for component stack source
// for snapshot testing
timelineData.schedulingEvents.forEach(event => {
if (event.componentStack) {
event.componentStack = normalizeCodeLocInfo(event.componentStack);
}});expect(timelineData).toMatchInlineSnapshot(`
{"batchUIDToMeasuresMap": Map {1 => [{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "layout-effects",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "passive-effects",},],2 => [{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render-idle",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "commit",},{"batchUID": 2,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "layout-effects",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "passive-effects",},],},"componentMeasures": [{"componentName": "App","duration": 0,"timestamp": 10,"type": "render","warning": null,},{"componentName": "App","duration": 0,"timestamp": 10,"type": "passive-effect-mount","warning": null,},{"componentName": "App","duration": 0,"timestamp": 10,"type": "render","warning": null,},{"componentName": "App","duration": 0,"timestamp": 10,"type": "passive-effect-mount","warning": null,},],"duration": 20,"flamechart": [],"internalModuleSourceToRanges": Map {},"laneToLabelMap": Map {1 => "SyncHydrationLane",2 => "Sync",4 => "InputContinuousHydration",8 => "InputContinuous",16 => "DefaultHydration",32 => "Default",64 => "TransitionHydration",128 => "Transition",256 => "Transition",512 => "Transition",1024 => "Transition",2048 => "Transition",4096 => "Transition",8192 => "Transition",16384 => "Transition",32768 => "Transition",65536 => "Transition",131072 => "Transition",262144 => "Transition",524288 => "Transition",1048576 => "Transition",2097152 => "Transition",4194304 => "Retry",8388608 => "Retry",16777216 => "Retry",33554432 => "Retry",67108864 => "SelectiveHydration",134217728 => "IdleHydration",268435456 => "Idle",536870912 => "Offscreen",1073741824 => "Deferred",},"laneToReactMeasureMap": Map {1 => [],2 => [],4 => [],8 => [],16 => [],32 => [{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render-idle",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "commit",},{"batchUID": 1,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "layout-effects",},{"batchUID": 1,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "passive-effects",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render-idle",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "render",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "commit",},{"batchUID": 2,"depth": 1,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "layout-effects",},{"batchUID": 2,"depth": 0,"duration": 0,"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "passive-effects",},],64 => [],128 => [],256 => [],512 => [],1024 => [],2048 => [],4096 => [],8192 => [],16384 => [],32768 => [],65536 => [],131072 => [],262144 => [],524288 => [],1048576 => [],2097152 => [],4194304 => [],8388608 => [],16777216 => [],33554432 => [],67108864 => [],134217728 => [],268435456 => [],536870912 => [],1073741824 => [],},"nativeEvents": [],"networkMeasures": [],"otherUserTimingMarks": [],"reactVersion": "<filtered-version>","schedulingEvents": [{"lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "schedule-render","warning": null,},{"componentName": "App","componentStack": "in App (at **)","lanes": "0b0000000000000000000000000100000","timestamp": 10,"type": "schedule-state-update","warning": null,},],"snapshotHeight": 0,"snapshots": [],"startTime": -10,"suspenseEvents": [],"thrownErrors": [],}`);
});});});