-
No profiling data has been recorded.
-
- Click the record button to start recording.
-
-
- Click{' '}
+
No profiling data has been recorded
+
+ Record a session to see which components rendered, how long they took,
+ and why.{' '}
- here
- {' '}
- to learn more about profiling.
+ Learn more
+
+
+ Start recording
+
);
}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
index a317e3fcf429..0e67848ebc1e 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
@@ -61,15 +61,44 @@
justify-content: center;
}
-.LearnMoreRow {
- margin-top: 1rem;
+.Header {
+ font-size: var(--font-size-sans-large);
+ font-weight: bold;
+ margin-bottom: 0.25rem;
+}
+
+.Description {
+ max-width: 26rem;
+ text-align: center;
color: var(--color-dim);
- font-size: var(--font-size-sans-small);
}
-.Header {
- font-size: var(--font-size-sans-large);
- margin-bottom: 0.5rem;
+.DescriptionLink {
+ color: var(--color-link);
+ text-decoration: underline;
+}
+
+.CTAButton {
+ margin-top: 0.5rem;
+ padding: 0.375rem 1rem;
+ border: none;
+ border-radius: 1rem;
+ background: var(--color-background-hover);
+ color: var(--color-button-focus);
+ font-family: inherit;
+ font-size: var(--font-size-sans-normal);
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.CTAButton:hover {
+ background: var(--color-background-selected);
+ color: var(--color-text-selected);
+}
+
+.CTAButton:focus-visible {
+ outline: 2px solid var(--color-button-active);
+ outline-offset: 2px;
}
.Toolbar {
@@ -127,9 +156,3 @@
display: flex;
align-items: center;
}
-
-.LearnMoreLink {
- color: var(--color-link);
- margin-left: 0.25rem;
- margin-right: 0.25rem;
-}
\ No newline at end of file
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress.js b/packages/react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress.js
index 16883cacfbf0..d61aead9816c 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress.js
@@ -8,17 +8,23 @@
*/
import * as React from 'react';
-import RecordToggle from './RecordToggle';
+import {useContext} from 'react';
+import {ProfilerContext} from './ProfilerContext';
import styles from './Profiler.css';
export default function RecordingInProgress(): React.Node {
+ const {stopProfiling} = useContext(ProfilerContext);
+
return (
Profiling is in progress...
-
- Click the record button to stop recording.
-
+
+ Stop recording
+
);
}
From c7cab6664db1b18c78044dde5317722acbeac1cd Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Mon, 3 Aug 2026 16:32:51 +0100
Subject: [PATCH 3/4] [DevTools] Remove Timeline profiler tab, suggest React
Performance tracks (#37186)
Cleaning up the Timeline profiler in the next commit on top of this one.
If user is debugging React 19.2+, we will show a suggestion to record a
trace on Performance panel. Otherwise, we will suggest to upgrade to
React 19.2 to unlock Performance tracks.
---
.../src/devtools/views/Icon.js | 9 ---
.../Profiler/ClearProfilingDataButton.js | 10 +--
.../views/Profiler/NoProfilingData.js | 41 ++++++++++-
.../src/devtools/views/Profiler/Profiler.css | 18 +++--
.../src/devtools/views/Profiler/Profiler.js | 57 +--------------
.../views/Profiler/ProfilerContext.js | 9 ++-
.../Profiler/ProfilingImportExportButtons.js | 73 ++++++++++---------
7 files changed, 101 insertions(+), 116 deletions(-)
diff --git a/packages/react-devtools-shared/src/devtools/views/Icon.js b/packages/react-devtools-shared/src/devtools/views/Icon.js
index f49bb4fd0380..d305c6e78d7b 100644
--- a/packages/react-devtools-shared/src/devtools/views/Icon.js
+++ b/packages/react-devtools-shared/src/devtools/views/Icon.js
@@ -21,7 +21,6 @@ export type IconType =
| 'flame-chart'
| 'profiler'
| 'ranked-chart'
- | 'timeline'
| 'search'
| 'settings'
| 'store-as-global-variable'
@@ -75,9 +74,6 @@ export default function Icon({
case 'ranked-chart':
pathData = PATH_RANKED_CHART;
break;
- case 'timeline':
- pathData = PATH_SCHEDULING_PROFILER;
- break;
case 'search':
pathData = PATH_SEARCH;
break;
@@ -159,11 +155,6 @@ const PATH_FLAME_CHART = `
const PATH_PROFILER = 'M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z';
-const PATH_SCHEDULING_PROFILER = `
- M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0
- 16H5V9h14v10zm0-12H5V5h14v2zM7 11h5v5H7z
-`;
-
const PATH_SEARCH = `
M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91
16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
index ea71d1b85a73..570765b14780 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
@@ -13,31 +13,23 @@ import {ProfilerContext} from './ProfilerContext';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import {StoreContext} from '../context';
-import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
export default function ClearProfilingDataButton(): React.Node {
const store = useContext(StoreContext);
const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
- const {file, setFile} = useContext(TimelineContext);
const {profilerStore} = store;
const doesHaveInMemoryData = didRecordCommits;
- const doesHaveUserTimingData = file !== null;
const clear = () => {
if (doesHaveInMemoryData) {
profilerStore.clear();
}
- if (doesHaveUserTimingData) {
- setFile(null);
- }
};
return (
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js b/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
index cb85948d58e5..4f0894ae876f 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
@@ -8,13 +8,38 @@
*/
import * as React from 'react';
-import {useContext} from 'react';
+import {useContext, useSyncExternalStore} from 'react';
import {ProfilerContext} from './ProfilerContext';
+import {StoreContext} from '../context';
import styles from './Profiler.css';
export default function NoProfilingData(): React.Node {
const {startProfiling} = useContext(ProfilerContext);
+ const store = useContext(StoreContext);
+
+ // React only started emitting Performance tracks in 19.2.
+ const supportsPerformanceTracks = useSyncExternalStore(
+ function subscribe(callback) {
+ store.addListener('rootSupportsPerformanceTracks', callback);
+ return function unsubscribe() {
+ store.removeListener('rootSupportsPerformanceTracks', callback);
+ };
+ },
+ function getState() {
+ return store.rootSupportsPerformanceTracks;
+ },
+ );
+
+ const performanceTracksLink = (
+
+ Performance tracks
+
+ );
return (
@@ -36,6 +61,20 @@ export default function NoProfilingData(): React.Node {
type="button">
Start recording
+
+ To profile scheduling and rendering on a timeline,{' '}
+ {supportsPerformanceTracks ? (
+ <>
+ record a profile in the Performance panel — React adds its own{' '}
+ {performanceTracksLink} there.
+ >
+ ) : (
+ <>
+ upgrade to React 19.2 or newer, which adds {performanceTracksLink}{' '}
+ to the Performance panel.
+ >
+ )}
+
);
}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
index 0e67848ebc1e..fbd2e4e948ee 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
@@ -68,7 +68,7 @@
}
.Description {
- max-width: 26rem;
+ max-width: 100%;
text-align: center;
color: var(--color-dim);
}
@@ -78,6 +78,16 @@
text-decoration: underline;
}
+.PerformanceTracksCard {
+ margin-top: 1.5rem;
+ max-width: 80%;
+ padding: 0.625rem 0.75rem;
+ background: var(--color-background-hover);
+ /* Squarer than the CTA's pill, so the two don't read as the same control. */
+ border-radius: 0.25rem;
+ text-align: center;
+}
+
.CTAButton {
margin-top: 0.5rem;
padding: 0.375rem 1rem;
@@ -150,9 +160,3 @@
.Link {
color: var(--color-button);
}
-
-.TimelineSearchInputContainer {
- flex: 1 1;
- display: flex;
- align-items: center;
-}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
index e9511e091627..8a4bf3b38135 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
@@ -16,8 +16,6 @@ import ClearProfilingDataButton from './ClearProfilingDataButton';
import CommitFlamegraph from './CommitFlamegraph';
import CommitRanked from './CommitRanked';
import RootSelector from './RootSelector';
-import {Timeline} from 'react-devtools-timeline/src/Timeline';
-import SidebarEventInfo from './SidebarEventInfo';
import RecordToggle from './RecordToggle';
import ReloadAndProfileButton from './ReloadAndProfileButton';
import ProfilingImportExportButtons from './ProfilingImportExportButtons';
@@ -32,8 +30,6 @@ import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/Set
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
import portaledContent from '../portaledContent';
-import {StoreContext} from '../context';
-import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
import styles from './Profiler.css';
@@ -58,13 +54,6 @@ function Profiler(_: {}) {
selectNextCommitIndex,
} = useContext(ProfilerContext);
- const {file: timelineTraceEventData, searchInputContainerRef} =
- useContext(TimelineContext);
-
- const {supportsTimeline} = useContext(StoreContext);
-
- const isLegacyProfilerSelected = selectedTabID !== 'timeline';
-
const handleKeyDown = useEffectEvent((event: KeyboardEvent) => {
const correctModifier = isMac ? event.metaKey : event.ctrlKey;
// Cmd+E to start/stop profiler recording
@@ -76,11 +65,7 @@ function Profiler(_: {}) {
}
event.preventDefault();
event.stopPropagation();
- } else if (
- isLegacyProfilerSelected &&
- didRecordCommits &&
- selectedCommitIndex !== null
- ) {
+ } else if (didRecordCommits && selectedCommitIndex !== null) {
// Cmd+Left/Right (Mac) or Ctrl+Left/Right (Windows/Linux) to navigate commits
if (
correctModifier &&
@@ -110,7 +95,7 @@ function Profiler(_: {}) {
}, []);
let view = null;
- if (didRecordCommits || selectedTabID === 'timeline') {
+ if (didRecordCommits) {
switch (selectedTabID) {
case 'flame-chart':
view = ;
@@ -118,9 +103,6 @@ function Profiler(_: {}) {
case 'ranked-chart':
view = ;
break;
- case 'timeline':
- view = ;
- break;
default:
break;
}
@@ -128,8 +110,6 @@ function Profiler(_: {}) {
view = ;
} else if (isProcessingData) {
view = ;
- } else if (timelineTraceEventData) {
- view = ;
} else if (supportsProfiling) {
view = ;
} else {
@@ -155,9 +135,6 @@ function Profiler(_: {}) {
}
}
break;
- case 'timeline':
- sidebar = ;
- break;
default:
break;
}
@@ -177,19 +154,13 @@ function Profiler(_: {}) {
currentTab={selectedTabID}
id="Profiler"
selectTab={selectTab}
- tabs={supportsTimeline ? tabsWithTimeline : tabs}
+ tabs={tabs}
type="profiler"
/>
- {!isLegacyProfilerSelected && (
-
- )}
- {isLegacyProfilerSelected && didRecordCommits && (
+ {didRecordCommits && (
@@ -208,15 +179,6 @@ function Profiler(_: {}) {
);
}
-const OnlyTimelineData = () => (
-
-
Timeline only
-
- The current profile contains only Timeline data.
-
-
-);
-
const tabs = [
{
id: 'flame-chart',
@@ -232,15 +194,4 @@ const tabs = [
},
];
-const tabsWithTimeline = [
- ...tabs,
- null, // Divider/separator
- {
- id: 'timeline',
- icon: 'timeline',
- label: 'Timeline',
- title: 'Timeline',
- },
-];
-
export default portaledContent(Profiler) as component();
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
index 07d82a54b48c..7539573fc6c0 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
@@ -29,7 +29,7 @@ import {useCommitFilteringAndNavigation} from './useCommitFilteringAndNavigation
import type {CommitDataFrontend, ProfilingDataFrontend} from './types';
-export type TabID = 'flame-chart' | 'ranked-chart' | 'timeline';
+export type TabID = 'flame-chart' | 'ranked-chart';
export type Context = {
// Which tab is selected in the Profiler UI?
@@ -204,7 +204,7 @@ function ProfilerContextController({children}: Props): React.Node {
}
}
- const [selectedTabID, selectTab] = useLocalStorage(
+ const [persistedTabID, selectTab] = useLocalStorage(
'React::DevTools::Profiler::defaultTab',
'flame-chart',
value => {
@@ -217,6 +217,11 @@ function ProfilerContextController({children}: Props): React.Node {
},
);
+ // The persisted value may name a tab that no longer exists,
+ // e.g. the removed "timeline" tab. Fall back rather than render nothing.
+ const selectedTabID: TabID =
+ persistedTabID === 'ranked-chart' ? persistedTabID : 'flame-chart';
+
const stopProfiling = useCallback(
() => store.profilerStore.stopProfiling(),
[store],
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
index 920785bad653..504137f3e485 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
@@ -19,7 +19,6 @@ import {
prepareProfilingDataFrontendFromExport,
} from './utils';
import {downloadFile} from '../utils';
-import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
import isArray from 'shared/isArray';
import hasOwnProperty from 'shared/hasOwnProperty';
@@ -29,7 +28,6 @@ import type {ProfilingDataExport} from './types';
export default function ProfilingImportExportButtons(): React.Node {
const {isProfiling, profilingData, rootID} = useContext(ProfilerContext);
- const {setFile} = useContext(TimelineContext);
const store = useContext(StoreContext);
const {profilerStore} = store;
@@ -76,6 +74,22 @@ export default function ProfilingImportExportButtons(): React.Node {
}
}, []);
+ const showImportError = (message: string | null) => {
+ modalDialogDispatch({
+ id: 'ProfilingImportExportButtons',
+ type: 'SHOW',
+ title: 'Import failed',
+ content: (
+
+ The profiling data you selected cannot be imported.
+ {message !== null && (
+ {message}
+ )}
+
+ ),
+ });
+ };
+
// TODO (profiling) We should probably use a transition for this and suspend while loading the file.
// Local files load so fast it's probably not very noticeable though.
const handleChange = () => {
@@ -87,39 +101,28 @@ export default function ProfilingImportExportButtons(): React.Node {
const fileReader = new FileReader();
fileReader.addEventListener('load', () => {
const raw = fileReader.result as any as string;
- const json = JSON.parse(raw);
-
- if (!isArray(json) && hasOwnProperty.call(json, 'version')) {
- // This looks like React profiling data.
- // But first, clear any User Timing marks; we should only have one type open at a time.
- setFile(null);
-
- try {
- const profilingDataExport = json as any as ProfilingDataExport;
- profilerStore.profilingData =
- prepareProfilingDataFrontendFromExport(profilingDataExport);
- } catch (error) {
- modalDialogDispatch({
- id: 'ProfilingImportExportButtons',
- type: 'SHOW',
- title: 'Import failed',
- content: (
-
- The profiling data you selected cannot be imported.
- {error !== null && (
- {error.message}
- )}
-
- ),
- });
- }
- } else {
- // Otherwise let's assume this is Trace Event data and pass it to the Timeline preprocessor.
- // But first, clear React profiling data; we should only have one type open at a time.
- profilerStore.clear();
-
- // TODO (timeline) We shouldn't need to re-open the File but we'll need to refactor to avoid this.
- setFile(file);
+
+ let json;
+ try {
+ json = JSON.parse(raw);
+ } catch (error) {
+ showImportError(error !== null ? error.message : null);
+ return;
+ }
+
+ if (isArray(json) || !hasOwnProperty.call(json, 'version')) {
+ showImportError(
+ 'This file does not look like a profile exported from the React DevTools profiler.',
+ );
+ return;
+ }
+
+ try {
+ const profilingDataExport = json as any as ProfilingDataExport;
+ profilerStore.profilingData =
+ prepareProfilingDataFrontendFromExport(profilingDataExport);
+ } catch (error) {
+ showImportError(error !== null ? error.message : null);
}
});
fileReader.readAsText(file);
From 7dfc7ccd12d0294debc69b9b9b4e9dd1fd42e08a Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Mon, 3 Aug 2026 16:32:51 +0100
Subject: [PATCH 4/4] [DevTools] Remove the dead Timeline profiler code
(#37187)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
With the tab gone, everything that fed it is unreachable. This deletes
`packages/react-devtools-timeline` (74 files) and the backend that
produced its data, `backend/profilingHooks.js`, along with
`SidebarEventInfo`, the two timeline test suites, the `timelineData`
snapshot serializer, and the scheduling-profiler fixture.
It also unwires the plumbing that only existed to carry timeline data:
`recordTimeline` across the reload-and-profile path (hook →
sessionStorage → agent → renderer), `timelineData` on
`ProfilingDataBackend` and the profile export, the `supportsTimeline`
Store config, the `rootSupportsTimelineProfiling` capability, the
`DevToolsProfilingHooks` type and the `ReactRenderer` members DevTools
used to inject it, the 40 `--color-timeline-*` theme variables in both
themes plus the orphaned `--color-scroll-caret`, and `hook.js`'s
internal-module-range tracking with its `react-devtools-facade` stubs.
`yarn.lock` is regenerated: 52 distinct package-versions and 68
requirement specs drop out, with no additions and no version changes to
anything that remains.
## Deliberate non-changes
- **`PROFILER_EXPORT_VERSION` stays at 5.**
`prepareProfilingDataFrontendFromExport` compares versions with `!==`,
so a bump would reject every profile anyone has already saved.
`timelineData` was an optional key, so dropping it is invisible in both
directions.
- **Profiling flag bit `0b010` is retired, not reused**, and the
constant is replaced by a comment saying so. Shipped backends keep
setting it, so renumbering `PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT`
into that slot would make a new frontend misread older backends as
tracks-capable.
- **The `displayName` properties on DevTools' cache thenables are
kept.** They look timeline-only, but `ReactFiberThenable` reads
`thenable.displayName` to name I/O in async debug info, which feeds the
Performance tracks. Only their stale comments are corrected.
- **`react-reconciler`, `shared/ReactFeatureFlags.js` and
`scripts/rollup` are untouched**; `enableSchedulingProfiler` is still
live for www and native-fb.
## Follow-ups (not in this stack)
Three stale comments still name the removed package:
`scripts/rollup/wrappers.js:532` and `ReactFiberLane.js:38,125`. Left
alone to keep this stack purely DevTools-side.
## Test plan
`yarn linc`, `yarn flow dom-node`, and the DevTools suite all pass on
this commit in isolation (40/40 suites, 582 tests).
---
.eslintignore | 2 -
.eslintrc.js | 3 +-
.gitignore | 1 -
.prettierignore | 2 -
.../devtools/scheduling-profiler/.gitignore | 1 -
.../devtools/scheduling-profiler/README.md | 15 -
fixtures/devtools/scheduling-profiler/app.js | 14 -
.../devtools/scheduling-profiler/index.html | 14 -
fixtures/devtools/scheduling-profiler/run.js | 78 -
packages/react-devtools-core/README.md | 2 +-
.../src/main/index.js | 2 -
.../src/DevToolsFacade.js | 5 -
.../react-devtools-inline/src/frontend.js | 1 -
.../src/__tests__/TimelineProfiler-test.js | 2643 -----------------
.../__serializers__/timelineDataSerializer.js | 29 -
.../src/__tests__/preprocessData-test.js | 2457 ---------------
.../src/__tests__/profilerContext-test.js | 25 +-
.../src/__tests__/setupTests.js | 4 +-
.../src/__tests__/utils.js | 3 -
.../src/backend/agent.js | 27 +-
.../src/backend/fiber/renderer.js | 84 +-
.../src/backend/profilingHooks.js | 989 ------
.../src/backend/types.js | 66 +-
.../react-devtools-shared/src/constants.js | 5 +-
.../src/devtools/ProfilerStore.js | 1 -
.../src/devtools/constants.js | 82 -
.../src/devtools/store.js | 39 +-
.../src/devtools/views/DevTools.js | 126 +-
.../src/devtools/views/ErrorBoundary/cache.js | 2 +-
.../views/Profiler/ReloadAndProfileButton.js | 3 +-
.../views/Profiler/SidebarEventInfo.css | 66 -
.../views/Profiler/SidebarEventInfo.js | 126 -
.../src/devtools/views/Profiler/types.js | 11 -
.../src/devtools/views/Profiler/utils.js | 125 +-
.../src/dynamicImportCache.js | 2 +-
packages/react-devtools-shared/src/hook.js | 43 -
.../src/hookNamesCache.js | 2 +-
.../src/inspectedElementCache.js | 2 +-
packages/react-devtools-shared/src/utils.js | 13 +-
packages/react-devtools-timeline/README.md | 3 -
packages/react-devtools-timeline/package.json | 29 -
.../src/CanvasPage.css | 7 -
.../react-devtools-timeline/src/CanvasPage.js | 729 -----
.../src/CanvasPageContextMenu.js | 182 --
.../src/EventTooltip.css | 88 -
.../src/EventTooltip.js | 513 ----
.../react-devtools-timeline/src/Timeline.css | 34 -
.../react-devtools-timeline/src/Timeline.js | 176 --
.../src/TimelineContext.js | 178 --
.../src/TimelineNotSupported.css | 38 -
.../src/TimelineNotSupported.js | 100 -
.../src/TimelineSearchContext.js | 166 --
.../src/TimelineSearchInput.js | 47 -
.../react-devtools-timeline/src/constants.js | 20 -
.../content-views/ComponentMeasuresView.js | 301 --
.../src/content-views/FlamechartView.js | 383 ---
.../src/content-views/NativeEventsView.js | 259 --
.../src/content-views/NetworkMeasuresView.js | 337 ---
.../src/content-views/ReactMeasuresView.js | 370 ---
.../src/content-views/SchedulingEventsView.js | 288 --
.../src/content-views/SnapshotsView.js | 260 --
.../src/content-views/SuspenseEventsView.js | 359 ---
.../src/content-views/ThrownErrorsView.js | 241 --
.../src/content-views/TimeAxisMarkersView.js | 163 -
.../src/content-views/UserTimingMarksView.js | 244 --
.../src/content-views/constants.js | 308 --
.../src/content-views/index.js | 20 -
.../utils/__tests__/__modules__/module-one.js | 16 -
.../utils/__tests__/__modules__/module-two.js | 18 -
.../utils/__tests__/colors-test.js | 93 -
.../utils/__tests__/moduleFilters-test.js | 79 -
.../src/content-views/utils/colors.js | 113 -
.../src/content-views/utils/moduleFilters.js | 73 -
.../src/content-views/utils/positioning.js | 41 -
.../src/content-views/utils/text.js | 134 -
.../src/createDataResourceFromImportedFile.js | 46 -
.../src/import-worker/InvalidProfileError.js | 13 -
.../src/import-worker/importFile.js | 46 -
.../src/import-worker/importFile.worker.js | 10 -
.../src/import-worker/index.js | 32 -
.../src/import-worker/preprocessData.js | 1178 --------
.../src/import-worker/readInputData.js | 35 -
.../src/timelineCache.js | 110 -
packages/react-devtools-timeline/src/types.js | 249 --
.../react-devtools-timeline/src/utils/flow.js | 16 -
.../src/utils/formatting.js | 45 -
.../src/utils/getBatchRange.js | 52 -
.../src/utils/useSmartTooltip.js | 81 -
.../src/view-base/BackgroundColorView.js | 28 -
.../src/view-base/HorizontalPanAndZoomView.js | 245 --
.../src/view-base/Surface.js | 154 -
.../src/view-base/VerticalScrollView.js | 297 --
.../src/view-base/View.js | 345 ---
.../src/view-base/__tests__/geometry-test.js | 272 --
.../src/view-base/constants.js | 14 -
.../src/view-base/geometry.js | 147 -
.../src/view-base/index.js | 19 -
.../src/view-base/layouter.js | 225 --
.../src/view-base/resizable/ResizableView.js | 299 --
.../src/view-base/resizable/ResizeBarView.js | 193 --
.../src/view-base/resizable/index.js | 11 -
.../src/view-base/useCanvasInteraction.js | 254 --
.../view-base/utils/__tests__/clamp-test.js | 29 -
.../utils/__tests__/scrollState-test.js | 261 --
.../src/view-base/utils/clamp.js | 17 -
.../src/view-base/utils/normalizeWheel.js | 85 -
.../src/view-base/utils/scrollState.js | 206 --
.../VerticalScrollBarView.js | 219 --
.../VerticalScrollOverflowView.js | 91 -
.../vertical-scroll-overflow/index.js | 11 -
.../withVerticalScrollbarLayout.js | 55 -
packages/react-devtools/OVERVIEW.md | 10 +-
scripts/devtools/configuration.js | 1 -
scripts/jest/config.build-devtools.js | 3 -
yarn.lock | 418 +--
115 files changed, 108 insertions(+), 19239 deletions(-)
delete mode 100644 fixtures/devtools/scheduling-profiler/.gitignore
delete mode 100644 fixtures/devtools/scheduling-profiler/README.md
delete mode 100644 fixtures/devtools/scheduling-profiler/app.js
delete mode 100644 fixtures/devtools/scheduling-profiler/index.html
delete mode 100755 fixtures/devtools/scheduling-profiler/run.js
delete mode 100644 packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
delete mode 100644 packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js
delete mode 100644 packages/react-devtools-shared/src/__tests__/preprocessData-test.js
delete mode 100644 packages/react-devtools-shared/src/backend/profilingHooks.js
delete mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.css
delete mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js
delete mode 100644 packages/react-devtools-timeline/README.md
delete mode 100644 packages/react-devtools-timeline/package.json
delete mode 100644 packages/react-devtools-timeline/src/CanvasPage.css
delete mode 100644 packages/react-devtools-timeline/src/CanvasPage.js
delete mode 100644 packages/react-devtools-timeline/src/CanvasPageContextMenu.js
delete mode 100644 packages/react-devtools-timeline/src/EventTooltip.css
delete mode 100644 packages/react-devtools-timeline/src/EventTooltip.js
delete mode 100644 packages/react-devtools-timeline/src/Timeline.css
delete mode 100644 packages/react-devtools-timeline/src/Timeline.js
delete mode 100644 packages/react-devtools-timeline/src/TimelineContext.js
delete mode 100644 packages/react-devtools-timeline/src/TimelineNotSupported.css
delete mode 100644 packages/react-devtools-timeline/src/TimelineNotSupported.js
delete mode 100644 packages/react-devtools-timeline/src/TimelineSearchContext.js
delete mode 100644 packages/react-devtools-timeline/src/TimelineSearchInput.js
delete mode 100644 packages/react-devtools-timeline/src/constants.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/FlamechartView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/NativeEventsView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/SnapshotsView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/TimeAxisMarkersView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/constants.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/index.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-one.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-two.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/__tests__/moduleFilters-test.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/colors.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/moduleFilters.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/positioning.js
delete mode 100644 packages/react-devtools-timeline/src/content-views/utils/text.js
delete mode 100644 packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/InvalidProfileError.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/importFile.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/importFile.worker.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/index.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/preprocessData.js
delete mode 100644 packages/react-devtools-timeline/src/import-worker/readInputData.js
delete mode 100644 packages/react-devtools-timeline/src/timelineCache.js
delete mode 100644 packages/react-devtools-timeline/src/types.js
delete mode 100644 packages/react-devtools-timeline/src/utils/flow.js
delete mode 100644 packages/react-devtools-timeline/src/utils/formatting.js
delete mode 100644 packages/react-devtools-timeline/src/utils/getBatchRange.js
delete mode 100644 packages/react-devtools-timeline/src/utils/useSmartTooltip.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/BackgroundColorView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/HorizontalPanAndZoomView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/Surface.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/VerticalScrollView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/View.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/__tests__/geometry-test.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/constants.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/geometry.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/index.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/layouter.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/resizable/ResizeBarView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/resizable/index.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/utils/__tests__/clamp-test.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/utils/__tests__/scrollState-test.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/utils/clamp.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/utils/normalizeWheel.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/utils/scrollState.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollBarView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollOverflowView.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/index.js
delete mode 100644 packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js
diff --git a/.eslintignore b/.eslintignore
index ab7e508d1b1e..956d0a182783 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -26,8 +26,6 @@ packages/react-devtools-inline/dist
packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
packages/react-devtools-shell/dist
-packages/react-devtools-timeline/dist
-packages/react-devtools-timeline/static
packages/react-devtools-cdt-mcp/dist
packages/react-devtools-cdt-mcp/fixtures
diff --git a/.eslintrc.js b/.eslintrc.js
index 99548b9dd067..e197bbf95271 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -337,8 +337,7 @@ module.exports = {
'packages/react-debug-tools/**/*.js',
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-facade/**/*.js',
- 'packages/react-devtools-timeline/**/*.js',
- 'packages/react-native-renderer/**/*.js',
+ 'packages/react-native-renderer/**/*.js',
'packages/eslint-plugin-react-hooks/**/*.js',
'packages/jest-react/**/*.js',
'packages/internal-test-utils/**/*.js',
diff --git a/.gitignore b/.gitignore
index ff6e57da6039..9f0da5163e69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,5 +41,4 @@ packages/react-devtools-extensions/.tempUserDataDir
packages/react-devtools-fusebox/dist
packages/react-devtools-inline/dist
packages/react-devtools-shell/dist
-packages/react-devtools-timeline/dist
packages/react-devtools-cdt-mcp/dist
diff --git a/.prettierignore b/.prettierignore
index eaed5e651000..2d63a8c6c88d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,8 +12,6 @@ packages/react-devtools-inline/dist
packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
packages/react-devtools-shell/dist
-packages/react-devtools-timeline/dist
-packages/react-devtools-timeline/static
# react compiler
compiler/**/dist
diff --git a/fixtures/devtools/scheduling-profiler/.gitignore b/fixtures/devtools/scheduling-profiler/.gitignore
deleted file mode 100644
index 8f6ca63cde27..000000000000
--- a/fixtures/devtools/scheduling-profiler/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-dependencies
diff --git a/fixtures/devtools/scheduling-profiler/README.md b/fixtures/devtools/scheduling-profiler/README.md
deleted file mode 100644
index 10f04c228d9d..000000000000
--- a/fixtures/devtools/scheduling-profiler/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Test fixture for `packages/react-devtools-scheduling-profiler`
-
-1. First, run the fixture:
-```sh
-# In the root directory
-# Download the latest *experimental* React build
-scripts/release/download-experimental-build.js
-
-# Run this fixtures
-fixtures/devtools/scheduling-profiler/run.js
-```
-
-2. Then open [localhost:8000/](http://localhost:8000/) and use the Performance tab in Chrome to reload-and-profile.
-3. Now stop profiling and export JSON.
-4. Lastly, open [react-scheduling-profiler.vercel.app](https://react-scheduling-profiler.vercel.app/) and upload the performance JSON data you just recorded.
\ No newline at end of file
diff --git a/fixtures/devtools/scheduling-profiler/app.js b/fixtures/devtools/scheduling-profiler/app.js
deleted file mode 100644
index 27e452a60ba6..000000000000
--- a/fixtures/devtools/scheduling-profiler/app.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const {createElement, useLayoutEffect, useState} = React;
-const {createRoot} = ReactDOM;
-
-function App() {
- const [isMounted, setIsMounted] = useState(false);
- useLayoutEffect(() => {
- setIsMounted(true);
- }, []);
- return createElement('div', null, `isMounted? ${isMounted}`);
-}
-
-const container = document.getElementById('container');
-const root = createRoot(container);
-root.render(createElement(App));
diff --git a/fixtures/devtools/scheduling-profiler/index.html b/fixtures/devtools/scheduling-profiler/index.html
deleted file mode 100644
index 4210a8e61f1a..000000000000
--- a/fixtures/devtools/scheduling-profiler/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- Scheduling Profiler Fixture
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/fixtures/devtools/scheduling-profiler/run.js b/fixtures/devtools/scheduling-profiler/run.js
deleted file mode 100755
index 5c0191acca3d..000000000000
--- a/fixtures/devtools/scheduling-profiler/run.js
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env node
-
-'use strict';
-
-const {
- copyFileSync,
- existsSync,
- mkdirSync,
- readFileSync,
- rmdirSync,
-} = require('fs');
-const {join} = require('path');
-const http = require('http');
-
-const DEPENDENCIES = [
- ['scheduler/umd/scheduler.development.js', 'scheduler.js'],
- ['react/umd/react.development.js', 'react.js'],
- ['react-dom/umd/react-dom.development.js', 'react-dom.js'],
-];
-
-const BUILD_DIRECTORY = '../../../build/oss-experimental/';
-const DEPENDENCIES_DIRECTORY = 'dependencies';
-
-function initDependencies() {
- if (existsSync(DEPENDENCIES_DIRECTORY)) {
- rmdirSync(DEPENDENCIES_DIRECTORY, {recursive: true});
- }
- mkdirSync(DEPENDENCIES_DIRECTORY);
-
- DEPENDENCIES.forEach(([from, to]) => {
- const fromPath = join(__dirname, BUILD_DIRECTORY, from);
- const toPath = join(__dirname, DEPENDENCIES_DIRECTORY, to);
- console.log(`Copying ${fromPath} => ${toPath}`);
- copyFileSync(fromPath, toPath);
- });
-}
-
-function initServer() {
- const host = 'localhost';
- const port = 8000;
-
- const requestListener = function (request, response) {
- let contents;
- switch (request.url) {
- case '/react.js':
- case '/react-dom.js':
- case '/scheduler.js':
- response.setHeader('Content-Type', 'text/javascript');
- response.writeHead(200);
- contents = readFileSync(
- join(__dirname, DEPENDENCIES_DIRECTORY, request.url)
- );
- response.end(contents);
- break;
- case '/app.js':
- response.setHeader('Content-Type', 'text/javascript');
- response.writeHead(200);
- contents = readFileSync(join(__dirname, 'app.js'));
- response.end(contents);
- break;
- case '/index.html':
- default:
- response.setHeader('Content-Type', 'text/html');
- response.writeHead(200);
- contents = readFileSync(join(__dirname, 'index.html'));
- response.end(contents);
- break;
- }
- };
-
- const server = http.createServer(requestListener);
- server.listen(port, host, () => {
- console.log(`Server is running on http://${host}:${port}`);
- });
-}
-
-initDependencies();
-initServer();
diff --git a/packages/react-devtools-core/README.md b/packages/react-devtools-core/README.md
index 3cced5e0e4bb..a80b2c0972ec 100644
--- a/packages/react-devtools-core/README.md
+++ b/packages/react-devtools-core/README.md
@@ -29,7 +29,7 @@ if (process.env.NODE_ENV !== 'production') {
|---------------------------|-------------|
| `settings` | Optional. If not specified, or received as null, then default settings are used. Can be plain object or a Promise that resolves with the [plain settings object](#Settings). If Promise rejects, the console will not be patched and some console features from React DevTools will not work. |
| `shouldStartProfilingNow` | Optional. Whether to start profiling immediately after installing the hook. Defaults to `false`. |
-| `profilingSettings` | Optional. Profiling settings used when `shouldStartProfilingNow` is `true`. Defaults to `{ recordChangeDescriptions: false, recordTimeline: false }`. |
+| `profilingSettings` | Optional. Profiling settings used when `shouldStartProfilingNow` is `true`. Defaults to `{ recordChangeDescriptions: false }`. |
| `componentFilters` | Optional. Array or Promise that resolves to an array of component filters to apply before DevTools connects. Defaults to the built-in host component filter. See [Component filters](#component-filters) for the full spec. |
#### `Settings`
diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js
index d3fc4c4d6038..7e7eb3c9f0a0 100644
--- a/packages/react-devtools-extensions/src/main/index.js
+++ b/packages/react-devtools-extensions/src/main/index.js
@@ -255,8 +255,6 @@ function createDevToolsInstance(): DevToolsInstance {
const store = new Store(bridge, {
isProfiling,
supportsReloadAndProfile: __IS_CHROME__ || __IS_EDGE__,
- // At this time, the timeline can only parse Chrome performance profiles.
- supportsTimeline: __IS_CHROME__,
supportsTraceUpdates: true,
supportsInspectMatchingDOMElement: true,
supportsClickToInspect: true,
diff --git a/packages/react-devtools-facade/src/DevToolsFacade.js b/packages/react-devtools-facade/src/DevToolsFacade.js
index b4fe09ac1176..a37d9790eac4 100644
--- a/packages/react-devtools-facade/src/DevToolsFacade.js
+++ b/packages/react-devtools-facade/src/DevToolsFacade.js
@@ -288,11 +288,6 @@ export function installFacade(target?: any = globalThis): Facade {
profilingState.onPostCommit(root);
}
},
- getInternalModuleRanges(): Array<[string, string]> {
- return [];
- },
- registerInternalModuleStart() {},
- registerInternalModuleStop() {},
};
Object.defineProperty(target, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {
diff --git a/packages/react-devtools-inline/src/frontend.js b/packages/react-devtools-inline/src/frontend.js
index 150b18a81d48..5853dbb85ee8 100644
--- a/packages/react-devtools-inline/src/frontend.js
+++ b/packages/react-devtools-inline/src/frontend.js
@@ -17,7 +17,6 @@ export function createStore(bridge: FrontendBridge, config?: Config): Store {
const store = new Store(bridge, {
checkBridgeProtocolCompatibility: true,
supportsTraceUpdates: true,
- supportsTimeline: true,
...config,
});
subscribeToStoreErrors(store, bridge);
diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
deleted file mode 100644
index 8d9f34371b34..000000000000
--- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+++ /dev/null
@@ -1,2643 +0,0 @@
-/**
- * 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 {
- getLegacyRenderImplementation,
- getModernRenderImplementation,
- normalizeCodeLocInfo,
-} from './utils';
-
-let React = require('react');
-let Scheduler;
-let store;
-let utils;
-
-// This flag is on experimental which disables timeline profiler.
-const enableComponentPerformanceTrack =
- React.version.startsWith('19') && React.version.includes('experimental');
-
-describe('Timeline profiler', () => {
- if (enableComponentPerformanceTrack) {
- test('no tests', () => {});
- // Ignore all tests.
- return;
- }
-
- beforeEach(() => {
- utils = require('./utils');
- utils.beforeEachProfiling();
-
- React = require('react');
- Scheduler = require('scheduler');
-
- store = global.store;
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('User Timing API', () => {
- let currentlyNotClearedMarks;
- let registeredMarks;
- let featureDetectionMarkName = null;
- let setPerformanceMock;
-
- function createUserTimingPolyfill() {
- featureDetectionMarkName = null;
-
- currentlyNotClearedMarks = [];
- registeredMarks = [];
-
- // 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 (:0:0)';
- } else if (markName.startsWith('--react-internal-module-stop')) {
- return '--react-internal-module-stop- at filtered (:1:1)';
- } else if (markName.startsWith('--react-version')) {
- return '--react-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);
-
- currentlyNotClearedMarks = currentlyNotClearedMarks.filter(
- mark => mark !== markName,
- );
- },
- mark(markName, markOptions) {
- markName = filterMarkData(markName);
-
- if (featureDetectionMarkName === null) {
- featureDetectionMarkName = markName;
- }
-
- registeredMarks.push(markName);
- currentlyNotClearedMarks.push(markName);
-
- if (markOptions != null) {
- // This is triggers the feature detection.
- markOptions.startTime++;
- }
- },
- };
- }
-
- function eraseRegisteredMarks() {
- registeredMarks.splice(0);
- }
-
- function dispatchAndSetCurrentEvent(element, event) {
- try {
- window.event = event;
- element.dispatchEvent(event);
- } finally {
- window.event = undefined;
- }
- }
-
- beforeEach(() => {
- setPerformanceMock =
- require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
- setPerformanceMock(createUserTimingPolyfill());
- });
-
- afterEach(() => {
- // Verify all logged marks also get cleared.
- expect(currentlyNotClearedMarks).toHaveLength(0);
-
- setPerformanceMock(null);
- });
-
- describe('with legacy render', () => {
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should mark sync render without suspends or state updates', () => {
- utils.act(() => store.profilerStore.startProfiling());
- legacyRender(
);
- utils.act(() => store.profilerStore.stopProfiling());
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--schedule-render-1",
- "--render-start-1",
- "--render-stop",
- "--commit-start-1",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--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",
- "--layout-effects-start-1",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- // TODO(hoxyq): investigate why running this test with React 18 fails
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should mark sync render with suspense that resolves', async () => {
- const fakeSuspensePromise = Promise.resolve(true);
- function Example() {
- throw fakeSuspensePromise;
- }
-
- legacyRender(
-
-
- ,
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-2",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--suspense-suspend-0-Example-mount-2-",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-2",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await fakeSuspensePromise;
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--suspense-resolved-0-Example",
- ]
- `);
- });
-
- // TODO(hoxyq): investigate why running this test with React 18 fails
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should mark sync render with suspense that rejects', async () => {
- const fakeSuspensePromise = Promise.reject(new Error('error'));
- function Example() {
- throw fakeSuspensePromise;
- }
-
- legacyRender(
-
-
- ,
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-2",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--suspense-suspend-0-Example-mount-2-",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-2",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await expect(fakeSuspensePromise).rejects.toThrow();
- expect(registeredMarks).toContain(`--suspense-rejected-0-Example`);
- });
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should mark sync render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
-
- 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');
- }
-
- utils.act(() => store.profilerStore.startProfiling());
- legacyRender(
-
-
- ,
- );
- utils.act(() => store.profilerStore.stopProfiling());
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--schedule-render-1",
- "--render-start-1",
- "--component-render-start-ErrorBoundary",
- "--component-render-stop",
- "--component-render-start-ExampleThatThrows",
- "--component-render-start-ExampleThatThrows",
- "--component-render-stop",
- "--error-ExampleThatThrows-mount-Expected error",
- "--render-stop",
- "--commit-start-1",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--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",
- "--layout-effects-start-1",
- "--schedule-state-update-1-ErrorBoundary",
- "--layout-effects-stop",
- "--commit-stop",
- "--render-start-1",
- "--component-render-start-ErrorBoundary",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-1",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--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",
- "--commit-stop",
- ]
- `);
- });
- });
-
- describe('with createRoot', () => {
- let waitFor;
- let waitForAll;
- let waitForPaint;
- let assertLog;
-
- beforeEach(() => {
- const InternalTestUtils = require('internal-test-utils');
- waitFor = InternalTestUtils.waitFor;
- waitForAll = InternalTestUtils.waitForAll;
- waitForPaint = InternalTestUtils.waitForPaint;
- assertLog = InternalTestUtils.assertLog;
- });
-
- const {render: modernRender} = getModernRenderImplementation();
-
- it('should mark concurrent render without suspends or state updates', async () => {
- modernRender(
);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark render yields', async () => {
- function Bar() {
- Scheduler.log('Bar');
- return null;
- }
-
- function Foo() {
- Scheduler.log('Foo');
- return ;
- }
-
- React.startTransition(() => {
- modernRender( );
- });
-
- await waitFor(['Foo']);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-128",
- "--render-start-128",
- "--component-render-start-Foo",
- "--component-render-stop",
- "--render-yield",
- ]
- `);
- });
-
- it('should mark concurrent render with suspense that resolves', async () => {
- let resolveFakePromise;
- const fakeSuspensePromise = new Promise(
- resolve => (resolveFakePromise = resolve),
- );
-
- function Example() {
- throw fakeSuspensePromise;
- }
-
- modernRender(
-
-
- ,
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--suspense-suspend-0-Example-mount-32-",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await resolveFakePromise();
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--suspense-resolved-0-Example",
- ]
- `);
- });
-
- it('should mark concurrent render with suspense that rejects', async () => {
- let rejectFakePromise;
- const fakeSuspensePromise = new Promise(
- (_, reject) => (rejectFakePromise = reject),
- );
-
- function Example() {
- throw fakeSuspensePromise;
- }
-
- modernRender(
-
-
- ,
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--suspense-suspend-0-Example-mount-32-",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await expect(() => {
- rejectFakePromise(new Error('error'));
- return fakeSuspensePromise;
- }).rejects.toThrow();
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--suspense-rejected-0-Example",
- ]
- `);
- });
-
- it('should mark cascading class component state updates', async () => {
- class Example extends React.Component {
- state = {didMount: false};
- componentDidMount() {
- this.setState({didMount: true});
- }
- render() {
- return null;
- }
- }
-
- modernRender( );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--schedule-state-update-2-Example",
- "--layout-effects-stop",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--commit-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark cascading class component force updates', async () => {
- class Example extends React.Component {
- componentDidMount() {
- this.forceUpdate();
- }
- render() {
- return null;
- }
- }
-
- modernRender( );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--schedule-forced-update-2-Example",
- "--layout-effects-stop",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--commit-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark render phase state updates for class component', async () => {
- class Example extends React.Component {
- state = {didRender: false};
- render() {
- if (this.state.didRender === false) {
- this.setState({didRender: true});
- }
- return null;
- }
- }
-
- modernRender( );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
-
- await waitForPaint([]);
-
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--schedule-state-update-32-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark render phase force updates for class component', async () => {
- let forced = false;
- class Example extends React.Component {
- render() {
- if (!forced) {
- forced = true;
- this.forceUpdate();
- }
- return null;
- }
- }
-
- modernRender( );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
-
- await waitForPaint([]);
-
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--schedule-forced-update-32-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark cascading layout updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useLayoutEffect(() => {
- setDidMount(true);
- }, []);
- return didMount;
- }
-
- modernRender( );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--component-layout-effect-mount-start-Example",
- "--schedule-state-update-2-Example",
- "--component-layout-effect-mount-stop",
- "--layout-effects-stop",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--commit-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark cascading passive updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useEffect(() => {
- setDidMount(true);
- }, []);
- return didMount;
- }
-
- modernRender( );
-
- await waitForAll([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- "--passive-effects-start-32",
- "--component-passive-effect-mount-start-Example",
- "--schedule-state-update-32-Example",
- "--component-passive-effect-mount-stop",
- "--passive-effects-stop",
- "--render-start-32",
- "--component-render-start-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark render phase updates', async () => {
- function Example() {
- const [didRender, setDidRender] = React.useState(false);
- if (!didRender) {
- setDidRender(true);
- }
- return didRender;
- }
-
- modernRender( );
-
- await waitForAll([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- "--render-start-32",
- "--component-render-start-Example",
- "--schedule-state-update-32-Example",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark concurrent render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
-
- 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() {
- // eslint-disable-next-line no-throw-literal
- throw 'Expected error';
- }
-
- modernRender(
-
-
- ,
- );
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForPaint([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--render-start-32",
- "--component-render-start-ErrorBoundary",
- "--component-render-stop",
- "--component-render-start-ExampleThatThrows",
- "--component-render-stop",
- "--error-ExampleThatThrows-mount-Expected error",
- "--render-stop",
- "--render-start-32",
- "--component-render-start-ErrorBoundary",
- "--component-render-stop",
- "--component-render-start-ExampleThatThrows",
- "--component-render-stop",
- "--error-ExampleThatThrows-mount-Expected error",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--schedule-state-update-2-ErrorBoundary",
- "--layout-effects-stop",
- "--render-start-2",
- "--component-render-start-ErrorBoundary",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--commit-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('should mark passive and layout effects', async () => {
- function ComponentWithEffects() {
- React.useLayoutEffect(() => {
- Scheduler.log('layout 1 mount');
- return () => {
- Scheduler.log('layout 1 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 1 mount');
- return () => {
- Scheduler.log('passive 1 unmount');
- };
- }, []);
-
- React.useLayoutEffect(() => {
- Scheduler.log('layout 2 mount');
- return () => {
- Scheduler.log('layout 2 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 2 mount');
- return () => {
- Scheduler.log('passive 2 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 3 mount');
- return () => {
- Scheduler.log('passive 3 unmount');
- };
- }, []);
-
- return null;
- }
-
- const unmount = modernRender( );
-
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- "--render-start-32",
- "--component-render-start-ComponentWithEffects",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-32",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-32",
- "--component-layout-effect-mount-start-ComponentWithEffects",
- "--component-layout-effect-mount-stop",
- "--component-layout-effect-mount-start-ComponentWithEffects",
- "--component-layout-effect-mount-stop",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForAll([
- 'passive 1 mount',
- 'passive 2 mount',
- 'passive 3 mount',
- ]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--passive-effects-start-32",
- "--component-passive-effect-mount-start-ComponentWithEffects",
- "--component-passive-effect-mount-stop",
- "--component-passive-effect-mount-start-ComponentWithEffects",
- "--component-passive-effect-mount-stop",
- "--component-passive-effect-mount-start-ComponentWithEffects",
- "--component-passive-effect-mount-stop",
- "--passive-effects-stop",
- ]
- `);
-
- eraseRegisteredMarks();
-
- await waitForAll([]);
-
- unmount();
-
- assertLog([
- 'layout 1 unmount',
- 'layout 2 unmount',
- 'passive 1 unmount',
- 'passive 2 unmount',
- 'passive 3 unmount',
- ]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-2",
- "--render-start-2",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--component-layout-effect-unmount-start-ComponentWithEffects",
- "--component-layout-effect-unmount-stop",
- "--component-layout-effect-unmount-start-ComponentWithEffects",
- "--component-layout-effect-unmount-stop",
- "--layout-effects-start-2",
- "--layout-effects-stop",
- "--passive-effects-start-2",
- "--component-passive-effect-unmount-start-ComponentWithEffects",
- "--component-passive-effect-unmount-stop",
- "--component-passive-effect-unmount-start-ComponentWithEffects",
- "--component-passive-effect-unmount-stop",
- "--component-passive-effect-unmount-start-ComponentWithEffects",
- "--component-passive-effect-unmount-stop",
- "--passive-effects-stop",
- "--commit-stop",
- ]
- `);
- });
- });
-
- describe('lane labels', () => {
- describe('with legacy render', () => {
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('regression test SyncLane', () => {
- utils.act(() => store.profilerStore.startProfiling());
- legacyRender(
);
- utils.act(() => store.profilerStore.stopProfiling());
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--schedule-render-1",
- "--render-start-1",
- "--render-stop",
- "--commit-start-1",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--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",
- "--layout-effects-start-1",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
- });
-
- describe('with createRoot()', () => {
- let waitForAll;
-
- beforeEach(() => {
- const InternalTestUtils = require('internal-test-utils');
- waitForAll = InternalTestUtils.waitForAll;
- });
-
- const {render: modernRender} = getModernRenderImplementation();
-
- it('regression test DefaultLane', () => {
- modernRender(
);
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
- });
-
- it('regression test InputDiscreteLane', async () => {
- const targetRef = React.createRef(null);
-
- function App() {
- const [count, setCount] = React.useState(0);
- const handleClick = () => {
- setCount(count + 1);
- };
- return ;
- }
-
- modernRender( );
- await waitForAll([]);
-
- eraseRegisteredMarks();
-
- targetRef.current.click();
-
- // Wait a frame, for React to process the "click" update.
- await Promise.resolve();
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-state-update-2-App",
- "--render-start-2",
- "--component-render-start-App",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-2",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-2",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
-
- it('regression test InputContinuousLane', async () => {
- const targetRef = React.createRef(null);
-
- function App() {
- const [count, setCount] = React.useState(0);
- const handleMouseOver = () => setCount(count + 1);
- return
;
- }
-
- modernRender( );
- await waitForAll([]);
-
- eraseRegisteredMarks();
-
- const event = document.createEvent('MouseEvents');
- event.initEvent('mouseover', true, true);
- dispatchAndSetCurrentEvent(targetRef.current, event);
-
- await waitForAll([]);
-
- expect(registeredMarks).toMatchInlineSnapshot(`
- [
- "--schedule-state-update-8-App",
- "--render-start-8",
- "--component-render-start-App",
- "--component-render-stop",
- "--render-stop",
- "--commit-start-8",
- "--react-version-",
- "--profiler-version-1",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-internal-module-start- at filtered (:0:0)",
- "--react-internal-module-stop- at filtered (:1:1)",
- "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
- "--layout-effects-start-8",
- "--layout-effects-stop",
- "--commit-stop",
- ]
- `);
- });
- });
- });
- });
-
- describe('DevTools hook (in memory)', () => {
- let getBatchOfWork;
- let stopProfilingAndGetTimelineData;
-
- beforeEach(() => {
- getBatchOfWork = index => {
- const timelineData = stopProfilingAndGetTimelineData();
- if (timelineData) {
- if (timelineData.batchUIDToMeasuresMap.size > index) {
- return Array.from(timelineData.batchUIDToMeasuresMap.values())[
- index
- ];
- }
- }
-
- return null;
- };
-
- stopProfilingAndGetTimelineData = () => {
- utils.act(() => store.profilerStore.stopProfiling());
-
- const timelineData = store.profilerStore.profilingData?.timelineData;
-
- if (timelineData) {
- expect(timelineData).toHaveLength(1);
-
- // normalize the location for component stack source
- // for snapshot testing
- timelineData.forEach(data => {
- data.schedulingEvents.forEach(event => {
- if (event.componentStack) {
- event.componentStack = normalizeCodeLocInfo(
- event.componentStack,
- );
- }
- });
- });
-
- return timelineData[0];
- } else {
- return null;
- }
- };
- });
-
- describe('when profiling', () => {
- describe('with legacy render', () => {
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- beforeEach(() => {
- utils.act(() => store.profilerStore.startProfiling());
- });
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should mark sync render without suspends or state updates', () => {
- legacyRender(
);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- ]
- `);
- });
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should mark sync render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- componentDidCatch(error) {
- this.setState({error});
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- if (this.state.error) {
- Scheduler.unstable_yieldValue('ErrorBoundary fallback');
- return null;
- }
- Scheduler.unstable_yieldValue('ErrorBoundary render');
- return this.props.children;
- }
- }
-
- function ExampleThatThrows() {
- Scheduler.unstable_yieldValue('ExampleThatThrows');
- throw Error('Expected error');
- }
-
- legacyRender(
-
-
- ,
- );
-
- expect(Scheduler.unstable_clearYields()).toEqual([
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ExampleThatThrows',
- 'ErrorBoundary fallback',
- ]);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ExampleThatThrows",
- "duration": 0,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "componentStack": "
- in ErrorBoundary (at **)",
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 20,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ExampleThatThrows",
- "message": "Expected error",
- "phase": "mount",
- "timestamp": 20,
- "type": "thrown-error",
- },
- ]
- `);
- });
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should mark sync render with suspense that resolves', async () => {
- let resolveFn;
- let resolved = false;
- const suspensePromise = new Promise(resolve => {
- resolveFn = () => {
- resolved = true;
- resolve();
- };
- });
-
- function Example() {
- Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
- if (!resolved) {
- throw suspensePromise;
- }
- return null;
- }
-
- legacyRender(
-
-
- ,
- );
-
- expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
-
- Scheduler.unstable_advanceTime(10);
- resolveFn();
- await suspensePromise;
-
- await Scheduler.unstable_flushAllWithoutAsserting();
- expect(Scheduler.unstable_clearYields()).toEqual(['resolved']);
-
- const timelineData = stopProfilingAndGetTimelineData();
-
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 0,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "unresolved",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- }
- `);
-
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
- });
-
- // @reactVersion = 18.2
- it('should mark sync render with suspense that rejects', async () => {
- let rejectFn;
- let rejected = false;
- const suspensePromise = new Promise((resolve, reject) => {
- rejectFn = () => {
- rejected = true;
- reject(new Error('error'));
- };
- });
-
- function Example() {
- Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
- if (!rejected) {
- throw suspensePromise;
- }
- return null;
- }
-
- legacyRender(
-
-
- ,
- );
-
- expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
-
- Scheduler.unstable_advanceTime(10);
- rejectFn();
- await expect(suspensePromise).rejects.toThrow();
-
- expect(Scheduler.unstable_clearYields()).toEqual(['rejected']);
-
- const timelineData = stopProfilingAndGetTimelineData();
-
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 0,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "unresolved",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- }
- `);
-
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
- });
- });
-
- describe('with createRoot()', () => {
- let waitFor;
- let waitForAll;
- let waitForPaint;
- let assertLog;
-
- beforeEach(() => {
- const InternalTestUtils = require('internal-test-utils');
- waitFor = InternalTestUtils.waitFor;
- waitForAll = InternalTestUtils.waitForAll;
- waitForPaint = InternalTestUtils.waitForPaint;
- assertLog = InternalTestUtils.assertLog;
- });
-
- const {render: modernRender} = getModernRenderImplementation();
-
- beforeEach(() => {
- utils.act(() => store.profilerStore.startProfiling());
- });
-
- it('should mark concurrent render without suspends or state updates', () => {
- utils.act(() => modernRender(
));
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark concurrent render without suspends with state updates', () => {
- let updaterFn;
-
- function Example() {
- const setHigh = React.useState(0)[1];
- const setLow = React.useState(0)[1];
-
- updaterFn = () => {
- React.startTransition(() => {
- setLow(prevLow => prevLow + 1);
- });
- setHigh(prevHigh => prevHigh + 1);
- };
-
- Scheduler.unstable_advanceTime(10);
-
- return null;
- }
-
- utils.act(() => modernRender( ));
- utils.act(() => store.profilerStore.stopProfiling());
- utils.act(() => store.profilerStore.startProfiling());
- utils.act(updaterFn);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000010000000",
- "timestamp": 10,
- "type": "schedule-state-update",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 0,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- });
-
- it('should mark render yields', async () => {
- function Bar() {
- Scheduler.log('Bar');
- return null;
- }
-
- function Foo() {
- Scheduler.log('Foo');
- return ;
- }
-
- React.startTransition(() => {
- modernRender( );
- });
-
- // Do one step of work.
- await waitFor(['Foo']);
-
- // Finish flushing so React commits;
- // Unless we do this, the ProfilerStore won't collect Profiling data.
- await waitForAll(['Bar']);
-
- // Since we yielded, the batch should report two separate "render" chunks.
- const batch = getBatchOfWork(0);
- expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
- });
-
- it('should mark concurrent render with suspense that resolves', async () => {
- let resolveFn;
- let resolved = false;
- const suspensePromise = new Promise(resolve => {
- resolveFn = () => {
- resolved = true;
- resolve();
- };
- });
-
- function Example() {
- Scheduler.log(resolved ? 'resolved' : 'suspended');
- if (!resolved) {
- throw suspensePromise;
- }
- return null;
- }
-
- modernRender(
-
-
- ,
- );
-
- await waitForAll([
- 'suspended',
- // pre-warming
- 'suspended',
- ]);
-
- Scheduler.unstable_advanceTime(10);
- resolveFn();
- await suspensePromise;
-
- await waitForAll(['resolved']);
-
- const timelineData = stopProfilingAndGetTimelineData();
-
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 10,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "resolved",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- },
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 10,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "resolved",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- },
- ]
- `);
-
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- // An additional measure with pre-warming
- expect(timelineData.componentMeasures).toHaveLength(3);
- });
-
- it('should mark concurrent render with suspense that rejects', async () => {
- let rejectFn;
- let rejected = false;
- const suspensePromise = new Promise((resolve, reject) => {
- rejectFn = () => {
- rejected = true;
- reject(new Error('error'));
- };
- });
-
- function Example() {
- Scheduler.log(rejected ? 'rejected' : 'suspended');
- if (!rejected) {
- throw suspensePromise;
- }
- return null;
- }
-
- modernRender(
-
-
- ,
- );
-
- await waitForAll(['suspended', 'suspended']);
-
- Scheduler.unstable_advanceTime(10);
- rejectFn();
- await expect(suspensePromise).rejects.toThrow();
-
- await waitForAll(['rejected']);
-
- const timelineData = stopProfilingAndGetTimelineData();
-
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 10,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "rejected",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- },
- {
- "componentName": "Example",
- "depth": 0,
- "duration": 10,
- "id": "0",
- "phase": "mount",
- "promiseName": "",
- "resolution": "rejected",
- "timestamp": 10,
- "type": "suspense",
- "warning": null,
- },
- ]
- `);
-
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- // An additional measure with pre-warming
- expect(timelineData.componentMeasures).toHaveLength(3);
- });
-
- it('should mark cascading class component state updates', async () => {
- class Example extends React.Component {
- state = {didMount: false};
- componentDidMount() {
- this.setState({didMount: true});
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(this.state.didMount ? 'update' : 'mount');
- return null;
- }
- }
-
- modernRender( );
-
- await waitForPaint(['mount', 'update']);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 20,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark cascading class component force updates', async () => {
- let forced = false;
- class Example extends React.Component {
- componentDidMount() {
- forced = true;
- this.forceUpdate();
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(forced ? 'force update' : 'mount');
- return null;
- }
- }
-
- modernRender( );
-
- await waitForPaint(['mount', 'force update']);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 20,
- "type": "schedule-force-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark render phase state updates for class component', async () => {
- class Example extends React.Component {
- state = {didRender: false};
- render() {
- if (this.state.didRender === false) {
- this.setState({didRender: true});
- }
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(
- this.state.didRender ? 'second render' : 'first render',
- );
- return null;
- }
- }
-
- modernRender( );
-
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
-
- await waitForAll(['first render', 'second render']);
-
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark render phase force updates for class component', async () => {
- let forced = false;
- class Example extends React.Component {
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(forced ? 'force update' : 'render');
- if (!forced) {
- forced = true;
- this.forceUpdate();
- }
- return null;
- }
- }
-
- modernRender( );
-
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
-
- await waitForAll(['render', 'force update']);
-
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 20,
- "type": "schedule-force-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark cascading layout updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(1);
- setDidMount(true);
- }, []);
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(didMount ? 'update' : 'mount');
- return didMount;
- }
-
- modernRender( );
-
- await waitForAll(['mount', 'update']);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 1,
- "timestamp": 20,
- "type": "layout-effect-mount",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 21,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 21,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark cascading passive updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(1);
- setDidMount(true);
- }, []);
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(didMount ? 'update' : 'mount');
- return didMount;
- }
-
- modernRender( );
- await waitForAll(['mount', 'update']);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 1,
- "timestamp": 20,
- "type": "passive-effect-mount",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 21,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 21,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark render phase updates', async () => {
- function Example() {
- const [didRender, setDidRender] = React.useState(false);
- Scheduler.unstable_advanceTime(10);
- if (!didRender) {
- setDidRender(true);
- }
- Scheduler.log(didRender ? 'update' : 'mount');
- return didRender;
- }
-
- modernRender( );
- await waitForAll(['mount', 'update']);
-
- const timelineData = stopProfilingAndGetTimelineData();
- // Render phase updates should be retried as part of the same batch.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "Example",
- "duration": 20,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 20,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
-
- it('should mark concurrent render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
-
- class ErrorBoundary extends React.Component {
- state = {error: null};
- componentDidCatch(error) {
- this.setState({error});
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- if (this.state.error) {
- Scheduler.log('ErrorBoundary fallback');
- return null;
- }
- Scheduler.log('ErrorBoundary render');
- return this.props.children;
- }
- }
-
- function ExampleThatThrows() {
- Scheduler.log('ExampleThatThrows');
- // eslint-disable-next-line no-throw-literal
- throw 'Expected error';
- }
-
- modernRender(
-
-
- ,
- );
-
- await waitForAll([
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ErrorBoundary fallback',
- ]);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ExampleThatThrows",
- "duration": 0,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ExampleThatThrows",
- "duration": 0,
- "timestamp": 30,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 30,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "componentStack": "
- in ErrorBoundary (at **)",
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 30,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ExampleThatThrows",
- "message": "Expected error",
- "phase": "mount",
- "timestamp": 20,
- "type": "thrown-error",
- },
- {
- "componentName": "ExampleThatThrows",
- "message": "Expected error",
- "phase": "mount",
- "timestamp": 30,
- "type": "thrown-error",
- },
- ]
- `);
- });
-
- it('should mark passive and layout effects', async () => {
- function ComponentWithEffects() {
- React.useLayoutEffect(() => {
- Scheduler.log('layout 1 mount');
- return () => {
- Scheduler.log('layout 1 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 1 mount');
- return () => {
- Scheduler.log('passive 1 unmount');
- };
- }, []);
-
- React.useLayoutEffect(() => {
- Scheduler.log('layout 2 mount');
- return () => {
- Scheduler.log('layout 2 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 2 mount');
- return () => {
- Scheduler.log('passive 2 unmount');
- };
- }, []);
-
- React.useEffect(() => {
- Scheduler.log('passive 3 mount');
- return () => {
- Scheduler.log('passive 3 unmount');
- };
- }, []);
-
- return null;
- }
-
- const unmount = modernRender( );
-
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
-
- await waitForAll([
- 'passive 1 mount',
- 'passive 2 mount',
- 'passive 3 mount',
- ]);
-
- await waitForAll([]);
-
- unmount();
-
- assertLog([
- 'layout 1 unmount',
- 'layout 2 unmount',
- 'passive 1 unmount',
- 'passive 2 unmount',
- 'passive 3 unmount',
- ]);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "layout-effect-mount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "layout-effect-mount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-mount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-mount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-mount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "layout-effect-unmount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "layout-effect-unmount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-unmount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-unmount",
- "warning": null,
- },
- {
- "componentName": "ComponentWithEffects",
- "duration": 0,
- "timestamp": 10,
- "type": "passive-effect-unmount",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
- 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": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "render-idle",
- },
- {
- "batchUID": 2,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "render",
- },
- {
- "batchUID": 2,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "commit",
- },
- {
- "batchUID": 2,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "layout-effects",
- },
- {
- "batchUID": 2,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "passive-effects",
- },
- ],
- }
- `);
- });
-
- it('should generate component stacks for state update', async () => {
- function CommponentWithChildren({initialRender}) {
- Scheduler.log('Render ComponentWithChildren');
- return ;
- }
-
- function Child({initialRender}) {
- const [didRender, setDidRender] = React.useState(initialRender);
- if (!didRender) {
- setDidRender(true);
- }
- Scheduler.log('Render Child');
- return null;
- }
-
- modernRender( );
-
- await waitForAll([
- 'Render ComponentWithChildren',
- 'Render Child',
- 'Render Child',
- ]);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "Child",
- "componentStack": "
- in Child (at **)
- in CommponentWithChildren (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- });
- });
- });
-
- describe('when not profiling', () => {
- describe('with legacy render', () => {
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should not log any marks', () => {
- legacyRender(
);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData).toBeNull();
- });
- });
- });
- });
-});
diff --git a/packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js
deleted file mode 100644
index fc406ca4d679..000000000000
--- a/packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import hasOwnProperty from 'shared/hasOwnProperty';
-import isArray from 'shared/isArray';
-
-function formatLanes(laneArray) {
- const lanes = laneArray.reduce((current, reduced) => current + reduced, 0);
- return '0b' + lanes.toString(2).padStart(31, '0');
-}
-
-// `test` is part of Jest's serializer API
-export function test(maybeTimelineData) {
- if (
- maybeTimelineData != null &&
- typeof maybeTimelineData === 'object' &&
- hasOwnProperty.call(maybeTimelineData, 'lanes') &&
- isArray(maybeTimelineData.lanes)
- ) {
- return true;
- }
-
- return false;
-}
-
-// print() is part of Jest's serializer API
-export function print(timelineData, serialize, indent) {
- return serialize({
- ...timelineData,
- lanes: formatLanes(timelineData.lanes),
- });
-}
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
deleted file mode 100644
index be2e0377d42b..000000000000
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ /dev/null
@@ -1,2457 +0,0 @@
-/**
- * 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 semver from 'semver';
-
-import {getLegacyRenderImplementation, normalizeCodeLocInfo} from './utils';
-import {ReactVersion} from '../../../../ReactVersions';
-
-const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion;
-
-let React = require('react');
-let ReactDOM;
-let ReactDOMClient;
-let Scheduler;
-let utils;
-let assertLog;
-let waitFor;
-
-describe('Timeline profiler', () => {
- describe('User Timing API', () => {
- let currentlyNotClearedMarks;
- let registeredMarks;
- let featureDetectionMarkName = null;
- let setPerformanceMock;
-
- function createUserTimingPolyfill() {
- featureDetectionMarkName = null;
-
- currentlyNotClearedMarks = [];
- registeredMarks = [];
-
- // 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 (:0:0)';
- } else if (markName.startsWith('--react-internal-module-stop')) {
- return '--react-internal-module-stop- at filtered (:1:1)';
- } else if (markName.startsWith('--react-version')) {
- return '--react-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);
-
- currentlyNotClearedMarks = currentlyNotClearedMarks.filter(
- mark => mark !== markName,
- );
- },
- mark(markName, markOptions) {
- markName = filterMarkData(markName);
-
- if (featureDetectionMarkName === null) {
- featureDetectionMarkName = markName;
- }
-
- registeredMarks.push(markName);
- currentlyNotClearedMarks.push(markName);
-
- if (markOptions != null) {
- // This is triggers the feature detection.
- markOptions.startTime++;
- }
- },
- };
- }
-
- function eraseRegisteredMarks() {
- registeredMarks.splice(0);
- }
-
- beforeEach(() => {
- // Mock react/jsx-dev-runtime for React 16.x
- // Although there are no tests in this suite which will run for React 16,
- // Jest will report an error trying to resolve this dependency
- if (semver.lt(ReactVersionTestingAgainst, '17.0.0')) {
- jest.mock('react/jsx-dev-runtime', () => {});
- }
-
- utils = require('./utils');
- utils.beforeEachProfiling();
-
- React = require('react');
- ReactDOM = require('react-dom');
- ReactDOMClient = require('react-dom/client');
- Scheduler = require('scheduler');
-
- if (typeof Scheduler.log !== 'function') {
- // backwards compat for older scheduler versions
- Scheduler.log = Scheduler.unstable_yieldValue;
- Scheduler.unstable_clearLog = Scheduler.unstable_clearYields;
- const InternalTestUtils = require('internal-test-utils');
- assertLog = InternalTestUtils.assertLog;
-
- // polyfill waitFor as Scheduler.toFlushAndYieldThrough
- waitFor = expectedYields => {
- let actualYields = Scheduler.unstable_clearYields();
- if (actualYields.length !== 0) {
- throw new Error(
- 'Log of yielded values is not empty. ' +
- 'Call expect(Scheduler).toHaveYielded(...) first.',
- );
- }
- Scheduler.unstable_flushNumberOfYields(expectedYields.length);
- actualYields = Scheduler.unstable_clearYields();
- expect(actualYields).toEqual(expectedYields);
- };
- } else {
- 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(currentlyNotClearedMarks).toHaveLength(0);
-
- eraseRegisteredMarks();
- setPerformanceMock(null);
- });
-
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- describe('getLanesFromTransportDecimalBitmask', () => {
- let getLanesFromTransportDecimalBitmask;
-
- beforeEach(() => {
- getLanesFromTransportDecimalBitmask =
- require('react-devtools-timeline/src/import-worker/preprocessData').getLanesFromTransportDecimalBitmask;
- });
-
- // @reactVersion >= 18.0
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- 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-',
- });
- }
-
- 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
- // @reactVersion < 19.2
- it('should throw given an empty timeline', async () => {
- await expect(async () => preprocessData([])).rejects.toThrow();
- });
-
- // @reactVersion >= 18.0
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- 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": "",
- "schedulingEvents": [],
- "snapshotHeight": 0,
- "snapshots": [],
- "startTime": 1,
- "suspenseEvents": [],
- "thrownErrors": [],
- }
- `);
- });
-
- // @reactVersion >= 18.0
- // @reactVersion < 19.2
- 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": "",
- "schedulingEvents": [
- {
- "lanes": "0b0000000000000000000000000001001",
- "timestamp": 0.005,
- "type": "schedule-render",
- "warning": null,
- },
- ],
- "snapshotHeight": 0,
- "snapshots": [],
- "startTime": 1,
- "suspenseEvents": [],
- "thrownErrors": [],
- }
- `);
- });
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should process a sample legacy render sequence', async () => {
- legacyRender(
);
-
- const data = await preprocessData([
- ...createBoilerplateEntries(),
- ...createUserTimingData(registeredMarks),
- ]);
- expect(data).toMatchInlineSnapshot(`
- {
- "batchUIDToMeasuresMap": Map {
- 0 => [
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.01,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.006,
- "type": "render-idle",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.006,
- "type": "render",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.008,
- "type": "commit",
- },
- {
- "batchUID": 0,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.014,
- "type": "layout-effects",
- },
- ],
- },
- "componentMeasures": [],
- "duration": 0.016,
- "flamechart": [],
- "internalModuleSourceToRanges": Map {
- undefined => [
- [
- {
- "columnNumber": 0,
- "functionName": "filtered",
- "lineNumber": 0,
- "source": " at filtered (:0:0)",
- },
- {
- "columnNumber": 1,
- "functionName": "filtered",
- "lineNumber": 1,
- "source": " at filtered (: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 => [
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.01,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.006,
- "type": "render-idle",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.006,
- "type": "render",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.008,
- "type": "commit",
- },
- {
- "batchUID": 0,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.014,
- "type": "layout-effects",
- },
- ],
- 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": "",
- "schedulingEvents": [
- {
- "lanes": "0b0000000000000000000000000000000",
- "timestamp": 0.005,
- "type": "schedule-render",
- "warning": null,
- },
- ],
- "snapshotHeight": 0,
- "snapshots": [],
- "startTime": 4,
- "suspenseEvents": [],
- "thrownErrors": [],
- }
- `);
- });
-
- // @reactVersion >= 19.1
- // @reactVersion < 19.2
- 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( ));
-
- const data = await preprocessData([
- ...createBoilerplateEntries(),
- ...createUserTimingData(registeredMarks),
- ]);
- expect(data).toMatchInlineSnapshot(`
- {
- "batchUIDToMeasuresMap": Map {
- 0 => [
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.012,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.008,
- "type": "render-idle",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.008,
- "type": "render",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.012,
- "type": "commit",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.004,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.021,
- "type": "passive-effects",
- },
- ],
- 1 => [
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.012,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.026,
- "type": "render-idle",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.026,
- "type": "render",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.03,
- "type": "commit",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.039,
- "type": "passive-effects",
- },
- ],
- },
- "componentMeasures": [
- {
- "componentName": "App",
- "duration": 0.001,
- "timestamp": 0.009,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "App",
- "duration": 0.002,
- "timestamp": 0.022,
- "type": "passive-effect-mount",
- "warning": null,
- },
- {
- "componentName": "App",
- "duration": 0.001,
- "timestamp": 0.027,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "App",
- "duration": 0.001,
- "timestamp": 0.04,
- "type": "passive-effect-mount",
- "warning": null,
- },
- ],
- "duration": 0.042,
- "flamechart": [],
- "internalModuleSourceToRanges": Map {
- undefined => [
- [
- {
- "columnNumber": 0,
- "functionName": "filtered",
- "lineNumber": 0,
- "source": " at filtered (:0:0)",
- },
- {
- "columnNumber": 1,
- "functionName": "filtered",
- "lineNumber": 1,
- "source": " at filtered (: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.008,
- "type": "render-idle",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.008,
- "type": "render",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.012,
- "type": "commit",
- },
- {
- "batchUID": 0,
- "depth": 0,
- "duration": 0.004,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.021,
- "type": "passive-effects",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.012,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.026,
- "type": "render-idle",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.026,
- "type": "render",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.008,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.03,
- "type": "commit",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0.003,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.039,
- "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": "",
- "schedulingEvents": [
- {
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.007,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "App",
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.023,
- "type": "schedule-state-update",
- "warning": null,
- },
- ],
- "snapshotHeight": 0,
- "snapshots": [],
- "startTime": 4,
- "suspenseEvents": [],
- "thrownErrors": [],
- }
- `);
- });
-
- // @reactVersion >= 18.0
- // @reactVersion <= 18.2
- it('should error if events and measures are incomplete', async () => {
- legacyRender(
);
-
- const invalidMarks = registeredMarks.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
- // @reactVersion <= 18.2
- it('should error if work is completed without being started', async () => {
- legacyRender(
);
-
- const invalidMarks = registeredMarks.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
- // @reactVersion < 19.2
- 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
- // @reactVersion < 19.2
- it('should include a suspended resource "displayName" if one is set', async () => {
- let promise = null;
- let resolvedValue = null;
- function readValue(value) {
- if (React.use) {
- if (promise === null) {
- promise = Promise.resolve(true).then(() => {
- return value;
- });
- promise.displayName = 'Testing displayName';
- }
- return React.use(promise);
- }
- 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'));
- await utils.actAsync(() =>
- root.render(
-
-
- ,
- ),
- );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- 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
- // @reactVersion <= 18.2
- 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),
- ];
-
- eraseRegisteredMarks();
- legacyRender( );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- const data = await preprocessData(testMarks);
- const event = data.nativeEvents.find(({type}) => type === 'click');
- expect(event.warning).toBe(null);
- });
-
- // @reactVersion >= 18.0
- // @reactVersion <= 18.2
- 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;
-
- eraseRegisteredMarks();
- legacyRender( );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- const data = await preprocessData(testMarks);
- const event = data.nativeEvents.find(({type}) => type === 'click');
- expect(event.warning).toBe(null);
- });
-
- // @reactVersion >= 18.0
- // @reactVersion <= 18.2
- 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),
- ];
-
- eraseRegisteredMarks();
- legacyRender( );
-
- registeredMarks.forEach(markName => {
- if (markName === '--render-stop') {
- // Fake a long running render
- startTime += 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
- // @reactVersion < 19.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(
- <>
-
-
- >,
- );
- });
-
- await waitFor(['A:1']);
-
- testMarks.push(...createUserTimingData(registeredMarks));
- eraseRegisteredMarks();
-
- // 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(
- <>
-
-
- >,
- );
- });
-
- assertLog(['A:2', 'B:2']);
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- const data = await preprocessData(testMarks);
- const event = data.nativeEvents.find(({type}) => type === 'click');
- expect(event.warning).toBe(null);
- });
- });
-
- describe('nested updates', () => {
- // @reactVersion >= 18.2
- // @reactVersion < 19.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( );
- });
-
- assertLog(['Component mount', 'Component update']);
-
- const data = await preprocessData([
- ...createBoilerplateEntries(),
- ...createUserTimingData(registeredMarks),
- ]);
-
- const event = data.schedulingEvents.find(
- ({type}) => type === 'schedule-state-update',
- );
- expect(event.warning).toBe(null);
- });
-
- // @reactVersion >= 18.2
- // @reactVersion < 19.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( );
- });
-
- assertLog(['Component mount', 'Component update']);
-
- const data = await preprocessData([
- ...createBoilerplateEntries(),
- ...createUserTimingData(registeredMarks),
- ]);
-
- 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 useDeferredValue
- // eslint-disable-next-line jest/no-disabled-tests
- it.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 render
- startTime += 20000;
- React.useLayoutEffect(() => {
- setDidMount(true);
- }, []);
- return didMount;
- }
-
- const cpuProfilerSample = creactCpuProfilerSample();
-
- const root = ReactDOMClient.createRoot(
- document.createElement('div'),
- );
- utils.act(() => {
- root.render( );
- });
-
- assertLog(['Component mount', 'Component update']);
-
- const testMarks = [];
- registeredMarks.forEach(markName => {
- if (markName === '--component-render-start-Component') {
- // Fake a long running render
- startTime += 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 useDeferredValue
- // eslint-disable-next-line jest/no-disabled-tests
- it.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( );
- });
-
- assertLog(['Component mount', 'Component update']);
-
- const testMarks = [];
- registeredMarks.forEach(markName => {
- if (markName === '--component-render-start-Component') {
- // Fake a long running render
- startTime += 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
- // @reactVersion < 19.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-vars
- const [isPending, startTransition] = React.useTransition();
-
- Scheduler.log(`Component rendered with value ${value}`);
-
- // Fake a long render
- if (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( );
- });
-
- assertLog([
- 'Component rendered with value 0',
- 'Component rendered with value 0',
- 'Component rendered with value 1',
- 'Long render',
- ]);
-
- const testMarks = [];
- registeredMarks.forEach(markName => {
- if (markName === '--component-render-start-Component') {
- // Fake a long running render
- startTime += 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 useDeferredValue
- // eslint-disable-next-line jest/no-disabled-tests
- it.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 render
- if (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( );
- });
-
- 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 = [];
- registeredMarks.forEach(markName => {
- if (markName === '--component-render-start-Component') {
- // Fake a long running render
- startTime += 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
- // @reactVersion < 19.2
- 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 app
- const root = ReactDOMClient.createRoot(
- document.createElement('div'),
- );
- utils.act(() =>
- root.render(
-
-
- ,
- ),
- );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- 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
- // @reactVersion < 19.2
- it('should warn about suspending during an update', async () => {
- let promise = null;
- let resolvedValue = null;
- function readValue(value) {
- if (React.use) {
- if (promise === null) {
- promise = Promise.resolve(true).then(() => {
- return value;
- });
- }
- return React.use(promise);
- }
- 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 app
- const root = ReactDOMClient.createRoot(
- document.createElement('div'),
- );
- utils.act(() =>
- root.render(
-
-
- ,
- ),
- );
-
- const testMarks = [creactCpuProfilerSample()];
-
- // Start profiling and suspend during a render.
- utils.act(() =>
- root.render(
-
-
- ,
- ),
- );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- 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
- // @reactVersion < 19.2
- it('should not warn about suspending during an transition', async () => {
- let promise = null;
- let resolvedValue = null;
- function readValue(value) {
- if (React.use) {
- if (promise === null) {
- promise = Promise.resolve(true).then(() => {
- return value;
- });
- }
- return React.use(promise);
- }
- 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 app
- const root = ReactDOMClient.createRoot(
- document.createElement('div'),
- );
- utils.act(() =>
- root.render(
-
-
- ,
- ),
- );
-
- const testMarks = [creactCpuProfilerSample()];
-
- // Start profiling and suspend during a render.
- await utils.actAsync(async () =>
- React.startTransition(() =>
- root.render(
-
-
- ,
- ),
- ),
- );
-
- testMarks.push(...createUserTimingData(registeredMarks));
-
- 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;
- });
-
- const {render: legacyRender} = getLegacyRenderImplementation();
-
- // @reactVersion <= 18.2
- // @reactVersion >= 18.0
- it('should process a sample legacy render sequence', async () => {
- legacyRender(
);
- 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": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "render-idle",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "render",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "commit",
- },
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "layout-effects",
- },
- ],
- },
- "componentMeasures": [],
- "duration": 20,
- "flamechart": [],
- "internalModuleSourceToRanges": Map {},
- "laneToLabelMap": Map {
- 1 => "Sync",
- 2 => "InputContinuousHydration",
- 4 => "InputContinuous",
- 8 => "DefaultHydration",
- 16 => "Default",
- 32 => "TransitionHydration",
- 64 => "Transition",
- 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 => "Retry",
- 134217728 => "SelectiveHydration",
- 268435456 => "IdleHydration",
- 536870912 => "Idle",
- 1073741824 => "Offscreen",
- },
- "laneToReactMeasureMap": Map {
- 1 => [
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "render-idle",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "render",
- },
- {
- "batchUID": 1,
- "depth": 0,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "commit",
- },
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "layout-effects",
- },
- ],
- 2 => [],
- 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": "",
- "schedulingEvents": [
- {
- "lanes": "0b0000000000000000000000000000001",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- ],
- "snapshotHeight": 0,
- "snapshots": [],
- "startTime": -10,
- "suspenseEvents": [],
- "thrownErrors": [],
- }
- `);
- });
-
- // @reactVersion >= 19.1
- // @reactVersion < 19.2
- 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( ));
- 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": 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": 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 => undefined,
- 128 => "TransitionHydration",
- 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": 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": 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": "",
- "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": [],
- }
- `);
- });
- });
-});
diff --git a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
index 9688adebf8be..55c937d2261a 100644
--- a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
+++ b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
@@ -589,9 +589,6 @@ describe('ProfilerContext', () => {
// Context providers
const Profiler =
require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
- const {
- TimelineContextController,
- } = require('react-devtools-timeline/src/TimelineContext');
const {
SettingsContextController,
} = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -615,9 +612,7 @@ describe('ProfilerContext', () => {
-
-
-
+
,
@@ -673,9 +668,6 @@ describe('ProfilerContext', () => {
const Profiler =
require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
- const {
- TimelineContextController,
- } = require('react-devtools-timeline/src/TimelineContext');
const {
SettingsContextController,
} = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -699,10 +691,8 @@ describe('ProfilerContext', () => {
-
-
-
-
+
+
,
@@ -886,9 +876,6 @@ describe('ProfilerContext', () => {
// Context providers
const Profiler =
require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
- const {
- TimelineContextController,
- } = require('react-devtools-timeline/src/TimelineContext');
const {
SettingsContextController,
} = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -912,10 +899,8 @@ describe('ProfilerContext', () => {
-
-
-
-
+
+
,
diff --git a/packages/react-devtools-shared/src/__tests__/setupTests.js b/packages/react-devtools-shared/src/__tests__/setupTests.js
index 085390814f52..44c59f59d7c6 100644
--- a/packages/react-devtools-shared/src/__tests__/setupTests.js
+++ b/packages/react-devtools-shared/src/__tests__/setupTests.js
@@ -312,9 +312,7 @@ beforeEach(() => {
};
const bridge = new Bridge(bridgeWall);
- const store = new Store(((bridge: any): FrontendBridge), {
- supportsTimeline: true,
- });
+ const store = new Store(((bridge: any): FrontendBridge));
const agent = new Agent(((bridge: any): BackendBridge));
const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
diff --git a/packages/react-devtools-shared/src/__tests__/utils.js b/packages/react-devtools-shared/src/__tests__/utils.js
index e84382f6fc0f..56d54fcdc5c9 100644
--- a/packages/react-devtools-shared/src/__tests__/utils.js
+++ b/packages/react-devtools-shared/src/__tests__/utils.js
@@ -425,9 +425,6 @@ export function exportImportHelper(bridge: FrontendBridge, store: Store): void {
expect(profilingDataFrontendInitial.dataForRoots).toEqual(
profilingDataFrontend.dataForRoots,
);
- expect(profilingDataFrontendInitial.timelineData).toEqual(
- profilingDataFrontend.timelineData,
- );
// Snapshot the JSON-parsed object, rather than the raw string, because Jest formats the diff nicer.
// expect(parsedProfilingDataExport).toMatchSnapshot('imported data');
diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js
index be566b4c4d35..4af6e23df483 100644
--- a/packages/react-devtools-shared/src/backend/agent.js
+++ b/packages/react-devtools-shared/src/backend/agent.js
@@ -282,17 +282,12 @@ export default class Agent extends EventEmitter<{
_persistedSelection: PersistedSelection | null = null;
_persistedSelectionMatch: PathMatch | null = null;
_traceUpdatesEnabled: boolean = false;
- _onReloadAndProfile:
- | ((recordChangeDescriptions: boolean, recordTimeline: boolean) => void)
- | void;
+ _onReloadAndProfile: ((recordChangeDescriptions: boolean) => void) | void;
constructor(
bridge: BackendBridge,
isProfiling: boolean = false,
- onReloadAndProfile?: (
- recordChangeDescriptions: boolean,
- recordTimeline: boolean,
- ) => void,
+ onReloadAndProfile?: (recordChangeDescriptions: boolean) => void,
) {
super();
@@ -909,12 +904,11 @@ export default class Agent extends EventEmitter<{
this._bridge.send('isReloadAndProfileSupportedByBackend', true);
};
- reloadAndProfile: ({
- recordChangeDescriptions: boolean,
- recordTimeline: boolean,
- }) => void = ({recordChangeDescriptions, recordTimeline}) => {
+ reloadAndProfile: ({recordChangeDescriptions: boolean}) => void = ({
+ recordChangeDescriptions,
+ }) => {
if (typeof this._onReloadAndProfile === 'function') {
- this._onReloadAndProfile(recordChangeDescriptions, recordTimeline);
+ this._onReloadAndProfile(recordChangeDescriptions);
}
// This code path should only be hit if the shell has explicitly told the Store that it supports profiling.
@@ -998,16 +992,15 @@ export default class Agent extends EventEmitter<{
this.removeAllListeners();
};
- startProfiling: ({
- recordChangeDescriptions: boolean,
- recordTimeline: boolean,
- }) => void = ({recordChangeDescriptions, recordTimeline}) => {
+ startProfiling: ({recordChangeDescriptions: boolean}) => void = ({
+ recordChangeDescriptions,
+ }) => {
this._isProfiling = true;
for (const rendererID in this._rendererInterfaces) {
const renderer = this._rendererInterfaces[
rendererID as any
] as any as RendererInterface;
- renderer.startProfiling(recordChangeDescriptions, recordTimeline);
+ renderer.startProfiling(recordChangeDescriptions);
}
this._bridge.send('profilingStatus', this._isProfiling);
};
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 51ecd113fa1f..b6f64cd48b97 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -75,7 +75,6 @@ import {
import {
__DEBUG__,
PROFILING_FLAG_BASIC_SUPPORT,
- PROFILING_FLAG_TIMELINE_SUPPORT,
PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT,
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
@@ -150,9 +149,7 @@ import {
} from './DevToolsFiberComponentStack';
import {getStyleXData} from '../StyleX/utils';
-import {createProfilingHooks} from '../profilingHooks';
-import type {GetTimelineData, ToggleProfilingStatus} from '../profilingHooks';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
ChangeDescription,
@@ -436,8 +433,6 @@ export function attach(
} = ReactPriorityLevels;
const {
- getLaneLabelMap,
- injectProfilingHooks,
overrideHookState,
overrideHookStateDeletePath,
overrideHookStateRenamePath,
@@ -475,26 +470,6 @@ export function attach(
};
}
- let getTimelineData: null | GetTimelineData = null;
- let toggleProfilingStatus: null | ToggleProfilingStatus = null;
- if (typeof injectProfilingHooks === 'function') {
- const response = createProfilingHooks({
- getDisplayNameForFiber,
- getIsProfiling: () => isProfiling,
- getLaneLabelMap,
- currentDispatcherRef: getDispatcherRef(renderer),
- workTagMap: ReactTypeOfWork,
- reactVersion: version,
- });
-
- // Pass the Profiling hooks to the reconciler for it to call during render.
- injectProfilingHooks(response.profilingHooks);
-
- // Hang onto this toggle so we can notify the external methods of profiling status changes.
- getTimelineData = response.getTimelineData;
- toggleProfilingStatus = response.toggleProfilingStatus;
- }
-
type ComponentLogs = {
errors: Map,
errorsCount: number,
@@ -1776,9 +1751,6 @@ export function attach(
let profilingFlags = 0;
if (isProfilingSupported) {
profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;
- if (typeof injectProfilingHooks === 'function') {
- profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT;
- }
if (supportsPerformanceTracks) {
profilingFlags |= PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT;
}
@@ -7251,7 +7223,6 @@ export function attach(
let isProfiling: boolean = false;
let profilingStartTime: number = 0;
let recordChangeDescriptions: boolean = false;
- let recordTimeline: boolean = false;
let rootToCommitProfilingMetadataMap: CommitProfilingMetadataMap | null =
null;
@@ -7335,43 +7306,9 @@ export function attach(
},
);
- let timelineData = null;
- if (typeof getTimelineData === 'function') {
- const currentTimelineData = getTimelineData();
- if (currentTimelineData) {
- const {
- batchUIDToMeasuresMap,
- internalModuleSourceToRanges,
- laneToLabelMap,
- laneToReactMeasureMap,
- ...rest
- } = currentTimelineData;
-
- timelineData = {
- ...rest,
-
- // Most of the data is safe to parse as-is,
- // but we need to convert the nested Arrays back to Maps.
- // Most of the data is safe to serialize as-is,
- // but we need to convert the Maps to nested Arrays.
- batchUIDToMeasuresKeyValueArray: Array.from(
- batchUIDToMeasuresMap.entries(),
- ),
- internalModuleSourceToRanges: Array.from(
- internalModuleSourceToRanges.entries(),
- ),
- laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
- laneToReactMeasureKeyValueArray: Array.from(
- laneToReactMeasureMap.entries(),
- ),
- };
- }
- }
-
return {
dataForRoots,
rendererID,
- timelineData,
};
}
@@ -7393,16 +7330,12 @@ export function attach(
}
}
- function startProfiling(
- shouldRecordChangeDescriptions: boolean,
- shouldRecordTimeline: boolean,
- ) {
+ function startProfiling(shouldRecordChangeDescriptions: boolean) {
if (isProfiling) {
return;
}
recordChangeDescriptions = shouldRecordChangeDescriptions;
- recordTimeline = shouldRecordTimeline;
// Capture initial values as of the time profiling starts.
// It's important we snapshot both the durations and the id-to-root map,
@@ -7434,29 +7367,16 @@ export function attach(
isProfiling = true;
profilingStartTime = getCurrentTime();
rootToCommitProfilingMetadataMap = new Map();
-
- if (toggleProfilingStatus !== null) {
- toggleProfilingStatus(true, recordTimeline);
- }
}
function stopProfiling() {
isProfiling = false;
recordChangeDescriptions = false;
-
- if (toggleProfilingStatus !== null) {
- toggleProfilingStatus(false, recordTimeline);
- }
-
- recordTimeline = false;
}
// Automatically start profiling so that we don't miss timing info from initial "mount".
if (shouldStartProfilingNow) {
- startProfiling(
- profilingSettings.recordChangeDescriptions,
- profilingSettings.recordTimeline,
- );
+ startProfiling(profilingSettings.recordChangeDescriptions);
}
function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber {
diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js
deleted file mode 100644
index 2a10eb5040f6..000000000000
--- a/packages/react-devtools-shared/src/backend/profilingHooks.js
+++ /dev/null
@@ -1,989 +0,0 @@
-/**
- * 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
- */
-
-import type {
- Lane,
- Lanes,
- DevToolsProfilingHooks,
- WorkTagMap,
- CurrentDispatcherRef,
-} from 'react-devtools-shared/src/backend/types';
-import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
-import type {Wakeable} from 'shared/ReactTypes';
-import type {
- BatchUID,
- InternalModuleSourceToRanges,
- LaneToLabelMap,
- ReactComponentMeasure,
- ReactLane,
- ReactMeasure,
- ReactMeasureType,
- ReactScheduleStateUpdateEvent,
- SchedulingEvent,
- SuspenseEvent,
- TimelineData,
-} from 'react-devtools-timeline/src/types';
-
-import isArray from 'shared/isArray';
-import {
- REACT_TOTAL_NUM_LANES,
- SCHEDULING_PROFILER_VERSION,
-} from 'react-devtools-timeline/src/constants';
-import {describeFiber} from './fiber/DevToolsFiberComponentStack';
-
-// Add padding to the start/stop time of the profile.
-// This makes the UI nicer to use.
-const TIME_OFFSET = 10;
-
-let performanceTarget: Performance | null = null;
-
-// If performance exists and supports the subset of the User Timing API that we require.
-let supportsUserTiming =
- typeof performance !== 'undefined' &&
- // $FlowFixMe[method-unbinding]
- typeof performance.mark === 'function' &&
- // $FlowFixMe[method-unbinding]
- typeof performance.clearMarks === 'function';
-
-let supportsUserTimingV3 = false;
-if (supportsUserTiming) {
- const CHECK_V3_MARK = '__v3';
- const markOptions: {
- detail?: mixed,
- startTime?: number,
- } = {};
- Object.defineProperty(markOptions, 'startTime', {
- get: function () {
- supportsUserTimingV3 = true;
- return 0;
- },
- set: function () {},
- });
-
- try {
- performance.mark(CHECK_V3_MARK, markOptions);
- } catch (error) {
- // Ignore
- } finally {
- performance.clearMarks(CHECK_V3_MARK);
- }
-}
-
-if (supportsUserTimingV3) {
- performanceTarget = performance;
-}
-
-// Some environments (e.g. React Native / Hermes) don't support the performance API yet.
-const getCurrentTime =
- // $FlowFixMe[method-unbinding]
- typeof performance === 'object' && typeof performance.now === 'function'
- ? () => performance.now()
- : () => Date.now();
-
-// Mocking the Performance Object (and User Timing APIs) for testing is fragile.
-// This API allows tests to directly override the User Timing APIs.
-export function setPerformanceMock_ONLY_FOR_TESTING(
- performanceMock: Performance | null,
-) {
- performanceTarget = performanceMock;
- supportsUserTiming = performanceMock !== null;
- supportsUserTimingV3 = performanceMock !== null;
-}
-
-export type GetTimelineData = () => TimelineData | null;
-export type ToggleProfilingStatus = (
- value: boolean,
- recordTimeline?: boolean,
-) => void;
-
-type Response = {
- getTimelineData: GetTimelineData,
- profilingHooks: DevToolsProfilingHooks,
- toggleProfilingStatus: ToggleProfilingStatus,
-};
-
-export function createProfilingHooks({
- getDisplayNameForFiber,
- getIsProfiling,
- getLaneLabelMap,
- workTagMap,
- currentDispatcherRef,
- reactVersion,
-}: {
- getDisplayNameForFiber: (fiber: Fiber) => string | null,
- getIsProfiling: () => boolean,
- getLaneLabelMap?: () => Map | null,
- currentDispatcherRef?: CurrentDispatcherRef,
- workTagMap: WorkTagMap,
- reactVersion: string,
-}): Response {
- let currentBatchUID: BatchUID = 0;
- let currentReactComponentMeasure: ReactComponentMeasure | null = null;
- let currentReactMeasuresStack: Array = [];
- let currentTimelineData: TimelineData | null = null;
- let currentFiberStacks: Map> = new Map();
- let isProfiling: boolean = false;
- let nextRenderShouldStartNewBatch: boolean = false;
-
- function getRelativeTime() {
- const currentTime = getCurrentTime();
-
- if (currentTimelineData) {
- if (currentTimelineData.startTime === 0) {
- currentTimelineData.startTime = currentTime - TIME_OFFSET;
- }
-
- return currentTime - currentTimelineData.startTime;
- }
-
- return 0;
- }
-
- function getInternalModuleRanges() {
- /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
- if (
- typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
- typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
- 'function'
- ) {
- // Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
- // Don't do this eagerly like the laneToLabelMap,
- // because some modules might not yet have registered their boundaries when the renderer is injected.
- const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
-
- // This check would not be required,
- // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
- if (isArray(ranges)) {
- return ranges;
- }
- }
-
- return null;
- }
-
- function getTimelineData(): TimelineData | null {
- return currentTimelineData;
- }
-
- function laneToLanesArray(lanes: Lane) {
- const lanesArray = [];
-
- let lane = 1;
- for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
- if (lane & lanes) {
- lanesArray.push(lane);
- }
- lane *= 2;
- }
-
- return lanesArray;
- }
-
- const laneToLabelMap: LaneToLabelMap | null =
- typeof getLaneLabelMap === 'function' ? getLaneLabelMap() : null;
-
- function markMetadata() {
- markAndClear(`--react-version-${reactVersion}`);
- markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
-
- const ranges = getInternalModuleRanges();
- if (ranges) {
- for (let i = 0; i < ranges.length; i++) {
- const range = ranges[i];
- if (isArray(range) && range.length === 2) {
- const [startStackFrame, stopStackFrame] = ranges[i];
-
- markAndClear(`--react-internal-module-start-${startStackFrame}`);
- markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
- }
- }
- }
-
- if (laneToLabelMap != null) {
- const labels = Array.from(laneToLabelMap.values()).join(',');
- markAndClear(`--react-lane-labels-${labels}`);
- }
- }
-
- function markAndClear(markName: string) {
- // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
- (performanceTarget as any as Performance).mark(markName);
- (performanceTarget as any as Performance).clearMarks(markName);
- }
-
- function recordReactMeasureStarted(
- type: ReactMeasureType,
- lanes: Lanes,
- ): void {
- // Decide what depth thi work should be rendered at, based on what's on the top of the stack.
- // It's okay to render over top of "idle" work but everything else should be on its own row.
- let depth = 0;
- if (currentReactMeasuresStack.length > 0) {
- const top =
- currentReactMeasuresStack[currentReactMeasuresStack.length - 1];
- depth = top.type === 'render-idle' ? top.depth : top.depth + 1;
- }
-
- const lanesArray = laneToLanesArray(lanes);
-
- const reactMeasure: ReactMeasure = {
- type,
- batchUID: currentBatchUID,
- depth,
- lanes: lanesArray,
- timestamp: getRelativeTime(),
- duration: 0,
- };
-
- currentReactMeasuresStack.push(reactMeasure);
-
- if (currentTimelineData) {
- const {batchUIDToMeasuresMap, laneToReactMeasureMap} =
- currentTimelineData;
-
- let reactMeasures = batchUIDToMeasuresMap.get(currentBatchUID);
- if (reactMeasures != null) {
- reactMeasures.push(reactMeasure);
- } else {
- batchUIDToMeasuresMap.set(currentBatchUID, [reactMeasure]);
- }
-
- lanesArray.forEach(lane => {
- reactMeasures = laneToReactMeasureMap.get(lane);
- if (reactMeasures) {
- reactMeasures.push(reactMeasure);
- }
- });
- }
- }
-
- function recordReactMeasureCompleted(type: ReactMeasureType): void {
- const currentTime = getRelativeTime();
-
- if (currentReactMeasuresStack.length === 0) {
- console.error(
- 'Unexpected type "%s" completed at %sms while currentReactMeasuresStack is empty.',
- type,
- currentTime,
- );
- // Ignore work "completion" user timing mark that doesn't complete anything
- return;
- }
-
- const top = currentReactMeasuresStack.pop();
- // $FlowFixMe[incompatible-type]
- // $FlowFixMe[incompatible-use]
- if (top.type !== type) {
- console.error(
- 'Unexpected type "%s" completed at %sms before "%s" completed.',
- type,
- currentTime,
- // $FlowFixMe[incompatible-use]
- top.type,
- );
- }
-
- // $FlowFixMe[cannot-write] This property should not be writable outside of this function.
- // $FlowFixMe[incompatible-use]
- top.duration = currentTime - top.timestamp;
-
- if (currentTimelineData) {
- currentTimelineData.duration = getRelativeTime() + TIME_OFFSET;
- }
- }
-
- function markCommitStarted(lanes: Lanes): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureStarted('commit', lanes);
-
- // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering.
- // This issue applies to the User Timing data also.
- nextRenderShouldStartNewBatch = true;
-
- if (supportsUserTimingV3) {
- markAndClear(`--commit-start-${lanes}`);
-
- // Some metadata only needs to be logged once per session,
- // but if profiling information is being recorded via the Performance tab,
- // DevTools has no way of knowing when the recording starts.
- // Because of that, we log thie type of data periodically (once per commit).
- markMetadata();
- }
- }
-
- function markCommitStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureCompleted('commit');
- recordReactMeasureCompleted('render-idle');
- if (supportsUserTimingV3) {
- markAndClear('--commit-stop');
- }
- }
-
- function markComponentRenderStarted(fiber: Fiber): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- currentReactComponentMeasure = {
- componentName,
- duration: 0,
- timestamp: getRelativeTime(),
- type: 'render',
- warning: null,
- };
-
- if (supportsUserTimingV3) {
- markAndClear(`--component-render-start-${componentName}`);
- }
- }
-
- function markComponentRenderStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentReactComponentMeasure) {
- if (currentTimelineData) {
- currentTimelineData.componentMeasures.push(
- currentReactComponentMeasure,
- );
- }
-
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentReactComponentMeasure.duration =
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- getRelativeTime() - currentReactComponentMeasure.timestamp;
- currentReactComponentMeasure = null;
- }
-
- if (supportsUserTimingV3) {
- markAndClear('--component-render-stop');
- }
- }
-
- function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- currentReactComponentMeasure = {
- componentName,
- duration: 0,
- timestamp: getRelativeTime(),
- type: 'layout-effect-mount',
- warning: null,
- };
-
- if (supportsUserTimingV3) {
- markAndClear(`--component-layout-effect-mount-start-${componentName}`);
- }
- }
-
- function markComponentLayoutEffectMountStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentReactComponentMeasure) {
- if (currentTimelineData) {
- currentTimelineData.componentMeasures.push(
- currentReactComponentMeasure,
- );
- }
-
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentReactComponentMeasure.duration =
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- getRelativeTime() - currentReactComponentMeasure.timestamp;
- currentReactComponentMeasure = null;
- }
-
- if (supportsUserTimingV3) {
- markAndClear('--component-layout-effect-mount-stop');
- }
- }
-
- function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- currentReactComponentMeasure = {
- componentName,
- duration: 0,
- timestamp: getRelativeTime(),
- type: 'layout-effect-unmount',
- warning: null,
- };
-
- if (supportsUserTimingV3) {
- markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
- }
- }
-
- function markComponentLayoutEffectUnmountStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentReactComponentMeasure) {
- if (currentTimelineData) {
- currentTimelineData.componentMeasures.push(
- currentReactComponentMeasure,
- );
- }
-
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentReactComponentMeasure.duration =
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- getRelativeTime() - currentReactComponentMeasure.timestamp;
- currentReactComponentMeasure = null;
- }
-
- if (supportsUserTimingV3) {
- markAndClear('--component-layout-effect-unmount-stop');
- }
- }
-
- function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- currentReactComponentMeasure = {
- componentName,
- duration: 0,
- timestamp: getRelativeTime(),
- type: 'passive-effect-mount',
- warning: null,
- };
-
- if (supportsUserTimingV3) {
- markAndClear(`--component-passive-effect-mount-start-${componentName}`);
- }
- }
-
- function markComponentPassiveEffectMountStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentReactComponentMeasure) {
- if (currentTimelineData) {
- currentTimelineData.componentMeasures.push(
- currentReactComponentMeasure,
- );
- }
-
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentReactComponentMeasure.duration =
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- getRelativeTime() - currentReactComponentMeasure.timestamp;
- currentReactComponentMeasure = null;
- }
-
- if (supportsUserTimingV3) {
- markAndClear('--component-passive-effect-mount-stop');
- }
- }
-
- function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- currentReactComponentMeasure = {
- componentName,
- duration: 0,
- timestamp: getRelativeTime(),
- type: 'passive-effect-unmount',
- warning: null,
- };
-
- if (supportsUserTimingV3) {
- markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
- }
- }
-
- function markComponentPassiveEffectUnmountStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentReactComponentMeasure) {
- if (currentTimelineData) {
- currentTimelineData.componentMeasures.push(
- currentReactComponentMeasure,
- );
- }
-
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentReactComponentMeasure.duration =
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- getRelativeTime() - currentReactComponentMeasure.timestamp;
- currentReactComponentMeasure = null;
- }
-
- if (supportsUserTimingV3) {
- markAndClear('--component-passive-effect-unmount-stop');
- }
- }
-
- function markComponentErrored(
- fiber: Fiber,
- thrownValue: mixed,
- lanes: Lanes,
- ): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
- const phase = fiber.alternate === null ? 'mount' : 'update';
-
- let message = '';
- if (
- thrownValue !== null &&
- typeof thrownValue === 'object' &&
- typeof thrownValue.message === 'string'
- ) {
- message = thrownValue.message;
- } else if (typeof thrownValue === 'string') {
- message = thrownValue;
- }
-
- // TODO (timeline) Record and cache component stack
- if (currentTimelineData) {
- currentTimelineData.thrownErrors.push({
- componentName,
- message,
- phase,
- timestamp: getRelativeTime(),
- type: 'thrown-error',
- });
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--error-${componentName}-${phase}-${message}`);
- }
- }
-
- const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
-
- // $FlowFixMe[incompatible-type]: Flow cannot handle polymorphic WeakMaps
- const wakeableIDs: WeakMap = new PossiblyWeakMap();
- let wakeableID: number = 0;
- function getWakeableID(wakeable: Wakeable): number {
- if (!wakeableIDs.has(wakeable)) {
- wakeableIDs.set(wakeable, wakeableID++);
- }
- return wakeableIDs.get(wakeable) as any as number;
- }
-
- function markComponentSuspended(
- fiber: Fiber,
- wakeable: Wakeable,
- lanes: Lanes,
- ): void {
- if (!isProfiling) {
- return;
- }
-
- const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
- const id = getWakeableID(wakeable);
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
- const phase = fiber.alternate === null ? 'mount' : 'update';
-
- // Following the non-standard fn.displayName convention,
- // frameworks like Relay may also annotate Promises with a displayName,
- // describing what operation/data the thrown Promise is related to.
- // When this is available we should pass it along to the Timeline.
- const displayName = (wakeable as any).displayName || '';
-
- let suspenseEvent: SuspenseEvent | null = null;
- // TODO (timeline) Record and cache component stack
- suspenseEvent = {
- componentName,
- depth: 0,
- duration: 0,
- id: `${id}`,
- phase,
- promiseName: displayName,
- resolution: 'unresolved',
- timestamp: getRelativeTime(),
- type: 'suspense',
- warning: null,
- };
-
- if (currentTimelineData) {
- currentTimelineData.suspenseEvents.push(suspenseEvent);
- }
-
- if (supportsUserTimingV3) {
- markAndClear(
- `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
- );
-
- wakeable.then(
- () => {
- if (suspenseEvent) {
- suspenseEvent.duration =
- getRelativeTime() - suspenseEvent.timestamp;
- suspenseEvent.resolution = 'resolved';
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--suspense-resolved-${id}-${componentName}`);
- }
- },
- () => {
- if (suspenseEvent) {
- suspenseEvent.duration =
- getRelativeTime() - suspenseEvent.timestamp;
- suspenseEvent.resolution = 'rejected';
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--suspense-rejected-${id}-${componentName}`);
- }
- },
- );
- }
- }
-
- function markLayoutEffectsStarted(lanes: Lanes): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureStarted('layout-effects', lanes);
- if (supportsUserTimingV3) {
- markAndClear(`--layout-effects-start-${lanes}`);
- }
- }
-
- function markLayoutEffectsStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureCompleted('layout-effects');
- if (supportsUserTimingV3) {
- markAndClear('--layout-effects-stop');
- }
- }
-
- function markPassiveEffectsStarted(lanes: Lanes): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureStarted('passive-effects', lanes);
- if (supportsUserTimingV3) {
- markAndClear(`--passive-effects-start-${lanes}`);
- }
- }
-
- function markPassiveEffectsStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureCompleted('passive-effects');
- if (supportsUserTimingV3) {
- markAndClear('--passive-effects-stop');
- }
- }
-
- function markRenderStarted(lanes: Lanes): void {
- if (!isProfiling) {
- return;
- }
-
- if (nextRenderShouldStartNewBatch) {
- nextRenderShouldStartNewBatch = false;
- currentBatchUID++;
- }
-
- // If this is a new batch of work, wrap an "idle" measure around it.
- // Log it before the "render" measure to preserve the stack ordering.
- if (
- currentReactMeasuresStack.length === 0 ||
- currentReactMeasuresStack[currentReactMeasuresStack.length - 1].type !==
- 'render-idle'
- ) {
- recordReactMeasureStarted('render-idle', lanes);
- }
-
- recordReactMeasureStarted('render', lanes);
- if (supportsUserTimingV3) {
- markAndClear(`--render-start-${lanes}`);
- }
- }
-
- function markRenderYielded(): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureCompleted('render');
- if (supportsUserTimingV3) {
- markAndClear('--render-yield');
- }
- }
-
- function markRenderStopped(): void {
- if (!isProfiling) {
- return;
- }
-
- recordReactMeasureCompleted('render');
- if (supportsUserTimingV3) {
- markAndClear('--render-stop');
- }
- }
-
- function markRenderScheduled(lane: Lane): void {
- if (!isProfiling) {
- return;
- }
-
- if (currentTimelineData) {
- currentTimelineData.schedulingEvents.push({
- lanes: laneToLanesArray(lane),
- timestamp: getRelativeTime(),
- type: 'schedule-render',
- warning: null,
- });
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--schedule-render-${lane}`);
- }
- }
-
- function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- if (currentTimelineData) {
- currentTimelineData.schedulingEvents.push({
- componentName,
- lanes: laneToLanesArray(lane),
- timestamp: getRelativeTime(),
- type: 'schedule-force-update',
- warning: null,
- });
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
- }
- }
-
- function getParentFibers(fiber: Fiber): Array {
- const parents = [];
- let parent: null | Fiber = fiber;
- while (parent !== null) {
- parents.push(parent);
- parent = parent.return;
- }
- return parents;
- }
-
- function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
- if (!isProfiling) {
- return;
- }
-
- const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
-
- // TODO (timeline) Record and cache component stack
- if (currentTimelineData) {
- const event: ReactScheduleStateUpdateEvent = {
- componentName,
- // Store the parent fibers so we can post process
- // them after we finish profiling
- lanes: laneToLanesArray(lane),
- timestamp: getRelativeTime(),
- type: 'schedule-state-update',
- warning: null,
- };
- currentFiberStacks.set(event, getParentFibers(fiber));
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- currentTimelineData.schedulingEvents.push(event);
- }
-
- if (supportsUserTimingV3) {
- markAndClear(`--schedule-state-update-${lane}-${componentName}`);
- }
- }
-
- function toggleProfilingStatus(
- value: boolean,
- recordTimeline: boolean = false,
- ) {
- if (isProfiling !== value) {
- isProfiling = value;
-
- if (isProfiling) {
- const internalModuleSourceToRanges: InternalModuleSourceToRanges =
- new Map();
-
- if (supportsUserTimingV3) {
- const ranges = getInternalModuleRanges();
- if (ranges) {
- for (let i = 0; i < ranges.length; i++) {
- const range = ranges[i];
- if (isArray(range) && range.length === 2) {
- const [startStackFrame, stopStackFrame] = ranges[i];
-
- markAndClear(
- `--react-internal-module-start-${startStackFrame}`,
- );
- markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
- }
- }
- }
- }
-
- const laneToReactMeasureMap = new Map();
- let lane = 1;
- for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
- laneToReactMeasureMap.set(lane, []);
- lane *= 2;
- }
-
- currentBatchUID = 0;
- currentReactComponentMeasure = null;
- currentReactMeasuresStack = [];
- currentFiberStacks = new Map();
- if (recordTimeline) {
- currentTimelineData = {
- // Session wide metadata; only collected once.
- internalModuleSourceToRanges,
- laneToLabelMap: laneToLabelMap || new Map(),
- reactVersion,
-
- // Data logged by React during profiling session.
- componentMeasures: [],
- schedulingEvents: [],
- suspenseEvents: [],
- thrownErrors: [],
-
- // Data inferred based on what React logs.
- batchUIDToMeasuresMap: new Map(),
- duration: 0,
- laneToReactMeasureMap,
- startTime: 0,
-
- // Data only available in Chrome profiles.
- flamechart: [],
- nativeEvents: [],
- networkMeasures: [],
- otherUserTimingMarks: [],
- snapshots: [],
- snapshotHeight: 0,
- };
- }
- nextRenderShouldStartNewBatch = true;
- } else {
- // This is __EXPENSIVE__.
- // We could end up with hundreds of state updated, and for each one of them
- // would try to create a component stack with possibly hundreds of Fibers.
- // Creating a cache of component stacks won't help, generating a single stack is already expensive enough.
- // We should find a way to lazily generate component stacks on demand, when user inspects a specific event.
- // If we succeed with moving React DevTools Timeline Profiler to Performance panel, then Timeline Profiler would probably be removed.
- // Now that owner stacks are adopted, revisit this again and cache component stacks per Fiber,
- // but only return them when needed, sending hundreds of component stacks is beyond the Bridge's bandwidth.
-
- // Postprocess Profile data
- if (currentTimelineData !== null) {
- currentTimelineData.schedulingEvents.forEach(event => {
- if (event.type === 'schedule-state-update') {
- // TODO(luna): We can optimize this by creating a map of
- // fiber to component stack instead of generating the stack
- // for every fiber every time
- const fiberStack = currentFiberStacks.get(event);
- if (fiberStack && currentDispatcherRef != null) {
- event.componentStack = fiberStack.reduce((trace, fiber) => {
- return (
- trace +
- describeFiber(workTagMap, fiber, currentDispatcherRef)
- );
- }, '');
- }
- }
- });
- }
-
- // Clear the current fiber stacks so we don't hold onto the fibers
- // in memory after profiling finishes
- currentFiberStacks.clear();
- }
- }
- }
-
- return {
- getTimelineData,
- profilingHooks: {
- markCommitStarted,
- markCommitStopped,
- markComponentRenderStarted,
- markComponentRenderStopped,
- markComponentPassiveEffectMountStarted,
- markComponentPassiveEffectMountStopped,
- markComponentPassiveEffectUnmountStarted,
- markComponentPassiveEffectUnmountStopped,
- markComponentLayoutEffectMountStarted,
- markComponentLayoutEffectMountStopped,
- markComponentLayoutEffectUnmountStarted,
- markComponentLayoutEffectUnmountStopped,
- markComponentErrored,
- markComponentSuspended,
- markLayoutEffectsStarted,
- markLayoutEffectsStopped,
- markPassiveEffectsStarted,
- markPassiveEffectsStopped,
- markRenderStarted,
- markRenderYielded,
- markRenderStopped,
- markRenderScheduled,
- markForceUpdateScheduled,
- markStateUpdateScheduled,
- },
- toggleProfilingStatus,
- };
-}
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 70ef8e4befef..484fff609c34 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -14,11 +14,7 @@
* Be mindful of backwards compatibility when making changes.
*/
-import type {
- ReactContext,
- Wakeable,
- ReactComponentInfo,
-} from 'shared/ReactTypes';
+import type {ReactContext, ReactComponentInfo} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
ComponentFilter,
@@ -30,7 +26,6 @@ import type {
SetupNativeStyleEditor,
} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
import type {InitBackend} from 'react-devtools-shared/src/backend';
-import type {TimelineDataExport} from 'react-devtools-timeline/src/types';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {ReactFunctionLocation, ReactStackTrace} from 'shared/ReactTypes';
import type Agent from './agent';
@@ -186,9 +181,6 @@ export type ReactRenderer = {
setErrorHandler?: ?(shouldError: (fiber: Object) => ?boolean) => void,
// Intentionally opaque type to avoid coupling DevTools to different Fast Refresh versions.
scheduleRefresh?: Function,
- // 18.0+
- injectProfilingHooks?: (profilingHooks: DevToolsProfilingHooks) => void,
- getLaneLabelMap?: () => Map | null,
...
};
@@ -231,7 +223,6 @@ export type ProfilingDataForRootBackend = {
export type ProfilingDataBackend = {
dataForRoots: Array,
rendererID: number,
- timelineData: TimelineDataExport | null,
};
export type PathFrame = {
@@ -474,10 +465,7 @@ export type RendererInterface = {
renderer: ReactRenderer | null,
setTraceUpdatesEnabled: (enabled: boolean) => void,
setTrackedPath: (path: Array | null) => void,
- startProfiling: (
- recordChangeDescriptions: boolean,
- recordTimeline: boolean,
- ) => void,
+ startProfiling: (recordChangeDescriptions: boolean) => void,
stopProfiling: () => void,
storeAsGlobal: (
id: number,
@@ -488,55 +476,11 @@ export type RendererInterface = {
updateComponentFilters: (componentFilters: Array) => void,
getEnvironmentNames: () => Array,
- // Timeline profiler interface
-
...
};
export type Handler = (data: any) => void;
-// Renderers use these APIs to report profiling data to DevTools at runtime.
-// They get passed from the DevTools backend to the reconciler during injection.
-export type DevToolsProfilingHooks = {
- // Scheduling methods:
- markRenderScheduled: (lane: Lane) => void,
- markStateUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
- markForceUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
-
- // Work loop level methods:
- markRenderStarted: (lanes: Lanes) => void,
- markRenderYielded: () => void,
- markRenderStopped: () => void,
- markCommitStarted: (lanes: Lanes) => void,
- markCommitStopped: () => void,
- markLayoutEffectsStarted: (lanes: Lanes) => void,
- markLayoutEffectsStopped: () => void,
- markPassiveEffectsStarted: (lanes: Lanes) => void,
- markPassiveEffectsStopped: () => void,
-
- // Fiber level methods:
- markComponentRenderStarted: (fiber: Fiber) => void,
- markComponentRenderStopped: () => void,
- markComponentErrored: (
- fiber: Fiber,
- thrownValue: mixed,
- lanes: Lanes,
- ) => void,
- markComponentSuspended: (
- fiber: Fiber,
- wakeable: Wakeable,
- lanes: Lanes,
- ) => void,
- markComponentLayoutEffectMountStarted: (fiber: Fiber) => void,
- markComponentLayoutEffectMountStopped: () => void,
- markComponentLayoutEffectUnmountStarted: (fiber: Fiber) => void,
- markComponentLayoutEffectUnmountStopped: () => void,
- markComponentPassiveEffectMountStarted: (fiber: Fiber) => void,
- markComponentPassiveEffectMountStopped: () => void,
- markComponentPassiveEffectUnmountStarted: (fiber: Fiber) => void,
- markComponentPassiveEffectUnmountStopped: () => void,
-};
-
export type DevToolsBackend = {
Agent: Class,
Bridge: Class,
@@ -546,7 +490,6 @@ export type DevToolsBackend = {
export type ProfilingSettings = {
recordChangeDescriptions: boolean,
- recordTimeline: boolean,
};
export type DevToolsHook = {
@@ -580,11 +523,6 @@ export type DevToolsHook = {
didError?: boolean,
) => void,
- // Timeline internal module filtering
- getInternalModuleRanges: () => Array<[string, string]>,
- registerInternalModuleStart: (moduleStartError: Error) => void,
- registerInternalModuleStop: (moduleStopError: Error) => void,
-
// Testing
dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js
index 53488cc2f454..e0c8a95ffa2f 100644
--- a/packages/react-devtools-shared/src/constants.js
+++ b/packages/react-devtools-shared/src/constants.js
@@ -32,7 +32,8 @@ export const SUSPENSE_TREE_OPERATION_SUSPENDERS = 12;
export const TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE = 13;
export const PROFILING_FLAG_BASIC_SUPPORT /*. */ = 0b001;
-export const PROFILING_FLAG_TIMELINE_SUPPORT /* */ = 0b010;
+// Bit 0b010 is retired: it flagged support for the removed Timeline profiler.
+// Older backends still set it, so it must never be reused for a new flag.
export const PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT /* */ = 0b100;
export const UNKNOWN_SUSPENDERS_NONE: UnknownSuspendersReason = 0; // If we had at least one debugInfo, then that might have been the reason.
@@ -57,8 +58,6 @@ export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
'React::DevTools::parseHookNames';
export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
'React::DevTools::recordChangeDescriptions';
-export const SESSION_STORAGE_RECORD_TIMELINE_KEY =
- 'React::DevTools::recordTimeline';
export const SESSION_STORAGE_RELOAD_AND_PROFILE_KEY =
'React::DevTools::reloadAndProfile';
export const LOCAL_STORAGE_BROWSER_THEME = 'React::DevTools::theme';
diff --git a/packages/react-devtools-shared/src/devtools/ProfilerStore.js b/packages/react-devtools-shared/src/devtools/ProfilerStore.js
index 33240bd77f3a..1fd92e178ccc 100644
--- a/packages/react-devtools-shared/src/devtools/ProfilerStore.js
+++ b/packages/react-devtools-shared/src/devtools/ProfilerStore.js
@@ -195,7 +195,6 @@ export default class ProfilerStore extends EventEmitter<{
this._bridge.send('startProfiling', {
recordChangeDescriptions: this._store.recordChangeDescriptions,
- recordTimeline: this._store.supportsTimeline,
});
this._isProfilingBasedOnUserInput = true;
diff --git a/packages/react-devtools-shared/src/devtools/constants.js b/packages/react-devtools-shared/src/devtools/constants.js
index d093a798bcd3..18a7398ea3f5 100644
--- a/packages/react-devtools-shared/src/devtools/constants.js
+++ b/packages/react-devtools-shared/src/devtools/constants.js
@@ -95,52 +95,11 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
'--color-resize-bar-active': '#dcdcdc',
'--color-resize-bar-border': '#d1d1d1',
'--color-resize-bar-dot': '#333333',
- '--color-timeline-internal-module': '#d1d1d1',
- '--color-timeline-internal-module-hover': '#c9c9c9',
- '--color-timeline-internal-module-text': '#444',
- '--color-timeline-native-event': '#ccc',
- '--color-timeline-native-event-hover': '#aaa',
- '--color-timeline-network-primary': '#fcf3dc',
- '--color-timeline-network-primary-hover': '#f0e7d1',
- '--color-timeline-network-secondary': '#efc457',
- '--color-timeline-network-secondary-hover': '#e3ba52',
- '--color-timeline-priority-background': '#f6f6f6',
- '--color-timeline-priority-border': '#eeeeee',
- '--color-timeline-user-timing': '#c9cacd',
- '--color-timeline-user-timing-hover': '#93959a',
- '--color-timeline-react-idle': '#d3e5f6',
- '--color-timeline-react-idle-hover': '#c3d9ef',
- '--color-timeline-react-render': '#9fc3f3',
- '--color-timeline-react-render-hover': '#83afe9',
- '--color-timeline-react-render-text': '#11365e',
- '--color-timeline-react-commit': '#c88ff0',
- '--color-timeline-react-commit-hover': '#b281d6',
- '--color-timeline-react-commit-text': '#3e2c4a',
- '--color-timeline-react-layout-effects': '#b281d6',
- '--color-timeline-react-layout-effects-hover': '#9d71bd',
- '--color-timeline-react-layout-effects-text': '#3e2c4a',
- '--color-timeline-react-passive-effects': '#b281d6',
- '--color-timeline-react-passive-effects-hover': '#9d71bd',
- '--color-timeline-react-passive-effects-text': '#3e2c4a',
- '--color-timeline-react-schedule': '#9fc3f3',
- '--color-timeline-react-schedule-hover': '#2683E2',
- '--color-timeline-react-suspense-rejected': '#f1cc14',
- '--color-timeline-react-suspense-rejected-hover': '#ffdf37',
- '--color-timeline-react-suspense-resolved': '#a6e59f',
- '--color-timeline-react-suspense-resolved-hover': '#89d281',
- '--color-timeline-react-suspense-unresolved': '#c9cacd',
- '--color-timeline-react-suspense-unresolved-hover': '#93959a',
- '--color-timeline-thrown-error': '#ee1638',
- '--color-timeline-thrown-error-hover': '#da1030',
- '--color-timeline-text-color': '#000000',
- '--color-timeline-text-dim-color': '#ccc',
- '--color-timeline-react-work-border': '#eeeeee',
'--color-timebar-background': '#f6f6f6',
'--color-search-match': 'yellow',
'--color-search-match-current': '#f7923b',
'--color-selected-tree-highlight-active': 'rgba(0, 136, 250, 0.1)',
'--color-selected-tree-highlight-inactive': 'rgba(0, 0, 0, 0.05)',
- '--color-scroll-caret': 'rgba(150, 150, 150, 0.5)',
'--color-tab-selected-border': '#0088fa',
'--color-text': '#000000',
'--color-text-invalid': '#ff0000',
@@ -255,52 +214,11 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
'--color-resize-bar-active': '#31363f',
'--color-resize-bar-border': '#3d424a',
'--color-resize-bar-dot': '#cfd1d5',
- '--color-timeline-internal-module': '#303542',
- '--color-timeline-internal-module-hover': '#363b4a',
- '--color-timeline-internal-module-text': '#7f8899',
- '--color-timeline-native-event': '#b2b2b2',
- '--color-timeline-native-event-hover': '#949494',
- '--color-timeline-network-primary': '#fcf3dc',
- '--color-timeline-network-primary-hover': '#e3dbc5',
- '--color-timeline-network-secondary': '#efc457',
- '--color-timeline-network-secondary-hover': '#d6af4d',
- '--color-timeline-priority-background': '#1d2129',
- '--color-timeline-priority-border': '#282c34',
- '--color-timeline-user-timing': '#c9cacd',
- '--color-timeline-user-timing-hover': '#93959a',
- '--color-timeline-react-idle': '#3d485b',
- '--color-timeline-react-idle-hover': '#465269',
- '--color-timeline-react-render': '#2683E2',
- '--color-timeline-react-render-hover': '#1a76d4',
- '--color-timeline-react-render-text': '#11365e',
- '--color-timeline-react-commit': '#731fad',
- '--color-timeline-react-commit-hover': '#611b94',
- '--color-timeline-react-commit-text': '#e5c1ff',
- '--color-timeline-react-layout-effects': '#611b94',
- '--color-timeline-react-layout-effects-hover': '#51167a',
- '--color-timeline-react-layout-effects-text': '#e5c1ff',
- '--color-timeline-react-passive-effects': '#611b94',
- '--color-timeline-react-passive-effects-hover': '#51167a',
- '--color-timeline-react-passive-effects-text': '#e5c1ff',
- '--color-timeline-react-schedule': '#2683E2',
- '--color-timeline-react-schedule-hover': '#1a76d4',
- '--color-timeline-react-suspense-rejected': '#f1cc14',
- '--color-timeline-react-suspense-rejected-hover': '#e4c00f',
- '--color-timeline-react-suspense-resolved': '#a6e59f',
- '--color-timeline-react-suspense-resolved-hover': '#89d281',
- '--color-timeline-react-suspense-unresolved': '#c9cacd',
- '--color-timeline-react-suspense-unresolved-hover': '#93959a',
- '--color-timeline-thrown-error': '#fb3655',
- '--color-timeline-thrown-error-hover': '#f82042',
- '--color-timeline-text-color': '#282c34',
- '--color-timeline-text-dim-color': '#555b66',
- '--color-timeline-react-work-border': '#3d424a',
'--color-timebar-background': '#1d2129',
'--color-search-match': 'yellow',
'--color-search-match-current': '#f7923b',
'--color-selected-tree-highlight-active': 'rgba(23, 143, 185, 0.15)',
'--color-selected-tree-highlight-inactive': 'rgba(255, 255, 255, 0.05)',
- '--color-scroll-caret': '#4f5766',
'--color-shadow': 'rgba(0, 0, 0, 0.5)',
'--color-tab-selected-border': '#178fb9',
'--color-text': '#ffffff',
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index 67836086f498..ee5fa5ad432e 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -12,7 +12,6 @@ import EventEmitter from '../events';
import {inspect} from 'util';
import {
PROFILING_FLAG_BASIC_SUPPORT,
- PROFILING_FLAG_TIMELINE_SUPPORT,
PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT,
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
@@ -133,14 +132,12 @@ export type Config = {
supportsInspectMatchingDOMElement?: boolean,
supportsClickToInspect?: boolean,
supportsReloadAndProfile?: boolean,
- supportsTimeline?: boolean,
supportsTraceUpdates?: boolean,
};
const ADVANCED_PROFILING_NONE = 0;
-const ADVANCED_PROFILING_TIMELINE = 1;
const ADVANCED_PROFILING_PERFORMANCE_TRACKS = 2;
-type AdvancedProfiling = 0 | 1 | 2;
+type AdvancedProfiling = 0 | 2;
export type Capabilities = {
supportsBasicProfiling: boolean,
@@ -201,7 +198,6 @@ export default class Store extends EventEmitter<{
recordChangeDescriptions: [],
roots: [],
rootSupportsBasicProfiling: [],
- rootSupportsTimelineProfiling: [],
rootSupportsPerformanceTracks: [],
suspenseTreeMutated: [[Map]],
supportsNativeStyleEditor: [],
@@ -280,7 +276,6 @@ export default class Store extends EventEmitter<{
// These options may be initially set by a configuration option when constructing the Store.
_supportsInspectMatchingDOMElement: boolean = false;
_supportsClickToInspect: boolean = false;
- _supportsTimeline: boolean = false;
_supportsTraceUpdates: boolean = false;
_isReloadAndProfileFrontendSupported: boolean = false;
@@ -288,7 +283,6 @@ export default class Store extends EventEmitter<{
// These options default to false but may be updated as roots are added and removed.
_rootSupportsBasicProfiling: boolean = false;
- _rootSupportsTimelineProfiling: boolean = false;
_rootSupportsPerformanceTracks: boolean = false;
_bridgeProtocol: BridgeProtocol | null = null;
@@ -336,7 +330,6 @@ export default class Store extends EventEmitter<{
supportsInspectMatchingDOMElement,
supportsClickToInspect,
supportsReloadAndProfile,
- supportsTimeline,
supportsTraceUpdates,
checkBridgeProtocolCompatibility,
} = config;
@@ -349,9 +342,6 @@ export default class Store extends EventEmitter<{
if (supportsReloadAndProfile) {
this._isReloadAndProfileFrontendSupported = true;
}
- if (supportsTimeline) {
- this._supportsTimeline = true;
- }
if (supportsTraceUpdates) {
this._supportsTraceUpdates = true;
}
@@ -575,11 +565,6 @@ export default class Store extends EventEmitter<{
return this._rootSupportsBasicProfiling;
}
- // At least one of the currently mounted roots support the Timeline profiler.
- get rootSupportsTimelineProfiling(): boolean {
- return this._rootSupportsTimelineProfiling;
- }
-
// At least one of the currently mounted roots support performance tracks.
get rootSupportsPerformanceTracks(): boolean {
return this._rootSupportsPerformanceTracks;
@@ -604,12 +589,6 @@ export default class Store extends EventEmitter<{
);
}
- // This build of DevTools supports the Timeline profiler.
- // This is a static flag, controlled by the Store config.
- get supportsTimeline(): boolean {
- return this._supportsTimeline;
- }
-
get supportsTraceUpdates(): boolean {
return this._supportsTraceUpdates;
}
@@ -1537,16 +1516,12 @@ export default class Store extends EventEmitter<{
const profilerFlags = operations[i++];
const supportsBasicProfiling =
(profilerFlags & PROFILING_FLAG_BASIC_SUPPORT) !== 0;
- const supportsTimeline =
- (profilerFlags & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0;
const supportsPerformanceTracks =
(profilerFlags & PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT) !== 0;
let supportsAdvancedProfiling: AdvancedProfiling =
ADVANCED_PROFILING_NONE;
if (supportsPerformanceTracks) {
supportsAdvancedProfiling = ADVANCED_PROFILING_PERFORMANCE_TRACKS;
- } else if (supportsTimeline) {
- supportsAdvancedProfiling = ADVANCED_PROFILING_TIMELINE;
}
let supportsStrictMode = false;
@@ -2301,14 +2276,11 @@ export default class Store extends EventEmitter<{
if (haveRootsChanged) {
const prevRootSupportsProfiling = this._rootSupportsBasicProfiling;
- const prevRootSupportsTimelineProfiling =
- this._rootSupportsTimelineProfiling;
const prevRootSupportsPerformanceTracks =
this._rootSupportsPerformanceTracks;
this._hasOwnerMetadata = false;
this._rootSupportsBasicProfiling = false;
- this._rootSupportsTimelineProfiling = false;
this._rootSupportsPerformanceTracks = false;
this._rootIDToCapabilities.forEach(
({
@@ -2322,9 +2294,6 @@ export default class Store extends EventEmitter<{
if (hasOwnerMetadata) {
this._hasOwnerMetadata = true;
}
- if (supportsAdvancedProfiling === ADVANCED_PROFILING_TIMELINE) {
- this._rootSupportsTimelineProfiling = true;
- }
if (
supportsAdvancedProfiling === ADVANCED_PROFILING_PERFORMANCE_TRACKS
) {
@@ -2339,12 +2308,6 @@ export default class Store extends EventEmitter<{
this.emit('rootSupportsBasicProfiling');
}
- if (
- this._rootSupportsTimelineProfiling !==
- prevRootSupportsTimelineProfiling
- ) {
- this.emit('rootSupportsTimelineProfiling');
- }
if (
this._rootSupportsPerformanceTracks !==
prevRootSupportsPerformanceTracks
diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js
index 9f3f8a41b04e..693c4a05643d 100644
--- a/packages/react-devtools-shared/src/devtools/views/DevTools.js
+++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js
@@ -35,7 +35,6 @@ import {InspectedElementContextController} from './Components/InspectedElementCo
import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
import {ProfilerContextController} from './Profiler/ProfilerContext';
import {SuspenseTreeContextController} from './SuspenseTab/SuspenseTreeContext';
-import {TimelineContextController} from 'react-devtools-timeline/src/TimelineContext';
import {ModalDialogContextController} from './ModalDialog';
import ReactLogo from './ReactLogo';
import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog';
@@ -301,77 +300,70 @@ export default function DevTools({
value={fetchFileWithCaching || null}>
-
-
-
-
-
- {showTabBar && (
-
-
-
- {process.env.DEVTOOLS_VERSION}
-
-
-
-
- )}
-
-
-
-
-
-
+
+
+
+ {showTabBar && (
+
+
+
+ {process.env.DEVTOOLS_VERSION}
+
+
+
-
- {editorPortalContainer ? (
-
- ) : null}
- {inspectedElementPortalContainer ? (
-
+
- ) : null}
-
-
-
-
+
+
+
+
+
+
+ {editorPortalContainer ? (
+
+ ) : null}
+ {inspectedElementPortalContainer ? (
+
+ ) : null}
+
+
+
diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
index 8ad55976582f..17adea59310e 100644
--- a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
+++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
@@ -69,7 +69,7 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
rejectCallbacks.add(reject);
},
- // Optional property used by Timeline:
+ // Optional property, read by React to name this I/O in async debug info:
displayName: `Searching GitHub issues for error "${errorMessage}"`,
};
const wake = () => {
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js
index 95ac82a97323..9306f8eb2986 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js
@@ -56,9 +56,8 @@ export default function ReloadAndProfileButton({
bridge.send('reloadAndProfile', {
recordChangeDescriptions,
- recordTimeline: store.supportsTimeline,
});
- }, [bridge, recordChangeDescriptions, store]);
+ }, [bridge, recordChangeDescriptions]);
if (!supportsReloadAndProfile) {
return null;
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.css b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.css
deleted file mode 100644
index 98b0dad5325d..000000000000
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.css
+++ /dev/null
@@ -1,66 +0,0 @@
-.Toolbar {
- height: 2.25rem;
- padding: 0 0.5rem;
- flex: 0 0 auto;
- display: flex;
- align-items: center;
- border-bottom: 1px solid var(--color-border);
-}
-
-.Content {
- padding: 0.5rem;
- user-select: none;
- overflow: auto;
-}
-
-.List {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-.ListItem {
- flex: 1 1;
- margin: 0 0 0.5rem;
-}
-
-.Label {
- overflow: hidden;
- text-overflow: ellipsis;
- font-weight: bold;
- flex: 1 1;
-}
-
-.Value {
- font-family: var(--font-family-monospace);
- font-size: var(--font-size-monospace-normal);
-}
-
-.Row {
- display: flex;
- flex-direction: row;
- align-items: center;
- border-top: 1px solid var(--color-border);
-}
-
-.UnclickableSource,
-.ClickableSource {
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-sans-normal);
-}
-
-.UnclickableSource {
- color: var(--color-dim);
-}
-
-.ClickableSource {
- color: var(--color-text);
-}
-
-.ClickableSource:focus,
-.ClickableSource:hover {
- background-color: var(--color-background-hover);
-}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js
deleted file mode 100644
index 8b55e0f8c0c0..000000000000
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * 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
- */
-
-import type {SchedulingEvent} from 'react-devtools-timeline/src/types';
-import type {ReactFunctionLocation} from 'shared/ReactTypes';
-
-import * as React from 'react';
-import Button from '../Button';
-import ButtonIcon from '../ButtonIcon';
-import {useContext} from 'react';
-import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
-import {
- formatTimestamp,
- getSchedulingEventLabel,
-} from 'react-devtools-timeline/src/utils/formatting';
-import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils';
-import {copy} from 'clipboard-js';
-import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
-import useOpenResource from '../useOpenResource';
-
-import styles from './SidebarEventInfo.css';
-
-export type Props = {};
-
-type FunctionLocationProps = {
- location: ReactFunctionLocation,
- displayName: string,
-};
-function FunctionLocation({location, displayName}: FunctionLocationProps) {
- // TODO: We should support symbolication here as well, but
- // symbolicating the whole stack can be expensive
- const [canViewSource, viewSource] = useOpenResource(location, null);
- return (
-
-
- {displayName}
-
-
- );
-}
-
-type SchedulingEventProps = {
- eventInfo: SchedulingEvent,
-};
-
-function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
- const {componentName, timestamp} = eventInfo;
- const componentStack = eventInfo.componentStack || null;
-
- return (
- <>
-
- {componentName} {getSchedulingEventLabel(eventInfo)}
-
-
- >
- );
-}
-
-export default function SidebarEventInfo(_: Props): React.Node {
- const {selectedEvent} = useContext(TimelineContext);
- // (TODO) Refactor in next PR so this supports multiple types of events
- if (selectedEvent && selectedEvent.schedulingEvent) {
- return ;
- }
-
- return null;
-}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
index 34f24122e073..825e95c2915a 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
@@ -11,10 +11,6 @@ import type {
ElementType,
SerializedElement,
} from 'react-devtools-shared/src/frontend/types';
-import type {
- TimelineData,
- TimelineDataExport,
-} from 'react-devtools-timeline/src/types';
export type CommitTreeNode = {
id: number,
@@ -118,9 +114,6 @@ export type ProfilingDataFrontend = {
// Legacy profiling data is per renderer + root.
dataForRoots: Map,
- // Timeline data is per rederer.
- timelineData: Array,
-
// Some functionality should be disabled for imported data.
// e.g. DevTools should not try to sync selection between Components and Profiler tabs,
// even if there are Fibers with the same IDs.
@@ -157,8 +150,4 @@ export type ProfilingDataExport = {
// Legacy profiling data is per renderer + root.
dataForRoots: Array,
-
- // Timeline data is per rederer.
- // Note that old exported profiles won't contain this key.
- timelineData?: Array,
};
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js
index 193ce766d705..476aa0537922 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js
@@ -18,10 +18,6 @@ import type {
ProfilingDataFrontend,
SnapshotNode,
} from './types';
-import type {
- TimelineData,
- TimelineDataExport,
-} from 'react-devtools-timeline/src/types';
const commitGradient = [
'var(--color-commit-gradient-0)',
@@ -45,31 +41,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
): ProfilingDataFrontend {
const dataForRoots: Map = new Map();
- const timelineDataArray = [];
-
dataBackends.forEach(dataBackend => {
- const {timelineData} = dataBackend;
- if (timelineData != null) {
- const {
- batchUIDToMeasuresKeyValueArray,
- internalModuleSourceToRanges,
- laneToLabelKeyValueArray,
- laneToReactMeasureKeyValueArray,
- ...rest
- } = timelineData;
-
- timelineDataArray.push({
- ...rest,
-
- // Most of the data is safe to parse as-is,
- // but we need to convert the nested Arrays back to Maps.
- batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
- internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
- laneToLabelMap: new Map(laneToLabelKeyValueArray),
- laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
- });
- }
-
dataBackend.dataForRoots.forEach(
({commitData, displayName, initialTreeBaseDurations, rootID}) => {
const operations = operationsByRootID.get(rootID);
@@ -125,7 +97,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
);
});
- return {dataForRoots, imported: false, timelineData: timelineDataArray};
+ return {dataForRoots, imported: false};
}
// Converts a Profiling data export into the format required by the Store.
@@ -140,50 +112,6 @@ export function prepareProfilingDataFrontendFromExport(
);
}
- const timelineData: Array = profilingDataExport.timelineData
- ? profilingDataExport.timelineData.map(
- ({
- batchUIDToMeasuresKeyValueArray,
- componentMeasures,
- duration,
- flamechart,
- internalModuleSourceToRanges,
- laneToLabelKeyValueArray,
- laneToReactMeasureKeyValueArray,
- nativeEvents,
- networkMeasures,
- otherUserTimingMarks,
- reactVersion,
- schedulingEvents,
- snapshots,
- snapshotHeight,
- startTime,
- suspenseEvents,
- thrownErrors,
- }) => ({
- // Most of the data is safe to parse as-is,
- // but we need to convert the nested Arrays back to Maps.
- batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
- componentMeasures,
- duration,
- flamechart,
- internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
- laneToLabelMap: new Map(laneToLabelKeyValueArray),
- laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
- nativeEvents,
- networkMeasures,
- otherUserTimingMarks,
- reactVersion,
- schedulingEvents,
- snapshots,
- snapshotHeight,
- startTime,
- suspenseEvents,
- thrownErrors,
- }),
- )
- : [];
-
const dataForRoots: Map = new Map();
profilingDataExport.dataForRoots.forEach(
({
@@ -231,7 +159,6 @@ export function prepareProfilingDataFrontendFromExport(
return {
dataForRoots,
imported: true,
- timelineData,
};
}
@@ -239,55 +166,6 @@ export function prepareProfilingDataFrontendFromExport(
export function prepareProfilingDataExport(
profilingDataFrontend: ProfilingDataFrontend,
): ProfilingDataExport {
- const timelineData: Array =
- profilingDataFrontend.timelineData.map(
- ({
- batchUIDToMeasuresMap,
- componentMeasures,
- duration,
- flamechart,
- internalModuleSourceToRanges,
- laneToLabelMap,
- laneToReactMeasureMap,
- nativeEvents,
- networkMeasures,
- otherUserTimingMarks,
- reactVersion,
- schedulingEvents,
- snapshots,
- snapshotHeight,
- startTime,
- suspenseEvents,
- thrownErrors,
- }) => ({
- // Most of the data is safe to serialize as-is,
- // but we need to convert the Maps to nested Arrays.
- batchUIDToMeasuresKeyValueArray: Array.from(
- batchUIDToMeasuresMap.entries(),
- ),
- componentMeasures: componentMeasures,
- duration,
- flamechart,
- internalModuleSourceToRanges: Array.from(
- internalModuleSourceToRanges.entries(),
- ),
- laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
- laneToReactMeasureKeyValueArray: Array.from(
- laneToReactMeasureMap.entries(),
- ),
- nativeEvents,
- networkMeasures,
- otherUserTimingMarks,
- reactVersion,
- schedulingEvents,
- snapshots,
- snapshotHeight,
- startTime,
- suspenseEvents,
- thrownErrors,
- }),
- );
-
const dataForRoots: Array = [];
profilingDataFrontend.dataForRoots.forEach(
({
@@ -339,7 +217,6 @@ export function prepareProfilingDataExport(
return {
version: PROFILER_EXPORT_VERSION,
dataForRoots,
- timelineData,
};
}
diff --git a/packages/react-devtools-shared/src/dynamicImportCache.js b/packages/react-devtools-shared/src/dynamicImportCache.js
index aa4add28b58b..2dd274af3314 100644
--- a/packages/react-devtools-shared/src/dynamicImportCache.js
+++ b/packages/react-devtools-shared/src/dynamicImportCache.js
@@ -71,7 +71,7 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
rejectCallbacks.add(reject);
},
- // Optional property used by Timeline:
+ // Optional property, read by React to name this I/O in async debug info:
displayName: `Loading module "${moduleLoaderFunction.name}"`,
};
diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js
index 5ec953101aa0..c1ac09946da5 100644
--- a/packages/react-devtools-shared/src/hook.js
+++ b/packages/react-devtools-shared/src/hook.js
@@ -54,7 +54,6 @@ const targetConsole: Object = console;
const defaultProfilingSettings: ProfilingSettings = {
recordChangeDescriptions: false,
- recordTimeline: false,
};
export function installHook(
@@ -404,41 +403,6 @@ export function installHook(
unpatchConsoleCallbacks.length = 0;
}
- type StackFrameString = string;
-
- const openModuleRangesStack: Array = [];
- const moduleRanges: Array<[StackFrameString, StackFrameString]> = [];
-
- function getTopStackFrameString(error: Error): StackFrameString | null {
- const frames = error.stack.split('\n');
- const frame = frames.length > 1 ? frames[1] : null;
- return frame;
- }
-
- function getInternalModuleRanges(): Array<
- [StackFrameString, StackFrameString],
- > {
- return moduleRanges;
- }
-
- function registerInternalModuleStart(error: Error) {
- const startStackFrame = getTopStackFrameString(error);
- if (startStackFrame !== null) {
- openModuleRangesStack.push(startStackFrame);
- }
- }
-
- function registerInternalModuleStop(error: Error) {
- if (openModuleRangesStack.length > 0) {
- const startStackFrame = openModuleRangesStack.pop();
- const stopStackFrame = getTopStackFrameString(error);
- if (stopStackFrame !== null) {
- // $FlowFixMe[incompatible-type]
- moduleRanges.push([startStackFrame, stopStackFrame]);
- }
- }
- }
-
// For Errors and Warnings we only patch console once
function patchConsoleForErrorsAndWarnings() {
// Don't patch console in case settings were not injected
@@ -665,13 +629,6 @@ export function installHook(
// React v18.0+
onPostCommitFiberRoot,
setStrictMode,
-
- // Schedule Profiler runtime helpers.
- // These internal React modules to report their own boundaries
- // which in turn enables the profiler to dim or filter internal frames.
- getInternalModuleRanges,
- registerInternalModuleStart,
- registerInternalModuleStop,
};
if (maybeSettingsOrSettingsPromise == null) {
diff --git a/packages/react-devtools-shared/src/hookNamesCache.js b/packages/react-devtools-shared/src/hookNamesCache.js
index 7123318cfb34..518c7a343161 100644
--- a/packages/react-devtools-shared/src/hookNamesCache.js
+++ b/packages/react-devtools-shared/src/hookNamesCache.js
@@ -99,7 +99,7 @@ export function loadHookNames(
rejectCallbacks.add(reject);
},
- // Optional property used by Timeline:
+ // Optional property, read by React to name this I/O in async debug info:
displayName: `Loading hook names for ${element.displayName || 'Unknown'}`,
};
diff --git a/packages/react-devtools-shared/src/inspectedElementCache.js b/packages/react-devtools-shared/src/inspectedElementCache.js
index 8a51e2543a9f..686f922231ae 100644
--- a/packages/react-devtools-shared/src/inspectedElementCache.js
+++ b/packages/react-devtools-shared/src/inspectedElementCache.js
@@ -95,7 +95,7 @@ export function inspectElement(
rejectCallbacks.add(reject);
},
- // Optional property used by Timeline:
+ // Optional property, read by React to name this I/O in async debug info:
displayName: `Inspecting ${element.displayName || 'Unknown'}`,
};
diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js
index 65609ab25321..c4f6c083a62c 100644
--- a/packages/react-devtools-shared/src/utils.js
+++ b/packages/react-devtools-shared/src/utils.js
@@ -39,7 +39,6 @@ import {
LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
- SESSION_STORAGE_RECORD_TIMELINE_KEY,
SUSPENSE_TREE_OPERATION_ADD,
SUSPENSE_TREE_OPERATION_REMOVE,
SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
@@ -1295,30 +1294,20 @@ export function getProfilingSettings(): ProfilingSettings {
recordChangeDescriptions:
sessionStorageGetItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY) ===
'true',
- recordTimeline:
- sessionStorageGetItem(SESSION_STORAGE_RECORD_TIMELINE_KEY) === 'true',
};
}
-export function onReloadAndProfile(
- recordChangeDescriptions: boolean,
- recordTimeline: boolean,
-): void {
+export function onReloadAndProfile(recordChangeDescriptions: boolean): void {
sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
sessionStorageSetItem(
SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
recordChangeDescriptions ? 'true' : 'false',
);
- sessionStorageSetItem(
- SESSION_STORAGE_RECORD_TIMELINE_KEY,
- recordTimeline ? 'true' : 'false',
- );
}
export function onReloadAndProfileFlagsReset(): void {
sessionStorageRemoveItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY);
sessionStorageRemoveItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY);
- sessionStorageRemoveItem(SESSION_STORAGE_RECORD_TIMELINE_KEY);
}
export function unionOfTwoArrays(a: Array, b: Array): Array {
diff --git a/packages/react-devtools-timeline/README.md b/packages/react-devtools-timeline/README.md
deleted file mode 100644
index 2b80944de281..000000000000
--- a/packages/react-devtools-timeline/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# React Concurrent Mode Profiler
-
-This package contains the new/experimental "timeline" for React 18. This profiler exists as its own project because it was initially deployed as a standalone app. It has since been moved into the DevTools Profiler under the "Scheduling" tab. This package will likely eventually be moved into `react-devtools-shared`.
\ No newline at end of file
diff --git a/packages/react-devtools-timeline/package.json b/packages/react-devtools-timeline/package.json
deleted file mode 100644
index 5e4df8355267..000000000000
--- a/packages/react-devtools-timeline/package.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "private": true,
- "name": "react-devtools-timeline",
- "version": "7.0.1",
- "license": "MIT",
- "dependencies": {
- "@elg/speedscope": "1.9.0-a6f84db",
- "clipboard-js": "^0.3.6",
- "memoize-one": "^5.1.1",
- "nullthrows": "^1.1.1",
- "pretty-ms": "^7.0.0",
- "react-virtualized-auto-sizer": "^1.0.23",
- "regenerator-runtime": "^0.13.7"
- },
- "devDependencies": {
- "@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
- "@reach/menu-button": "^0.16.1",
- "@reach/tooltip": "^0.16.0",
- "babel-loader": "^8.1.0",
- "css-loader": "^4.2.1",
- "file-loader": "^6.0.0",
- "style-loader": "^1.2.1",
- "url-loader": "^4.1.0",
- "vercel": "^20.1.0",
- "webpack": "^5.82.1",
- "webpack-cli": "^5.1.1",
- "webpack-dev-server": "^4.15.0"
- }
-}
diff --git a/packages/react-devtools-timeline/src/CanvasPage.css b/packages/react-devtools-timeline/src/CanvasPage.css
deleted file mode 100644
index 8c7633a1b897..000000000000
--- a/packages/react-devtools-timeline/src/CanvasPage.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.CanvasPage {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-}
diff --git a/packages/react-devtools-timeline/src/CanvasPage.js b/packages/react-devtools-timeline/src/CanvasPage.js
deleted file mode 100644
index 104a92727d52..000000000000
--- a/packages/react-devtools-timeline/src/CanvasPage.js
+++ /dev/null
@@ -1,729 +0,0 @@
-/**
- * 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
- */
-
-import type {Interaction, Point} from './view-base';
-import type {ReactEventInfo, TimelineData, ViewState} from './types';
-
-import * as React from 'react';
-import {
- Fragment,
- useContext,
- useEffect,
- useLayoutEffect,
- useRef,
- useState,
- useCallback,
-} from 'react';
-import AutoSizer from 'react-virtualized-auto-sizer';
-
-import {
- HorizontalPanAndZoomView,
- ResizableView,
- VerticalScrollOverflowView,
- Surface,
- VerticalScrollView,
- View,
- useCanvasInteraction,
- verticallyStackedLayout,
- zeroPoint,
-} from './view-base';
-import {
- ComponentMeasuresView,
- FlamechartView,
- NativeEventsView,
- NetworkMeasuresView,
- ReactMeasuresView,
- SchedulingEventsView,
- SnapshotsView,
- SuspenseEventsView,
- ThrownErrorsView,
- TimeAxisMarkersView,
- UserTimingMarksView,
-} from './content-views';
-import {COLORS} from './content-views/constants';
-import {clampState, moveStateToRange} from './view-base/utils/scrollState';
-import EventTooltip from './EventTooltip';
-import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
-import {TimelineSearchContext} from './TimelineSearchContext';
-import {TimelineContext} from './TimelineContext';
-import CanvasPageContextMenu from './CanvasPageContextMenu';
-
-import type {ContextMenuRef} from 'react-devtools-shared/src/devtools/ContextMenu/types';
-
-import styles from './CanvasPage.css';
-
-type Props = {
- profilerData: TimelineData,
- viewState: ViewState,
-};
-
-function CanvasPage({profilerData, viewState}: Props): React.Node {
- return (
-
-
- {({height, width}: {height: number, width: number}) => (
-
- )}
-
-
- );
-}
-
-const EMPTY_CONTEXT_INFO: ReactEventInfo = {
- componentMeasure: null,
- flamechartStackFrame: null,
- measure: null,
- nativeEvent: null,
- networkMeasure: null,
- schedulingEvent: null,
- snapshot: null,
- suspenseEvent: null,
- thrownError: null,
- userTimingMark: null,
-};
-
-type AutoSizedCanvasProps = {
- data: TimelineData,
- height: number,
- viewState: ViewState,
- width: number,
-};
-
-function AutoSizedCanvas({
- data,
- height,
- viewState,
- width,
-}: AutoSizedCanvasProps) {
- const canvasRef = useRef(null);
-
- const [mouseLocation, setMouseLocation] = useState(zeroPoint); // DOM coordinates
- const [hoveredEvent, setHoveredEvent] = useState(null);
- const [lastHoveredEvent, setLastHoveredEvent] =
- useState(null);
-
- const contextMenuRef: ContextMenuRef = useRef(null);
-
- const resetHoveredEvent = useCallback(
- () => setHoveredEvent(EMPTY_CONTEXT_INFO),
- [],
- );
- const updateHoveredEvent = useCallback(
- (event: ReactEventInfo) => {
- setHoveredEvent(event);
-
- // If menu is already open, don't update the hovered event data
- // So the same set of menu items is preserved until the current context menu is closed
- if (contextMenuRef.current?.isShown()) {
- return;
- }
-
- const {
- componentMeasure,
- flamechartStackFrame,
- measure,
- networkMeasure,
- schedulingEvent,
- suspenseEvent,
- } = event;
-
- // We have to keep track of last non-empty hovered event, since this will be the input for context menu items
- // We can't just pass hoveredEvent to ContextMenuContainer,
- // since it will be reset each time user moves mouse away from event object on the canvas
- if (
- componentMeasure != null ||
- flamechartStackFrame != null ||
- measure != null ||
- networkMeasure != null ||
- schedulingEvent != null ||
- suspenseEvent != null
- ) {
- setLastHoveredEvent(event);
- }
- },
- [contextMenuRef],
- );
-
- const {searchIndex, searchRegExp, searchResults} = useContext(
- TimelineSearchContext,
- );
-
- // This effect searches timeline data and scrolls to the next match wen search criteria change.
- useLayoutEffect(() => {
- viewState.updateSearchRegExpState(searchRegExp);
-
- const componentMeasureSearchResult =
- searchResults.length > 0 ? searchResults[searchIndex] : null;
- if (componentMeasureSearchResult != null) {
- const scrollState = moveStateToRange({
- state: viewState.horizontalScrollState,
- rangeStart: componentMeasureSearchResult.timestamp,
- rangeEnd:
- componentMeasureSearchResult.timestamp +
- componentMeasureSearchResult.duration,
- contentLength: data.duration,
- minContentLength: data.duration * MIN_ZOOM_LEVEL,
- maxContentLength: data.duration * MAX_ZOOM_LEVEL,
- containerLength: width,
- });
-
- viewState.updateHorizontalScrollState(scrollState);
- }
-
- surfaceRef.current.displayIfNeeded();
- }, [searchIndex, searchRegExp, searchResults, viewState]);
-
- const surfaceRef = useRef(new Surface(resetHoveredEvent));
- const userTimingMarksViewRef = useRef(null);
- const nativeEventsViewRef = useRef(null);
- const schedulingEventsViewRef = useRef(null);
- const suspenseEventsViewRef = useRef(null);
- const componentMeasuresViewRef = useRef(null);
- const reactMeasuresViewRef = useRef(null);
- const flamechartViewRef = useRef(null);
- const networkMeasuresViewRef = useRef(null);
- const snapshotsViewRef = useRef(null);
- const thrownErrorsViewRef = useRef(null);
-
- useLayoutEffect(() => {
- const surface = surfaceRef.current;
- const defaultFrame = {origin: zeroPoint, size: {width, height}};
-
- // Auto hide context menu when panning.
- viewState.onHorizontalScrollStateChange(scrollState => {
- contextMenuRef.current?.hide();
- });
-
- // Initialize horizontal view state
- viewState.updateHorizontalScrollState(
- clampState({
- state: viewState.horizontalScrollState,
- minContentLength: data.duration * MIN_ZOOM_LEVEL,
- maxContentLength: data.duration * MAX_ZOOM_LEVEL,
- containerLength: defaultFrame.size.width,
- }),
- );
-
- function createViewHelper(
- view: View,
- label: string,
- shouldScrollVertically: boolean = false,
- shouldResizeVertically: boolean = false,
- ): View {
- let verticalScrollView = null;
- if (shouldScrollVertically) {
- verticalScrollView = new VerticalScrollView(
- surface,
- defaultFrame,
- view,
- viewState,
- label,
- );
- }
-
- const horizontalPanAndZoomView = new HorizontalPanAndZoomView(
- surface,
- defaultFrame,
- verticalScrollView !== null ? verticalScrollView : view,
- data.duration,
- viewState,
- );
-
- let resizableView = null;
- if (shouldResizeVertically) {
- resizableView = new ResizableView(
- surface,
- defaultFrame,
- horizontalPanAndZoomView,
- viewState,
- canvasRef,
- label,
- );
- }
-
- return resizableView || horizontalPanAndZoomView;
- }
-
- const axisMarkersView = new TimeAxisMarkersView(
- surface,
- defaultFrame,
- data.duration,
- );
- const axisMarkersViewWrapper = createViewHelper(axisMarkersView, 'time');
-
- let userTimingMarksViewWrapper = null;
- if (data.otherUserTimingMarks.length > 0) {
- const userTimingMarksView = new UserTimingMarksView(
- surface,
- defaultFrame,
- data.otherUserTimingMarks,
- data.duration,
- );
- userTimingMarksViewRef.current = userTimingMarksView;
- userTimingMarksViewWrapper = createViewHelper(
- userTimingMarksView,
- 'user timing api',
- );
- }
-
- let nativeEventsViewWrapper = null;
- if (data.nativeEvents.length > 0) {
- const nativeEventsView = new NativeEventsView(
- surface,
- defaultFrame,
- data,
- );
- nativeEventsViewRef.current = nativeEventsView;
- nativeEventsViewWrapper = createViewHelper(
- nativeEventsView,
- 'events',
- true,
- true,
- );
- }
-
- let thrownErrorsViewWrapper = null;
- if (data.thrownErrors.length > 0) {
- const thrownErrorsView = new ThrownErrorsView(
- surface,
- defaultFrame,
- data,
- );
- thrownErrorsViewRef.current = thrownErrorsView;
- thrownErrorsViewWrapper = createViewHelper(
- thrownErrorsView,
- 'thrown errors',
- );
- }
-
- let schedulingEventsViewWrapper = null;
- if (data.schedulingEvents.length > 0) {
- const schedulingEventsView = new SchedulingEventsView(
- surface,
- defaultFrame,
- data,
- );
- schedulingEventsViewRef.current = schedulingEventsView;
- schedulingEventsViewWrapper = createViewHelper(
- schedulingEventsView,
- 'react updates',
- );
- }
-
- let suspenseEventsViewWrapper = null;
- if (data.suspenseEvents.length > 0) {
- const suspenseEventsView = new SuspenseEventsView(
- surface,
- defaultFrame,
- data,
- );
- suspenseEventsViewRef.current = suspenseEventsView;
- suspenseEventsViewWrapper = createViewHelper(
- suspenseEventsView,
- 'suspense',
- true,
- true,
- );
- }
-
- const reactMeasuresView = new ReactMeasuresView(
- surface,
- defaultFrame,
- data,
- );
- reactMeasuresViewRef.current = reactMeasuresView;
- const reactMeasuresViewWrapper = createViewHelper(
- reactMeasuresView,
- 'react scheduling',
- true,
- true,
- );
-
- let componentMeasuresViewWrapper = null;
- if (data.componentMeasures.length > 0) {
- const componentMeasuresView = new ComponentMeasuresView(
- surface,
- defaultFrame,
- data,
- viewState,
- );
- componentMeasuresViewRef.current = componentMeasuresView;
- componentMeasuresViewWrapper = createViewHelper(
- componentMeasuresView,
- 'react components',
- );
- }
-
- let snapshotsViewWrapper = null;
- if (data.snapshots.length > 0) {
- const snapshotsView = new SnapshotsView(surface, defaultFrame, data);
- snapshotsViewRef.current = snapshotsView;
- snapshotsViewWrapper = createViewHelper(
- snapshotsView,
- 'snapshots',
- true,
- true,
- );
- }
-
- let networkMeasuresViewWrapper = null;
- if (data.snapshots.length > 0) {
- const networkMeasuresView = new NetworkMeasuresView(
- surface,
- defaultFrame,
- data,
- );
- networkMeasuresViewRef.current = networkMeasuresView;
- networkMeasuresViewWrapper = createViewHelper(
- networkMeasuresView,
- 'network',
- true,
- true,
- );
- }
-
- let flamechartViewWrapper = null;
- if (data.flamechart.length > 0) {
- const flamechartView = new FlamechartView(
- surface,
- defaultFrame,
- data.flamechart,
- data.internalModuleSourceToRanges,
- data.duration,
- );
- flamechartViewRef.current = flamechartView;
- flamechartViewWrapper = createViewHelper(
- flamechartView,
- 'flamechart',
- true,
- true,
- );
- }
-
- // Root view contains all of the sub views defined above.
- // The order we add them below determines their vertical position.
- const rootView = new View(
- surface,
- defaultFrame,
- verticallyStackedLayout,
- defaultFrame,
- COLORS.BACKGROUND,
- );
- rootView.addSubview(axisMarkersViewWrapper);
- if (userTimingMarksViewWrapper !== null) {
- rootView.addSubview(userTimingMarksViewWrapper);
- }
- if (nativeEventsViewWrapper !== null) {
- rootView.addSubview(nativeEventsViewWrapper);
- }
- if (schedulingEventsViewWrapper !== null) {
- rootView.addSubview(schedulingEventsViewWrapper);
- }
- if (thrownErrorsViewWrapper !== null) {
- rootView.addSubview(thrownErrorsViewWrapper);
- }
- if (suspenseEventsViewWrapper !== null) {
- rootView.addSubview(suspenseEventsViewWrapper);
- }
- // $FlowFixMe[invalid-compare]
- if (reactMeasuresViewWrapper !== null) {
- rootView.addSubview(reactMeasuresViewWrapper);
- }
- if (componentMeasuresViewWrapper !== null) {
- rootView.addSubview(componentMeasuresViewWrapper);
- }
- if (snapshotsViewWrapper !== null) {
- rootView.addSubview(snapshotsViewWrapper);
- }
- if (networkMeasuresViewWrapper !== null) {
- rootView.addSubview(networkMeasuresViewWrapper);
- }
- if (flamechartViewWrapper !== null) {
- rootView.addSubview(flamechartViewWrapper);
- }
-
- const verticalScrollOverflowView = new VerticalScrollOverflowView(
- surface,
- defaultFrame,
- rootView,
- viewState,
- );
-
- surfaceRef.current.rootView = verticalScrollOverflowView;
- }, [data]);
-
- useLayoutEffect(() => {
- if (canvasRef.current) {
- surfaceRef.current.setCanvas(canvasRef.current, {width, height});
- }
- }, [width, height]);
-
- const interactor = useCallback((interaction: Interaction) => {
- const canvas = canvasRef.current;
- if (canvas === null) {
- return;
- }
-
- const surface = surfaceRef.current;
- surface.handleInteraction(interaction);
-
- // Flush any display work that got queued up as part of the previous interaction.
- // Typically there should be no work, but certain interactions may need a second pass.
- // For example, the ResizableView may collapse/expand its contents,
- // which requires a second layout pass for an ancestor VerticalScrollOverflowView.
- //
- // TODO It would be nice to remove this call for performance reasons.
- // To do that, we'll need to address the UX bug with VerticalScrollOverflowView.
- // For more info see: https://github.com/facebook/react/pull/22005#issuecomment-896953399
- surface.displayIfNeeded();
-
- canvas.style.cursor = surface.getCurrentCursor() || 'default';
-
- // Defer drawing to canvas until React's commit phase, to avoid drawing
- // twice and to ensure that both the canvas and DOM elements managed by
- // React are in sync.
- setMouseLocation({
- x: interaction.payload.event.x,
- y: interaction.payload.event.y,
- });
- }, []);
-
- useCanvasInteraction(canvasRef, interactor);
-
- const {selectEvent} = useContext(TimelineContext);
-
- useEffect(() => {
- const {current: userTimingMarksView} = userTimingMarksViewRef;
- if (userTimingMarksView) {
- userTimingMarksView.onHover = userTimingMark => {
- if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- userTimingMark,
- });
- }
- };
- }
-
- const {current: nativeEventsView} = nativeEventsViewRef;
- if (nativeEventsView) {
- nativeEventsView.onHover = nativeEvent => {
- if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- nativeEvent,
- });
- }
- };
- }
-
- const {current: schedulingEventsView} = schedulingEventsViewRef;
- if (schedulingEventsView) {
- schedulingEventsView.onHover = schedulingEvent => {
- if (!hoveredEvent || hoveredEvent.schedulingEvent !== schedulingEvent) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- schedulingEvent,
- });
- }
- };
- schedulingEventsView.onClick = schedulingEvent => {
- selectEvent({
- ...EMPTY_CONTEXT_INFO,
- schedulingEvent,
- });
- };
- }
-
- const {current: suspenseEventsView} = suspenseEventsViewRef;
- if (suspenseEventsView) {
- suspenseEventsView.onHover = suspenseEvent => {
- if (!hoveredEvent || hoveredEvent.suspenseEvent !== suspenseEvent) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- suspenseEvent,
- });
- }
- };
- }
-
- const {current: reactMeasuresView} = reactMeasuresViewRef;
- if (reactMeasuresView) {
- reactMeasuresView.onHover = measure => {
- if (!hoveredEvent || hoveredEvent.measure !== measure) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- measure,
- });
- }
- };
- }
-
- const {current: componentMeasuresView} = componentMeasuresViewRef;
- if (componentMeasuresView) {
- componentMeasuresView.onHover = componentMeasure => {
- if (
- !hoveredEvent ||
- hoveredEvent.componentMeasure !== componentMeasure
- ) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- componentMeasure,
- });
- }
- };
- }
-
- const {current: snapshotsView} = snapshotsViewRef;
- if (snapshotsView) {
- snapshotsView.onHover = snapshot => {
- if (!hoveredEvent || hoveredEvent.snapshot !== snapshot) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- snapshot,
- });
- }
- };
- }
-
- const {current: flamechartView} = flamechartViewRef;
- if (flamechartView) {
- flamechartView.setOnHover(flamechartStackFrame => {
- if (
- !hoveredEvent ||
- hoveredEvent.flamechartStackFrame !== flamechartStackFrame
- ) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- flamechartStackFrame,
- });
- }
- });
- }
-
- const {current: networkMeasuresView} = networkMeasuresViewRef;
- if (networkMeasuresView) {
- networkMeasuresView.onHover = networkMeasure => {
- if (!hoveredEvent || hoveredEvent.networkMeasure !== networkMeasure) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- networkMeasure,
- });
- }
- };
- }
-
- const {current: thrownErrorsView} = thrownErrorsViewRef;
- if (thrownErrorsView) {
- thrownErrorsView.onHover = thrownError => {
- if (!hoveredEvent || hoveredEvent.thrownError !== thrownError) {
- updateHoveredEvent({
- ...EMPTY_CONTEXT_INFO,
- thrownError,
- });
- }
- };
- }
- }, [
- hoveredEvent,
- data, // Attach onHover callbacks when views are re-created on data change
- ]);
-
- useLayoutEffect(() => {
- const userTimingMarksView = userTimingMarksViewRef.current;
- if (userTimingMarksView) {
- userTimingMarksView.setHoveredMark(
- hoveredEvent ? hoveredEvent.userTimingMark : null,
- );
- }
-
- const nativeEventsView = nativeEventsViewRef.current;
- if (nativeEventsView) {
- nativeEventsView.setHoveredEvent(
- hoveredEvent ? hoveredEvent.nativeEvent : null,
- );
- }
-
- const schedulingEventsView = schedulingEventsViewRef.current;
- if (schedulingEventsView) {
- schedulingEventsView.setHoveredEvent(
- hoveredEvent ? hoveredEvent.schedulingEvent : null,
- );
- }
-
- const suspenseEventsView = suspenseEventsViewRef.current;
- if (suspenseEventsView) {
- suspenseEventsView.setHoveredEvent(
- hoveredEvent ? hoveredEvent.suspenseEvent : null,
- );
- }
-
- const reactMeasuresView = reactMeasuresViewRef.current;
- if (reactMeasuresView) {
- reactMeasuresView.setHoveredMeasure(
- hoveredEvent ? hoveredEvent.measure : null,
- );
- }
-
- const flamechartView = flamechartViewRef.current;
- if (flamechartView) {
- flamechartView.setHoveredFlamechartStackFrame(
- hoveredEvent ? hoveredEvent.flamechartStackFrame : null,
- );
- }
-
- const networkMeasuresView = networkMeasuresViewRef.current;
- if (networkMeasuresView) {
- networkMeasuresView.setHoveredEvent(
- hoveredEvent ? hoveredEvent.networkMeasure : null,
- );
- }
- }, [hoveredEvent]);
-
- // Draw to canvas in React's commit phase
- useLayoutEffect(() => {
- surfaceRef.current.displayIfNeeded();
- });
-
- return (
-
-
-
-
- ) : null
- }
- ref={contextMenuRef}
- />
-
- );
-}
-
-export default CanvasPage;
diff --git a/packages/react-devtools-timeline/src/CanvasPageContextMenu.js b/packages/react-devtools-timeline/src/CanvasPageContextMenu.js
deleted file mode 100644
index 5bd4da3141b7..000000000000
--- a/packages/react-devtools-timeline/src/CanvasPageContextMenu.js
+++ /dev/null
@@ -1,182 +0,0 @@
-/**
- * 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
- */
-
-import * as React from 'react';
-import {useMemo} from 'react';
-import {copy} from 'clipboard-js';
-import prettyMilliseconds from 'pretty-ms';
-
-import ContextMenuContainer from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenuContainer';
-import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
-
-import {getBatchRange} from './utils/getBatchRange';
-import {moveStateToRange} from './view-base/utils/scrollState';
-import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
-
-import type {
- ContextMenuItem,
- ContextMenuRef,
-} from 'react-devtools-shared/src/devtools/ContextMenu/types';
-import type {
- ReactEventInfo,
- ReactMeasure,
- TimelineData,
- ViewState,
-} from './types';
-
-function zoomToBatch(
- data: TimelineData,
- measure: ReactMeasure,
- viewState: ViewState,
- width: number,
-) {
- const {batchUID} = measure;
- const [rangeStart, rangeEnd] = getBatchRange(batchUID, data);
-
- // Convert from time range to ScrollState
- const scrollState = moveStateToRange({
- state: viewState.horizontalScrollState,
- rangeStart,
- rangeEnd,
- contentLength: data.duration,
-
- minContentLength: data.duration * MIN_ZOOM_LEVEL,
- maxContentLength: data.duration * MAX_ZOOM_LEVEL,
- containerLength: width,
- });
-
- viewState.updateHorizontalScrollState(scrollState);
-}
-
-function copySummary(data: TimelineData, measure: ReactMeasure) {
- const {batchUID, duration, timestamp, type} = measure;
-
- const [startTime, stopTime] = getBatchRange(batchUID, data);
-
- copy(
- JSON.stringify({
- type,
- timestamp: prettyMilliseconds(timestamp),
- duration: prettyMilliseconds(duration),
- batchDuration: prettyMilliseconds(stopTime - startTime),
- }),
- );
-}
-
-type Props = {
- canvasRef: {current: HTMLCanvasElement | null},
- hoveredEvent: ReactEventInfo | null,
- timelineData: TimelineData,
- viewState: ViewState,
- canvasWidth: number,
- closedMenuStub: React.Node,
- ref: ContextMenuRef,
-};
-
-export default function CanvasPageContextMenu({
- canvasRef,
- timelineData,
- hoveredEvent,
- viewState,
- canvasWidth,
- closedMenuStub,
- ref,
-}: Props): React.Node {
- const menuItems = useMemo(() => {
- if (hoveredEvent == null) {
- return [];
- }
-
- const {
- componentMeasure,
- flamechartStackFrame,
- measure,
- networkMeasure,
- schedulingEvent,
- suspenseEvent,
- } = hoveredEvent;
- const items: ContextMenuItem[] = [];
-
- if (componentMeasure != null) {
- items.push({
- onClick: () => copy(componentMeasure.componentName),
- content: 'Copy component name',
- });
- }
-
- if (networkMeasure != null) {
- items.push({
- onClick: () => copy(networkMeasure.url),
- content: 'Copy URL',
- });
- }
-
- if (schedulingEvent != null) {
- items.push({
- onClick: () => copy(schedulingEvent.componentName),
- content: 'Copy component name',
- });
- }
-
- if (suspenseEvent != null) {
- items.push({
- onClick: () => copy(suspenseEvent.componentName),
- content: 'Copy component name',
- });
- }
-
- if (measure != null) {
- items.push(
- {
- onClick: () =>
- zoomToBatch(timelineData, measure, viewState, canvasWidth),
- content: 'Zoom to batch',
- },
- {
- onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
- copySummary(timelineData, measure),
- ),
- content: 'Copy summary',
- },
- );
- }
-
- if (flamechartStackFrame != null) {
- items.push(
- {
- onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
- copy(flamechartStackFrame.scriptUrl),
- ),
- content: 'Copy file path',
- },
- {
- onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
- copy(
- `line ${flamechartStackFrame.locationLine ?? ''}, column ${
- flamechartStackFrame.locationColumn ?? ''
- }`,
- ),
- ),
- content: 'Copy location',
- },
- );
- }
-
- return items;
- }, [hoveredEvent, viewState, canvasWidth]);
-
- return (
-
- );
-}
diff --git a/packages/react-devtools-timeline/src/EventTooltip.css b/packages/react-devtools-timeline/src/EventTooltip.css
deleted file mode 100644
index 60551c631ef0..000000000000
--- a/packages/react-devtools-timeline/src/EventTooltip.css
+++ /dev/null
@@ -1,88 +0,0 @@
-.Tooltip {
- position: fixed;
-}
-
-.TooltipSection,
-.TooltipWarningSection,
-.SingleLineTextSection {
- display: block;
- border-radius: 0.125rem;
- padding: 0.25rem;
- user-select: none;
- pointer-events: none;
- background-color: var(--color-tooltip-background);
- box-shadow: 1px 1px 2px var(--color-shadow);
- color: var(--color-tooltip-text);
- font-size: 11px;
-}
-.TooltipWarningSection {
- margin-top: 0.25rem;
- background-color: var(--color-warning-background);
-}
-.TooltipSection,
-.TooltipWarningSection {
- max-width: 300px;
-}
-.SingleLineTextSection {
- white-space: nowrap;
-}
-
-.Divider {
- height: 1px;
- background-color: #aaa;
- margin: 0.25rem 0;
-}
-
-.DetailsGrid {
- display: grid;
- padding-top: 5px;
- grid-gap: 2px 5px;
- grid-template-columns: min-content auto;
-}
-
-.DetailsGridLabel {
- color: var(--color-dim);
- text-align: right;
- white-space: nowrap;
-}
-
-.DetailsGridLongValue {
- word-break: break-all;
- max-height: 50vh;
- overflow: hidden;
-}
-
-.FlamechartStackFrameName {
- word-break: break-word;
-}
-
-.ComponentName {
- font-weight: bold;
- word-break: break-word;
- margin-right: 0.25rem;
-}
-
-.ReactMeasureLabel {
-}
-
-.UserTimingLabel {
- word-break: break-word;
-}
-
-.NativeEventName {
- font-weight: bold;
- word-break: break-word;
- margin-right: 0.25rem;
-}
-
-.WarningText {
- color: var(--color-warning-text-color);
-}
-
-.Image {
- border: 1px solid var(--color-border);
-}
-
-.DimText {
- color: var(--color-dim);
-}
\ No newline at end of file
diff --git a/packages/react-devtools-timeline/src/EventTooltip.js b/packages/react-devtools-timeline/src/EventTooltip.js
deleted file mode 100644
index ab0a409d7ceb..000000000000
--- a/packages/react-devtools-timeline/src/EventTooltip.js
+++ /dev/null
@@ -1,513 +0,0 @@
-/**
- * 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
- */
-
-import type {Point} from './view-base';
-import type {
- FlamechartStackFrame,
- NativeEvent,
- NetworkMeasure,
- ReactComponentMeasure,
- ReactEventInfo,
- ReactMeasure,
- ReactMeasureType,
- SchedulingEvent,
- Snapshot,
- SuspenseEvent,
- ThrownError,
- TimelineData,
- UserTimingMark,
-} from './types';
-
-import * as React from 'react';
-import {
- formatDuration,
- formatTimestamp,
- trimString,
- getSchedulingEventLabel,
-} from './utils/formatting';
-import {getBatchRange} from './utils/getBatchRange';
-import useSmartTooltip from './utils/useSmartTooltip';
-import styles from './EventTooltip.css';
-
-const MAX_TOOLTIP_TEXT_LENGTH = 60;
-
-type Props = {
- canvasRef: {current: HTMLCanvasElement | null},
- data: TimelineData,
- height: number,
- hoveredEvent: ReactEventInfo | null,
- origin: Point,
- width: number,
-};
-
-function getReactMeasureLabel(type: ReactMeasureType): string | null {
- switch (type) {
- case 'commit':
- return 'react commit';
- case 'render-idle':
- return 'react idle';
- case 'render':
- return 'react render';
- case 'layout-effects':
- return 'react layout effects';
- case 'passive-effects':
- return 'react passive effects';
- default:
- return null;
- }
-}
-
-export default function EventTooltip({
- canvasRef,
- data,
- height,
- hoveredEvent,
- origin,
- width,
-}: Props): React.Node {
- const ref = useSmartTooltip({
- canvasRef,
- mouseX: origin.x,
- mouseY: origin.y,
- });
-
- if (hoveredEvent === null) {
- return null;
- }
-
- const {
- componentMeasure,
- flamechartStackFrame,
- measure,
- nativeEvent,
- networkMeasure,
- schedulingEvent,
- snapshot,
- suspenseEvent,
- thrownError,
- userTimingMark,
- } = hoveredEvent;
-
- let content = null;
- if (componentMeasure !== null) {
- content = (
-
- );
- } else if (nativeEvent !== null) {
- content = ;
- } else if (networkMeasure !== null) {
- content = ;
- } else if (schedulingEvent !== null) {
- content = (
-
- );
- } else if (snapshot !== null) {
- content = (
-
- );
- } else if (suspenseEvent !== null) {
- content = ;
- } else if (measure !== null) {
- content = ;
- } else if (flamechartStackFrame !== null) {
- content = ;
- } else if (userTimingMark !== null) {
- content = ;
- } else if (thrownError !== null) {
- content = ;
- }
-
- if (content !== null) {
- return (
-
- {content}
-
- );
- } else {
- return null;
- }
-}
-
-const TooltipReactComponentMeasure = ({
- componentMeasure,
-}: {
- componentMeasure: ReactComponentMeasure,
-}) => {
- const {componentName, duration, timestamp, type, warning} = componentMeasure;
-
- let label = componentName;
- switch (type) {
- case 'render':
- label += ' rendered';
- break;
- case 'layout-effect-mount':
- label += ' mounted layout effect';
- break;
- case 'layout-effect-unmount':
- label += ' unmounted layout effect';
- break;
- case 'passive-effect-mount':
- label += ' mounted passive effect';
- break;
- case 'passive-effect-unmount':
- label += ' unmounted passive effect';
- break;
- }
-
- return (
- <>
-
- {trimString(label, 768)}
-
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
-
Duration:
-
{formatDuration(duration)}
-
-
- {warning !== null && (
-
- )}
- >
- );
-};
-
-const TooltipFlamechartNode = ({
- stackFrame,
-}: {
- stackFrame: FlamechartStackFrame,
-}) => {
- const {name, timestamp, duration, locationLine, locationColumn} = stackFrame;
- return (
-
-
{name}
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
-
Duration:
-
{formatDuration(duration)}
- {(locationLine !== undefined || locationColumn !== undefined) && (
- <>
-
Location:
-
- line {locationLine}, column {locationColumn}
-
- >
- )}
-
-
- );
-};
-
-const TooltipNativeEvent = ({nativeEvent}: {nativeEvent: NativeEvent}) => {
- const {duration, timestamp, type, warning} = nativeEvent;
-
- return (
- <>
-
-
{trimString(type, 768)}
- event
-
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
-
Duration:
-
{formatDuration(duration)}
-
-
- {warning !== null && (
-
- )}
- >
- );
-};
-
-const TooltipNetworkMeasure = ({
- networkMeasure,
-}: {
- networkMeasure: NetworkMeasure,
-}) => {
- const {
- finishTimestamp,
- lastReceivedDataTimestamp,
- priority,
- sendRequestTimestamp,
- url,
- } = networkMeasure;
-
- let urlToDisplay = url;
- if (urlToDisplay.length > MAX_TOOLTIP_TEXT_LENGTH) {
- const half = Math.floor(MAX_TOOLTIP_TEXT_LENGTH / 2);
- urlToDisplay = url.slice(0, half) + '…' + url.slice(url.length - half);
- }
-
- const timestampBegin = sendRequestTimestamp;
- const timestampEnd = finishTimestamp || lastReceivedDataTimestamp;
- const duration =
- timestampEnd > 0
- ? formatDuration(finishTimestamp - timestampBegin)
- : '(incomplete)';
-
- return (
-
- {duration} {priority} {' '}
- {urlToDisplay}
-
- );
-};
-
-const TooltipSchedulingEvent = ({
- data,
- schedulingEvent,
-}: {
- data: TimelineData,
- schedulingEvent: SchedulingEvent,
-}) => {
- const label = getSchedulingEventLabel(schedulingEvent);
- if (!label) {
- if (__DEV__) {
- console.warn(
- 'Unexpected schedulingEvent type "%s"',
- schedulingEvent.type,
- );
- }
- return null;
- }
-
- let laneLabels = null;
- let lanes = null;
- switch (schedulingEvent.type) {
- case 'schedule-render':
- case 'schedule-state-update':
- case 'schedule-force-update':
- lanes = schedulingEvent.lanes;
- laneLabels = lanes.map(
- lane => data.laneToLabelMap.get(lane) as any as string,
- );
- break;
- }
-
- const {componentName, timestamp, warning} = schedulingEvent;
-
- return (
- <>
-
- {componentName && (
-
- {trimString(componentName, 100)}
-
- )}
- {label}
-
-
- {laneLabels !== null && lanes !== null && (
- <>
-
Lanes:
-
- {laneLabels.join(', ')} ({lanes.join(', ')})
-
- >
- )}
-
Timestamp:
-
{formatTimestamp(timestamp)}
-
-
- {warning !== null && (
-
- )}
- >
- );
-};
-
-const TooltipSnapshot = ({
- height,
- snapshot,
- width,
-}: {
- height: number,
- snapshot: Snapshot,
- width: number,
-}) => {
- const aspectRatio = snapshot.width / snapshot.height;
-
- // Zoomed in view should not be any bigger than the DevTools viewport.
- let safeWidth = snapshot.width;
- let safeHeight = snapshot.height;
- if (safeWidth > width) {
- safeWidth = width;
- safeHeight = safeWidth / aspectRatio;
- }
- if (safeHeight > height) {
- safeHeight = height;
- safeWidth = safeHeight * aspectRatio;
- }
-
- return (
-
- );
-};
-
-const TooltipSuspenseEvent = ({
- suspenseEvent,
-}: {
- suspenseEvent: SuspenseEvent,
-}) => {
- const {
- componentName,
- duration,
- phase,
- promiseName,
- resolution,
- timestamp,
- warning,
- } = suspenseEvent;
-
- let label = 'suspended';
- if (phase !== null) {
- label += ` during ${phase}`;
- }
-
- return (
- <>
-
- {componentName && (
-
- {trimString(componentName, 100)}
-
- )}
- {label}
-
-
- {promiseName !== null && (
- <>
-
Resource:
-
{promiseName}
- >
- )}
-
Status:
-
{resolution}
-
Timestamp:
-
{formatTimestamp(timestamp)}
- {duration !== null && (
- <>
-
Duration:
-
{formatDuration(duration)}
- >
- )}
-
-
- {warning !== null && (
-
- )}
- >
- );
-};
-
-const TooltipReactMeasure = ({
- data,
- measure,
-}: {
- data: TimelineData,
- measure: ReactMeasure,
-}) => {
- const label = getReactMeasureLabel(measure.type);
- if (!label) {
- if (__DEV__) {
- console.warn('Unexpected measure type "%s"', measure.type);
- }
- return null;
- }
-
- const {batchUID, duration, timestamp, lanes} = measure;
- const [startTime, stopTime] = getBatchRange(batchUID, data);
-
- const laneLabels = lanes.map(
- lane => data.laneToLabelMap.get(lane) as any as string,
- );
-
- return (
-
-
{label}
-
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
- {measure.type !== 'render-idle' && (
- <>
-
Duration:
-
{formatDuration(duration)}
- >
- )}
-
Batch duration:
-
{formatDuration(stopTime - startTime)}
-
- Lane{lanes.length === 1 ? '' : 's'}:
-
-
- {laneLabels.length > 0
- ? `${laneLabels.join(', ')} (${lanes.join(', ')})`
- : lanes.join(', ')}
-
-
-
- );
-};
-
-const TooltipUserTimingMark = ({mark}: {mark: UserTimingMark}) => {
- const {name, timestamp} = mark;
- return (
-
-
{name}
-
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
-
-
- );
-};
-
-const TooltipThrownError = ({thrownError}: {thrownError: ThrownError}) => {
- const {componentName, message, phase, timestamp} = thrownError;
- const label = `threw an error during ${phase}`;
- return (
-
- {componentName && (
-
- {trimString(componentName, 100)}
-
- )}
-
{label}
-
-
-
Timestamp:
-
{formatTimestamp(timestamp)}
- {message !== '' && (
- <>
-
Error:
-
{message}
- >
- )}
-
-
- );
-};
diff --git a/packages/react-devtools-timeline/src/Timeline.css b/packages/react-devtools-timeline/src/Timeline.css
deleted file mode 100644
index 977516fdb25f..000000000000
--- a/packages/react-devtools-timeline/src/Timeline.css
+++ /dev/null
@@ -1,34 +0,0 @@
-.Content {
- width: 100%;
- position: relative;
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-}
-
-
-.ErrorMessage {
- margin: 0.5rem 0;
- color: var(--color-dim);
- font-family: var(--font-family-monospace);
- font-size: var(--font-size-monospace-normal);
-}
-
-.Row {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- flex-flow: wrap;
-}
-
-.EmptyStateContainer {
- text-align: center;
-}
-
-.Header {
- font-size: var(--font-size-sans-large);
- margin-bottom: 0.5rem;
-}
diff --git a/packages/react-devtools-timeline/src/Timeline.js b/packages/react-devtools-timeline/src/Timeline.js
deleted file mode 100644
index 5c427bd9b74c..000000000000
--- a/packages/react-devtools-timeline/src/Timeline.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/**
- * 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
- */
-
-import type {ViewState} from './types';
-
-import * as React from 'react';
-import {
- Suspense,
- useContext,
- useDeferredValue,
- useLayoutEffect,
- useRef,
- useState,
-} from 'react';
-import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
-import {ProfilerContext} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext';
-import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
-import RecordingInProgress from 'react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress';
-import {updateColorsToMatchTheme} from './content-views/constants';
-import {TimelineContext} from './TimelineContext';
-import CanvasPage from './CanvasPage';
-import {importFile} from './timelineCache';
-import TimelineSearchInput from './TimelineSearchInput';
-import TimelineNotSupported from './TimelineNotSupported';
-import {TimelineSearchContextController} from './TimelineSearchContext';
-
-import styles from './Timeline.css';
-
-export function Timeline(_: {}): React.Node {
- const {
- file,
- inMemoryTimelineData,
- isPerformanceTracksSupported,
- isTimelineSupported,
- setFile,
- viewState,
- } = useContext(TimelineContext);
- const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
-
- const ref = useRef(null);
-
- // HACK: Canvas rendering uses an imperative API,
- // but DevTools colors are stored in CSS variables (see root.css and SettingsContext).
- // When the theme changes, we need to trigger update the imperative colors and re-draw the Canvas.
- const {theme} = useContext(SettingsContext);
- // HACK: SettingsContext also uses a useLayoutEffect to update styles;
- // make sure the theme context in SettingsContext updates before this code.
- const deferredTheme = useDeferredValue(theme);
- // HACK: Schedule a re-render of the Canvas once colors have been updated.
- // The easiest way to guarangee this happens is to recreate the inner Canvas component.
- const [key, setKey] = useState(theme);
- useLayoutEffect(() => {
- const pollForTheme = () => {
- if (updateColorsToMatchTheme(ref.current as any as HTMLDivElement)) {
- clearInterval(intervalID);
- setKey(deferredTheme);
- }
- };
-
- const intervalID = setInterval(pollForTheme, 50);
-
- return () => {
- clearInterval(intervalID);
- };
- }, [deferredTheme]);
-
- let content = null;
- if (isProfiling) {
- content = ;
- } else if (inMemoryTimelineData && inMemoryTimelineData.length > 0) {
- // TODO (timeline) Support multiple renderers.
- const timelineData = inMemoryTimelineData[0];
-
- content = (
-
-
-
-
- );
- } else if (file) {
- content = (
- }>
-
-
- );
- } else if (didRecordCommits) {
- content = ;
- } else if (isTimelineSupported) {
- content = ;
- } else {
- content = (
-
- );
- }
-
- return (
-
- {content}
-
- );
-}
-
-const ProcessingData = () => (
-
-
Processing data...
-
This should only take a minute.
-
-);
-
-// $FlowFixMe[missing-local-annot]
-const CouldNotLoadProfile = ({error, onFileSelect}) => (
-
-
Could not load profile
- {error.message && (
-
- )}
-
- Try importing another Chrome performance profile.
-
-
-);
-
-const NoTimelineData = () => (
-
-
- This current profile does not contain timeline data.
-
-
-);
-
-const FileLoader = ({
- file,
- onFileSelect,
- viewState,
-}: {
- file: File | null,
- onFileSelect: (file: File) => void,
- viewState: ViewState,
-}) => {
- if (file === null) {
- return null;
- }
-
- const dataOrError = importFile(file);
- if (dataOrError instanceof Error) {
- return (
-
- );
- }
-
- return (
-
-
-
-
- );
-};
diff --git a/packages/react-devtools-timeline/src/TimelineContext.js b/packages/react-devtools-timeline/src/TimelineContext.js
deleted file mode 100644
index a6f52f5fa9d3..000000000000
--- a/packages/react-devtools-timeline/src/TimelineContext.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * 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
- */
-
-import type {ReactContext, RefObject} from 'shared/ReactTypes';
-
-import * as React from 'react';
-import {
- createContext,
- useContext,
- useMemo,
- useRef,
- useState,
- useSyncExternalStore,
-} from 'react';
-import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
-
-import type {
- HorizontalScrollStateChangeCallback,
- TimelineData,
- SearchRegExpStateChangeCallback,
- ViewState,
- ReactEventInfo,
-} from './types';
-
-export type Context = {
- file: File | null,
- inMemoryTimelineData: Array | null,
- isPerformanceTracksSupported: boolean,
- isTimelineSupported: boolean,
- searchInputContainerRef: RefObject,
- setFile: (file: File | null) => void,
- viewState: ViewState,
- selectEvent: ReactEventInfo => void,
- selectedEvent: ReactEventInfo,
-};
-
-const TimelineContext: ReactContext = createContext(
- null as any as Context,
-);
-TimelineContext.displayName = 'TimelineContext';
-
-type Props = {
- children: React$Node,
-};
-
-function TimelineContextController({children}: Props): React.Node {
- const searchInputContainerRef = useRef(null);
- const [file, setFile] = useState(null);
-
- const store = useContext(StoreContext);
-
- const isTimelineSupported = useSyncExternalStore(
- function subscribe(callback) {
- store.addListener('rootSupportsTimelineProfiling', callback);
- return function unsubscribe() {
- store.removeListener('rootSupportsTimelineProfiling', callback);
- };
- },
- function getState() {
- return store.rootSupportsTimelineProfiling;
- },
- );
-
- const isPerformanceTracksSupported = useSyncExternalStore(
- function subscribe(callback) {
- store.addListener('rootSupportsPerformanceTracks', callback);
- return function unsubscribe() {
- store.removeListener('rootSupportsPerformanceTracks', callback);
- };
- },
- function getState() {
- return store.rootSupportsPerformanceTracks;
- },
- );
-
- const inMemoryTimelineData = useSyncExternalStore | null>(
- function subscribe(callback) {
- store.profilerStore.addListener('isProcessingData', callback);
- store.profilerStore.addListener('profilingData', callback);
- return function unsubscribe() {
- store.profilerStore.removeListener('isProcessingData', callback);
- store.profilerStore.removeListener('profilingData', callback);
- };
- },
- function getState() {
- return store.profilerStore.profilingData?.timelineData || null;
- },
- );
-
- // Recreate view state any time new profiling data is imported.
- const viewState = useMemo(() => {
- const horizontalScrollStateChangeCallbacks: Set =
- new Set();
- const searchRegExpStateChangeCallbacks: Set =
- new Set();
-
- const horizontalScrollState = {
- offset: 0,
- length: 0,
- };
-
- const state: ViewState = {
- horizontalScrollState,
- onHorizontalScrollStateChange: callback => {
- horizontalScrollStateChangeCallbacks.add(callback);
- },
- onSearchRegExpStateChange: callback => {
- searchRegExpStateChangeCallbacks.add(callback);
- },
- searchRegExp: null,
- updateHorizontalScrollState: scrollState => {
- if (
- horizontalScrollState.offset === scrollState.offset &&
- horizontalScrollState.length === scrollState.length
- ) {
- return;
- }
-
- horizontalScrollState.offset = scrollState.offset;
- horizontalScrollState.length = scrollState.length;
-
- horizontalScrollStateChangeCallbacks.forEach(callback => {
- callback(scrollState);
- });
- },
- updateSearchRegExpState: (searchRegExp: RegExp | null) => {
- state.searchRegExp = searchRegExp;
-
- searchRegExpStateChangeCallbacks.forEach(callback => {
- callback(searchRegExp);
- });
- },
- viewToMutableViewStateMap: new Map(),
- };
-
- return state;
- }, [file]);
-
- const [selectedEvent, selectEvent] = useState(null);
-
- const value = useMemo(
- () => ({
- file,
- inMemoryTimelineData,
- isPerformanceTracksSupported,
- isTimelineSupported,
- searchInputContainerRef,
- setFile,
- viewState,
- selectEvent,
- selectedEvent,
- }),
- [
- file,
- inMemoryTimelineData,
- isPerformanceTracksSupported,
- isTimelineSupported,
- setFile,
- viewState,
- selectEvent,
- selectedEvent,
- ],
- );
-
- return (
-
- {children}
-
- );
-}
-
-export {TimelineContext, TimelineContextController};
diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.css b/packages/react-devtools-timeline/src/TimelineNotSupported.css
deleted file mode 100644
index 971723520a45..000000000000
--- a/packages/react-devtools-timeline/src/TimelineNotSupported.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.Column {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 0 1rem;
-}
-
-.Header {
- font-size: var(--font-size-sans-large);
- margin-bottom: 0.5rem;
-}
-
-.Paragraph {
- text-align: center;
- margin: 0;
-}
-
-.Link {
- color: var(--color-link);
-}
-
-.LearnMoreRow {
- margin-top: 1rem;
- color: var(--color-dim);
- font-size: var(--font-size-sans-small);
-}
-
-.Code {
- color: var(--color-bridge-version-number);
-}
-
-.MetaGKRow {
- background: var(--color-background-hover);
- padding: 0.25rem 0.5rem;
- border-radius: 0.25rem;
- margin-top: 1rem;
-}
\ No newline at end of file
diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.js b/packages/react-devtools-timeline/src/TimelineNotSupported.js
deleted file mode 100644
index 7eac79da94f5..000000000000
--- a/packages/react-devtools-timeline/src/TimelineNotSupported.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * 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
- */
-
-import * as React from 'react';
-import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
-
-import styles from './TimelineNotSupported.css';
-
-type Props = {
- isPerformanceTracksSupported: boolean,
-};
-
-function PerformanceTracksSupported() {
- return (
- <>
-
-
- Please use{' '}
-
- React Performance tracks
- {' '}
- instead of the Timeline profiler.
-
-
- >
- );
-}
-
-function UnknownUnsupportedReason() {
- return (
- <>
-
- Timeline profiler requires a development or profiling build of{' '}
- react-dom@{'>='}18.
-
-
- In React 19.2 and above{' '}
-
- React Performance tracks
- {' '}
- can be used instead.
-
-
- Click{' '}
-
- here
- {' '}
- to learn more about profiling.
-
- >
- );
-}
-
-export default function TimelineNotSupported({
- isPerformanceTracksSupported,
-}: Props): React.Node {
- return (
-
-
Timeline profiling not supported.
-
- {isPerformanceTracksSupported ? (
-
- ) : (
-
- )}
-
- {isInternalFacebookBuild && (
-
- )}
-
- );
-}
diff --git a/packages/react-devtools-timeline/src/TimelineSearchContext.js b/packages/react-devtools-timeline/src/TimelineSearchContext.js
deleted file mode 100644
index 2a38dbcfd8c3..000000000000
--- a/packages/react-devtools-timeline/src/TimelineSearchContext.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * 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
- */
-
-import type {ReactContext} from 'shared/ReactTypes';
-
-import * as React from 'react';
-import {createContext, useMemo, useReducer} from 'react';
-
-import type {ReactComponentMeasure, TimelineData, ViewState} from './types';
-
-type State = {
- profilerData: TimelineData,
- searchIndex: number,
- searchRegExp: RegExp | null,
- searchResults: Array,
- searchText: string,
-};
-
-type ACTION_GO_TO_NEXT_SEARCH_RESULT = {
- type: 'GO_TO_NEXT_SEARCH_RESULT',
-};
-type ACTION_GO_TO_PREVIOUS_SEARCH_RESULT = {
- type: 'GO_TO_PREVIOUS_SEARCH_RESULT',
-};
-type ACTION_SET_SEARCH_TEXT = {
- type: 'SET_SEARCH_TEXT',
- payload: string,
-};
-
-type Action =
- | ACTION_GO_TO_NEXT_SEARCH_RESULT
- | ACTION_GO_TO_PREVIOUS_SEARCH_RESULT
- | ACTION_SET_SEARCH_TEXT;
-
-type Dispatch = (action: Action) => void;
-
-const EMPTY_ARRAY: Array = [];
-
-function reducer(state: State, action: Action): State {
- let {searchIndex, searchRegExp, searchResults, searchText} = state;
-
- switch (action.type) {
- case 'GO_TO_NEXT_SEARCH_RESULT':
- if (searchResults.length > 0) {
- if (searchIndex === -1 || searchIndex + 1 === searchResults.length) {
- searchIndex = 0;
- } else {
- searchIndex++;
- }
- }
- break;
- case 'GO_TO_PREVIOUS_SEARCH_RESULT':
- if (searchResults.length > 0) {
- if (searchIndex === -1 || searchIndex === 0) {
- searchIndex = searchResults.length - 1;
- } else {
- searchIndex--;
- }
- }
- break;
- case 'SET_SEARCH_TEXT':
- searchText = action.payload;
- searchRegExp = null;
- searchResults = [];
-
- if (searchText !== '') {
- const safeSearchText = searchText.replace(
- /[.*+?^${}()|[\]\\]/g,
- '\\$&',
- );
- searchRegExp = new RegExp(`^${safeSearchText}`, 'i');
-
- // If something is zoomed in on already, and the new search still contains it,
- // don't change the selection (even if overall search results set changes).
- let prevSelectedMeasure = null;
- if (searchIndex >= 0 && searchResults.length > searchIndex) {
- prevSelectedMeasure = searchResults[searchIndex];
- }
-
- const componentMeasures = state.profilerData.componentMeasures;
-
- let prevSelectedMeasureIndex = -1;
-
- for (let i = 0; i < componentMeasures.length; i++) {
- const componentMeasure = componentMeasures[i];
- if (componentMeasure.componentName.match(searchRegExp)) {
- searchResults.push(componentMeasure);
-
- if (componentMeasure === prevSelectedMeasure) {
- prevSelectedMeasureIndex = searchResults.length - 1;
- }
- }
- }
-
- searchIndex =
- prevSelectedMeasureIndex >= 0 ? prevSelectedMeasureIndex : 0;
- }
- break;
- }
-
- return {
- profilerData: state.profilerData,
- searchIndex,
- searchRegExp,
- searchResults,
- searchText,
- };
-}
-
-export type Context = {
- profilerData: TimelineData,
-
- // Search state
- dispatch: Dispatch,
- searchIndex: number,
- searchRegExp: null,
- searchResults: Array,
- searchText: string,
-};
-
-const TimelineSearchContext: ReactContext = createContext(
- null as any as Context,
-);
-TimelineSearchContext.displayName = 'TimelineSearchContext';
-
-type Props = {
- children: React$Node,
- profilerData: TimelineData,
- viewState: ViewState,
-};
-
-function TimelineSearchContextController({
- children,
- profilerData,
- viewState,
-}: Props): React.Node {
- const [state, dispatch] = useReducer(reducer, {
- profilerData,
- searchIndex: -1,
- searchRegExp: null,
- searchResults: EMPTY_ARRAY,
- searchText: '',
- });
-
- const value = useMemo(
- () => ({
- ...state,
- dispatch,
- }),
- [state],
- );
-
- return (
-
- {children}
-
- );
-}
-
-export {TimelineSearchContext, TimelineSearchContextController};
diff --git a/packages/react-devtools-timeline/src/TimelineSearchInput.js b/packages/react-devtools-timeline/src/TimelineSearchInput.js
deleted file mode 100644
index c448bded2a3a..000000000000
--- a/packages/react-devtools-timeline/src/TimelineSearchInput.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * 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
- */
-
-import * as React from 'react';
-import {useContext} from 'react';
-import {createPortal} from 'react-dom';
-import SearchInput from 'react-devtools-shared/src/devtools/views/SearchInput';
-import {TimelineContext} from './TimelineContext';
-import {TimelineSearchContext} from './TimelineSearchContext';
-
-type Props = {};
-
-export default function TimelineSearchInput(props: Props): React.Node {
- const {searchInputContainerRef} = useContext(TimelineContext);
- const {dispatch, searchIndex, searchResults, searchText} = useContext(
- TimelineSearchContext,
- );
-
- if (searchInputContainerRef.current === null) {
- return null;
- }
-
- const search = (text: string) =>
- dispatch({type: 'SET_SEARCH_TEXT', payload: text});
- const goToNextResult = () => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'});
- const goToPreviousResult = () =>
- dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'});
-
- return createPortal(
- ,
- searchInputContainerRef.current,
- );
-}
diff --git a/packages/react-devtools-timeline/src/constants.js b/packages/react-devtools-timeline/src/constants.js
deleted file mode 100644
index 92c75d8e9f9c..000000000000
--- a/packages/react-devtools-timeline/src/constants.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * 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
- */
-
-export {
- COMFORTABLE_LINE_HEIGHT,
- COMPACT_LINE_HEIGHT,
-} from 'react-devtools-shared/src/devtools/constants.js';
-
-export const REACT_TOTAL_NUM_LANES = 31;
-
-// Increment this number any time a backwards breaking change is made to the profiler metadata.
-export const SCHEDULING_PROFILER_VERSION = 1;
-
-export const SNAPSHOT_MAX_HEIGHT = 60;
diff --git a/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js b/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js
deleted file mode 100644
index dc1c68045e32..000000000000
--- a/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js
+++ /dev/null
@@ -1,301 +0,0 @@
-/**
- * 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
- */
-
-import type {ReactComponentMeasure, TimelineData, ViewState} from '../types';
-import type {
- Interaction,
- IntrinsicSize,
- MouseMoveInteraction,
- Rect,
- ViewRefs,
-} from '../view-base';
-
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
-} from './utils/positioning';
-import {drawText} from './utils/text';
-import {formatDuration} from '../utils/formatting';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {BORDER_SIZE, COLORS, NATIVE_EVENT_HEIGHT} from './constants';
-
-const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
-
-export class ComponentMeasuresView extends View {
- _cachedSearchMatches: Map;
- _cachedSearchRegExp: RegExp | null = null;
- _hoveredComponentMeasure: ReactComponentMeasure | null = null;
- _intrinsicSize: IntrinsicSize;
- _profilerData: TimelineData;
- _viewState: ViewState;
-
- onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
-
- constructor(
- surface: Surface,
- frame: Rect,
- profilerData: TimelineData,
- viewState: ViewState,
- ) {
- super(surface, frame);
-
- this._profilerData = profilerData;
- this._viewState = viewState;
-
- this._cachedSearchMatches = new Map();
- this._cachedSearchRegExp = null;
-
- viewState.onSearchRegExpStateChange(() => {
- this.setNeedsDisplay();
- });
-
- this._intrinsicSize = {
- width: profilerData.duration,
- height: ROW_WITH_BORDER_HEIGHT,
- };
- }
-
- desiredSize(): IntrinsicSize {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(hoveredEvent: ReactComponentMeasure | null) {
- if (this._hoveredComponentMeasure === hoveredEvent) {
- return;
- }
- this._hoveredComponentMeasure = hoveredEvent;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `ReactComponentMeasure` as a box/span with text inside of it.
- */
- _drawSingleReactComponentMeasure(
- context: CanvasRenderingContext2D,
- rect: Rect,
- componentMeasure: ReactComponentMeasure,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ): boolean {
- const {frame} = this;
- const {componentName, duration, timestamp, type, warning} =
- componentMeasure;
-
- const xStart = timestampToPosition(timestamp, scaleFactor, frame);
- const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
- const componentMeasureRect: Rect = {
- origin: {
- x: xStart,
- y: frame.origin.y,
- },
- size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
- };
- if (!rectIntersectsRect(componentMeasureRect, rect)) {
- return false; // Not in view
- }
-
- const width = durationToWidth(duration, scaleFactor);
- if (width < 1) {
- return false; // Too small to render at this zoom level
- }
-
- let textFillStyle = null as any as string;
- let typeLabel = null as any as string;
-
- const drawableRect = intersectionOfRects(componentMeasureRect, rect);
- context.beginPath();
- if (warning !== null) {
- context.fillStyle = showHoverHighlight
- ? COLORS.WARNING_BACKGROUND_HOVER
- : COLORS.WARNING_BACKGROUND;
- } else {
- switch (type) {
- case 'render':
- context.fillStyle = showHoverHighlight
- ? COLORS.REACT_RENDER_HOVER
- : COLORS.REACT_RENDER;
- textFillStyle = COLORS.REACT_RENDER_TEXT;
- typeLabel = 'rendered';
- break;
- case 'layout-effect-mount':
- context.fillStyle = showHoverHighlight
- ? COLORS.REACT_LAYOUT_EFFECTS_HOVER
- : COLORS.REACT_LAYOUT_EFFECTS;
- textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
- typeLabel = 'mounted layout effect';
- break;
- case 'layout-effect-unmount':
- context.fillStyle = showHoverHighlight
- ? COLORS.REACT_LAYOUT_EFFECTS_HOVER
- : COLORS.REACT_LAYOUT_EFFECTS;
- textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
- typeLabel = 'unmounted layout effect';
- break;
- case 'passive-effect-mount':
- context.fillStyle = showHoverHighlight
- ? COLORS.REACT_PASSIVE_EFFECTS_HOVER
- : COLORS.REACT_PASSIVE_EFFECTS;
- textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
- typeLabel = 'mounted passive effect';
- break;
- case 'passive-effect-unmount':
- context.fillStyle = showHoverHighlight
- ? COLORS.REACT_PASSIVE_EFFECTS_HOVER
- : COLORS.REACT_PASSIVE_EFFECTS;
- textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
- typeLabel = 'unmounted passive effect';
- break;
- }
- }
-
- let isMatch = false;
- const cachedSearchRegExp = this._cachedSearchRegExp;
- if (cachedSearchRegExp !== null) {
- const cachedSearchMatches = this._cachedSearchMatches;
- const cachedValue = cachedSearchMatches.get(componentName);
- if (cachedValue != null) {
- isMatch = cachedValue;
- } else {
- isMatch = componentName.match(cachedSearchRegExp) !== null;
- cachedSearchMatches.set(componentName, isMatch);
- }
- }
-
- if (isMatch) {
- context.fillStyle = COLORS.SEARCH_RESULT_FILL;
- }
-
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- const label = `${componentName} ${typeLabel} - ${formatDuration(duration)}`;
-
- drawText(label, context, componentMeasureRect, drawableRect, {
- fillStyle: textFillStyle,
- });
-
- return true;
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {componentMeasures},
- _hoveredComponentMeasure,
- visibleArea,
- } = this;
-
- const searchRegExp = this._viewState.searchRegExp;
- if (this._cachedSearchRegExp !== searchRegExp) {
- this._cachedSearchMatches = new Map();
- this._cachedSearchRegExp = searchRegExp;
- }
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw events
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- let didDrawMeasure = false;
- componentMeasures.forEach(componentMeasure => {
- didDrawMeasure =
- this._drawSingleReactComponentMeasure(
- context,
- visibleArea,
- componentMeasure,
- scaleFactor,
- componentMeasure === _hoveredComponentMeasure,
- ) || didDrawMeasure;
- });
-
- if (!didDrawMeasure) {
- drawText(
- '(zoom or pan to see React components)',
- context,
- visibleArea,
- visibleArea,
- {fillStyle: COLORS.TEXT_DIM_COLOR, textAlign: 'center'},
- );
- }
-
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y + ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
- visibleArea.size.width,
- BORDER_SIZE,
- );
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, _intrinsicSize, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
-
- const componentMeasures = this._profilerData.componentMeasures;
- for (let index = componentMeasures.length - 1; index >= 0; index--) {
- const componentMeasure = componentMeasures[index];
- const {duration, timestamp} = componentMeasure;
-
- if (
- hoverTimestamp >= timestamp &&
- hoverTimestamp <= timestamp + duration
- ) {
- this.currentCursor = 'context-menu';
- viewRefs.hoveredView = this;
- onHover(componentMeasure);
- return;
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/FlamechartView.js b/packages/react-devtools-timeline/src/content-views/FlamechartView.js
deleted file mode 100644
index 33d505ca7d5a..000000000000
--- a/packages/react-devtools-timeline/src/content-views/FlamechartView.js
+++ /dev/null
@@ -1,383 +0,0 @@
-/**
- * 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
- */
-
-import type {
- Flamechart,
- FlamechartStackFrame,
- FlamechartStackLayer,
- InternalModuleSourceToRanges,
-} from '../types';
-import type {
- Interaction,
- MouseMoveInteraction,
- Rect,
- Size,
- ViewRefs,
-} from '../view-base';
-
-import {
- BackgroundColorView,
- Surface,
- View,
- layeredLayout,
- rectContainsPoint,
- intersectionOfRects,
- rectIntersectsRect,
- verticallyStackedLayout,
-} from '../view-base';
-import {isInternalModule} from './utils/moduleFilters';
-import {
- durationToWidth,
- positioningScaleFactor,
- timestampToPosition,
-} from './utils/positioning';
-import {drawText} from './utils/text';
-import {
- COLORS,
- FLAMECHART_FRAME_HEIGHT,
- COLOR_HOVER_DIM_DELTA,
- BORDER_SIZE,
-} from './constants';
-import {ColorGenerator, dimmedColor, hslaColorToString} from './utils/colors';
-
-// Source: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/timeline/TimelineUIUtils.js;l=2109;drc=fb32e928d79707a693351b806b8710b2f6b7d399
-const colorGenerator = new ColorGenerator(
- {min: 30, max: 330},
- {min: 50, max: 80, count: 3},
- 85,
-);
-colorGenerator.setColorForID('', {h: 43.6, s: 45.8, l: 90.6, a: 100});
-
-function defaultHslaColorForStackFrame({scriptUrl}: FlamechartStackFrame) {
- return colorGenerator.colorForID(scriptUrl ?? '');
-}
-
-function defaultColorForStackFrame(stackFrame: FlamechartStackFrame): string {
- const color = defaultHslaColorForStackFrame(stackFrame);
- return hslaColorToString(color);
-}
-
-function hoverColorForStackFrame(stackFrame: FlamechartStackFrame): string {
- const color = dimmedColor(
- defaultHslaColorForStackFrame(stackFrame),
- COLOR_HOVER_DIM_DELTA,
- );
- return hslaColorToString(color);
-}
-
-class FlamechartStackLayerView extends View {
- /** Layer to display */
- _stackLayer: FlamechartStackLayer;
-
- /** A set of `stackLayer`'s frames, for efficient lookup. */
- _stackFrameSet: Set;
-
- _internalModuleSourceToRanges: InternalModuleSourceToRanges;
-
- _intrinsicSize: Size;
-
- _hoveredStackFrame: FlamechartStackFrame | null = null;
- _onHover: ((node: FlamechartStackFrame | null) => void) | null = null;
-
- constructor(
- surface: Surface,
- frame: Rect,
- stackLayer: FlamechartStackLayer,
- internalModuleSourceToRanges: InternalModuleSourceToRanges,
- duration: number,
- ) {
- super(surface, frame);
- this._stackLayer = stackLayer;
- this._stackFrameSet = new Set(stackLayer);
- this._internalModuleSourceToRanges = internalModuleSourceToRanges;
- this._intrinsicSize = {
- width: duration,
- height: FLAMECHART_FRAME_HEIGHT,
- };
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- setHoveredFlamechartStackFrame(
- hoveredStackFrame: FlamechartStackFrame | null,
- ) {
- if (this._hoveredStackFrame === hoveredStackFrame) {
- return; // We're already hovering over this frame
- }
-
- // Only care about frames displayed by this view.
- const stackFrameToSet =
- hoveredStackFrame && this._stackFrameSet.has(hoveredStackFrame)
- ? hoveredStackFrame
- : null;
- if (this._hoveredStackFrame === stackFrameToSet) {
- return; // Resulting state is unchanged
- }
- this._hoveredStackFrame = stackFrameToSet;
- this.setNeedsDisplay();
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _stackLayer,
- _hoveredStackFrame,
- _intrinsicSize,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.PRIORITY_BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
-
- for (let i = 0; i < _stackLayer.length; i++) {
- const stackFrame = _stackLayer[i];
- const {name, timestamp, duration} = stackFrame;
-
- const width = durationToWidth(duration, scaleFactor);
- if (width < 1) {
- continue; // Too small to render at this zoom level
- }
-
- const x = Math.floor(timestampToPosition(timestamp, scaleFactor, frame));
- const nodeRect: Rect = {
- origin: {x, y: frame.origin.y},
- size: {
- width: Math.floor(width - BORDER_SIZE),
- height: Math.floor(FLAMECHART_FRAME_HEIGHT - BORDER_SIZE),
- },
- };
- if (!rectIntersectsRect(nodeRect, visibleArea)) {
- continue; // Not in view
- }
-
- const showHoverHighlight = _hoveredStackFrame === _stackLayer[i];
-
- let textFillStyle;
- if (isInternalModule(this._internalModuleSourceToRanges, stackFrame)) {
- context.fillStyle = showHoverHighlight
- ? COLORS.INTERNAL_MODULE_FRAME_HOVER
- : COLORS.INTERNAL_MODULE_FRAME;
- textFillStyle = COLORS.INTERNAL_MODULE_FRAME_TEXT;
- } else {
- context.fillStyle = showHoverHighlight
- ? hoverColorForStackFrame(stackFrame)
- : defaultColorForStackFrame(stackFrame);
- textFillStyle = COLORS.TEXT_COLOR;
- }
-
- const drawableRect = intersectionOfRects(nodeRect, visibleArea);
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- drawText(name, context, nodeRect, drawableRect, {
- fillStyle: textFillStyle,
- });
- }
-
- // Render bottom border.
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + FLAMECHART_FRAME_HEIGHT - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {_stackLayer, frame, _intrinsicSize, _onHover, visibleArea} = this;
- const {location} = interaction.payload;
- if (!_onHover || !rectContainsPoint(location, visibleArea)) {
- return;
- }
-
- // Find the node being hovered over.
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- let startIndex = 0;
- let stopIndex = _stackLayer.length - 1;
- while (startIndex <= stopIndex) {
- const currentIndex = Math.floor((startIndex + stopIndex) / 2);
- const flamechartStackFrame = _stackLayer[currentIndex];
- const {timestamp, duration} = flamechartStackFrame;
-
- const x = Math.floor(timestampToPosition(timestamp, scaleFactor, frame));
- const width = durationToWidth(duration, scaleFactor);
-
- // Don't show tooltips for nodes that are too small to render at this zoom level.
- if (Math.floor(width - BORDER_SIZE) >= 1) {
- if (x <= location.x && x + width >= location.x) {
- this.currentCursor = 'context-menu';
- viewRefs.hoveredView = this;
- _onHover(flamechartStackFrame);
- return;
- }
- }
-
- if (x > location.x) {
- stopIndex = currentIndex - 1;
- } else {
- startIndex = currentIndex + 1;
- }
- }
-
- _onHover(null);
- }
-
- _didGrab: boolean = false;
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
-
-export class FlamechartView extends View {
- _flamechartRowViews: FlamechartStackLayerView[] = [];
-
- /** Container view that vertically stacks flamechart rows */
- _verticalStackView: View;
-
- _hoveredStackFrame: FlamechartStackFrame | null = null;
- _onHover: ((node: FlamechartStackFrame | null) => void) | null = null;
-
- constructor(
- surface: Surface,
- frame: Rect,
- flamechart: Flamechart,
- internalModuleSourceToRanges: InternalModuleSourceToRanges,
- duration: number,
- ) {
- super(surface, frame, layeredLayout);
- this.setDataAndUpdateSubviews(
- flamechart,
- internalModuleSourceToRanges,
- duration,
- );
- }
-
- setDataAndUpdateSubviews(
- flamechart: Flamechart,
- internalModuleSourceToRanges: InternalModuleSourceToRanges,
- duration: number,
- ) {
- const {surface, frame, _onHover, _hoveredStackFrame} = this;
-
- // Clear existing rows on data update
- if (this._verticalStackView) {
- this.removeAllSubviews();
- this._flamechartRowViews = [];
- }
-
- this._verticalStackView = new View(surface, frame, verticallyStackedLayout);
- this._flamechartRowViews = flamechart.map(stackLayer => {
- const rowView = new FlamechartStackLayerView(
- surface,
- frame,
- stackLayer,
- internalModuleSourceToRanges,
- duration,
- );
- this._verticalStackView.addSubview(rowView);
-
- // Update states
- rowView._onHover = _onHover;
- rowView.setHoveredFlamechartStackFrame(_hoveredStackFrame);
- return rowView;
- });
-
- // Add a plain background view to prevent gaps from appearing between flamechartRowViews.
- this.addSubview(new BackgroundColorView(surface, frame));
- this.addSubview(this._verticalStackView);
- }
-
- setHoveredFlamechartStackFrame(
- hoveredStackFrame: FlamechartStackFrame | null,
- ) {
- this._hoveredStackFrame = hoveredStackFrame;
- this._flamechartRowViews.forEach(rowView =>
- rowView.setHoveredFlamechartStackFrame(hoveredStackFrame),
- );
- }
-
- setOnHover(onHover: (node: FlamechartStackFrame | null) => void) {
- this._onHover = onHover;
- this._flamechartRowViews.forEach(rowView => (rowView._onHover = onHover));
- }
-
- desiredSize(): {
- height: number,
- hideScrollBarIfLessThanHeight?: number,
- maxInitialHeight?: number,
- width: number,
- } {
- // Ignore the wishes of the background color view
- const intrinsicSize = this._verticalStackView.desiredSize();
- return {
- ...intrinsicSize,
- // Collapsed by default
- maxInitialHeight: 0,
- };
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction) {
- const {_onHover, visibleArea} = this;
- if (!_onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- // Clear out any hovered flamechart stack frame
- _onHover(null);
- }
- }
-
- handleInteraction(interaction: Interaction) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/NativeEventsView.js b/packages/react-devtools-timeline/src/content-views/NativeEventsView.js
deleted file mode 100644
index 5bb88bce797b..000000000000
--- a/packages/react-devtools-timeline/src/content-views/NativeEventsView.js
+++ /dev/null
@@ -1,259 +0,0 @@
-/**
- * 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
- */
-
-import type {NativeEvent, TimelineData} from '../types';
-import type {
- Interaction,
- IntrinsicSize,
- MouseMoveInteraction,
- Rect,
- ViewRefs,
-} from '../view-base';
-
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
-} from './utils/positioning';
-import {drawText} from './utils/text';
-import {formatDuration} from '../utils/formatting';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {COLORS, NATIVE_EVENT_HEIGHT, BORDER_SIZE} from './constants';
-
-const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
-
-export class NativeEventsView extends View {
- _depthToNativeEvent: Map;
- _hoveredEvent: NativeEvent | null = null;
- _intrinsicSize: IntrinsicSize;
- _maxDepth: number = 0;
- _profilerData: TimelineData;
-
- onHover: ((event: NativeEvent | null) => void) | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
-
- this._profilerData = profilerData;
-
- this._performPreflightComputations();
- }
-
- _performPreflightComputations() {
- this._depthToNativeEvent = new Map();
-
- const {duration, nativeEvents} = this._profilerData;
-
- nativeEvents.forEach(event => {
- const depth = event.depth;
-
- this._maxDepth = Math.max(this._maxDepth, depth);
-
- if (!this._depthToNativeEvent.has(depth)) {
- this._depthToNativeEvent.set(depth, [event]);
- } else {
- // $FlowFixMe[incompatible-use] This is unnecessary.
- this._depthToNativeEvent.get(depth).push(event);
- }
- });
-
- this._intrinsicSize = {
- width: duration,
- height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
- hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
- };
- }
-
- desiredSize(): IntrinsicSize {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(hoveredEvent: NativeEvent | null) {
- if (this._hoveredEvent === hoveredEvent) {
- return;
- }
- this._hoveredEvent = hoveredEvent;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `NativeEvent` as a box/span with text inside of it.
- */
- _drawSingleNativeEvent(
- context: CanvasRenderingContext2D,
- rect: Rect,
- event: NativeEvent,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame} = this;
- const {depth, duration, timestamp, type, warning} = event;
-
- baseY += depth * ROW_WITH_BORDER_HEIGHT;
-
- const xStart = timestampToPosition(timestamp, scaleFactor, frame);
- const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
- const eventRect: Rect = {
- origin: {
- x: xStart,
- y: baseY,
- },
- size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
- };
- if (!rectIntersectsRect(eventRect, rect)) {
- return; // Not in view
- }
-
- const width = durationToWidth(duration, scaleFactor);
- if (width < 1) {
- return; // Too small to render at this zoom level
- }
-
- const drawableRect = intersectionOfRects(eventRect, rect);
- context.beginPath();
- if (warning !== null) {
- context.fillStyle = showHoverHighlight
- ? COLORS.WARNING_BACKGROUND_HOVER
- : COLORS.WARNING_BACKGROUND;
- } else {
- context.fillStyle = showHoverHighlight
- ? COLORS.NATIVE_EVENT_HOVER
- : COLORS.NATIVE_EVENT;
- }
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- const label = `${type} - ${formatDuration(duration)}`;
-
- drawText(label, context, eventRect, drawableRect);
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {nativeEvents},
- _hoveredEvent,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.PRIORITY_BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw events
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- nativeEvents.forEach(event => {
- this._drawSingleNativeEvent(
- context,
- visibleArea,
- event,
- frame.origin.y,
- scaleFactor,
- event === _hoveredEvent,
- );
- });
-
- // Render bottom borders.
- for (let i = 0; i <= this._maxDepth; i++) {
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + NATIVE_EVENT_HEIGHT,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(
- borderFrame,
- visibleArea,
- );
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, _intrinsicSize, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
-
- const adjustedCanvasMouseY = location.y - frame.origin.y;
- const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
- const nativeEventsAtDepth = this._depthToNativeEvent.get(depth);
-
- if (nativeEventsAtDepth) {
- // Find the event being hovered over.
- for (let index = nativeEventsAtDepth.length - 1; index >= 0; index--) {
- const nativeEvent = nativeEventsAtDepth[index];
- const {duration, timestamp} = nativeEvent;
-
- if (
- hoverTimestamp >= timestamp &&
- hoverTimestamp <= timestamp + duration
- ) {
- viewRefs.hoveredView = this;
- onHover(nativeEvent);
- return;
- }
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js b/packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js
deleted file mode 100644
index f2563852339b..000000000000
--- a/packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js
+++ /dev/null
@@ -1,337 +0,0 @@
-/**
- * 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
- */
-
-import type {NetworkMeasure, TimelineData} from '../types';
-import type {
- Interaction,
- IntrinsicSize,
- MouseMoveInteraction,
- Rect,
- ViewRefs,
-} from '../view-base';
-
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
-} from './utils/positioning';
-import {drawText} from './utils/text';
-import {formatDuration} from '../utils/formatting';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {BORDER_SIZE, COLORS, SUSPENSE_EVENT_HEIGHT} from './constants';
-
-const HEIGHT = SUSPENSE_EVENT_HEIGHT; // TODO Constant name
-const ROW_WITH_BORDER_HEIGHT = HEIGHT + BORDER_SIZE;
-
-const BASE_URL_REGEX = /([^:]+:\/\/[^\/]+)/;
-
-export class NetworkMeasuresView extends View {
- _depthToNetworkMeasure: Map;
- _hoveredNetworkMeasure: NetworkMeasure | null = null;
- _intrinsicSize: IntrinsicSize;
- _maxDepth: number = 0;
- _profilerData: TimelineData;
-
- onHover: ((event: NetworkMeasure | null) => void) | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
-
- this._profilerData = profilerData;
-
- this._performPreflightComputations();
- }
-
- _performPreflightComputations() {
- this._depthToNetworkMeasure = new Map();
-
- const {duration, networkMeasures} = this._profilerData;
-
- networkMeasures.forEach(event => {
- const depth = event.depth;
-
- this._maxDepth = Math.max(this._maxDepth, depth);
-
- if (!this._depthToNetworkMeasure.has(depth)) {
- this._depthToNetworkMeasure.set(depth, [event]);
- } else {
- // $FlowFixMe[incompatible-use] This is unnecessary.
- this._depthToNetworkMeasure.get(depth).push(event);
- }
- });
-
- this._intrinsicSize = {
- width: duration,
- height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
- // Collapsed by default
- maxInitialHeight: 0,
- };
- }
-
- desiredSize(): IntrinsicSize {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(networkMeasure: NetworkMeasure | null) {
- if (this._hoveredNetworkMeasure === networkMeasure) {
- return;
- }
- this._hoveredNetworkMeasure = networkMeasure;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `NetworkMeasure` as a box/span with text inside of it.
- */
- _drawSingleNetworkMeasure(
- context: CanvasRenderingContext2D,
- networkMeasure: NetworkMeasure,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame, visibleArea} = this;
- const {
- depth,
- finishTimestamp,
- firstReceivedDataTimestamp,
- lastReceivedDataTimestamp,
- receiveResponseTimestamp,
- sendRequestTimestamp,
- url,
- } = networkMeasure;
-
- // Account for requests that did not complete while we were profiling.
- // As well as requests that did not receive data before finish (cached?).
- const duration = this._profilerData.duration;
- const timestampBegin = sendRequestTimestamp;
- const timestampEnd =
- finishTimestamp || lastReceivedDataTimestamp || duration;
- const timestampMiddle =
- receiveResponseTimestamp || firstReceivedDataTimestamp || timestampEnd;
-
- // Convert all timestamps to x coordinates.
- const xStart = timestampToPosition(timestampBegin, scaleFactor, frame);
- const xMiddle = timestampToPosition(timestampMiddle, scaleFactor, frame);
- const xStop = timestampToPosition(timestampEnd, scaleFactor, frame);
-
- const width = durationToWidth(xStop - xStart, scaleFactor);
- if (width < 1) {
- return; // Too small to render at this zoom level
- }
-
- baseY += depth * ROW_WITH_BORDER_HEIGHT;
-
- const outerRect: Rect = {
- origin: {
- x: xStart,
- y: baseY,
- },
- size: {
- width: xStop - xStart,
- height: HEIGHT,
- },
- };
- if (!rectIntersectsRect(outerRect, visibleArea)) {
- return; // Not in view
- }
-
- // Draw the secondary rect first (since it also shows as a thin border around the primary rect).
- let rect = {
- origin: {
- x: xStart,
- y: baseY,
- },
- size: {
- width: xStop - xStart,
- height: HEIGHT,
- },
- };
- if (rectIntersectsRect(rect, visibleArea)) {
- context.beginPath();
- context.fillStyle =
- this._hoveredNetworkMeasure === networkMeasure
- ? COLORS.NETWORK_SECONDARY_HOVER
- : COLORS.NETWORK_SECONDARY;
- context.fillRect(
- rect.origin.x,
- rect.origin.y,
- rect.size.width,
- rect.size.height,
- );
- }
-
- rect = {
- origin: {
- x: xStart + BORDER_SIZE,
- y: baseY + BORDER_SIZE,
- },
- size: {
- width: xMiddle - xStart - BORDER_SIZE,
- height: HEIGHT - BORDER_SIZE * 2,
- },
- };
- if (rectIntersectsRect(rect, visibleArea)) {
- context.beginPath();
- context.fillStyle =
- this._hoveredNetworkMeasure === networkMeasure
- ? COLORS.NETWORK_PRIMARY_HOVER
- : COLORS.NETWORK_PRIMARY;
- context.fillRect(
- rect.origin.x,
- rect.origin.y,
- rect.size.width,
- rect.size.height,
- );
- }
-
- const baseUrl = url.match(BASE_URL_REGEX);
- const displayUrl = baseUrl !== null ? baseUrl[1] : url;
-
- const durationLabel =
- finishTimestamp !== 0
- ? `${formatDuration(finishTimestamp - sendRequestTimestamp)} - `
- : '';
-
- const label = durationLabel + displayUrl;
-
- drawText(label, context, outerRect, visibleArea);
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {networkMeasures},
- _hoveredNetworkMeasure,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.PRIORITY_BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- networkMeasures.forEach(networkMeasure => {
- this._drawSingleNetworkMeasure(
- context,
- networkMeasure,
- frame.origin.y,
- scaleFactor,
- networkMeasure === _hoveredNetworkMeasure,
- );
- });
-
- // Render bottom borders.
- for (let i = 0; i <= this._maxDepth; i++) {
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + (i + 1) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(
- borderFrame,
- visibleArea,
- );
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, _intrinsicSize, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
-
- const adjustedCanvasMouseY = location.y - frame.origin.y;
- const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
- const networkMeasuresAtDepth = this._depthToNetworkMeasure.get(depth);
-
- const duration = this._profilerData.duration;
-
- if (networkMeasuresAtDepth) {
- // Find the event being hovered over.
- for (let index = networkMeasuresAtDepth.length - 1; index >= 0; index--) {
- const networkMeasure = networkMeasuresAtDepth[index];
- const {
- finishTimestamp,
- lastReceivedDataTimestamp,
- sendRequestTimestamp,
- } = networkMeasure;
-
- const timestampBegin = sendRequestTimestamp;
- const timestampEnd =
- finishTimestamp || lastReceivedDataTimestamp || duration;
-
- if (
- hoverTimestamp >= timestampBegin &&
- hoverTimestamp <= timestampEnd
- ) {
- this.currentCursor = 'context-menu';
- viewRefs.hoveredView = this;
- onHover(networkMeasure);
- return;
- }
- }
- }
-
- if (viewRefs.hoveredView === this) {
- viewRefs.hoveredView = null;
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js b/packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js
deleted file mode 100644
index f85aba3b3ca9..000000000000
--- a/packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js
+++ /dev/null
@@ -1,370 +0,0 @@
-/**
- * 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
- */
-
-import type {ReactLane, ReactMeasure, TimelineData} from '../types';
-import type {
- Interaction,
- IntrinsicSize,
- MouseMoveInteraction,
- Rect,
- ViewRefs,
-} from '../view-base';
-
-import {formatDuration} from '../utils/formatting';
-import {drawText} from './utils/text';
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
-} from './utils/positioning';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-
-import {COLORS, BORDER_SIZE, REACT_MEASURE_HEIGHT} from './constants';
-
-const REACT_LANE_HEIGHT = REACT_MEASURE_HEIGHT + BORDER_SIZE;
-const MAX_ROWS_TO_SHOW_INITIALLY = 5;
-
-export class ReactMeasuresView extends View {
- _intrinsicSize: IntrinsicSize;
- _lanesToRender: ReactLane[];
- _profilerData: TimelineData;
- _hoveredMeasure: ReactMeasure | null = null;
-
- onHover: ((measure: ReactMeasure | null) => void) | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
- this._profilerData = profilerData;
- this._performPreflightComputations();
- }
-
- _performPreflightComputations() {
- this._lanesToRender = [];
-
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
- for (const [lane, measuresForLane] of this._profilerData
- .laneToReactMeasureMap) {
- // Only show lanes with measures
- if (measuresForLane.length > 0) {
- this._lanesToRender.push(lane);
- }
- }
-
- this._intrinsicSize = {
- width: this._profilerData.duration,
- height: this._lanesToRender.length * REACT_LANE_HEIGHT,
- hideScrollBarIfLessThanHeight: REACT_LANE_HEIGHT,
- maxInitialHeight: MAX_ROWS_TO_SHOW_INITIALLY * REACT_LANE_HEIGHT,
- };
- }
-
- desiredSize(): IntrinsicSize {
- return this._intrinsicSize;
- }
-
- setHoveredMeasure(hoveredMeasure: ReactMeasure | null) {
- if (this._hoveredMeasure === hoveredMeasure) {
- return;
- }
- this._hoveredMeasure = hoveredMeasure;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `ReactMeasure` as a bar in the canvas.
- */
- _drawSingleReactMeasure(
- context: CanvasRenderingContext2D,
- rect: Rect,
- measure: ReactMeasure,
- nextMeasure: ReactMeasure | null,
- baseY: number,
- scaleFactor: number,
- showGroupHighlight: boolean,
- showHoverHighlight: boolean,
- ): void {
- const {frame, visibleArea} = this;
- const {timestamp, type, duration} = measure;
-
- let fillStyle = null;
- let hoveredFillStyle = null;
- let groupSelectedFillStyle = null;
- let textFillStyle = null;
-
- // We could change the max to 0 and just skip over rendering anything that small,
- // but this has the effect of making the chart look very empty when zoomed out.
- // So long as perf is okay- it might be best to err on the side of showing things.
- const width = durationToWidth(duration, scaleFactor);
- if (width <= 0) {
- return; // Too small to render at this zoom level
- }
-
- const x = timestampToPosition(timestamp, scaleFactor, frame);
- const measureRect: Rect = {
- origin: {x, y: baseY},
- size: {width, height: REACT_MEASURE_HEIGHT},
- };
- if (!rectIntersectsRect(measureRect, rect)) {
- return; // Not in view
- }
-
- const drawableRect = intersectionOfRects(measureRect, rect);
- let textRect = measureRect;
-
- switch (type) {
- case 'commit':
- fillStyle = COLORS.REACT_COMMIT;
- hoveredFillStyle = COLORS.REACT_COMMIT_HOVER;
- groupSelectedFillStyle = COLORS.REACT_COMMIT_HOVER;
- textFillStyle = COLORS.REACT_COMMIT_TEXT;
-
- // Commit phase rects are overlapped by layout and passive rects,
- // and it looks bad if text flows underneath/behind these overlayed rects.
- if (nextMeasure != null) {
- // This clipping shouldn't apply for measures that don't overlap though,
- // like passive effects that are processed after a delay,
- // or if there are now layout or passive effects and the next measure is render or idle.
- if (nextMeasure.timestamp < measure.timestamp + measure.duration) {
- textRect = {
- ...measureRect,
- size: {
- width:
- timestampToPosition(
- nextMeasure.timestamp,
- scaleFactor,
- frame,
- ) - x,
- height: REACT_MEASURE_HEIGHT,
- },
- };
- }
- }
- break;
- case 'render-idle':
- // We could render idle time as diagonal hashes.
- // This looks nicer when zoomed in, but not so nice when zoomed out.
- // color = context.createPattern(getIdlePattern(), 'repeat');
- fillStyle = COLORS.REACT_IDLE;
- hoveredFillStyle = COLORS.REACT_IDLE_HOVER;
- groupSelectedFillStyle = COLORS.REACT_IDLE_HOVER;
- break;
- case 'render':
- fillStyle = COLORS.REACT_RENDER;
- hoveredFillStyle = COLORS.REACT_RENDER_HOVER;
- groupSelectedFillStyle = COLORS.REACT_RENDER_HOVER;
- textFillStyle = COLORS.REACT_RENDER_TEXT;
- break;
- case 'layout-effects':
- fillStyle = COLORS.REACT_LAYOUT_EFFECTS;
- hoveredFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
- groupSelectedFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
- textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
- break;
- case 'passive-effects':
- fillStyle = COLORS.REACT_PASSIVE_EFFECTS;
- hoveredFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
- groupSelectedFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
- textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
- break;
- default:
- throw new Error(`Unexpected measure type "${type}"`);
- }
-
- context.fillStyle = showHoverHighlight
- ? hoveredFillStyle
- : showGroupHighlight
- ? groupSelectedFillStyle
- : fillStyle;
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- if (textFillStyle !== null) {
- drawText(formatDuration(duration), context, textRect, visibleArea, {
- fillStyle: textFillStyle,
- });
- }
- }
-
- draw(context: CanvasRenderingContext2D): void {
- const {frame, _hoveredMeasure, _lanesToRender, _profilerData, visibleArea} =
- this;
-
- context.fillStyle = COLORS.PRIORITY_BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- for (let i = 0; i < _lanesToRender.length; i++) {
- const lane = _lanesToRender[i];
- const baseY = frame.origin.y + i * REACT_LANE_HEIGHT;
- const measuresForLane = _profilerData.laneToReactMeasureMap.get(lane);
-
- if (!measuresForLane) {
- throw new Error(
- 'No measures found for a React lane! This is a bug in this profiler tool. Please file an issue.',
- );
- }
-
- // Render lane labels
- const label = _profilerData.laneToLabelMap.get(lane);
- if (label == null) {
- console.warn(`Could not find label for lane ${lane}.`);
- } else {
- const labelRect = {
- origin: {
- x: visibleArea.origin.x,
- y: baseY,
- },
- size: {
- width: visibleArea.size.width,
- height: REACT_LANE_HEIGHT,
- },
- };
-
- drawText(label, context, labelRect, visibleArea, {
- fillStyle: COLORS.TEXT_DIM_COLOR,
- });
- }
-
- // Draw measures
- for (let j = 0; j < measuresForLane.length; j++) {
- const measure = measuresForLane[j];
- const showHoverHighlight = _hoveredMeasure === measure;
- const showGroupHighlight =
- !!_hoveredMeasure && _hoveredMeasure.batchUID === measure.batchUID;
-
- this._drawSingleReactMeasure(
- context,
- visibleArea,
- measure,
- measuresForLane[j + 1] || null,
- baseY,
- scaleFactor,
- showGroupHighlight,
- showHoverHighlight,
- );
- }
-
- // Render bottom border
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + (i + 1) * REACT_LANE_HEIGHT - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(
- borderFrame,
- visibleArea,
- );
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {
- frame,
- _intrinsicSize,
- _lanesToRender,
- onHover,
- _profilerData,
- visibleArea,
- } = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- // Identify the lane being hovered over
- const adjustedCanvasMouseY = location.y - frame.origin.y;
- const renderedLaneIndex = Math.floor(
- adjustedCanvasMouseY / REACT_LANE_HEIGHT,
- );
- if (renderedLaneIndex < 0 || renderedLaneIndex >= _lanesToRender.length) {
- onHover(null);
- return;
- }
- const lane = _lanesToRender[renderedLaneIndex];
-
- // Find the measure in `lane` being hovered over.
- //
- // Because data ranges may overlap, we want to find the last intersecting item.
- // This will always be the one on "top" (the one the user is hovering over).
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
- const measures = _profilerData.laneToReactMeasureMap.get(lane);
- if (!measures) {
- onHover(null);
- return;
- }
-
- for (let index = measures.length - 1; index >= 0; index--) {
- const measure = measures[index];
- const {duration, timestamp} = measure;
-
- if (
- hoverTimestamp >= timestamp &&
- hoverTimestamp <= timestamp + duration
- ) {
- this.currentCursor = 'context-menu';
- viewRefs.hoveredView = this;
- onHover(measure);
- return;
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js b/packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js
deleted file mode 100644
index 39114cb88815..000000000000
--- a/packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js
+++ /dev/null
@@ -1,288 +0,0 @@
-/**
- * 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
- */
-
-import type {SchedulingEvent, TimelineData} from '../types';
-import type {
- ClickInteraction,
- Interaction,
- MouseMoveInteraction,
- Rect,
- Size,
- ViewRefs,
-} from '../view-base';
-
-import {
- positioningScaleFactor,
- timestampToPosition,
- positionToTimestamp,
- widthToDuration,
-} from './utils/positioning';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {
- COLORS,
- TOP_ROW_PADDING,
- REACT_EVENT_DIAMETER,
- BORDER_SIZE,
-} from './constants';
-
-const EVENT_ROW_HEIGHT_FIXED =
- TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
-
-export class SchedulingEventsView extends View {
- _profilerData: TimelineData;
- _intrinsicSize: Size;
-
- _hoveredEvent: SchedulingEvent | null = null;
- onHover: ((event: SchedulingEvent | null) => void) | null = null;
- onClick:
- | ((event: SchedulingEvent | null, eventIndex: number | null) => void)
- | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
- this._profilerData = profilerData;
-
- this._intrinsicSize = {
- width: this._profilerData.duration,
- height: EVENT_ROW_HEIGHT_FIXED,
- };
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(hoveredEvent: SchedulingEvent | null) {
- if (this._hoveredEvent === hoveredEvent) {
- return;
- }
- this._hoveredEvent = hoveredEvent;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `SchedulingEvent` as a circle in the canvas.
- */
- _drawSingleSchedulingEvent(
- context: CanvasRenderingContext2D,
- rect: Rect,
- event: SchedulingEvent,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame} = this;
- const {timestamp, type, warning} = event;
-
- const x = timestampToPosition(timestamp, scaleFactor, frame);
- const radius = REACT_EVENT_DIAMETER / 2;
- const eventRect: Rect = {
- origin: {
- x: x - radius,
- y: baseY,
- },
- size: {width: REACT_EVENT_DIAMETER, height: REACT_EVENT_DIAMETER},
- };
- if (!rectIntersectsRect(eventRect, rect)) {
- return; // Not in view
- }
-
- let fillStyle = null;
-
- if (warning !== null) {
- fillStyle = showHoverHighlight
- ? COLORS.WARNING_BACKGROUND_HOVER
- : COLORS.WARNING_BACKGROUND;
- } else {
- switch (type) {
- case 'schedule-render':
- case 'schedule-state-update':
- case 'schedule-force-update':
- fillStyle = showHoverHighlight
- ? COLORS.REACT_SCHEDULE_HOVER
- : COLORS.REACT_SCHEDULE;
- break;
- default:
- if (__DEV__) {
- console.warn('Unexpected event type "%s"', type);
- }
- break;
- }
- }
-
- if (fillStyle !== null) {
- const y = eventRect.origin.y + radius;
-
- context.beginPath();
- context.fillStyle = fillStyle;
- context.arc(x, y, radius, 0, 2 * Math.PI);
- context.fill();
- }
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {schedulingEvents},
- _hoveredEvent,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw events
- const baseY = frame.origin.y + TOP_ROW_PADDING;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- const highlightedEvents: SchedulingEvent[] = [];
-
- schedulingEvents.forEach(event => {
- if (event === _hoveredEvent) {
- highlightedEvents.push(event);
- return;
- }
- this._drawSingleSchedulingEvent(
- context,
- visibleArea,
- event,
- baseY,
- scaleFactor,
- false,
- );
- });
-
- // Draw the highlighted items on top so they stand out.
- // This is helpful if there are multiple (overlapping) items close to each other.
- highlightedEvents.forEach(event => {
- this._drawSingleSchedulingEvent(
- context,
- visibleArea,
- event,
- baseY,
- scaleFactor,
- true,
- );
- });
-
- // Render bottom border.
- // Propose border rect, check if intersects with `rect`, draw intersection.
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
- context.fillStyle = COLORS.REACT_WORK_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const {
- _profilerData: {schedulingEvents},
- } = this;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
- const eventTimestampAllowance = widthToDuration(
- REACT_EVENT_DIAMETER / 2,
- scaleFactor,
- );
-
- // Because data ranges may overlap, we want to find the last intersecting item.
- // This will always be the one on "top" (the one the user is hovering over).
- for (let index = schedulingEvents.length - 1; index >= 0; index--) {
- const event = schedulingEvents[index];
- const {timestamp} = event;
-
- if (
- timestamp - eventTimestampAllowance <= hoverTimestamp &&
- hoverTimestamp <= timestamp + eventTimestampAllowance
- ) {
- this.currentCursor = 'pointer';
- viewRefs.hoveredView = this;
- onHover(event);
- return;
- }
- }
-
- onHover(null);
- }
-
- /**
- * @private
- */
- _handleClick(interaction: ClickInteraction) {
- const {onClick} = this;
- if (onClick) {
- const {
- _profilerData: {schedulingEvents},
- } = this;
- const eventIndex = schedulingEvents.findIndex(
- event => event === this._hoveredEvent,
- );
- // onHover is going to take care of all the difficult logic here of
- // figuring out which event when they're proximity is close.
- onClick(this._hoveredEvent, eventIndex >= 0 ? eventIndex : null);
- }
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- case 'click':
- this._handleClick(interaction);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/SnapshotsView.js b/packages/react-devtools-timeline/src/content-views/SnapshotsView.js
deleted file mode 100644
index 23d61a3b63b2..000000000000
--- a/packages/react-devtools-timeline/src/content-views/SnapshotsView.js
+++ /dev/null
@@ -1,260 +0,0 @@
-/**
- * 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
- */
-
-import type {Snapshot, TimelineData} from '../types';
-import type {
- Interaction,
- Point,
- Rect,
- Size,
- Surface,
- ViewRefs,
-} from '../view-base';
-
-import {positioningScaleFactor, timestampToPosition} from './utils/positioning';
-import {
- intersectionOfRects,
- rectContainsPoint,
- rectEqualToRect,
- View,
-} from '../view-base';
-import {BORDER_SIZE, COLORS, SNAPSHOT_SCRUBBER_SIZE} from './constants';
-
-type OnHover = (node: Snapshot | null) => void;
-
-export class SnapshotsView extends View {
- _hoverLocation: Point | null = null;
- _intrinsicSize: Size;
- _profilerData: TimelineData;
-
- onHover: OnHover | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
-
- this._intrinsicSize = {
- width: profilerData.duration,
- height: profilerData.snapshotHeight,
- };
- this._profilerData = profilerData;
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- draw(context: CanvasRenderingContext2D) {
- const snapshotHeight = this._profilerData.snapshotHeight;
- const {visibleArea} = this;
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- const y = visibleArea.origin.y;
-
- let x = visibleArea.origin.x;
-
- // Rather than drawing each snapshot where it occurred,
- // draw them at fixed intervals and just show the nearest one.
- while (x < visibleArea.origin.x + visibleArea.size.width) {
- const snapshot = this._findClosestSnapshot(x);
- if (snapshot === null) {
- // This shold never happen.
- break;
- }
-
- const scaledHeight = snapshotHeight;
- const scaledWidth = (snapshot.width * snapshotHeight) / snapshot.height;
-
- const imageRect: Rect = {
- origin: {
- x,
- y,
- },
- size: {width: scaledWidth, height: scaledHeight},
- };
-
- // Lazily create and cache Image objects as we render a snapsho for the first time.
- if (snapshot.image === null) {
- const img = (snapshot.image = new Image());
- img.onload = () => {
- this._drawSnapshotImage(context, snapshot, imageRect);
- };
- img.src = snapshot.imageSource;
- } else {
- this._drawSnapshotImage(context, snapshot, imageRect);
- }
-
- x += scaledWidth + BORDER_SIZE;
- }
-
- const hoverLocation = this._hoverLocation;
- if (hoverLocation !== null) {
- const scrubberWidth = SNAPSHOT_SCRUBBER_SIZE + BORDER_SIZE * 2;
- const scrubberOffset = scrubberWidth / 2;
-
- context.fillStyle = COLORS.SCRUBBER_BORDER;
- context.fillRect(
- hoverLocation.x - scrubberOffset,
- visibleArea.origin.y,
- scrubberWidth,
- visibleArea.size.height,
- );
-
- context.fillStyle = COLORS.SCRUBBER_BACKGROUND;
- context.fillRect(
- hoverLocation.x - scrubberOffset + BORDER_SIZE,
- visibleArea.origin.y,
- SNAPSHOT_SCRUBBER_SIZE,
- visibleArea.size.height,
- );
- }
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- case 'wheel-control':
- case 'wheel-meta':
- case 'wheel-plain':
- case 'wheel-shift':
- this._updateHover(interaction.payload.location, viewRefs);
- break;
- }
- }
-
- _drawSnapshotImage(
- context: CanvasRenderingContext2D,
- snapshot: Snapshot,
- imageRect: Rect,
- ) {
- const visibleArea = this.visibleArea;
-
- // Prevent snapshot from visibly overflowing its container when clipped.
- // View clips by default, but since this view may draw async (on Image load) we re-clip.
- const shouldClip = !rectEqualToRect(imageRect, visibleArea);
- if (shouldClip) {
- const clippedRect = intersectionOfRects(imageRect, visibleArea);
- context.save();
- context.beginPath();
- context.rect(
- clippedRect.origin.x,
- clippedRect.origin.y,
- clippedRect.size.width,
- clippedRect.size.height,
- );
- context.closePath();
- context.clip();
- }
-
- context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
- context.fillRect(
- imageRect.origin.x,
- imageRect.origin.y,
- imageRect.size.width,
- imageRect.size.height,
- );
-
- // $FlowFixMe[incompatible-type] Flow doesn't know about the 9 argument variant of drawImage()
- context.drawImage(
- snapshot.image,
-
- // Image coordinates
- 0,
- 0,
-
- // Native image size
- snapshot.width,
- snapshot.height,
-
- // Canvas coordinates
- imageRect.origin.x + BORDER_SIZE,
- imageRect.origin.y + BORDER_SIZE,
-
- // Scaled image size
- imageRect.size.width - BORDER_SIZE * 2,
- imageRect.size.height - BORDER_SIZE * 2,
- );
-
- if (shouldClip) {
- context.restore();
- }
- }
-
- _findClosestSnapshot(x: number): Snapshot | null {
- const frame = this.frame;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- const snapshots = this._profilerData.snapshots;
-
- let startIndex = 0;
- let stopIndex = snapshots.length - 1;
- while (startIndex <= stopIndex) {
- const currentIndex = Math.floor((startIndex + stopIndex) / 2);
- const snapshot = snapshots[currentIndex];
- const {timestamp} = snapshot;
-
- const snapshotX = Math.floor(
- timestampToPosition(timestamp, scaleFactor, frame),
- );
-
- if (x < snapshotX) {
- stopIndex = currentIndex - 1;
- } else {
- startIndex = currentIndex + 1;
- }
- }
-
- return snapshots[stopIndex] || null;
- }
-
- /**
- * @private
- */
- _updateHover(location: Point, viewRefs: ViewRefs) {
- const {onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- if (!rectContainsPoint(location, visibleArea)) {
- if (this._hoverLocation !== null) {
- this._hoverLocation = null;
-
- this.setNeedsDisplay();
- }
-
- onHover(null);
- return;
- }
-
- const snapshot = this._findClosestSnapshot(location.x);
- if (snapshot !== null) {
- this._hoverLocation = location;
-
- onHover(snapshot);
- } else {
- this._hoverLocation = null;
-
- onHover(null);
- }
-
- // Any time the mouse moves within the boundaries of this view, we need to re-render.
- // This is because we draw a scrubbing bar that shows the location corresponding to the current tooltip.
- this.setNeedsDisplay();
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js b/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js
deleted file mode 100644
index 3c1fe88e91f7..000000000000
--- a/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js
+++ /dev/null
@@ -1,359 +0,0 @@
-/**
- * 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
- */
-
-import type {SuspenseEvent, TimelineData} from '../types';
-import type {
- Interaction,
- IntrinsicSize,
- MouseMoveInteraction,
- Rect,
- ViewRefs,
-} from '../view-base';
-
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
- widthToDuration,
-} from './utils/positioning';
-import {drawText} from './utils/text';
-import {formatDuration} from '../utils/formatting';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {
- BORDER_SIZE,
- COLORS,
- PENDING_SUSPENSE_EVENT_SIZE,
- SUSPENSE_EVENT_HEIGHT,
-} from './constants';
-
-const ROW_WITH_BORDER_HEIGHT = SUSPENSE_EVENT_HEIGHT + BORDER_SIZE;
-const MAX_ROWS_TO_SHOW_INITIALLY = 3;
-
-export class SuspenseEventsView extends View {
- _depthToSuspenseEvent: Map;
- _hoveredEvent: SuspenseEvent | null = null;
- _intrinsicSize: IntrinsicSize;
- _maxDepth: number = 0;
- _profilerData: TimelineData;
-
- onHover: ((event: SuspenseEvent | null) => void) | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
-
- this._profilerData = profilerData;
-
- this._performPreflightComputations();
- }
-
- _performPreflightComputations() {
- this._depthToSuspenseEvent = new Map();
-
- const {duration, suspenseEvents} = this._profilerData;
-
- suspenseEvents.forEach(event => {
- const depth = event.depth;
-
- this._maxDepth = Math.max(this._maxDepth, depth);
-
- if (!this._depthToSuspenseEvent.has(depth)) {
- this._depthToSuspenseEvent.set(depth, [event]);
- } else {
- // $FlowFixMe[incompatible-use] This is unnecessary.
- this._depthToSuspenseEvent.get(depth).push(event);
- }
- });
-
- this._intrinsicSize = {
- width: duration,
- height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
- hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
- maxInitialHeight: ROW_WITH_BORDER_HEIGHT * MAX_ROWS_TO_SHOW_INITIALLY,
- };
- }
-
- desiredSize(): IntrinsicSize {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(hoveredEvent: SuspenseEvent | null) {
- if (this._hoveredEvent === hoveredEvent) {
- return;
- }
- this._hoveredEvent = hoveredEvent;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `SuspenseEvent` as a box/span with text inside of it.
- */
- _drawSingleSuspenseEvent(
- context: CanvasRenderingContext2D,
- rect: Rect,
- event: SuspenseEvent,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame} = this;
- const {
- componentName,
- depth,
- duration,
- phase,
- promiseName,
- resolution,
- timestamp,
- warning,
- } = event;
-
- baseY += depth * ROW_WITH_BORDER_HEIGHT;
-
- let fillStyle = null as any as string;
- if (warning !== null) {
- fillStyle = showHoverHighlight
- ? COLORS.WARNING_BACKGROUND_HOVER
- : COLORS.WARNING_BACKGROUND;
- } else {
- switch (resolution) {
- case 'rejected':
- fillStyle = showHoverHighlight
- ? COLORS.REACT_SUSPENSE_REJECTED_EVENT_HOVER
- : COLORS.REACT_SUSPENSE_REJECTED_EVENT;
- break;
- case 'resolved':
- fillStyle = showHoverHighlight
- ? COLORS.REACT_SUSPENSE_RESOLVED_EVENT_HOVER
- : COLORS.REACT_SUSPENSE_RESOLVED_EVENT;
- break;
- case 'unresolved':
- fillStyle = showHoverHighlight
- ? COLORS.REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER
- : COLORS.REACT_SUSPENSE_UNRESOLVED_EVENT;
- break;
- }
- }
-
- const xStart = timestampToPosition(timestamp, scaleFactor, frame);
-
- // Pending suspense events (ones that never resolved) won't have durations.
- // So instead we draw them as diamonds.
- if (duration === null) {
- const size = PENDING_SUSPENSE_EVENT_SIZE;
- const halfSize = size / 2;
-
- baseY += (SUSPENSE_EVENT_HEIGHT - PENDING_SUSPENSE_EVENT_SIZE) / 2;
-
- const y = baseY + halfSize;
-
- const suspenseRect: Rect = {
- origin: {
- x: xStart - halfSize,
- y: baseY,
- },
- size: {width: size, height: size},
- };
- if (!rectIntersectsRect(suspenseRect, rect)) {
- return; // Not in view
- }
-
- context.beginPath();
- context.fillStyle = fillStyle;
- context.moveTo(xStart, y - halfSize);
- context.lineTo(xStart + halfSize, y);
- context.lineTo(xStart, y + halfSize);
- context.lineTo(xStart - halfSize, y);
- context.fill();
- } else {
- const xStop = timestampToPosition(
- timestamp + duration,
- scaleFactor,
- frame,
- );
- const eventRect: Rect = {
- origin: {
- x: xStart,
- y: baseY,
- },
- size: {width: xStop - xStart, height: SUSPENSE_EVENT_HEIGHT},
- };
- if (!rectIntersectsRect(eventRect, rect)) {
- return; // Not in view
- }
-
- const width = durationToWidth(duration, scaleFactor);
- if (width < 1) {
- return; // Too small to render at this zoom level
- }
-
- const drawableRect = intersectionOfRects(eventRect, rect);
- context.beginPath();
- context.fillStyle = fillStyle;
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- let label = 'suspended';
- if (promiseName != null) {
- label = promiseName;
- } else if (componentName != null) {
- label = `${componentName} ${label}`;
- }
- if (phase !== null) {
- label += ` during ${phase}`;
- }
- if (resolution !== 'unresolved') {
- label += ` - ${formatDuration(duration)}`;
- }
-
- drawText(label, context, eventRect, drawableRect);
- }
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {suspenseEvents},
- _hoveredEvent,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.PRIORITY_BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw events
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- suspenseEvents.forEach(event => {
- this._drawSingleSuspenseEvent(
- context,
- visibleArea,
- event,
- frame.origin.y,
- scaleFactor,
- event === _hoveredEvent,
- );
- });
-
- // Render bottom borders.
- for (let i = 0; i <= this._maxDepth; i++) {
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + (i + 1) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(
- borderFrame,
- visibleArea,
- );
- context.fillStyle = COLORS.REACT_WORK_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, _intrinsicSize, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
-
- const adjustedCanvasMouseY = location.y - frame.origin.y;
- const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
- const suspenseEventsAtDepth = this._depthToSuspenseEvent.get(depth);
-
- if (suspenseEventsAtDepth) {
- // Find the event being hovered over.
- for (let index = suspenseEventsAtDepth.length - 1; index >= 0; index--) {
- const suspenseEvent = suspenseEventsAtDepth[index];
- const {duration, timestamp} = suspenseEvent;
-
- if (duration === null) {
- const timestampAllowance = widthToDuration(
- PENDING_SUSPENSE_EVENT_SIZE / 2,
- scaleFactor,
- );
-
- if (
- timestamp - timestampAllowance <= hoverTimestamp &&
- hoverTimestamp <= timestamp + timestampAllowance
- ) {
- this.currentCursor = 'context-menu';
-
- viewRefs.hoveredView = this;
-
- onHover(suspenseEvent);
- return;
- }
- } else if (
- hoverTimestamp >= timestamp &&
- hoverTimestamp <= timestamp + duration
- ) {
- this.currentCursor = 'context-menu';
-
- viewRefs.hoveredView = this;
-
- onHover(suspenseEvent);
- return;
- }
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js b/packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js
deleted file mode 100644
index c507352255df..000000000000
--- a/packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js
+++ /dev/null
@@ -1,241 +0,0 @@
-/**
- * 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
- */
-
-import type {ThrownError, TimelineData} from '../types';
-import type {
- Interaction,
- MouseMoveInteraction,
- Rect,
- Size,
- ViewRefs,
-} from '../view-base';
-
-import {
- positioningScaleFactor,
- timestampToPosition,
- positionToTimestamp,
- widthToDuration,
-} from './utils/positioning';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {
- COLORS,
- TOP_ROW_PADDING,
- REACT_EVENT_DIAMETER,
- BORDER_SIZE,
-} from './constants';
-
-const EVENT_ROW_HEIGHT_FIXED =
- TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
-
-export class ThrownErrorsView extends View {
- _profilerData: TimelineData;
- _intrinsicSize: Size;
- _hoveredEvent: ThrownError | null = null;
- onHover: ((event: ThrownError | null) => void) | null = null;
-
- constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
- super(surface, frame);
- this._profilerData = profilerData;
-
- this._intrinsicSize = {
- width: this._profilerData.duration,
- height: EVENT_ROW_HEIGHT_FIXED,
- };
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- setHoveredEvent(hoveredEvent: ThrownError | null) {
- if (this._hoveredEvent === hoveredEvent) {
- return;
- }
- this._hoveredEvent = hoveredEvent;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `ThrownError` as a circle in the canvas.
- */
- _drawSingleThrownError(
- context: CanvasRenderingContext2D,
- rect: Rect,
- thrownError: ThrownError,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame} = this;
- const {timestamp} = thrownError;
-
- const x = timestampToPosition(timestamp, scaleFactor, frame);
- const radius = REACT_EVENT_DIAMETER / 2;
- const eventRect: Rect = {
- origin: {
- x: x - radius,
- y: baseY,
- },
- size: {width: REACT_EVENT_DIAMETER, height: REACT_EVENT_DIAMETER},
- };
- if (!rectIntersectsRect(eventRect, rect)) {
- return; // Not in view
- }
-
- const fillStyle = showHoverHighlight
- ? COLORS.REACT_THROWN_ERROR_HOVER
- : COLORS.REACT_THROWN_ERROR;
-
- const y = eventRect.origin.y + radius;
-
- context.beginPath();
- context.fillStyle = fillStyle;
- context.arc(x, y, radius, 0, 2 * Math.PI);
- context.fill();
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {
- frame,
- _profilerData: {thrownErrors},
- _hoveredEvent,
- visibleArea,
- } = this;
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw events
- const baseY = frame.origin.y + TOP_ROW_PADDING;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- const highlightedEvents: ThrownError[] = [];
-
- thrownErrors.forEach(thrownError => {
- if (thrownError === _hoveredEvent) {
- highlightedEvents.push(thrownError);
- return;
- }
- this._drawSingleThrownError(
- context,
- visibleArea,
- thrownError,
- baseY,
- scaleFactor,
- false,
- );
- });
-
- // Draw the highlighted items on top so they stand out.
- // This is helpful if there are multiple (overlapping) items close to each other.
- highlightedEvents.forEach(thrownError => {
- this._drawSingleThrownError(
- context,
- visibleArea,
- thrownError,
- baseY,
- scaleFactor,
- true,
- );
- });
-
- // Render bottom borders.
- // Propose border rect, check if intersects with `rect`, draw intersection.
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
- context.fillStyle = COLORS.REACT_WORK_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const {
- _profilerData: {thrownErrors},
- } = this;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
- const eventTimestampAllowance = widthToDuration(
- REACT_EVENT_DIAMETER / 2,
- scaleFactor,
- );
-
- // Because data ranges may overlap, we want to find the last intersecting item.
- // This will always be the one on "top" (the one the user is hovering over).
- for (let index = thrownErrors.length - 1; index >= 0; index--) {
- const event = thrownErrors[index];
- const {timestamp} = event;
-
- if (
- timestamp - eventTimestampAllowance <= hoverTimestamp &&
- hoverTimestamp <= timestamp + eventTimestampAllowance
- ) {
- this.currentCursor = 'context-menu';
- viewRefs.hoveredView = this;
- onHover(event);
- return;
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/TimeAxisMarkersView.js b/packages/react-devtools-timeline/src/content-views/TimeAxisMarkersView.js
deleted file mode 100644
index 22e60876de40..000000000000
--- a/packages/react-devtools-timeline/src/content-views/TimeAxisMarkersView.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect, Size} from '../view-base';
-
-import {
- durationToWidth,
- positioningScaleFactor,
- positionToTimestamp,
- timestampToPosition,
-} from './utils/positioning';
-import {
- View,
- Surface,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {
- COLORS,
- INTERVAL_TIMES,
- LABEL_SIZE,
- FONT_SIZE,
- MARKER_HEIGHT,
- MARKER_TEXT_PADDING,
- MARKER_TICK_HEIGHT,
- MIN_INTERVAL_SIZE_PX,
- BORDER_SIZE,
-} from './constants';
-
-const HEADER_HEIGHT_FIXED = MARKER_HEIGHT + BORDER_SIZE;
-const LABEL_FIXED_WIDTH = LABEL_SIZE + BORDER_SIZE;
-
-export class TimeAxisMarkersView extends View {
- _totalDuration: number;
- _intrinsicSize: Size;
-
- constructor(surface: Surface, frame: Rect, totalDuration: number) {
- super(surface, frame);
- this._totalDuration = totalDuration;
- this._intrinsicSize = {
- width: this._totalDuration,
- height: HEADER_HEIGHT_FIXED,
- };
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- // Time mark intervals vary based on the current zoom range and the time it represents.
- // In Chrome, these seem to range from 70-140 pixels wide.
- // Time wise, they represent intervals of e.g. 1s, 500ms, 200ms, 100ms, 50ms, 20ms.
- // Based on zoom, we should determine which amount to actually show.
- _getTimeTickInterval(scaleFactor: number): number {
- for (let i = 0; i < INTERVAL_TIMES.length; i++) {
- const currentInterval = INTERVAL_TIMES[i];
- const intervalWidth = durationToWidth(currentInterval, scaleFactor);
- if (intervalWidth > MIN_INTERVAL_SIZE_PX) {
- return currentInterval;
- }
- }
- return INTERVAL_TIMES[0];
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {frame, _intrinsicSize, visibleArea} = this;
- const clippedFrame = {
- origin: frame.origin,
- size: {
- width: frame.size.width,
- height: _intrinsicSize.height,
- },
- };
- const drawableRect = intersectionOfRects(clippedFrame, visibleArea);
-
- // Clear background
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
-
- const scaleFactor = positioningScaleFactor(
- _intrinsicSize.width,
- clippedFrame,
- );
- const interval = this._getTimeTickInterval(scaleFactor);
- const firstIntervalTimestamp =
- Math.ceil(
- positionToTimestamp(
- drawableRect.origin.x - LABEL_FIXED_WIDTH,
- scaleFactor,
- clippedFrame,
- ) / interval,
- ) * interval;
-
- for (
- let markerTimestamp = firstIntervalTimestamp;
- true;
- markerTimestamp += interval
- ) {
- if (markerTimestamp <= 0) {
- continue; // Timestamps < are probably a bug; markers at 0 are ugly.
- }
-
- const x = timestampToPosition(markerTimestamp, scaleFactor, clippedFrame);
- if (x > drawableRect.origin.x + drawableRect.size.width) {
- break; // Not in view
- }
-
- const markerLabel = Math.round(markerTimestamp);
-
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- x,
- drawableRect.origin.y + MARKER_HEIGHT - MARKER_TICK_HEIGHT,
- BORDER_SIZE,
- MARKER_TICK_HEIGHT,
- );
-
- context.fillStyle = COLORS.TIME_MARKER_LABEL;
- context.textAlign = 'right';
- context.textBaseline = 'middle';
- context.font = `${FONT_SIZE}px sans-serif`;
- context.fillText(
- `${markerLabel}ms`,
- x - MARKER_TEXT_PADDING,
- MARKER_HEIGHT / 2,
- );
- }
-
- // Render bottom border.
- // Propose border rect, check if intersects with `rect`, draw intersection.
- const borderFrame: Rect = {
- origin: {
- x: clippedFrame.origin.x,
- y: clippedFrame.origin.y + clippedFrame.size.height - BORDER_SIZE,
- },
- size: {
- width: clippedFrame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js b/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js
deleted file mode 100644
index 8967d8463dfa..000000000000
--- a/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js
+++ /dev/null
@@ -1,244 +0,0 @@
-/**
- * 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
- */
-
-import type {UserTimingMark} from '../types';
-import type {
- Interaction,
- MouseMoveInteraction,
- Rect,
- Size,
- ViewRefs,
-} from '../view-base';
-
-import {
- positioningScaleFactor,
- timestampToPosition,
- positionToTimestamp,
- widthToDuration,
-} from './utils/positioning';
-import {
- View,
- Surface,
- rectContainsPoint,
- rectIntersectsRect,
- intersectionOfRects,
-} from '../view-base';
-import {
- COLORS,
- TOP_ROW_PADDING,
- USER_TIMING_MARK_SIZE,
- BORDER_SIZE,
-} from './constants';
-
-const ROW_HEIGHT_FIXED =
- TOP_ROW_PADDING + USER_TIMING_MARK_SIZE + TOP_ROW_PADDING;
-
-export class UserTimingMarksView extends View {
- _marks: UserTimingMark[];
- _intrinsicSize: Size;
-
- _hoveredMark: UserTimingMark | null = null;
- onHover: ((mark: UserTimingMark | null) => void) | null = null;
-
- constructor(
- surface: Surface,
- frame: Rect,
- marks: UserTimingMark[],
- duration: number,
- ) {
- super(surface, frame);
- this._marks = marks;
-
- this._intrinsicSize = {
- width: duration,
- height: ROW_HEIGHT_FIXED,
- };
- }
-
- desiredSize(): Size {
- return this._intrinsicSize;
- }
-
- setHoveredMark(hoveredMark: UserTimingMark | null) {
- if (this._hoveredMark === hoveredMark) {
- return;
- }
- this._hoveredMark = hoveredMark;
- this.setNeedsDisplay();
- }
-
- /**
- * Draw a single `UserTimingMark` as a circle in the canvas.
- */
- _drawSingleMark(
- context: CanvasRenderingContext2D,
- rect: Rect,
- mark: UserTimingMark,
- baseY: number,
- scaleFactor: number,
- showHoverHighlight: boolean,
- ) {
- const {frame} = this;
- const {timestamp} = mark;
-
- const x = timestampToPosition(timestamp, scaleFactor, frame);
- const size = USER_TIMING_MARK_SIZE;
- const halfSize = size / 2;
-
- const markRect: Rect = {
- origin: {
- x: x - halfSize,
- y: baseY,
- },
- size: {width: size, height: size},
- };
- if (!rectIntersectsRect(markRect, rect)) {
- return; // Not in view
- }
-
- const fillStyle = showHoverHighlight
- ? COLORS.USER_TIMING_HOVER
- : COLORS.USER_TIMING;
-
- // $FlowFixMe[invalid-compare]
- if (fillStyle !== null) {
- const y = baseY + halfSize;
-
- context.beginPath();
- context.fillStyle = fillStyle;
- context.moveTo(x, y - halfSize);
- context.lineTo(x + halfSize, y);
- context.lineTo(x, y + halfSize);
- context.lineTo(x - halfSize, y);
- context.fill();
- }
- }
-
- draw(context: CanvasRenderingContext2D) {
- const {frame, _marks, _hoveredMark, visibleArea} = this;
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
-
- // Draw marks
- const baseY = frame.origin.y + TOP_ROW_PADDING;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
-
- _marks.forEach(mark => {
- if (mark === _hoveredMark) {
- return;
- }
- this._drawSingleMark(
- context,
- visibleArea,
- mark,
- baseY,
- scaleFactor,
- false,
- );
- });
-
- // Draw the hovered and/or selected items on top so they stand out.
- // This is helpful if there are multiple (overlapping) items close to each other.
- if (_hoveredMark !== null) {
- this._drawSingleMark(
- context,
- visibleArea,
- _hoveredMark,
- baseY,
- scaleFactor,
- true,
- );
- }
-
- // Render bottom border.
- // Propose border rect, check if intersects with `rect`, draw intersection.
- const borderFrame: Rect = {
- origin: {
- x: frame.origin.x,
- y: frame.origin.y + ROW_HEIGHT_FIXED - BORDER_SIZE,
- },
- size: {
- width: frame.size.width,
- height: BORDER_SIZE,
- },
- };
- if (rectIntersectsRect(borderFrame, visibleArea)) {
- const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
- context.fillStyle = COLORS.PRIORITY_BORDER;
- context.fillRect(
- borderDrawableRect.origin.x,
- borderDrawableRect.origin.y,
- borderDrawableRect.size.width,
- borderDrawableRect.size.height,
- );
- }
- }
-
- /**
- * @private
- */
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {frame, onHover, visibleArea} = this;
- if (!onHover) {
- return;
- }
-
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, visibleArea)) {
- onHover(null);
- return;
- }
-
- const {_marks} = this;
- const scaleFactor = positioningScaleFactor(
- this._intrinsicSize.width,
- frame,
- );
- const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
- const timestampAllowance = widthToDuration(
- USER_TIMING_MARK_SIZE / 2,
- scaleFactor,
- );
-
- // Because data ranges may overlap, we want to find the last intersecting item.
- // This will always be the one on "top" (the one the user is hovering over).
- for (let index = _marks.length - 1; index >= 0; index--) {
- const mark = _marks[index];
- const {timestamp} = mark;
-
- if (
- timestamp - timestampAllowance <= hoverTimestamp &&
- hoverTimestamp <= timestamp + timestampAllowance
- ) {
- viewRefs.hoveredView = this;
- onHover(mark);
- return;
- }
- }
-
- onHover(null);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/constants.js b/packages/react-devtools-timeline/src/content-views/constants.js
deleted file mode 100644
index 1d68ab6edfe6..000000000000
--- a/packages/react-devtools-timeline/src/content-views/constants.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * 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
- */
-
-export const DPR: number = window.devicePixelRatio || 1;
-export const LABEL_SIZE = 80;
-export const MARKER_HEIGHT = 20;
-export const MARKER_TICK_HEIGHT = 8;
-export const FONT_SIZE = 10;
-export const MARKER_TEXT_PADDING = 8;
-export const COLOR_HOVER_DIM_DELTA = 5;
-export const TOP_ROW_PADDING = 4;
-export const NATIVE_EVENT_HEIGHT = 14;
-export const SUSPENSE_EVENT_HEIGHT: number = 14;
-export const PENDING_SUSPENSE_EVENT_SIZE = 8;
-export const REACT_EVENT_DIAMETER = 6;
-export const USER_TIMING_MARK_SIZE = 8;
-export const REACT_MEASURE_HEIGHT = 14;
-export const BORDER_SIZE = 1 / DPR;
-export const FLAMECHART_FRAME_HEIGHT = 14;
-export const TEXT_PADDING = 3;
-export const SNAPSHOT_SCRUBBER_SIZE = 3;
-
-export const INTERVAL_TIMES = [
- 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000,
-];
-export const MIN_INTERVAL_SIZE_PX = 70;
-
-// TODO Replace this with "export let" vars
-export let COLORS: {
- BACKGROUND: string,
- INTERNAL_MODULE_FRAME: string,
- INTERNAL_MODULE_FRAME_HOVER: string,
- INTERNAL_MODULE_FRAME_TEXT: string,
- NATIVE_EVENT: string,
- NATIVE_EVENT_HOVER: string,
- NETWORK_PRIMARY: string,
- NETWORK_PRIMARY_HOVER: string,
- NETWORK_SECONDARY: string,
- NETWORK_SECONDARY_HOVER: string,
- PRIORITY_BACKGROUND: string,
- PRIORITY_BORDER: string,
- PRIORITY_LABEL: string,
- REACT_COMMIT: string,
- REACT_COMMIT_HOVER: string,
- REACT_COMMIT_TEXT: string,
- REACT_IDLE: string,
- REACT_IDLE_HOVER: string,
- REACT_LAYOUT_EFFECTS: string,
- REACT_LAYOUT_EFFECTS_HOVER: string,
- REACT_LAYOUT_EFFECTS_TEXT: string,
- REACT_PASSIVE_EFFECTS: string,
- REACT_PASSIVE_EFFECTS_HOVER: string,
- REACT_PASSIVE_EFFECTS_TEXT: string,
- REACT_RENDER: string,
- REACT_RENDER_HOVER: string,
- REACT_RENDER_TEXT: string,
- REACT_RESIZE_BAR: string,
- REACT_RESIZE_BAR_ACTIVE: string,
- REACT_RESIZE_BAR_BORDER: string,
- REACT_RESIZE_BAR_DOT: string,
- REACT_SCHEDULE: string,
- REACT_SCHEDULE_HOVER: string,
- REACT_SUSPENSE_REJECTED_EVENT: string,
- REACT_SUSPENSE_REJECTED_EVENT_HOVER: string,
- REACT_SUSPENSE_RESOLVED_EVENT: string,
- REACT_SUSPENSE_RESOLVED_EVENT_HOVER: string,
- REACT_SUSPENSE_UNRESOLVED_EVENT: string,
- REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: string,
- REACT_THROWN_ERROR: string,
- REACT_THROWN_ERROR_HOVER: string,
- REACT_WORK_BORDER: string,
- SCROLL_CARET: string,
- SCRUBBER_BACKGROUND: string,
- SCRUBBER_BORDER: string,
- SEARCH_RESULT_FILL: string,
- TEXT_COLOR: string,
- TEXT_DIM_COLOR: string,
- TIME_MARKER_LABEL: string,
- USER_TIMING: string,
- USER_TIMING_HOVER: string,
- WARNING_BACKGROUND: string,
- WARNING_BACKGROUND_HOVER: string,
- WARNING_TEXT: string,
- WARNING_TEXT_INVERED: string,
-} = {
- BACKGROUND: '',
- INTERNAL_MODULE_FRAME: '',
- INTERNAL_MODULE_FRAME_HOVER: '',
- INTERNAL_MODULE_FRAME_TEXT: '',
- NATIVE_EVENT: '',
- NATIVE_EVENT_HOVER: '',
- NETWORK_PRIMARY: '',
- NETWORK_PRIMARY_HOVER: '',
- NETWORK_SECONDARY: '',
- NETWORK_SECONDARY_HOVER: '',
- PRIORITY_BACKGROUND: '',
- PRIORITY_BORDER: '',
- PRIORITY_LABEL: '',
- USER_TIMING: '',
- USER_TIMING_HOVER: '',
- REACT_IDLE: '',
- REACT_IDLE_HOVER: '',
- REACT_RENDER: '',
- REACT_RENDER_HOVER: '',
- REACT_RENDER_TEXT: '',
- REACT_COMMIT: '',
- REACT_COMMIT_HOVER: '',
- REACT_COMMIT_TEXT: '',
- REACT_LAYOUT_EFFECTS: '',
- REACT_LAYOUT_EFFECTS_HOVER: '',
- REACT_LAYOUT_EFFECTS_TEXT: '',
- REACT_PASSIVE_EFFECTS: '',
- REACT_PASSIVE_EFFECTS_HOVER: '',
- REACT_PASSIVE_EFFECTS_TEXT: '',
- REACT_RESIZE_BAR: '',
- REACT_RESIZE_BAR_ACTIVE: '',
- REACT_RESIZE_BAR_BORDER: '',
- REACT_RESIZE_BAR_DOT: '',
- REACT_SCHEDULE: '',
- REACT_SCHEDULE_HOVER: '',
- REACT_SUSPENSE_REJECTED_EVENT: '',
- REACT_SUSPENSE_REJECTED_EVENT_HOVER: '',
- REACT_SUSPENSE_RESOLVED_EVENT: '',
- REACT_SUSPENSE_RESOLVED_EVENT_HOVER: '',
- REACT_SUSPENSE_UNRESOLVED_EVENT: '',
- REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: '',
- REACT_THROWN_ERROR: '',
- REACT_THROWN_ERROR_HOVER: '',
- REACT_WORK_BORDER: '',
- SCROLL_CARET: '',
- SCRUBBER_BACKGROUND: '',
- SCRUBBER_BORDER: '',
- SEARCH_RESULT_FILL: '',
- TEXT_COLOR: '',
- TEXT_DIM_COLOR: '',
- TIME_MARKER_LABEL: '',
- WARNING_BACKGROUND: '',
- WARNING_BACKGROUND_HOVER: '',
- WARNING_TEXT: '',
- WARNING_TEXT_INVERED: '',
-};
-
-export function updateColorsToMatchTheme(element: Element): boolean {
- const computedStyle = getComputedStyle(element);
-
- // Check to see if styles have been initialized...
- if (computedStyle.getPropertyValue('--color-background') == null) {
- return false;
- }
-
- COLORS = {
- BACKGROUND: computedStyle.getPropertyValue('--color-background'),
- INTERNAL_MODULE_FRAME: computedStyle.getPropertyValue(
- '--color-timeline-internal-module',
- ),
- INTERNAL_MODULE_FRAME_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-internal-module-hover',
- ),
- INTERNAL_MODULE_FRAME_TEXT: computedStyle.getPropertyValue(
- '--color-timeline-internal-module-text',
- ),
- NATIVE_EVENT: computedStyle.getPropertyValue(
- '--color-timeline-native-event',
- ),
- NATIVE_EVENT_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-native-event-hover',
- ),
- NETWORK_PRIMARY: computedStyle.getPropertyValue(
- '--color-timeline-network-primary',
- ),
- NETWORK_PRIMARY_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-network-primary-hover',
- ),
- NETWORK_SECONDARY: computedStyle.getPropertyValue(
- '--color-timeline-network-secondary',
- ),
- NETWORK_SECONDARY_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-network-secondary-hover',
- ),
- PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
- '--color-timeline-priority-background',
- ),
- PRIORITY_BORDER: computedStyle.getPropertyValue(
- '--color-timeline-priority-border',
- ),
- PRIORITY_LABEL: computedStyle.getPropertyValue('--color-text'),
- USER_TIMING: computedStyle.getPropertyValue('--color-timeline-user-timing'),
- USER_TIMING_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-user-timing-hover',
- ),
- REACT_IDLE: computedStyle.getPropertyValue('--color-timeline-react-idle'),
- REACT_IDLE_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-idle-hover',
- ),
- REACT_RENDER: computedStyle.getPropertyValue(
- '--color-timeline-react-render',
- ),
- REACT_RENDER_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-render-hover',
- ),
- REACT_RENDER_TEXT: computedStyle.getPropertyValue(
- '--color-timeline-react-render-text',
- ),
- REACT_COMMIT: computedStyle.getPropertyValue(
- '--color-timeline-react-commit',
- ),
- REACT_COMMIT_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-commit-hover',
- ),
- REACT_COMMIT_TEXT: computedStyle.getPropertyValue(
- '--color-timeline-react-commit-text',
- ),
- REACT_LAYOUT_EFFECTS: computedStyle.getPropertyValue(
- '--color-timeline-react-layout-effects',
- ),
- REACT_LAYOUT_EFFECTS_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-layout-effects-hover',
- ),
- REACT_LAYOUT_EFFECTS_TEXT: computedStyle.getPropertyValue(
- '--color-timeline-react-layout-effects-text',
- ),
- REACT_PASSIVE_EFFECTS: computedStyle.getPropertyValue(
- '--color-timeline-react-passive-effects',
- ),
- REACT_PASSIVE_EFFECTS_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-passive-effects-hover',
- ),
- REACT_PASSIVE_EFFECTS_TEXT: computedStyle.getPropertyValue(
- '--color-timeline-react-passive-effects-text',
- ),
- REACT_RESIZE_BAR: computedStyle.getPropertyValue('--color-resize-bar'),
- REACT_RESIZE_BAR_ACTIVE: computedStyle.getPropertyValue(
- '--color-resize-bar-active',
- ),
- REACT_RESIZE_BAR_BORDER: computedStyle.getPropertyValue(
- '--color-resize-bar-border',
- ),
- REACT_RESIZE_BAR_DOT: computedStyle.getPropertyValue(
- '--color-resize-bar-dot',
- ),
- REACT_SCHEDULE: computedStyle.getPropertyValue(
- '--color-timeline-react-schedule',
- ),
- REACT_SCHEDULE_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-schedule-hover',
- ),
- REACT_SUSPENSE_REJECTED_EVENT: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-rejected',
- ),
- REACT_SUSPENSE_REJECTED_EVENT_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-rejected-hover',
- ),
- REACT_SUSPENSE_RESOLVED_EVENT: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-resolved',
- ),
- REACT_SUSPENSE_RESOLVED_EVENT_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-resolved-hover',
- ),
- REACT_SUSPENSE_UNRESOLVED_EVENT: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-unresolved',
- ),
- REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-unresolved-hover',
- ),
- REACT_THROWN_ERROR: computedStyle.getPropertyValue(
- '--color-timeline-thrown-error',
- ),
- REACT_THROWN_ERROR_HOVER: computedStyle.getPropertyValue(
- '--color-timeline-thrown-error-hover',
- ),
- REACT_WORK_BORDER: computedStyle.getPropertyValue(
- '--color-timeline-react-work-border',
- ),
- SCROLL_CARET: computedStyle.getPropertyValue('--color-scroll-caret'),
- SCRUBBER_BACKGROUND: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-rejected',
- ),
- SEARCH_RESULT_FILL: computedStyle.getPropertyValue(
- '--color-timeline-react-suspense-rejected',
- ),
- SCRUBBER_BORDER: computedStyle.getPropertyValue(
- '--color-timeline-text-color',
- ),
- TEXT_COLOR: computedStyle.getPropertyValue('--color-timeline-text-color'),
- TEXT_DIM_COLOR: computedStyle.getPropertyValue(
- '--color-timeline-text-dim-color',
- ),
- TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
- WARNING_BACKGROUND: computedStyle.getPropertyValue(
- '--color-warning-background',
- ),
- WARNING_BACKGROUND_HOVER: computedStyle.getPropertyValue(
- '--color-warning-background-hover',
- ),
- WARNING_TEXT: computedStyle.getPropertyValue('--color-warning-text-color'),
- WARNING_TEXT_INVERED: computedStyle.getPropertyValue(
- '--color-warning-text-color-inverted',
- ),
- };
-
- return true;
-}
diff --git a/packages/react-devtools-timeline/src/content-views/index.js b/packages/react-devtools-timeline/src/content-views/index.js
deleted file mode 100644
index bdc102e22c55..000000000000
--- a/packages/react-devtools-timeline/src/content-views/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * 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
- */
-
-export * from './ComponentMeasuresView';
-export * from './FlamechartView';
-export * from './NativeEventsView';
-export * from './NetworkMeasuresView';
-export * from './ReactMeasuresView';
-export * from './SchedulingEventsView';
-export * from './SnapshotsView';
-export * from './SuspenseEventsView';
-export * from './ThrownErrorsView';
-export * from './TimeAxisMarkersView';
-export * from './UserTimingMarksView';
diff --git a/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-one.js b/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-one.js
deleted file mode 100644
index 5a4d18d08aa6..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-one.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * 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
- */
-
-export const outerErrorA = new Error();
-
-export const moduleStartError = new Error();
-export const innerError = new Error();
-export const moduleStopError = new Error();
-
-export const outerErrorB = new Error();
diff --git a/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-two.js b/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-two.js
deleted file mode 100644
index 82d9bd61d040..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-two.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * 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
- */
-
-export const moduleAStartError = new Error();
-export const innerErrorA = new Error();
-export const moduleAStopError = new Error();
-
-export const outerError = new Error();
-
-export const moduleBStartError = new Error();
-export const innerErrorB = new Error();
-export const moduleBStopError = new Error();
diff --git a/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js b/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js
deleted file mode 100644
index c8ba8d484996..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * 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
- */
-
-import {hslaColorToString, dimmedColor, ColorGenerator} from '../colors';
-
-describe('hslaColorToString', () => {
- it('should transform colors to strings', () => {
- expect(hslaColorToString({h: 1, s: 2, l: 3, a: 4})).toEqual(
- 'hsl(1deg 2% 3% / 4)',
- );
- expect(hslaColorToString({h: 3.14, s: 6.28, l: 1.68, a: 100})).toEqual(
- 'hsl(3.14deg 6.28% 1.68% / 100)',
- );
- });
-});
-
-describe('dimmedColor', () => {
- it('should dim luminosity using delta', () => {
- expect(dimmedColor({h: 1, s: 2, l: 3, a: 4}, 3)).toEqual({
- h: 1,
- s: 2,
- l: 0,
- a: 4,
- });
- expect(dimmedColor({h: 1, s: 2, l: 3, a: 4}, -3)).toEqual({
- h: 1,
- s: 2,
- l: 6,
- a: 4,
- });
- });
-});
-
-describe('ColorGenerator', () => {
- describe('colorForID', () => {
- it('should generate a color for an ID', () => {
- expect(new ColorGenerator().colorForID('123')).toMatchInlineSnapshot(`
- {
- "a": 1,
- "h": 190,
- "l": 80,
- "s": 67,
- }
- `);
- });
-
- it('should generate colors deterministically given an ID', () => {
- expect(new ColorGenerator().colorForID('id1')).toEqual(
- new ColorGenerator().colorForID('id1'),
- );
- expect(new ColorGenerator().colorForID('id2')).toEqual(
- new ColorGenerator().colorForID('id2'),
- );
- });
-
- it('should generate different colors for different IDs', () => {
- expect(new ColorGenerator().colorForID('id1')).not.toEqual(
- new ColorGenerator().colorForID('id2'),
- );
- });
-
- it('should return colors that have been set manually', () => {
- const generator = new ColorGenerator();
- const manualColor = {h: 1, s: 2, l: 3, a: 4};
- generator.setColorForID('id with set color', manualColor);
- expect(generator.colorForID('id with set color')).toEqual(manualColor);
- expect(generator.colorForID('some other id')).not.toEqual(manualColor);
- });
-
- it('should generate colors from fixed color spaces', () => {
- const generator = new ColorGenerator(1, 2, 3, 4);
- expect(generator.colorForID('123')).toEqual({h: 1, s: 2, l: 3, a: 4});
- expect(generator.colorForID('234')).toEqual({h: 1, s: 2, l: 3, a: 4});
- });
-
- it('should generate colors from range color spaces', () => {
- const generator = new ColorGenerator(
- {min: 0, max: 360, count: 2},
- 2,
- 3,
- 4,
- );
- expect(generator.colorForID('123')).toEqual({h: 0, s: 2, l: 3, a: 4});
- expect(generator.colorForID('234')).toEqual({h: 360, s: 2, l: 3, a: 4});
- });
- });
-});
diff --git a/packages/react-devtools-timeline/src/content-views/utils/__tests__/moduleFilters-test.js b/packages/react-devtools-timeline/src/content-views/utils/__tests__/moduleFilters-test.js
deleted file mode 100644
index 19751d48df3c..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/__tests__/moduleFilters-test.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * 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
- */
-
-import {isInternalModule} from '../moduleFilters';
-
-describe('isInternalModule', () => {
- let map;
-
- function createFlamechartStackFrame(scriptUrl, locationLine, locationColumn) {
- return {
- name: 'test',
- timestamp: 0,
- duration: 1,
- scriptUrl,
- locationLine,
- locationColumn,
- };
- }
-
- function createStackFrame(fileName, lineNumber, columnNumber) {
- return {
- columnNumber: columnNumber,
- lineNumber: lineNumber,
- fileName: fileName,
- functionName: 'test',
- source: ` at test (${fileName}:${lineNumber}:${columnNumber})`,
- };
- }
-
- beforeEach(() => {
- map = new Map();
- map.set('foo', [
- [createStackFrame('foo', 10, 0), createStackFrame('foo', 15, 100)],
- ]);
- map.set('bar', [
- [createStackFrame('bar', 10, 0), createStackFrame('bar', 15, 100)],
- [createStackFrame('bar', 20, 0), createStackFrame('bar', 25, 100)],
- ]);
- });
-
- it('should properly identify stack frames within the provided module ranges', () => {
- expect(
- isInternalModule(map, createFlamechartStackFrame('foo', 10, 0)),
- ).toBe(true);
- expect(
- isInternalModule(map, createFlamechartStackFrame('foo', 12, 35)),
- ).toBe(true);
- expect(
- isInternalModule(map, createFlamechartStackFrame('foo', 15, 100)),
- ).toBe(true);
- expect(
- isInternalModule(map, createFlamechartStackFrame('bar', 12, 0)),
- ).toBe(true);
- expect(
- isInternalModule(map, createFlamechartStackFrame('bar', 22, 125)),
- ).toBe(true);
- });
-
- it('should properly identify stack frames outside of the provided module ranges', () => {
- expect(isInternalModule(map, createFlamechartStackFrame('foo', 9, 0))).toBe(
- false,
- );
- expect(
- isInternalModule(map, createFlamechartStackFrame('foo', 15, 101)),
- ).toBe(false);
- expect(
- isInternalModule(map, createFlamechartStackFrame('bar', 17, 0)),
- ).toBe(false);
- expect(
- isInternalModule(map, createFlamechartStackFrame('baz', 12, 0)),
- ).toBe(false);
- });
-});
diff --git a/packages/react-devtools-timeline/src/content-views/utils/colors.js b/packages/react-devtools-timeline/src/content-views/utils/colors.js
deleted file mode 100644
index 63a9e6a989b0..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/colors.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * 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
- */
-
-type ColorSpace = number | {min: number, max: number, count?: number};
-
-// Docstrings from https://www.w3schools.com/css/css_colors_hsl.asp
-type HslaColor = $ReadOnly<{
- /** Hue is a degree on the color wheel from 0 to 360. 0 is red, 120 is green, and 240 is blue. */
- h: number,
- /** Saturation is a percentage value, 0% means a shade of gray, and 100% is the full color. */
- s: number,
- /** Lightness is a percentage, 0% is black, 50% is neither light or dark, 100% is white. */
- l: number,
- /** Alpha is a percentage, 0% is fully transparent, and 100 is not transparent at all. */
- a: number,
-}>;
-
-export function hslaColorToString({h, s, l, a}: HslaColor): string {
- return `hsl(${h}deg ${s}% ${l}% / ${a})`;
-}
-
-export function dimmedColor(color: HslaColor, dimDelta: number): HslaColor {
- return {
- ...color,
- l: color.l - dimDelta,
- };
-}
-
-// Source: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/platform/utilities.js;l=120
-function hashCode(string: string): number {
- // Hash algorithm for substrings is described in "Über die Komplexität der Multiplikation in
- // eingeschränkten Branchingprogrammmodellen" by Woelfe.
- // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
- const p = (1 << 30) * 4 - 5; // prime: 2^32 - 5
- const z = 0x5033d967; // 32 bits from random.org
- const z2 = 0x59d2f15d; // random odd 32 bit number
- let s = 0;
- let zi = 1;
- for (let i = 0; i < string.length; i++) {
- const xi = string.charCodeAt(i) * z2;
- s = (s + zi * xi) % p;
- zi = (zi * z) % p;
- }
- s = (s + zi * (p - 1)) % p;
- return Math.abs(s | 0);
-}
-
-function indexToValueInSpace(index: number, space: ColorSpace): number {
- if (typeof space === 'number') {
- return space;
- }
- const count = space.count || space.max - space.min;
- index %= count;
- return (
- space.min + Math.floor((index / (count - 1)) * (space.max - space.min))
- );
-}
-
-/**
- * Deterministic color generator.
- *
- * Adapted from: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/common/Color.js
- */
-export class ColorGenerator {
- _hueSpace: ColorSpace;
- _satSpace: ColorSpace;
- _lightnessSpace: ColorSpace;
- _alphaSpace: ColorSpace;
- _colors: Map;
-
- constructor(
- hueSpace?: ColorSpace,
- satSpace?: ColorSpace,
- lightnessSpace?: ColorSpace,
- alphaSpace?: ColorSpace,
- ) {
- this._hueSpace = hueSpace || {min: 0, max: 360};
- this._satSpace = satSpace || 67;
- this._lightnessSpace = lightnessSpace || 80;
- this._alphaSpace = alphaSpace || 1;
- this._colors = new Map();
- }
-
- setColorForID(id: string, color: HslaColor) {
- this._colors.set(id, color);
- }
-
- colorForID(id: string): HslaColor {
- const cachedColor = this._colors.get(id);
- if (cachedColor) {
- return cachedColor;
- }
- const color = this._generateColorForID(id);
- this._colors.set(id, color);
- return color;
- }
-
- _generateColorForID(id: string): HslaColor {
- const hash = hashCode(id);
- return {
- h: indexToValueInSpace(hash, this._hueSpace),
- s: indexToValueInSpace(hash >> 8, this._satSpace),
- l: indexToValueInSpace(hash >> 16, this._lightnessSpace),
- a: indexToValueInSpace(hash >> 24, this._alphaSpace),
- };
- }
-}
diff --git a/packages/react-devtools-timeline/src/content-views/utils/moduleFilters.js b/packages/react-devtools-timeline/src/content-views/utils/moduleFilters.js
deleted file mode 100644
index ff35f0440f8b..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/moduleFilters.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * 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
- */
-
-import type {
- FlamechartStackFrame,
- InternalModuleSourceToRanges,
-} from '../../types';
-
-import {
- CHROME_WEBSTORE_EXTENSION_ID,
- INTERNAL_EXTENSION_ID,
- LOCAL_EXTENSION_ID,
-} from 'react-devtools-shared/src/constants';
-
-export function isInternalModule(
- internalModuleSourceToRanges: InternalModuleSourceToRanges,
- flamechartStackFrame: FlamechartStackFrame,
-): boolean {
- const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame;
-
- if (scriptUrl == null || locationColumn == null || locationLine == null) {
- // This could indicate a browser-internal API like performance.mark().
- return false;
- }
-
- // Internal modules are only registered if DevTools was running when the profile was captured,
- // but DevTools should also hide its own frames to avoid over-emphasizing them.
- if (
- // Handle webpack-internal:// sources
- scriptUrl.includes('/react-devtools') ||
- scriptUrl.includes('/react_devtools') ||
- // Filter out known extension IDs
- scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) ||
- scriptUrl.includes(INTERNAL_EXTENSION_ID) ||
- scriptUrl.includes(LOCAL_EXTENSION_ID)
- // Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files.
- ) {
- return true;
- }
-
- // Filter out React internal packages.
- const ranges = internalModuleSourceToRanges.get(scriptUrl);
- if (ranges != null) {
- for (let i = 0; i < ranges.length; i++) {
- const [startStackFrame, stopStackFrame] = ranges[i];
-
- const isAfterStart =
- // $FlowFixMe[invalid-compare] -- TODO: Revealed when adding types to error-stack-parser
- locationLine > startStackFrame.lineNumber ||
- (locationLine === startStackFrame.lineNumber &&
- // $FlowFixMe[invalid-compare]
- locationColumn >= startStackFrame.columnNumber);
- const isBeforeStop =
- // $FlowFixMe[invalid-compare]
- locationLine < stopStackFrame.lineNumber ||
- (locationLine === stopStackFrame.lineNumber &&
- // $FlowFixMe[invalid-compare]
- locationColumn <= stopStackFrame.columnNumber);
-
- if (isAfterStart && isBeforeStop) {
- return true;
- }
- }
- }
-
- return false;
-}
diff --git a/packages/react-devtools-timeline/src/content-views/utils/positioning.js b/packages/react-devtools-timeline/src/content-views/utils/positioning.js
deleted file mode 100644
index 8052f86e32df..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/positioning.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect} from '../../view-base';
-
-export function positioningScaleFactor(
- intrinsicWidth: number,
- frame: Rect,
-): number {
- return frame.size.width / intrinsicWidth;
-}
-
-export function timestampToPosition(
- timestamp: number,
- scaleFactor: number,
- frame: Rect,
-): number {
- return frame.origin.x + timestamp * scaleFactor;
-}
-
-export function positionToTimestamp(
- position: number,
- scaleFactor: number,
- frame: Rect,
-): number {
- return (position - frame.origin.x) / scaleFactor;
-}
-
-export function durationToWidth(duration: number, scaleFactor: number): number {
- return duration * scaleFactor;
-}
-
-export function widthToDuration(width: number, scaleFactor: number): number {
- return width / scaleFactor;
-}
diff --git a/packages/react-devtools-timeline/src/content-views/utils/text.js b/packages/react-devtools-timeline/src/content-views/utils/text.js
deleted file mode 100644
index 30b576c83bed..000000000000
--- a/packages/react-devtools-timeline/src/content-views/utils/text.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect} from '../../view-base';
-
-import {rectEqualToRect} from '../../view-base';
-import {COLORS, FONT_SIZE, TEXT_PADDING} from '../constants';
-
-const cachedTextWidths = new Map();
-
-export function getTextWidth(
- context: CanvasRenderingContext2D,
- text: string,
-): number {
- let measuredWidth = cachedTextWidths.get(text);
- if (measuredWidth == null) {
- measuredWidth = context.measureText(text).width;
- cachedTextWidths.set(text, measuredWidth);
- }
-
- return measuredWidth as any as number;
-}
-
-export function trimText(
- context: CanvasRenderingContext2D,
- text: string,
- width: number,
-): string | null {
- const maxIndex = text.length - 1;
-
- let startIndex = 0;
- let stopIndex = maxIndex;
-
- let longestValidIndex = 0;
- let longestValidText = null;
-
- // Trimming long text could be really slow if we decrease only 1 character at a time.
- // Trimming with more of a binary search approach is faster in the worst cases.
- while (startIndex <= stopIndex) {
- const currentIndex = Math.floor((startIndex + stopIndex) / 2);
- const trimmedText =
- currentIndex === maxIndex ? text : text.slice(0, currentIndex) + '…';
-
- if (getTextWidth(context, trimmedText) <= width) {
- if (longestValidIndex < currentIndex) {
- longestValidIndex = currentIndex;
- longestValidText = trimmedText;
- }
-
- startIndex = currentIndex + 1;
- } else {
- stopIndex = currentIndex - 1;
- }
- }
-
- return longestValidText;
-}
-
-type TextConfig = {
- fillStyle?: string,
- fontSize?: number,
- textAlign?: 'left' | 'center',
-};
-
-export function drawText(
- text: string,
- context: CanvasRenderingContext2D,
- fullRect: Rect,
- drawableRect: Rect,
- config?: TextConfig,
-): void {
- const {
- fillStyle = COLORS.TEXT_COLOR,
- fontSize = FONT_SIZE,
- textAlign = 'left',
- } = config || {};
-
- if (fullRect.size.width > TEXT_PADDING * 2) {
- context.textAlign = textAlign;
- context.textBaseline = 'middle';
- context.font = `${fontSize}px sans-serif`;
-
- const {x, y} = fullRect.origin;
-
- const trimmedName = trimText(
- context,
- text,
- fullRect.size.width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
- );
-
- if (trimmedName !== null) {
- context.fillStyle = fillStyle;
-
- // Prevent text from visibly overflowing its container when clipped.
- const textOverflowsViewableArea = !rectEqualToRect(
- drawableRect,
- fullRect,
- );
- if (textOverflowsViewableArea) {
- context.save();
- context.beginPath();
- context.rect(
- drawableRect.origin.x,
- drawableRect.origin.y,
- drawableRect.size.width,
- drawableRect.size.height,
- );
- context.closePath();
- context.clip();
- }
-
- let textX;
- if (textAlign === 'center') {
- textX = x + fullRect.size.width / 2 + TEXT_PADDING - (x < 0 ? x : 0);
- } else {
- textX = x + TEXT_PADDING - (x < 0 ? x : 0);
- }
-
- const textY = y + fullRect.size.height / 2;
-
- context.fillText(trimmedName, textX, textY);
-
- if (textOverflowsViewableArea) {
- context.restore();
- }
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js b/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js
deleted file mode 100644
index 852ad542def5..000000000000
--- a/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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
- */
-
-import {createResource} from 'react-devtools-shared/src/devtools/cache';
-import {importFile} from './import-worker';
-
-import type {Resource} from 'react-devtools-shared/src/devtools/cache';
-import type {TimelineData} from './types';
-import type {ImportWorkerOutputData} from './import-worker/index';
-
-export type DataResource = Resource;
-
-export default function createDataResourceFromImportedFile(
- file: File,
-): DataResource {
- return createResource(
- () => {
- return new Promise((resolve, reject) => {
- const promise = importFile(
- file,
- ) as any as Promise;
- promise.then(data => {
- switch (data.status) {
- case 'SUCCESS':
- resolve(data.processedData);
- break;
- case 'INVALID_PROFILE_ERROR':
- resolve(data.error);
- break;
- case 'UNEXPECTED_ERROR':
- reject(data.error);
- break;
- }
- });
- });
- },
- () => file,
- {useWeakMap: true},
- );
-}
diff --git a/packages/react-devtools-timeline/src/import-worker/InvalidProfileError.js b/packages/react-devtools-timeline/src/import-worker/InvalidProfileError.js
deleted file mode 100644
index 84c11c43b9d0..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/InvalidProfileError.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * 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
- */
-
-/**
- * An error thrown when an invalid profile could not be processed.
- */
-export default class InvalidProfileError extends Error {}
diff --git a/packages/react-devtools-timeline/src/import-worker/importFile.js b/packages/react-devtools-timeline/src/import-worker/importFile.js
deleted file mode 100644
index c57f436e6fde..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/importFile.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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
- */
-
-import 'regenerator-runtime/runtime';
-
-import type {TimelineEvent} from '@elg/speedscope';
-import type {ImportWorkerOutputData} from './index';
-
-import preprocessData from './preprocessData';
-import {readInputData} from './readInputData';
-import InvalidProfileError from './InvalidProfileError';
-
-export async function importFile(file: File): Promise {
- try {
- const readFile = await readInputData(file);
- const events: TimelineEvent[] = JSON.parse(readFile);
- if (events.length === 0) {
- throw new InvalidProfileError('No profiling data found in file.');
- }
-
- const processedData = await preprocessData(events);
-
- return {
- status: 'SUCCESS',
- processedData,
- };
- } catch (error) {
- if (error instanceof InvalidProfileError) {
- return {
- status: 'INVALID_PROFILE_ERROR',
- error,
- };
- } else {
- return {
- status: 'UNEXPECTED_ERROR',
- error,
- };
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/import-worker/importFile.worker.js b/packages/react-devtools-timeline/src/import-worker/importFile.worker.js
deleted file mode 100644
index 70a32945067c..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/importFile.worker.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * 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.
- */
-
-import * as importFileModule from './importFile';
-
-export const importFile = importFileModule.importFile;
diff --git a/packages/react-devtools-timeline/src/import-worker/index.js b/packages/react-devtools-timeline/src/import-worker/index.js
deleted file mode 100644
index 7c05ec897132..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/index.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * 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
- */
-
-// This file uses workerize to load ./importFile.worker as a webworker and instanciates it,
-// exposing flow typed functions that can be used on other files.
-
-import * as importFileModule from './importFile';
-import WorkerizedImportFile from './importFile.worker';
-
-import type {TimelineData} from '../types';
-
-type ImportFileModule = typeof importFileModule;
-
-const workerizedImportFile: ImportFileModule = window.Worker
- ? WorkerizedImportFile()
- : importFileModule;
-
-export type ImportWorkerOutputData =
- | {status: 'SUCCESS', processedData: TimelineData}
- | {status: 'INVALID_PROFILE_ERROR', error: Error}
- | {status: 'UNEXPECTED_ERROR', error: Error};
-
-export type importFileFunction = (file: File) => ImportWorkerOutputData;
-
-export const importFile = (file: File): Promise =>
- workerizedImportFile.importFile(file);
diff --git a/packages/react-devtools-timeline/src/import-worker/preprocessData.js b/packages/react-devtools-timeline/src/import-worker/preprocessData.js
deleted file mode 100644
index 50a3091b8eda..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/preprocessData.js
+++ /dev/null
@@ -1,1178 +0,0 @@
-/**
- * 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
- */
-
-import {
- importFromChromeTimeline,
- Flamechart as SpeedscopeFlamechart,
-} from '@elg/speedscope';
-import type {TimelineEvent} from '@elg/speedscope';
-import type {
- ErrorStackFrame,
- BatchUID,
- Flamechart,
- Milliseconds,
- NativeEvent,
- NetworkMeasure,
- Phase,
- ReactLane,
- ReactComponentMeasure,
- ReactComponentMeasureType,
- ReactMeasure,
- ReactMeasureType,
- TimelineData,
- SchedulingEvent,
- SuspenseEvent,
- Snapshot,
-} from '../types';
-import {
- REACT_TOTAL_NUM_LANES,
- SCHEDULING_PROFILER_VERSION,
- SNAPSHOT_MAX_HEIGHT,
-} from '../constants';
-import InvalidProfileError from './InvalidProfileError';
-import {getBatchRange} from '../utils/getBatchRange';
-import ErrorStackParser from 'error-stack-parser';
-
-type MeasureStackElement = {
- type: ReactMeasureType,
- depth: number,
- measure: ReactMeasure,
- startTime: Milliseconds,
- stopTime?: Milliseconds,
-};
-
-type ProcessorState = {
- asyncProcessingPromises: Promise[],
- batchUID: BatchUID,
- currentReactComponentMeasure: ReactComponentMeasure | null,
- internalModuleCurrentStackFrame: ErrorStackFrame | null,
- internalModuleStackStringSet: Set,
- measureStack: MeasureStackElement[],
- nativeEventStack: NativeEvent[],
- nextRenderShouldGenerateNewBatchID: boolean,
- potentialLongEvents: Array<[NativeEvent, BatchUID]>,
- potentialLongNestedUpdate: SchedulingEvent | null,
- potentialLongNestedUpdates: Array<[SchedulingEvent, BatchUID]>,
- potentialSuspenseEventsOutsideOfTransition: Array<
- [SuspenseEvent, ReactLane[]],
- >,
- requestIdToNetworkMeasureMap: Map,
- uidCounter: BatchUID,
- unresolvedSuspenseEvents: Map,
-};
-
-const NATIVE_EVENT_DURATION_THRESHOLD = 20;
-const NESTED_UPDATE_DURATION_THRESHOLD = 20;
-
-const WARNING_STRINGS = {
- LONG_EVENT_HANDLER:
- 'An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work.',
- NESTED_UPDATE:
- '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).',
- SUSPEND_DURING_UPDATE:
- '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.",
-};
-
-// Exported for tests
-export function getLanesFromTransportDecimalBitmask(
- laneBitmaskString: string,
-): ReactLane[] {
- const laneBitmask = parseInt(laneBitmaskString, 10);
-
- // As negative numbers are stored in two's complement format, our bitmask
- // checks will be thrown off by them.
- if (laneBitmask < 0) {
- return [];
- }
-
- const lanes = [];
- let powersOfTwo = 0;
- while (powersOfTwo <= REACT_TOTAL_NUM_LANES) {
- if ((1 << powersOfTwo) & laneBitmask) {
- lanes.push(powersOfTwo);
- }
- powersOfTwo++;
- }
- return lanes;
-}
-
-function updateLaneToLabelMap(
- profilerData: TimelineData,
- laneLabelTuplesString: string,
-): void {
- // These marks appear multiple times in the data;
- // We only need to extact them once.
- if (profilerData.laneToLabelMap.size === 0) {
- const laneLabelTuples = laneLabelTuplesString.split(',');
- for (let laneIndex = 0; laneIndex < laneLabelTuples.length; laneIndex++) {
- // The numeric lane value (e.g. 64) isn't important.
- // The profiler parses and stores the lane's position within the bitmap,
- // (e.g. lane 1 is index 0, lane 16 is index 4).
- profilerData.laneToLabelMap.set(laneIndex, laneLabelTuples[laneIndex]);
- }
- }
-}
-
-let profilerVersion = null;
-
-function getLastType(stack: ProcessorState['measureStack']) {
- if (stack.length > 0) {
- const {type} = stack[stack.length - 1];
- return type;
- }
- return null;
-}
-
-function getDepth(stack: ProcessorState['measureStack']) {
- if (stack.length > 0) {
- const {depth, type} = stack[stack.length - 1];
- return type === 'render-idle' ? depth : depth + 1;
- }
- return 0;
-}
-
-function markWorkStarted(
- type: ReactMeasureType,
- startTime: Milliseconds,
- lanes: ReactLane[],
- currentProfilerData: TimelineData,
- state: ProcessorState,
-) {
- const {batchUID, measureStack} = state;
- const depth = getDepth(measureStack);
-
- const measure: ReactMeasure = {
- type,
- batchUID,
- depth,
- lanes,
- timestamp: startTime,
- duration: 0,
- };
-
- state.measureStack.push({depth, measure, startTime, type});
-
- // This array is pre-initialized when the batchUID is generated.
- const measures = currentProfilerData.batchUIDToMeasuresMap.get(batchUID);
- if (measures != null) {
- measures.push(measure);
- } else {
- currentProfilerData.batchUIDToMeasuresMap.set(state.batchUID, [measure]);
- }
-
- // This array is pre-initialized before processing starts.
- lanes.forEach(lane => {
- (
- currentProfilerData.laneToReactMeasureMap.get(
- lane,
- ) as any as Array
- ).push(measure);
- });
-}
-
-function markWorkCompleted(
- type: ReactMeasureType,
- stopTime: Milliseconds,
- currentProfilerData: TimelineData,
- stack: ProcessorState['measureStack'],
-) {
- if (stack.length === 0) {
- console.error(
- 'Unexpected type "%s" completed at %sms while stack is empty.',
- type,
- stopTime,
- );
- // Ignore work "completion" user timing mark that doesn't complete anything
- return;
- }
-
- const last = stack[stack.length - 1];
- if (last.type !== type) {
- console.error(
- 'Unexpected type "%s" completed at %sms before "%s" completed.',
- type,
- stopTime,
- last.type,
- );
- }
-
- // $FlowFixMe[incompatible-use]
- const {measure, startTime} = stack.pop();
- if (!measure) {
- console.error('Could not find matching measure for type "%s".', type);
- }
-
- // $FlowFixMe[cannot-write] This property should not be writable outside of this function.
- measure.duration = stopTime - startTime;
-}
-
-function throwIfIncomplete(
- type: ReactMeasureType,
- stack: ProcessorState['measureStack'],
-) {
- const lastIndex = stack.length - 1;
- if (lastIndex >= 0) {
- const last = stack[lastIndex];
- if (last.stopTime === undefined && last.type === type) {
- throw new InvalidProfileError(
- `Unexpected type "${type}" started before "${last.type}" completed.`,
- );
- }
- }
-}
-
-function processEventDispatch(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const data = event.args.data;
- const type = data.type;
-
- if (type.startsWith('react-')) {
- const stackTrace = data.stackTrace;
- if (stackTrace) {
- const topFrame = stackTrace[stackTrace.length - 1];
- if (topFrame.url.includes('/react-dom.')) {
- // Filter out fake React events dispatched by invokeGuardedCallbackDev.
- return;
- }
- }
- }
-
- // Reduce noise from events like DOMActivate, load/unload, etc. which are usually not relevant
- if (
- type === 'blur' ||
- type === 'click' ||
- type === 'input' ||
- type.startsWith('focus') ||
- type.startsWith('key') ||
- type.startsWith('mouse') ||
- type.startsWith('pointer')
- ) {
- const duration = event.dur / 1000;
-
- let depth = 0;
-
- while (state.nativeEventStack.length > 0) {
- const prevNativeEvent =
- state.nativeEventStack[state.nativeEventStack.length - 1];
- const prevStopTime = prevNativeEvent.timestamp + prevNativeEvent.duration;
-
- if (timestamp < prevStopTime) {
- depth = prevNativeEvent.depth + 1;
- break;
- } else {
- state.nativeEventStack.pop();
- }
- }
-
- const nativeEvent = {
- depth,
- duration,
- timestamp,
- type,
- warning: null,
- };
-
- // $FlowFixMe[incompatible-type]
- profilerData.nativeEvents.push(nativeEvent);
-
- // Keep track of curent event in case future ones overlap.
- // We separate them into different vertical lanes in this case.
- // $FlowFixMe[incompatible-type]
- state.nativeEventStack.push(nativeEvent);
- }
-}
-
-function processResourceFinish(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const requestId = event.args.data.requestId;
- const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
- if (networkMeasure != null) {
- networkMeasure.finishTimestamp = timestamp;
- if (networkMeasure.firstReceivedDataTimestamp === 0) {
- networkMeasure.firstReceivedDataTimestamp = timestamp;
- }
- if (networkMeasure.lastReceivedDataTimestamp === 0) {
- networkMeasure.lastReceivedDataTimestamp = timestamp;
- }
-
- // Clean up now that the resource is done.
- state.requestIdToNetworkMeasureMap.delete(event.args.data.requestId);
- }
-}
-
-function processResourceReceivedData(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const requestId = event.args.data.requestId;
- const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
- if (networkMeasure != null) {
- if (networkMeasure.firstReceivedDataTimestamp === 0) {
- networkMeasure.firstReceivedDataTimestamp = timestamp;
- }
- networkMeasure.lastReceivedDataTimestamp = timestamp;
- networkMeasure.finishTimestamp = timestamp;
- }
-}
-
-function processResourceReceiveResponse(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const requestId = event.args.data.requestId;
- const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
- if (networkMeasure != null) {
- networkMeasure.receiveResponseTimestamp = timestamp;
- }
-}
-
-function processScreenshot(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const encodedSnapshot = event.args.snapshot; // Base 64 encoded
-
- const snapshot: Snapshot = {
- height: 0,
- image: null,
- imageSource: `data:image/png;base64,${encodedSnapshot}`,
- timestamp,
- width: 0,
- };
-
- // Delay processing until we've extracted snapshot dimensions.
- let resolveFn = null as any as Function;
- state.asyncProcessingPromises.push(
- new Promise(resolve => {
- resolveFn = resolve;
- }),
- );
-
- // Parse the Base64 image data to determine native size.
- // This will be used later to scale for display within the thumbnail strip.
- fetch(snapshot.imageSource)
- .then(response => response.blob())
- .then(blob => {
- // $FlowFixMe[cannot-resolve-name] createImageBitmap
- createImageBitmap(blob).then(bitmap => {
- snapshot.height = bitmap.height;
- snapshot.width = bitmap.width;
-
- resolveFn();
- });
- });
-
- profilerData.snapshots.push(snapshot);
-}
-
-function processResourceSendRequest(
- event: TimelineEvent,
- timestamp: Milliseconds,
- profilerData: TimelineData,
- state: ProcessorState,
-) {
- const data = event.args.data;
- const requestId = data.requestId;
-
- const availableDepths = new Array(
- state.requestIdToNetworkMeasureMap.size + 1,
- ).fill(true);
- state.requestIdToNetworkMeasureMap.forEach(({depth}) => {
- availableDepths[depth] = false;
- });
-
- let depth = 0;
- for (let i = 0; i < availableDepths.length; i++) {
- if (availableDepths[i]) {
- depth = i;
- break;
- }
- }
-
- const networkMeasure: NetworkMeasure = {
- depth,
- finishTimestamp: 0,
- firstReceivedDataTimestamp: 0,
- lastReceivedDataTimestamp: 0,
- requestId,
- requestMethod: data.requestMethod,
- priority: data.priority,
- sendRequestTimestamp: timestamp,
- receiveResponseTimestamp: 0,
- url: data.url,
- };
-
- state.requestIdToNetworkMeasureMap.set(requestId, networkMeasure);
-
- profilerData.networkMeasures.push(networkMeasure);
- networkMeasure.sendRequestTimestamp = timestamp;
-}
-
-function processTimelineEvent(
- event: TimelineEvent,
- /** Finalized profiler data up to `event`. May be mutated. */
- currentProfilerData: TimelineData,
- /** Intermediate processor state. May be mutated. */
- state: ProcessorState,
-) {
- const {cat, name, ts, ph} = event;
-
- const startTime = (ts - currentProfilerData.startTime) / 1000;
-
- switch (cat) {
- case 'disabled-by-default-devtools.screenshot':
- processScreenshot(event, startTime, currentProfilerData, state);
- break;
- case 'devtools.timeline':
- switch (name) {
- case 'EventDispatch':
- processEventDispatch(event, startTime, currentProfilerData, state);
- break;
- case 'ResourceFinish':
- processResourceFinish(event, startTime, currentProfilerData, state);
- break;
- case 'ResourceReceivedData':
- processResourceReceivedData(
- event,
- startTime,
- currentProfilerData,
- state,
- );
- break;
- case 'ResourceReceiveResponse':
- processResourceReceiveResponse(
- event,
- startTime,
- currentProfilerData,
- state,
- );
- break;
- case 'ResourceSendRequest':
- processResourceSendRequest(
- event,
- startTime,
- currentProfilerData,
- state,
- );
- break;
- }
- break;
- case 'blink.user_timing':
- if (name.startsWith('--react-version-')) {
- const [reactVersion] = name.slice(16).split('-');
- currentProfilerData.reactVersion = reactVersion;
- } else if (name.startsWith('--profiler-version-')) {
- const [versionString] = name.slice(19).split('-');
- profilerVersion = parseInt(versionString, 10);
- if (profilerVersion !== SCHEDULING_PROFILER_VERSION) {
- throw new InvalidProfileError(
- `This version of profiling data (${versionString}) is not supported by the current profiler.`,
- );
- }
- } else if (name.startsWith('--react-lane-labels-')) {
- const [laneLabelTuplesString] = name.slice(20).split('-');
- updateLaneToLabelMap(currentProfilerData, laneLabelTuplesString);
- } else if (name.startsWith('--component-')) {
- processReactComponentMeasure(
- name,
- startTime,
- currentProfilerData,
- state,
- );
- } else if (name.startsWith('--schedule-render-')) {
- const [laneBitmaskString] = name.slice(18).split('-');
-
- currentProfilerData.schedulingEvents.push({
- type: 'schedule-render',
- lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
- timestamp: startTime,
- warning: null,
- });
- } else if (name.startsWith('--schedule-forced-update-')) {
- const [laneBitmaskString, componentName] = name.slice(25).split('-');
-
- const forceUpdateEvent: SchedulingEvent = {
- type: 'schedule-force-update',
- lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
- componentName,
- timestamp: startTime,
- warning: null,
- };
-
- // If this is a nested update, make a note of it.
- // Once we're done processing events, we'll check to see if it was a long update and warn about it.
- if (state.measureStack.find(({type}) => type === 'commit')) {
- state.potentialLongNestedUpdate = forceUpdateEvent;
- }
-
- currentProfilerData.schedulingEvents.push(forceUpdateEvent);
- } else if (name.startsWith('--schedule-state-update-')) {
- const [laneBitmaskString, componentName] = name.slice(24).split('-');
-
- const stateUpdateEvent: SchedulingEvent = {
- type: 'schedule-state-update',
- lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
- componentName,
- timestamp: startTime,
- warning: null,
- };
-
- // If this is a nested update, make a note of it.
- // Once we're done processing events, we'll check to see if it was a long update and warn about it.
- if (state.measureStack.find(({type}) => type === 'commit')) {
- state.potentialLongNestedUpdate = stateUpdateEvent;
- }
-
- currentProfilerData.schedulingEvents.push(stateUpdateEvent);
- } else if (name.startsWith('--error-')) {
- const [componentName, phase, message] = name.slice(8).split('-');
-
- currentProfilerData.thrownErrors.push({
- componentName,
- message,
- phase: phase as any as Phase,
- timestamp: startTime,
- type: 'thrown-error',
- });
- } else if (name.startsWith('--suspense-suspend-')) {
- const [id, componentName, phase, laneBitmaskString, promiseName] = name
- .slice(19)
- .split('-');
- const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
-
- const availableDepths = new Array(
- state.unresolvedSuspenseEvents.size + 1,
- ).fill(true);
- state.unresolvedSuspenseEvents.forEach(({depth}) => {
- availableDepths[depth] = false;
- });
-
- let depth = 0;
- for (let i = 0; i < availableDepths.length; i++) {
- if (availableDepths[i]) {
- depth = i;
- break;
- }
- }
-
- // TODO (timeline) Maybe we should calculate depth in post,
- // so unresolved Suspense requests don't take up space.
- // We can't know if they'll be resolved or not at this point.
- // We'll just give them a default (fake) duration width.
-
- const suspenseEvent: SuspenseEvent = {
- componentName,
- depth,
- duration: null,
- id,
- phase: phase as any as Phase,
- promiseName: promiseName || null,
- resolution: 'unresolved',
- timestamp: startTime,
- type: 'suspense',
- warning: null,
- };
-
- if (phase === 'update') {
- // If a component suspended during an update, we should verify that it was during a transition.
- // We need the lane metadata to verify this though.
- // Since that data is only logged during commit, we may not have it yet.
- // Store these events for post-processing then.
- state.potentialSuspenseEventsOutsideOfTransition.push([
- suspenseEvent,
- lanes,
- ]);
- }
-
- currentProfilerData.suspenseEvents.push(suspenseEvent);
- state.unresolvedSuspenseEvents.set(id, suspenseEvent);
- } else if (name.startsWith('--suspense-resolved-')) {
- const [id] = name.slice(20).split('-');
- const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
- if (suspenseEvent != null) {
- state.unresolvedSuspenseEvents.delete(id);
-
- suspenseEvent.duration = startTime - suspenseEvent.timestamp;
- suspenseEvent.resolution = 'resolved';
- }
- } else if (name.startsWith('--suspense-rejected-')) {
- const [id] = name.slice(20).split('-');
- const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
- if (suspenseEvent != null) {
- state.unresolvedSuspenseEvents.delete(id);
-
- suspenseEvent.duration = startTime - suspenseEvent.timestamp;
- suspenseEvent.resolution = 'rejected';
- }
- } else if (name.startsWith('--render-start-')) {
- if (state.nextRenderShouldGenerateNewBatchID) {
- state.nextRenderShouldGenerateNewBatchID = false;
- state.batchUID = state.uidCounter++ as any as BatchUID;
- }
-
- // If this render is the result of a nested update, make a note of it.
- // Once we're done processing events, we'll check to see if it was a long update and warn about it.
- if (state.potentialLongNestedUpdate !== null) {
- state.potentialLongNestedUpdates.push([
- state.potentialLongNestedUpdate,
- state.batchUID,
- ]);
- state.potentialLongNestedUpdate = null;
- }
-
- const [laneBitmaskString] = name.slice(15).split('-');
-
- throwIfIncomplete('render', state.measureStack);
- if (getLastType(state.measureStack) !== 'render-idle') {
- markWorkStarted(
- 'render-idle',
- startTime,
- getLanesFromTransportDecimalBitmask(laneBitmaskString),
- currentProfilerData,
- state,
- );
- }
- markWorkStarted(
- 'render',
- startTime,
- getLanesFromTransportDecimalBitmask(laneBitmaskString),
- currentProfilerData,
- state,
- );
-
- for (let i = 0; i < state.nativeEventStack.length; i++) {
- const nativeEvent = state.nativeEventStack[i];
- const stopTime = nativeEvent.timestamp + nativeEvent.duration;
-
- // If React work was scheduled during an event handler, and the event had a long duration,
- // it might be because the React render was long and stretched the event.
- // It might also be that the React work was short and that something else stretched the event.
- // Make a note of this event for now and we'll examine the batch of React render work later.
- // (We can't know until we're done processing the React update anyway.)
- if (stopTime > startTime) {
- state.potentialLongEvents.push([nativeEvent, state.batchUID]);
- }
- }
- } else if (
- name.startsWith('--render-stop') ||
- name.startsWith('--render-yield')
- ) {
- markWorkCompleted(
- 'render',
- startTime,
- currentProfilerData,
- state.measureStack,
- );
- } else if (name.startsWith('--commit-start-')) {
- state.nextRenderShouldGenerateNewBatchID = true;
- const [laneBitmaskString] = name.slice(15).split('-');
-
- markWorkStarted(
- 'commit',
- startTime,
- getLanesFromTransportDecimalBitmask(laneBitmaskString),
- currentProfilerData,
- state,
- );
- } else if (name.startsWith('--commit-stop')) {
- markWorkCompleted(
- 'commit',
- startTime,
- currentProfilerData,
- state.measureStack,
- );
- markWorkCompleted(
- 'render-idle',
- startTime,
- currentProfilerData,
- state.measureStack,
- );
- } else if (name.startsWith('--layout-effects-start-')) {
- const [laneBitmaskString] = name.slice(23).split('-');
-
- markWorkStarted(
- 'layout-effects',
- startTime,
- getLanesFromTransportDecimalBitmask(laneBitmaskString),
- currentProfilerData,
- state,
- );
- } else if (name.startsWith('--layout-effects-stop')) {
- markWorkCompleted(
- 'layout-effects',
- startTime,
- currentProfilerData,
- state.measureStack,
- );
- } else if (name.startsWith('--passive-effects-start-')) {
- const [laneBitmaskString] = name.slice(24).split('-');
-
- markWorkStarted(
- 'passive-effects',
- startTime,
- getLanesFromTransportDecimalBitmask(laneBitmaskString),
- currentProfilerData,
- state,
- );
- } else if (name.startsWith('--passive-effects-stop')) {
- markWorkCompleted(
- 'passive-effects',
- startTime,
- currentProfilerData,
- state.measureStack,
- );
- } else if (name.startsWith('--react-internal-module-start-')) {
- const stackFrameStart = name.slice(30);
-
- if (!state.internalModuleStackStringSet.has(stackFrameStart)) {
- state.internalModuleStackStringSet.add(stackFrameStart);
-
- const parsedStackFrameStart = parseStackFrame(stackFrameStart);
-
- state.internalModuleCurrentStackFrame = parsedStackFrameStart;
- }
- } else if (name.startsWith('--react-internal-module-stop-')) {
- const stackFrameStop = name.slice(29);
-
- if (!state.internalModuleStackStringSet.has(stackFrameStop)) {
- state.internalModuleStackStringSet.add(stackFrameStop);
-
- const parsedStackFrameStop = parseStackFrame(stackFrameStop);
-
- if (
- parsedStackFrameStop !== null &&
- state.internalModuleCurrentStackFrame !== null
- ) {
- const parsedStackFrameStart = state.internalModuleCurrentStackFrame;
-
- state.internalModuleCurrentStackFrame = null;
-
- const range = [parsedStackFrameStart, parsedStackFrameStop];
- const ranges = currentProfilerData.internalModuleSourceToRanges.get(
- parsedStackFrameStart.fileName,
- );
- if (ranges == null) {
- currentProfilerData.internalModuleSourceToRanges.set(
- parsedStackFrameStart.fileName,
- [range],
- );
- } else {
- ranges.push(range);
- }
- }
- }
- } else if (ph === 'R' || ph === 'n') {
- // User Timing mark
- currentProfilerData.otherUserTimingMarks.push({
- name,
- timestamp: startTime,
- });
- } else if (ph === 'b') {
- // TODO: Begin user timing measure
- } else if (ph === 'e') {
- // TODO: End user timing measure
- } else if (ph === 'i' || ph === 'I') {
- // Instant events.
- // Note that the capital "I" is a deprecated value that exists in Chrome Canary traces.
- } else {
- throw new InvalidProfileError(
- `Unrecognized event ${JSON.stringify(
- event,
- )}! This is likely a bug in this profiler tool.`,
- );
- }
- break;
- }
-}
-
-function assertNoOverlappingComponentMeasure(state: ProcessorState) {
- if (state.currentReactComponentMeasure !== null) {
- console.error(
- 'Component measure started while another measure in progress:',
- state.currentReactComponentMeasure,
- );
- }
-}
-
-function assertCurrentComponentMeasureType(
- state: ProcessorState,
- type: ReactComponentMeasureType,
-): void {
- if (state.currentReactComponentMeasure === null) {
- console.error(
- `Component measure type "${type}" stopped while no measure was in progress`,
- );
- } else if (state.currentReactComponentMeasure.type !== type) {
- console.error(
- `Component measure type "${type}" stopped while type ${state.currentReactComponentMeasure.type} in progress`,
- );
- }
-}
-
-function processReactComponentMeasure(
- name: string,
- startTime: Milliseconds,
- currentProfilerData: TimelineData,
- state: ProcessorState,
-): void {
- if (name.startsWith('--component-render-start-')) {
- const [componentName] = name.slice(25).split('-');
-
- assertNoOverlappingComponentMeasure(state);
-
- state.currentReactComponentMeasure = {
- componentName,
- timestamp: startTime,
- duration: 0,
- type: 'render',
- warning: null,
- };
- } else if (name === '--component-render-stop') {
- assertCurrentComponentMeasureType(state, 'render');
-
- if (state.currentReactComponentMeasure !== null) {
- const componentMeasure = state.currentReactComponentMeasure;
- componentMeasure.duration = startTime - componentMeasure.timestamp;
-
- state.currentReactComponentMeasure = null;
-
- currentProfilerData.componentMeasures.push(componentMeasure);
- }
- } else if (name.startsWith('--component-layout-effect-mount-start-')) {
- const [componentName] = name.slice(38).split('-');
-
- assertNoOverlappingComponentMeasure(state);
-
- state.currentReactComponentMeasure = {
- componentName,
- timestamp: startTime,
- duration: 0,
- type: 'layout-effect-mount',
- warning: null,
- };
- } else if (name === '--component-layout-effect-mount-stop') {
- assertCurrentComponentMeasureType(state, 'layout-effect-mount');
-
- if (state.currentReactComponentMeasure !== null) {
- const componentMeasure = state.currentReactComponentMeasure;
- componentMeasure.duration = startTime - componentMeasure.timestamp;
-
- state.currentReactComponentMeasure = null;
-
- currentProfilerData.componentMeasures.push(componentMeasure);
- }
- } else if (name.startsWith('--component-layout-effect-unmount-start-')) {
- const [componentName] = name.slice(40).split('-');
-
- assertNoOverlappingComponentMeasure(state);
-
- state.currentReactComponentMeasure = {
- componentName,
- timestamp: startTime,
- duration: 0,
- type: 'layout-effect-unmount',
- warning: null,
- };
- } else if (name === '--component-layout-effect-unmount-stop') {
- assertCurrentComponentMeasureType(state, 'layout-effect-unmount');
-
- if (state.currentReactComponentMeasure !== null) {
- const componentMeasure = state.currentReactComponentMeasure;
- componentMeasure.duration = startTime - componentMeasure.timestamp;
-
- state.currentReactComponentMeasure = null;
-
- currentProfilerData.componentMeasures.push(componentMeasure);
- }
- } else if (name.startsWith('--component-passive-effect-mount-start-')) {
- const [componentName] = name.slice(39).split('-');
-
- assertNoOverlappingComponentMeasure(state);
-
- state.currentReactComponentMeasure = {
- componentName,
- timestamp: startTime,
- duration: 0,
- type: 'passive-effect-mount',
- warning: null,
- };
- } else if (name === '--component-passive-effect-mount-stop') {
- assertCurrentComponentMeasureType(state, 'passive-effect-mount');
-
- if (state.currentReactComponentMeasure !== null) {
- const componentMeasure = state.currentReactComponentMeasure;
- componentMeasure.duration = startTime - componentMeasure.timestamp;
-
- state.currentReactComponentMeasure = null;
-
- currentProfilerData.componentMeasures.push(componentMeasure);
- }
- } else if (name.startsWith('--component-passive-effect-unmount-start-')) {
- const [componentName] = name.slice(41).split('-');
-
- assertNoOverlappingComponentMeasure(state);
-
- state.currentReactComponentMeasure = {
- componentName,
- timestamp: startTime,
- duration: 0,
- type: 'passive-effect-unmount',
- warning: null,
- };
- } else if (name === '--component-passive-effect-unmount-stop') {
- assertCurrentComponentMeasureType(state, 'passive-effect-unmount');
-
- if (state.currentReactComponentMeasure !== null) {
- const componentMeasure = state.currentReactComponentMeasure;
- componentMeasure.duration = startTime - componentMeasure.timestamp;
-
- state.currentReactComponentMeasure = null;
-
- currentProfilerData.componentMeasures.push(componentMeasure);
- }
- }
-}
-
-function preprocessFlamechart(rawData: TimelineEvent[]): Flamechart {
- let parsedData;
- try {
- parsedData = importFromChromeTimeline(rawData, 'react-devtools');
- } catch (error) {
- // Assume any Speedscope errors are caused by bad profiles
- const errorToRethrow = new InvalidProfileError(error.message);
- errorToRethrow.stack = error.stack;
- throw errorToRethrow;
- }
-
- const profile = parsedData.profiles[0]; // TODO: Choose the main CPU thread only
-
- const speedscopeFlamechart = new SpeedscopeFlamechart({
- // $FlowFixMe[method-unbinding]
- getTotalWeight: profile.getTotalWeight.bind(profile),
- // $FlowFixMe[method-unbinding]
- forEachCall: profile.forEachCall.bind(profile),
- // $FlowFixMe[method-unbinding]
- formatValue: profile.formatValue.bind(profile),
- getColorBucketForFrame: () => 0,
- });
-
- const flamechart: Flamechart = speedscopeFlamechart.getLayers().map(layer =>
- layer.map(
- ({
- start,
- end,
- node: {
- frame: {name, file, line, col},
- },
- }) => ({
- name,
- timestamp: start / 1000,
- duration: (end - start) / 1000,
- scriptUrl: file,
- locationLine: line,
- locationColumn: col,
- }),
- ),
- );
-
- return flamechart;
-}
-
-function parseStackFrame(stackFrame: string): ErrorStackFrame | null {
- const error = new Error();
- error.stack = stackFrame;
-
- const frames = ErrorStackParser.parse(error);
-
- return frames.length === 1 ? frames[0] : null;
-}
-
-export default async function preprocessData(
- timeline: TimelineEvent[],
-): Promise {
- const flamechart = preprocessFlamechart(timeline);
-
- const laneToReactMeasureMap: Map> = new Map();
- for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
- laneToReactMeasureMap.set(lane, []);
- }
-
- const profilerData: TimelineData = {
- batchUIDToMeasuresMap: new Map(),
- componentMeasures: [],
- duration: 0,
- flamechart,
- internalModuleSourceToRanges: new Map(),
- laneToLabelMap: new Map(),
- laneToReactMeasureMap,
- nativeEvents: [],
- networkMeasures: [],
- otherUserTimingMarks: [],
- reactVersion: null,
- schedulingEvents: [],
- snapshots: [],
- snapshotHeight: 0,
- startTime: 0,
- suspenseEvents: [],
- thrownErrors: [],
- };
-
- // Sort `timeline`. JSON Array Format trace events need not be ordered. See:
- // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.f2f0yd51wi15
- timeline = timeline.filter(Boolean).sort((a, b) => (a.ts > b.ts ? 1 : -1));
-
- // Events displayed in flamechart have timestamps relative to the profile
- // event's startTime. Source: https://github.com/v8/v8/blob/44bd8fd7/src/inspector/js_protocol.json#L1486
- //
- // We'll thus expect there to be a 'Profile' event; if there is not one, we
- // can deduce that there are no flame chart events. As we expect React
- // scheduling profiling user timing marks to be recorded together with browser
- // flame chart events, we can futher deduce that the data is invalid and we
- // don't bother finding React events.
- const indexOfProfileEvent = timeline.findIndex(
- event => event.name === 'Profile',
- );
- if (indexOfProfileEvent === -1) {
- return profilerData;
- }
-
- // Use Profile event's `startTime` as the start time to align with flame chart.
- // TODO: Remove assumption that there'll only be 1 'Profile' event. If this
- // assumption does not hold, the chart may start at the wrong time.
- profilerData.startTime = timeline[indexOfProfileEvent].args.data.startTime;
- profilerData.duration =
- (timeline[timeline.length - 1].ts - profilerData.startTime) / 1000;
-
- const state: ProcessorState = {
- asyncProcessingPromises: [],
- batchUID: 0,
- currentReactComponentMeasure: null,
- internalModuleCurrentStackFrame: null,
- internalModuleStackStringSet: new Set(),
- measureStack: [],
- nativeEventStack: [],
- nextRenderShouldGenerateNewBatchID: true,
- potentialLongEvents: [],
- potentialLongNestedUpdate: null,
- potentialLongNestedUpdates: [],
- potentialSuspenseEventsOutsideOfTransition: [],
- requestIdToNetworkMeasureMap: new Map(),
- uidCounter: 0,
- unresolvedSuspenseEvents: new Map(),
- };
-
- timeline.forEach(event => processTimelineEvent(event, profilerData, state));
-
- if (profilerVersion === null) {
- if (
- profilerData.schedulingEvents.length === 0 &&
- profilerData.batchUIDToMeasuresMap.size === 0
- ) {
- // No profiler version could indicate data was logged using an older build of React,
- // before an explicitly profiler version was included in the logging data.
- // But it could also indicate that the website was either not using React, or using a production build.
- // The easiest way to check for this case is to see if the data contains any scheduled updates or render work.
- throw new InvalidProfileError(
- 'No React marks were found in the provided profile.' +
- ' Please provide profiling data from an React application running in development or profiling mode.',
- );
- }
-
- throw new InvalidProfileError(
- `This version of profiling data is not supported by the current profiler.`,
- );
- }
-
- // Validate that all events and measures are complete
- const {measureStack} = state;
- if (measureStack.length > 0) {
- console.error('Incomplete events or measures', measureStack);
- }
-
- // Check for warnings.
- state.potentialLongEvents.forEach(([nativeEvent, batchUID]) => {
- // See how long the subsequent batch of React work was.
- // Ignore any work that was already started.
- const [startTime, stopTime] = getBatchRange(
- batchUID,
- profilerData,
- nativeEvent.timestamp,
- );
- if (stopTime - startTime > NATIVE_EVENT_DURATION_THRESHOLD) {
- nativeEvent.warning = WARNING_STRINGS.LONG_EVENT_HANDLER;
- }
- });
- state.potentialLongNestedUpdates.forEach(([schedulingEvent, batchUID]) => {
- // See how long the subsequent batch of React work was.
- const [startTime, stopTime] = getBatchRange(batchUID, profilerData);
- if (stopTime - startTime > NESTED_UPDATE_DURATION_THRESHOLD) {
- // Don't warn about transition updates scheduled during the commit phase.
- // e.g. useTransition, useDeferredValue
- // These are allowed to be long-running.
- if (
- !schedulingEvent.lanes.some(
- lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
- )
- ) {
- // FIXME: This warning doesn't account for "nested updates" that are
- // spawned by useDeferredValue. Disabling temporarily until we figure
- // out the right way to handle this.
- // schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE;
- }
- }
- });
- state.potentialSuspenseEventsOutsideOfTransition.forEach(
- ([suspenseEvent, lanes]) => {
- // HACK This is a bit gross but the numeric lane value might change between render versions.
- if (
- !lanes.some(
- lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
- )
- ) {
- suspenseEvent.warning = WARNING_STRINGS.SUSPEND_DURING_UPDATE;
- }
- },
- );
-
- // Wait for any async processing to complete before returning.
- // Since processing is done in a worker, async work must complete before data is serialized and returned.
- await Promise.all(state.asyncProcessingPromises);
-
- // Now that all images have been loaded, let's figure out the display size we're going to use for our thumbnails:
- // both the ones rendered to the canvas and the ones shown on hover.
- if (profilerData.snapshots.length > 0) {
- // NOTE We assume a static window size here, which is not necessarily true but should be for most cases.
- // Regardless, Chrome also sets a single size/ratio and stick with it- so we'll do the same.
- const snapshot = profilerData.snapshots[0];
-
- profilerData.snapshotHeight = Math.min(
- snapshot.height,
- SNAPSHOT_MAX_HEIGHT,
- );
- }
-
- return profilerData;
-}
diff --git a/packages/react-devtools-timeline/src/import-worker/readInputData.js b/packages/react-devtools-timeline/src/import-worker/readInputData.js
deleted file mode 100644
index d889dbb5240b..000000000000
--- a/packages/react-devtools-timeline/src/import-worker/readInputData.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * 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
- */
-
-import nullthrows from 'nullthrows';
-import InvalidProfileError from './InvalidProfileError';
-
-export const readInputData = (file: File): Promise => {
- if (!file.name.endsWith('.json')) {
- throw new InvalidProfileError(
- 'Invalid file type. Only JSON performance profiles are supported',
- );
- }
-
- const fileReader = new FileReader();
-
- return new Promise((resolve, reject) => {
- fileReader.onload = () => {
- const result = nullthrows(fileReader.result);
- if (typeof result === 'string') {
- resolve(result);
- }
- reject(new InvalidProfileError('Input file was not read as a string'));
- };
-
- fileReader.onerror = () => reject(fileReader.error);
-
- fileReader.readAsText(file);
- });
-};
diff --git a/packages/react-devtools-timeline/src/timelineCache.js b/packages/react-devtools-timeline/src/timelineCache.js
deleted file mode 100644
index 78b2516d8e3e..000000000000
--- a/packages/react-devtools-timeline/src/timelineCache.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * 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
- */
-
-import type {
- Thenable,
- FulfilledThenable,
- RejectedThenable,
-} from 'shared/ReactTypes';
-import type {TimelineData} from './types';
-
-import * as React from 'react';
-
-import {importFile as importFileWorker} from './import-worker';
-
-// This is intentionally a module-level Map, rather than a React-managed one.
-// Otherwise, refreshing the inspected element cache would also clear this cache.
-// Profiler file contents are static anyway.
-const fileNameToProfilerDataMap: Map<
- string,
- Thenable,
-> = new Map();
-
-function readRecord(record: Thenable): T | Error {
- if (typeof React.use === 'function') {
- try {
- // eslint-disable-next-line react-hooks-published/rules-of-hooks
- return React.use(record);
- } catch (x) {
- if (record.status === 'rejected') {
- return record.reason as any;
- }
- throw x;
- }
- }
- if (record.status === 'fulfilled') {
- return record.value;
- } else if (record.status === 'rejected') {
- return record.reason as any;
- } else {
- throw record;
- }
-}
-
-export function importFile(file: File): TimelineData | Error {
- const fileName = file.name;
- let record = fileNameToProfilerDataMap.get(fileName);
-
- if (!record) {
- const callbacks = new Set<(value: any) => mixed>();
- const rejectCallbacks = new Set<(reason: mixed) => mixed>();
- const thenable: Thenable = {
- status: 'pending',
- value: null,
- reason: null,
- then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
- callbacks.add(callback);
- rejectCallbacks.add(reject);
- },
-
- // Optional property used by Timeline:
- displayName: `Importing file "${fileName}"`,
- };
-
- const wake = () => {
- // This assumes they won't throw.
- callbacks.forEach(callback => callback((thenable as any).value));
- callbacks.clear();
- rejectCallbacks.clear();
- };
- const wakeRejections = () => {
- // This assumes they won't throw.
- rejectCallbacks.forEach(callback => callback((thenable as any).reason));
- rejectCallbacks.clear();
- callbacks.clear();
- };
-
- record = thenable;
-
- importFileWorker(file).then(data => {
- switch (data.status) {
- case 'SUCCESS':
- const fulfilledThenable: FulfilledThenable =
- thenable as any;
- fulfilledThenable.status = 'fulfilled';
- fulfilledThenable.value = data.processedData;
- wake();
- break;
- case 'INVALID_PROFILE_ERROR':
- case 'UNEXPECTED_ERROR':
- const rejectedThenable: RejectedThenable =
- thenable as any;
- rejectedThenable.status = 'rejected';
- rejectedThenable.reason = data.error;
- wakeRejections();
- break;
- }
- });
-
- fileNameToProfilerDataMap.set(fileName, record);
- }
-
- const response = readRecord(record);
- return response;
-}
diff --git a/packages/react-devtools-timeline/src/types.js b/packages/react-devtools-timeline/src/types.js
deleted file mode 100644
index 7ba6f935f2ac..000000000000
--- a/packages/react-devtools-timeline/src/types.js
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * 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
- */
-import type {StackFrame as ErrorStackFrame} from 'error-stack-parser';
-import type {ScrollState} from './view-base/utils/scrollState';
-
-// Source: https://github.com/facebook/flow/issues/4002#issuecomment-323612798
-// eslint-disable-next-line no-unused-vars
-type Return_) => R> = R;
-/** Get return type of a function. */
-export type Return = Return_;
-
-// Project types
-export type {ErrorStackFrame};
-
-export type Milliseconds = number;
-
-export type ReactLane = number;
-
-export type NativeEvent = {
- +depth: number,
- +duration: Milliseconds,
- +timestamp: Milliseconds,
- +type: string,
- warning: string | null,
-};
-
-type BaseReactEvent = {
- +componentName?: string,
- +timestamp: Milliseconds,
- warning: string | null,
-};
-
-type BaseReactScheduleEvent = {
- ...BaseReactEvent,
- +lanes: ReactLane[],
-};
-export type ReactScheduleRenderEvent = {
- ...BaseReactScheduleEvent,
- +type: 'schedule-render',
-};
-export type ReactScheduleStateUpdateEvent = {
- ...BaseReactScheduleEvent,
- +componentStack?: string,
- +type: 'schedule-state-update',
-};
-export type ReactScheduleForceUpdateEvent = {
- ...BaseReactScheduleEvent,
- +type: 'schedule-force-update',
-};
-
-export type Phase = 'mount' | 'update';
-
-export type SuspenseEvent = {
- ...BaseReactEvent,
- depth: number,
- duration: number | null,
- +id: string,
- +phase: Phase | null,
- promiseName: string | null,
- resolution: 'rejected' | 'resolved' | 'unresolved',
- +type: 'suspense',
-};
-
-export type ThrownError = {
- +componentName?: string,
- +message: string,
- +phase: Phase,
- +timestamp: Milliseconds,
- +type: 'thrown-error',
-};
-
-export type SchedulingEvent =
- | ReactScheduleRenderEvent
- | ReactScheduleStateUpdateEvent
- | ReactScheduleForceUpdateEvent;
-export type SchedulingEventType = SchedulingEvent['type'];
-
-export type ReactMeasureType =
- | 'commit'
- // render-idle: A measure spanning the time when a render starts, through all
- // yields and restarts, and ends when commit stops OR render is cancelled.
- | 'render-idle'
- | 'render'
- | 'layout-effects'
- | 'passive-effects';
-
-export type BatchUID = number;
-
-export type ReactMeasure = {
- +type: ReactMeasureType,
- +lanes: ReactLane[],
- +timestamp: Milliseconds,
- +duration: Milliseconds,
- +batchUID: BatchUID,
- +depth: number,
-};
-
-export type NetworkMeasure = {
- +depth: number,
- finishTimestamp: Milliseconds,
- firstReceivedDataTimestamp: Milliseconds,
- lastReceivedDataTimestamp: Milliseconds,
- priority: string,
- receiveResponseTimestamp: Milliseconds,
- +requestId: string,
- requestMethod: string,
- sendRequestTimestamp: Milliseconds,
- url: string,
-};
-
-export type ReactComponentMeasureType =
- | 'render'
- | 'layout-effect-mount'
- | 'layout-effect-unmount'
- | 'passive-effect-mount'
- | 'passive-effect-unmount';
-
-export type ReactComponentMeasure = {
- +componentName: string,
- duration: Milliseconds,
- +timestamp: Milliseconds,
- +type: ReactComponentMeasureType,
- warning: string | null,
-};
-
-/**
- * A flamechart stack frame belonging to a stack trace.
- */
-export type FlamechartStackFrame = {
- name: string,
- timestamp: Milliseconds,
- duration: Milliseconds,
- scriptUrl?: string,
- locationLine?: number,
- locationColumn?: number,
-};
-
-export type UserTimingMark = {
- name: string,
- timestamp: Milliseconds,
-};
-
-export type Snapshot = {
- height: number,
- image: Image | null,
- +imageSource: string,
- +timestamp: Milliseconds,
- width: number,
-};
-
-/**
- * A "layer" of stack frames in the profiler UI, i.e. all stack frames of the
- * same depth across all stack traces. Displayed as a flamechart row in the UI.
- */
-export type FlamechartStackLayer = FlamechartStackFrame[];
-
-export type Flamechart = FlamechartStackLayer[];
-
-export type HorizontalScrollStateChangeCallback = (
- scrollState: ScrollState,
-) => void;
-export type SearchRegExpStateChangeCallback = (
- searchRegExp: RegExp | null,
-) => void;
-
-// Imperative view state that corresponds to profiler data.
-// This state lives outside of React's lifecycle
-// and should be erased/reset whenever new profiler data is loaded.
-export type ViewState = {
- horizontalScrollState: ScrollState,
- onHorizontalScrollStateChange: (
- callback: HorizontalScrollStateChangeCallback,
- ) => void,
- onSearchRegExpStateChange: (
- callback: SearchRegExpStateChangeCallback,
- ) => void,
- searchRegExp: RegExp | null,
- updateHorizontalScrollState: (scrollState: ScrollState) => void,
- updateSearchRegExpState: (searchRegExp: RegExp | null) => void,
- viewToMutableViewStateMap: Map,
-};
-
-export type InternalModuleSourceToRanges = Map<
- string | void,
- Array<[ErrorStackFrame, ErrorStackFrame]>,
->;
-
-export type LaneToLabelMap = Map;
-
-export type TimelineData = {
- batchUIDToMeasuresMap: Map,
- componentMeasures: ReactComponentMeasure[],
- duration: number,
- flamechart: Flamechart,
- internalModuleSourceToRanges: InternalModuleSourceToRanges,
- laneToLabelMap: LaneToLabelMap,
- laneToReactMeasureMap: Map,
- nativeEvents: NativeEvent[],
- networkMeasures: NetworkMeasure[],
- otherUserTimingMarks: UserTimingMark[],
- reactVersion: string | null,
- schedulingEvents: SchedulingEvent[],
- snapshots: Snapshot[],
- snapshotHeight: number,
- startTime: number,
- suspenseEvents: SuspenseEvent[],
- thrownErrors: ThrownError[],
-};
-
-export type TimelineDataExport = {
- batchUIDToMeasuresKeyValueArray: Array<[BatchUID, ReactMeasure[]]>,
- componentMeasures: ReactComponentMeasure[],
- duration: number,
- flamechart: Flamechart,
- internalModuleSourceToRanges: Array<
- [string | void, Array<[ErrorStackFrame, ErrorStackFrame]>],
- >,
- laneToLabelKeyValueArray: Array<[ReactLane, string]>,
- laneToReactMeasureKeyValueArray: Array<[ReactLane, ReactMeasure[]]>,
- nativeEvents: NativeEvent[],
- networkMeasures: NetworkMeasure[],
- otherUserTimingMarks: UserTimingMark[],
- reactVersion: string | null,
- schedulingEvents: SchedulingEvent[],
- snapshots: Snapshot[],
- snapshotHeight: number,
- startTime: number,
- suspenseEvents: SuspenseEvent[],
- thrownErrors: ThrownError[],
-};
-
-export type ReactEventInfo = {
- componentMeasure: ReactComponentMeasure | null,
- flamechartStackFrame: FlamechartStackFrame | null,
- measure: ReactMeasure | null,
- nativeEvent: NativeEvent | null,
- networkMeasure: NetworkMeasure | null,
- schedulingEvent: SchedulingEvent | null,
- suspenseEvent: SuspenseEvent | null,
- snapshot: Snapshot | null,
- thrownError: ThrownError | null,
- userTimingMark: UserTimingMark | null,
-};
diff --git a/packages/react-devtools-timeline/src/utils/flow.js b/packages/react-devtools-timeline/src/utils/flow.js
deleted file mode 100644
index cfdd3688c9d5..000000000000
--- a/packages/react-devtools-timeline/src/utils/flow.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * 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
- */
-import type {ReactScheduleStateUpdateEvent, SchedulingEvent} from '../types';
-
-export function isStateUpdateEvent(
- event: SchedulingEvent,
- // eslint-disable-next-line
-): event is ReactScheduleStateUpdateEvent {
- return event.type === 'schedule-state-update';
-}
diff --git a/packages/react-devtools-timeline/src/utils/formatting.js b/packages/react-devtools-timeline/src/utils/formatting.js
deleted file mode 100644
index 725197b13835..000000000000
--- a/packages/react-devtools-timeline/src/utils/formatting.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * 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
- */
-
-import type {SchedulingEvent} from '../types';
-
-import prettyMilliseconds from 'pretty-ms';
-
-export function formatTimestamp(ms: number): string {
- return (
- ms.toLocaleString(undefined, {
- minimumFractionDigits: 1,
- maximumFractionDigits: 1,
- }) + 'ms'
- );
-}
-
-export function formatDuration(ms: number): string {
- return prettyMilliseconds(ms, {millisecondsDecimalDigits: 1});
-}
-
-export function trimString(string: string, length: number): string {
- if (string.length > length) {
- return `${string.slice(0, length - 1)}…`;
- }
- return string;
-}
-
-export function getSchedulingEventLabel(event: SchedulingEvent): string | null {
- switch (event.type) {
- case 'schedule-render':
- return 'render scheduled';
- case 'schedule-state-update':
- return 'state update scheduled';
- case 'schedule-force-update':
- return 'force update scheduled';
- default:
- return null;
- }
-}
diff --git a/packages/react-devtools-timeline/src/utils/getBatchRange.js b/packages/react-devtools-timeline/src/utils/getBatchRange.js
deleted file mode 100644
index d6f334761bd1..000000000000
--- a/packages/react-devtools-timeline/src/utils/getBatchRange.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * 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
- */
-
-import memoize from 'memoize-one';
-
-import type {
- BatchUID,
- Milliseconds,
- ReactMeasure,
- TimelineData,
-} from '../types';
-
-function unmemoizedGetBatchRange(
- batchUID: BatchUID,
- data: TimelineData,
- minStartTime?: number = 0,
-): [Milliseconds, Milliseconds] {
- const measures = data.batchUIDToMeasuresMap.get(batchUID);
- if (measures == null || measures.length === 0) {
- throw Error(`Could not find measures with batch UID "${batchUID}"`);
- }
-
- const lastMeasure = measures[measures.length - 1] as any as ReactMeasure;
- const stopTime = lastMeasure.timestamp + lastMeasure.duration;
-
- if (stopTime < minStartTime) {
- return [0, 0];
- }
-
- let startTime = minStartTime;
- for (let index = 0; index < measures.length; index++) {
- const measure = measures[index];
- if (measure.timestamp >= minStartTime) {
- startTime = measure.timestamp;
- break;
- }
- }
-
- return [startTime, stopTime];
-}
-
-export const getBatchRange: (
- batchUID: BatchUID,
- data: TimelineData,
- minStartTime?: number,
-) => [Milliseconds, Milliseconds] = memoize(unmemoizedGetBatchRange);
diff --git a/packages/react-devtools-timeline/src/utils/useSmartTooltip.js b/packages/react-devtools-timeline/src/utils/useSmartTooltip.js
deleted file mode 100644
index 73c6ce246d71..000000000000
--- a/packages/react-devtools-timeline/src/utils/useSmartTooltip.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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
- */
-
-import {useLayoutEffect, useRef} from 'react';
-
-const TOOLTIP_OFFSET_BOTTOM = 10;
-const TOOLTIP_OFFSET_TOP = 5;
-
-export default function useSmartTooltip({
- canvasRef,
- mouseX,
- mouseY,
-}: {
- canvasRef: {current: HTMLCanvasElement | null},
- mouseX: number,
- mouseY: number,
-}): {current: HTMLElement | null} {
- const ref = useRef(null);
-
- // HACK: Browser extension reports window.innerHeight of 0,
- // so we fallback to using the tooltip target element.
- let height = window.innerHeight;
- let width = window.innerWidth;
- const target = canvasRef.current;
- if (target !== null) {
- const rect = target.getBoundingClientRect();
- height = rect.top + rect.height;
- width = rect.left + rect.width;
- }
-
- useLayoutEffect(() => {
- const element = ref.current;
- if (element !== null) {
- // Let's check the vertical position.
- if (mouseY + TOOLTIP_OFFSET_BOTTOM + element.offsetHeight >= height) {
- // The tooltip doesn't fit below the mouse cursor (which is our
- // default strategy). Therefore we try to position it either above the
- // mouse cursor or finally aligned with the window's top edge.
- if (mouseY - TOOLTIP_OFFSET_TOP - element.offsetHeight > 0) {
- // We position the tooltip above the mouse cursor if it fits there.
- element.style.top = `${
- mouseY - element.offsetHeight - TOOLTIP_OFFSET_TOP
- }px`;
- } else {
- // Otherwise we align the tooltip with the window's top edge.
- element.style.top = '0px';
- }
- } else {
- element.style.top = `${mouseY + TOOLTIP_OFFSET_BOTTOM}px`;
- }
-
- // Now let's check the horizontal position.
- if (mouseX + TOOLTIP_OFFSET_BOTTOM + element.offsetWidth >= width) {
- // The tooltip doesn't fit at the right of the mouse cursor (which is
- // our default strategy). Therefore we try to position it either at the
- // left of the mouse cursor or finally aligned with the window's left
- // edge.
- if (mouseX - TOOLTIP_OFFSET_TOP - element.offsetWidth > 0) {
- // We position the tooltip at the left of the mouse cursor if it fits
- // there.
- element.style.left = `${
- mouseX - element.offsetWidth - TOOLTIP_OFFSET_TOP
- }px`;
- } else {
- // Otherwise, align the tooltip with the window's left edge.
- element.style.left = '0px';
- }
- } else {
- element.style.left = `${mouseX + TOOLTIP_OFFSET_BOTTOM}px`;
- }
- }
- });
-
- return ref;
-}
diff --git a/packages/react-devtools-timeline/src/view-base/BackgroundColorView.js b/packages/react-devtools-timeline/src/view-base/BackgroundColorView.js
deleted file mode 100644
index 97096346c733..000000000000
--- a/packages/react-devtools-timeline/src/view-base/BackgroundColorView.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * 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
- */
-
-import {View} from './View';
-import {COLORS} from '../content-views/constants';
-
-/**
- * View that fills its visible area with a CSS color.
- */
-export class BackgroundColorView extends View {
- draw(context: CanvasRenderingContext2D) {
- const {visibleArea} = this;
-
- context.fillStyle = COLORS.BACKGROUND;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/HorizontalPanAndZoomView.js b/packages/react-devtools-timeline/src/view-base/HorizontalPanAndZoomView.js
deleted file mode 100644
index 23ad1e5a5d4f..000000000000
--- a/packages/react-devtools-timeline/src/view-base/HorizontalPanAndZoomView.js
+++ /dev/null
@@ -1,245 +0,0 @@
-/**
- * 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
- */
-
-import type {Size, IntrinsicSize, Rect} from './geometry';
-import type {
- Interaction,
- MouseDownInteraction,
- MouseMoveInteraction,
- MouseUpInteraction,
- WheelPlainInteraction,
- WheelWithShiftInteraction,
-} from './useCanvasInteraction';
-import type {ScrollState} from './utils/scrollState';
-import type {ViewRefs} from './Surface';
-import type {ViewState} from '../types';
-
-import {Surface} from './Surface';
-import {View} from './View';
-import {rectContainsPoint} from './geometry';
-import {
- clampState,
- moveStateToRange,
- areScrollStatesEqual,
- translateState,
- zoomState,
-} from './utils/scrollState';
-import {
- MAX_ZOOM_LEVEL,
- MIN_ZOOM_LEVEL,
- MOVE_WHEEL_DELTA_THRESHOLD,
-} from './constants';
-
-export class HorizontalPanAndZoomView extends View {
- _contentView: View;
- _intrinsicContentWidth: number;
- _isPanning: boolean = false;
- _viewState: ViewState;
-
- constructor(
- surface: Surface,
- frame: Rect,
- contentView: View,
- intrinsicContentWidth: number,
- viewState: ViewState,
- ) {
- super(surface, frame);
-
- this._contentView = contentView;
- this._intrinsicContentWidth = intrinsicContentWidth;
- this._viewState = viewState;
-
- viewState.onHorizontalScrollStateChange(scrollState => {
- this.zoomToRange(scrollState.offset, scrollState.length);
- });
-
- this.addSubview(contentView);
- }
-
- /**
- * Just sets scroll state.
- * Use `_setStateAndInformCallbacksIfChanged` if this view's callbacks should also be called.
- *
- * @returns Whether state was changed
- * @private
- */
- setScrollState(proposedState: ScrollState) {
- const clampedState = clampState({
- state: proposedState,
- minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
- maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
- containerLength: this.frame.size.width,
- });
- if (
- !areScrollStatesEqual(clampedState, this._viewState.horizontalScrollState)
- ) {
- this.setNeedsDisplay();
- }
- }
-
- /**
- * Zoom to a specific range of the content specified as a range of the
- * content view's intrinsic content size.
- *
- * Does not inform callbacks of state change since this is a public API.
- */
- zoomToRange(rangeStart: number, rangeEnd: number) {
- const newState = moveStateToRange({
- state: this._viewState.horizontalScrollState,
- rangeStart,
- rangeEnd,
- contentLength: this._intrinsicContentWidth,
-
- minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
- maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
- containerLength: this.frame.size.width,
- });
- this.setScrollState(newState);
- }
-
- desiredSize(): Size | IntrinsicSize {
- return this._contentView.desiredSize();
- }
-
- layoutSubviews() {
- const {offset, length} = this._viewState.horizontalScrollState;
- const proposedFrame = {
- origin: {
- x: this.frame.origin.x + offset,
- y: this.frame.origin.y,
- },
- size: {
- width: length,
- height: this.frame.size.height,
- },
- };
- this._contentView.setFrame(proposedFrame);
- super.layoutSubviews();
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousedown':
- this._handleMouseDown(interaction, viewRefs);
- break;
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- case 'mouseup':
- this._handleMouseUp(interaction, viewRefs);
- break;
- case 'wheel-plain':
- case 'wheel-shift':
- this._handleWheel(interaction);
- break;
- }
- }
-
- _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
- if (rectContainsPoint(interaction.payload.location, this.frame)) {
- this._isPanning = true;
-
- viewRefs.activeView = this;
-
- this.currentCursor = 'grabbing';
- }
- }
-
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const isHovered = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
- if (isHovered && viewRefs.hoveredView === null) {
- viewRefs.hoveredView = this;
- }
-
- if (viewRefs.activeView === this) {
- this.currentCursor = 'grabbing';
- } else if (isHovered) {
- this.currentCursor = 'grab';
- }
-
- if (!this._isPanning) {
- return;
- }
-
- // Don't prevent mouse-move events from bubbling if they are vertical drags.
- const {movementX, movementY} = interaction.payload.event;
- if (Math.abs(movementX) < Math.abs(movementY)) {
- return;
- }
-
- const newState = translateState({
- state: this._viewState.horizontalScrollState,
- delta: movementX,
- containerLength: this.frame.size.width,
- });
- this._viewState.updateHorizontalScrollState(newState);
- }
-
- _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
- if (this._isPanning) {
- this._isPanning = false;
- }
-
- if (viewRefs.activeView === this) {
- viewRefs.activeView = null;
- }
- }
-
- _handleWheel(interaction: WheelPlainInteraction | WheelWithShiftInteraction) {
- const {
- location,
- delta: {deltaX, deltaY},
- } = interaction.payload;
-
- if (!rectContainsPoint(location, this.frame)) {
- return; // Not scrolling on view
- }
-
- const absDeltaX = Math.abs(deltaX);
- const absDeltaY = Math.abs(deltaY);
-
- // Vertical scrolling zooms in and out (unless the SHIFT modifier is used).
- // Horizontal scrolling pans.
- if (absDeltaY > absDeltaX) {
- if (absDeltaY < MOVE_WHEEL_DELTA_THRESHOLD) {
- return;
- }
-
- if (interaction.type === 'wheel-shift') {
- // Shift modifier is for scrolling, not zooming.
- return;
- }
-
- const newState = zoomState({
- state: this._viewState.horizontalScrollState,
- multiplier: 1 + 0.005 * -deltaY,
- fixedPoint: location.x - this._viewState.horizontalScrollState.offset,
-
- minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
- maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
- containerLength: this.frame.size.width,
- });
- this._viewState.updateHorizontalScrollState(newState);
- } else {
- if (absDeltaX < MOVE_WHEEL_DELTA_THRESHOLD) {
- return;
- }
-
- const newState = translateState({
- state: this._viewState.horizontalScrollState,
- delta: -deltaX,
- containerLength: this.frame.size.width,
- });
- this._viewState.updateHorizontalScrollState(newState);
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/Surface.js b/packages/react-devtools-timeline/src/view-base/Surface.js
deleted file mode 100644
index 81c0199cc743..000000000000
--- a/packages/react-devtools-timeline/src/view-base/Surface.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * 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
- */
-
-import type {Interaction} from './useCanvasInteraction';
-import type {Size} from './geometry';
-
-import memoize from 'memoize-one';
-
-import {View} from './View';
-import {zeroPoint} from './geometry';
-import {DPR} from '../content-views/constants';
-
-export type ViewRefs = {
- activeView: View | null,
- hoveredView: View | null,
-};
-
-// hidpi canvas: https://web.dev/articles/canvas-hidipi
-function configureRetinaCanvas(
- canvas: HTMLCanvasElement,
- height: number,
- width: number,
-) {
- canvas.width = width * DPR;
- canvas.height = height * DPR;
- canvas.style.width = `${width}px`;
- canvas.style.height = `${height}px`;
-}
-
-const getCanvasContext = memoize(
- (
- canvas: HTMLCanvasElement,
- height: number,
- width: number,
- scaleCanvas: boolean = true,
- ): CanvasRenderingContext2D => {
- const context = canvas.getContext('2d', {alpha: false});
- if (scaleCanvas) {
- configureRetinaCanvas(canvas, height, width);
-
- // Scale all drawing operations by the dpr, so you don't have to worry about the difference.
- context.scale(DPR, DPR);
- }
- return context;
- },
-);
-
-type ResetHoveredEventFn = () => void;
-
-/**
- * Represents the canvas surface and a view heirarchy. A surface is also the
- * place where all interactions enter the view heirarchy.
- */
-export class Surface {
- rootView: ?View;
-
- _context: ?CanvasRenderingContext2D;
- _canvasSize: ?Size;
-
- _resetHoveredEvent: ResetHoveredEventFn;
-
- _viewRefs: ViewRefs = {
- activeView: null,
- hoveredView: null,
- };
-
- constructor(resetHoveredEvent: ResetHoveredEventFn) {
- this._resetHoveredEvent = resetHoveredEvent;
- }
-
- hasActiveView(): boolean {
- return this._viewRefs.activeView !== null;
- }
-
- setCanvas(canvas: HTMLCanvasElement, canvasSize: Size) {
- this._context = getCanvasContext(
- canvas,
- canvasSize.height,
- canvasSize.width,
- );
- this._canvasSize = canvasSize;
-
- if (this.rootView) {
- this.rootView.setNeedsDisplay();
- }
- }
-
- displayIfNeeded() {
- const {rootView, _canvasSize, _context} = this;
- if (!rootView || !_context || !_canvasSize) {
- return;
- }
- rootView.setFrame({
- origin: zeroPoint,
- size: _canvasSize,
- });
- rootView.setVisibleArea({
- origin: zeroPoint,
- size: _canvasSize,
- });
- rootView.displayIfNeeded(_context, this._viewRefs);
- }
-
- getCurrentCursor(): string | null {
- const {activeView, hoveredView} = this._viewRefs;
- if (activeView !== null) {
- return activeView.currentCursor;
- } else if (hoveredView !== null) {
- return hoveredView.currentCursor;
- } else {
- return null;
- }
- }
-
- handleInteraction(interaction: Interaction) {
- const rootView = this.rootView;
- if (rootView != null) {
- const viewRefs = this._viewRefs;
- switch (interaction.type) {
- case 'mousemove':
- case 'wheel-control':
- case 'wheel-meta':
- case 'wheel-plain':
- case 'wheel-shift':
- // Clean out the hovered view before processing this type of interaction.
- const hoveredView = viewRefs.hoveredView;
- viewRefs.hoveredView = null;
-
- rootView.handleInteractionAndPropagateToSubviews(
- interaction,
- viewRefs,
- );
-
- // If a previously hovered view is no longer hovered, update the outer state.
- if (hoveredView !== null && viewRefs.hoveredView === null) {
- this._resetHoveredEvent();
- }
- break;
- default:
- rootView.handleInteractionAndPropagateToSubviews(
- interaction,
- viewRefs,
- );
- break;
- }
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js b/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js
deleted file mode 100644
index d48c6083b865..000000000000
--- a/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js
+++ /dev/null
@@ -1,297 +0,0 @@
-/**
- * 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
- */
-
-import type {Size, IntrinsicSize, Rect} from './geometry';
-import type {
- Interaction,
- MouseDownInteraction,
- MouseMoveInteraction,
- MouseUpInteraction,
- WheelWithShiftInteraction,
-} from './useCanvasInteraction';
-import type {ScrollState} from './utils/scrollState';
-import type {ViewRefs} from './Surface';
-import type {ViewState} from '../types';
-
-import {Surface} from './Surface';
-import {View} from './View';
-import {rectContainsPoint} from './geometry';
-import {
- clampState,
- areScrollStatesEqual,
- translateState,
-} from './utils/scrollState';
-import {MOVE_WHEEL_DELTA_THRESHOLD} from './constants';
-import {COLORS} from '../content-views/constants';
-
-const CARET_MARGIN = 3;
-const CARET_WIDTH = 5;
-const CARET_HEIGHT = 3;
-
-type OnChangeCallback = (
- scrollState: ScrollState,
- containerLength: number,
-) => void;
-
-export class VerticalScrollView extends View {
- _contentView: View;
- _isPanning: boolean;
- _mutableViewStateKey: string;
- _onChangeCallback: OnChangeCallback | null;
- _scrollState: ScrollState;
- _viewState: ViewState;
-
- constructor(
- surface: Surface,
- frame: Rect,
- contentView: View,
- viewState: ViewState,
- label: string,
- ) {
- super(surface, frame);
-
- this._contentView = contentView;
- this._isPanning = false;
- this._mutableViewStateKey = label + ':VerticalScrollView';
- this._onChangeCallback = null;
- this._scrollState = {
- offset: 0,
- length: 0,
- };
- this._viewState = viewState;
-
- this.addSubview(contentView);
-
- this._restoreMutableViewState();
- }
-
- setFrame(newFrame: Rect) {
- super.setFrame(newFrame);
-
- // Revalidate scrollState
- this._setScrollState(this._scrollState);
- }
-
- desiredSize(): Size | IntrinsicSize {
- return this._contentView.desiredSize();
- }
-
- draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
- super.draw(context, viewRefs);
-
- // Show carets if there's scroll overflow above or below the viewable area.
- if (this.frame.size.height > CARET_HEIGHT * 2 + CARET_MARGIN * 3) {
- const offset = this._scrollState.offset;
- const desiredSize = this._contentView.desiredSize();
-
- const above = offset;
- const below = this.frame.size.height - desiredSize.height - offset;
-
- if (above < 0 || below < 0) {
- const {visibleArea} = this;
- const {x, y} = visibleArea.origin;
- const {width, height} = visibleArea.size;
- const horizontalCenter = x + width / 2;
-
- const halfWidth = CARET_WIDTH;
- const left = horizontalCenter + halfWidth;
- const right = horizontalCenter - halfWidth;
-
- if (above < 0) {
- const topY = y + CARET_MARGIN;
-
- context.beginPath();
- context.moveTo(horizontalCenter, topY);
- context.lineTo(left, topY + CARET_HEIGHT);
- context.lineTo(right, topY + CARET_HEIGHT);
- context.closePath();
- context.fillStyle = COLORS.SCROLL_CARET;
- context.fill();
- }
-
- if (below < 0) {
- const bottomY = y + height - CARET_MARGIN;
-
- context.beginPath();
- context.moveTo(horizontalCenter, bottomY);
- context.lineTo(left, bottomY - CARET_HEIGHT);
- context.lineTo(right, bottomY - CARET_HEIGHT);
- context.closePath();
- context.fillStyle = COLORS.SCROLL_CARET;
- context.fill();
- }
- }
- }
- }
-
- layoutSubviews() {
- const {offset} = this._scrollState;
- const desiredSize = this._contentView.desiredSize();
-
- const minimumHeight = this.frame.size.height;
- const desiredHeight = desiredSize ? desiredSize.height : 0;
- // Force view to take up at least all remaining vertical space.
- const height = Math.max(desiredHeight, minimumHeight);
-
- const proposedFrame = {
- origin: {
- x: this.frame.origin.x,
- y: this.frame.origin.y + offset,
- },
- size: {
- width: this.frame.size.width,
- height,
- },
- };
- this._contentView.setFrame(proposedFrame);
- super.layoutSubviews();
- }
-
- handleInteraction(interaction: Interaction): ?boolean {
- switch (interaction.type) {
- case 'mousedown':
- return this._handleMouseDown(interaction);
- case 'mousemove':
- return this._handleMouseMove(interaction);
- case 'mouseup':
- return this._handleMouseUp(interaction);
- case 'wheel-shift':
- return this._handleWheelShift(interaction);
- }
- }
-
- onChange(callback: OnChangeCallback) {
- this._onChangeCallback = callback;
- }
-
- scrollBy(deltaY: number): boolean {
- const newState = translateState({
- state: this._scrollState,
- delta: -deltaY,
- containerLength: this.frame.size.height,
- });
-
- // If the state is updated by this wheel scroll,
- // return true to prevent the interaction from bubbling.
- // For instance, this prevents the outermost container from also scrolling.
- return this._setScrollState(newState);
- }
-
- _handleMouseDown(interaction: MouseDownInteraction) {
- if (rectContainsPoint(interaction.payload.location, this.frame)) {
- const frameHeight = this.frame.size.height;
- const contentHeight = this._contentView.desiredSize().height;
- // Don't claim drag operations if the content is not tall enough to be scrollable.
- // This would block any outer scroll views from working.
- if (frameHeight < contentHeight) {
- this._isPanning = true;
- }
- }
- }
-
- _handleMouseMove(interaction: MouseMoveInteraction): void | boolean {
- if (!this._isPanning) {
- return;
- }
-
- // Don't prevent mouse-move events from bubbling if they are horizontal drags.
- const {movementX, movementY} = interaction.payload.event;
- if (Math.abs(movementX) > Math.abs(movementY)) {
- return;
- }
-
- const newState = translateState({
- state: this._scrollState,
- delta: interaction.payload.event.movementY,
- containerLength: this.frame.size.height,
- });
- this._setScrollState(newState);
-
- return true;
- }
-
- _handleMouseUp(interaction: MouseUpInteraction) {
- if (this._isPanning) {
- this._isPanning = false;
- }
- }
-
- _handleWheelShift(interaction: WheelWithShiftInteraction): boolean {
- const {
- location,
- delta: {deltaX, deltaY},
- } = interaction.payload;
-
- if (!rectContainsPoint(location, this.frame)) {
- return false; // Not scrolling on view
- }
-
- const absDeltaX = Math.abs(deltaX);
- const absDeltaY = Math.abs(deltaY);
- if (absDeltaX > absDeltaY) {
- return false; // Scrolling horizontally
- }
-
- if (absDeltaY < MOVE_WHEEL_DELTA_THRESHOLD) {
- return false; // Movement was too small and should be ignored.
- }
-
- return this.scrollBy(deltaY);
- }
-
- _restoreMutableViewState() {
- if (
- this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
- ) {
- this._scrollState = this._viewState.viewToMutableViewStateMap.get(
- this._mutableViewStateKey,
- ) as any as ScrollState;
- } else {
- this._viewState.viewToMutableViewStateMap.set(
- this._mutableViewStateKey,
- this._scrollState,
- );
- }
-
- this.setNeedsDisplay();
- }
-
- _setScrollState(proposedState: ScrollState): boolean {
- const contentHeight = this._contentView.frame.size.height;
- const containerHeight = this.frame.size.height;
-
- const clampedState = clampState({
- state: proposedState,
- minContentLength: contentHeight,
- maxContentLength: contentHeight,
- containerLength: containerHeight,
- });
- if (!areScrollStatesEqual(clampedState, this._scrollState)) {
- this._scrollState.offset = clampedState.offset;
- this._scrollState.length = clampedState.length;
-
- this.setNeedsDisplay();
-
- if (this._onChangeCallback !== null) {
- this._onChangeCallback(clampedState, this.frame.size.height);
- }
-
- return true;
- }
-
- // Don't allow wheel events to bubble past this view even if we've scrolled to the edge.
- // It just feels bad to have the scrolling jump unexpectedly from in a container to the outer page.
- // The only exception is when the container fits the content (no scrolling).
- if (contentHeight === containerHeight) {
- return false;
- }
-
- return true;
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/View.js b/packages/react-devtools-timeline/src/view-base/View.js
deleted file mode 100644
index 9ded7b64b33b..000000000000
--- a/packages/react-devtools-timeline/src/view-base/View.js
+++ /dev/null
@@ -1,345 +0,0 @@
-/**
- * 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
- */
-
-import type {Interaction} from './useCanvasInteraction';
-import type {IntrinsicSize, Rect, Size} from './geometry';
-import type {Layouter} from './layouter';
-import type {ViewRefs} from './Surface';
-
-import {Surface} from './Surface';
-import {
- rectEqualToRect,
- intersectionOfRects,
- rectIntersectsRect,
- sizeIsEmpty,
- sizeIsValid,
- unionOfRects,
- zeroRect,
-} from './geometry';
-import {noopLayout, viewsToLayout, collapseLayoutIntoViews} from './layouter';
-
-/**
- * Base view class that can be subclassed to draw custom content or manage
- * subclasses.
- */
-export class View {
- _backgroundColor: string | null;
-
- currentCursor: string | null = null;
-
- surface: Surface;
-
- frame: Rect;
- visibleArea: Rect;
-
- superview: ?View;
- subviews: View[] = [];
-
- /**
- * An injected function that lays out our subviews.
- * @private
- */
- _layouter: Layouter;
-
- /**
- * Whether this view needs to be drawn.
- *
- * NOTE: Do not set directly! Use `setNeedsDisplay`.
- *
- * @see setNeedsDisplay
- * @private
- */
- _needsDisplay: boolean = true;
-
- /**
- * Whether the hierarchy below this view has subviews that need display.
- *
- * NOTE: Do not set directly! Use `setSubviewsNeedDisplay`.
- *
- * @see setSubviewsNeedDisplay
- * @private
- */
- _subviewsNeedDisplay: boolean = false;
-
- constructor(
- surface: Surface,
- frame: Rect,
- layouter: Layouter = noopLayout,
- visibleArea: Rect = frame,
- backgroundColor?: string | null = null,
- ) {
- this._backgroundColor = backgroundColor || null;
- this.surface = surface;
- this.frame = frame;
- this._layouter = layouter;
- this.visibleArea = visibleArea;
- }
-
- /**
- * Invalidates view's contents.
- *
- * Downward propagating; once called, all subviews of this view should also
- * be invalidated.
- */
- setNeedsDisplay() {
- this._needsDisplay = true;
- if (this.superview) {
- this.superview._setSubviewsNeedDisplay();
- }
- this.subviews.forEach(subview => subview.setNeedsDisplay());
- }
-
- /**
- * Informs superview that it has subviews that need to be drawn.
- *
- * Upward propagating; once called, all superviews of this view should also
- * have `subviewsNeedDisplay` = true.
- *
- * @private
- */
- _setSubviewsNeedDisplay() {
- this._subviewsNeedDisplay = true;
- if (this.superview) {
- this.superview._setSubviewsNeedDisplay();
- }
- }
-
- setFrame(newFrame: Rect) {
- if (!rectEqualToRect(this.frame, newFrame)) {
- this.frame = newFrame;
- if (sizeIsValid(newFrame.size)) {
- this.frame = newFrame;
- } else {
- this.frame = zeroRect;
- }
- this.setNeedsDisplay();
- }
- }
-
- setVisibleArea(newVisibleArea: Rect) {
- if (!rectEqualToRect(this.visibleArea, newVisibleArea)) {
- if (sizeIsValid(newVisibleArea.size)) {
- this.visibleArea = newVisibleArea;
- } else {
- this.visibleArea = zeroRect;
- }
- this.setNeedsDisplay();
- }
- }
-
- /**
- * A size that can be used as a hint by layout functions.
- *
- * Implementations should typically return the intrinsic content size or a
- * size that fits all the view's content.
- *
- * The default implementation returns a size that fits all the view's
- * subviews.
- *
- * Can be overridden by subclasses.
- */
- desiredSize(): Size | IntrinsicSize {
- if (this._needsDisplay) {
- this.layoutSubviews();
- }
- const frames = this.subviews.map(subview => subview.frame);
- return unionOfRects(...frames).size;
- }
-
- /**
- * Appends `view` to the list of this view's `subviews`.
- */
- addSubview(view: View) {
- if (this.subviews.includes(view)) {
- return;
- }
- this.subviews.push(view);
- view.superview = this;
- }
-
- /**
- * Breaks the subview-superview relationship between `view` and this view, if
- * `view` is a subview of this view.
- */
- removeSubview(view: View) {
- const subviewIndex = this.subviews.indexOf(view);
- if (subviewIndex === -1) {
- return;
- }
- view.superview = undefined;
- this.subviews.splice(subviewIndex, 1);
- }
-
- /**
- * Removes all subviews from this view.
- */
- removeAllSubviews() {
- this.subviews.forEach(subview => (subview.superview = undefined));
- this.subviews = [];
- }
-
- /**
- * Executes the display flow if this view needs to be drawn.
- *
- * 1. Lays out subviews with `layoutSubviews`.
- * 2. Draws content with `draw`.
- */
- displayIfNeeded(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
- if (
- (this._needsDisplay || this._subviewsNeedDisplay) &&
- rectIntersectsRect(this.frame, this.visibleArea) &&
- !sizeIsEmpty(this.visibleArea.size)
- ) {
- this.layoutSubviews();
- if (this._needsDisplay) {
- this._needsDisplay = false;
- }
- if (this._subviewsNeedDisplay) this._subviewsNeedDisplay = false;
-
- // Clip anything drawn by the view to prevent it from overflowing its visible area.
- const visibleArea = this.visibleArea;
- const region = new Path2D();
- region.rect(
- visibleArea.origin.x,
- visibleArea.origin.y,
- visibleArea.size.width,
- visibleArea.size.height,
- );
- context.save();
- context.clip(region);
- context.beginPath();
-
- this.draw(context, viewRefs);
-
- // Stop clipping
- context.restore();
- }
- }
-
- /**
- * Layout self and subviews.
- *
- * Implementations should call `setNeedsDisplay` if a draw is required.
- *
- * The default implementation uses `this.layouter` to lay out subviews.
- *
- * Can be overwritten by subclasses that wish to manually manage their
- * subviews' layout.
- *
- * NOTE: Do not call directly! Use `displayIfNeeded`.
- *
- * @see displayIfNeeded
- */
- layoutSubviews() {
- const {frame, _layouter, subviews, visibleArea} = this;
- const existingLayout = viewsToLayout(subviews);
- const newLayout = _layouter(existingLayout, frame);
- collapseLayoutIntoViews(newLayout);
-
- subviews.forEach((subview, subviewIndex) => {
- if (rectIntersectsRect(visibleArea, subview.frame)) {
- subview.setVisibleArea(intersectionOfRects(visibleArea, subview.frame));
- } else {
- subview.setVisibleArea(zeroRect);
- }
- });
- }
-
- /**
- * Draw the contents of this view in the given canvas `context`.
- *
- * Defaults to drawing this view's `subviews`.
- *
- * To be overwritten by subclasses that wish to draw custom content.
- *
- * NOTE: Do not call directly! Use `displayIfNeeded`.
- *
- * @see displayIfNeeded
- */
- draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
- const {subviews, visibleArea} = this;
- subviews.forEach(subview => {
- if (rectIntersectsRect(visibleArea, subview.visibleArea)) {
- subview.displayIfNeeded(context, viewRefs);
- }
- });
-
- const backgroundColor = this._backgroundColor;
- if (backgroundColor !== null) {
- const desiredSize = this.desiredSize();
- if (visibleArea.size.height > desiredSize.height) {
- context.fillStyle = backgroundColor;
- context.fillRect(
- visibleArea.origin.x,
- visibleArea.origin.y + desiredSize.height,
- visibleArea.size.width,
- visibleArea.size.height - desiredSize.height,
- );
- }
- }
- }
-
- /**
- * Handle an `interaction`.
- *
- * To be overwritten by subclasses that wish to handle interactions.
- *
- * NOTE: Do not call directly! Use `handleInteractionAndPropagateToSubviews`
- */
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs): ?boolean {}
-
- /**
- * Handle an `interaction` and propagates it to all of this view's
- * `subviews`.
- *
- * NOTE: Should not be overridden! Subclasses should override
- * `handleInteraction` instead.
- *
- * @see handleInteraction
- * @protected
- */
- handleInteractionAndPropagateToSubviews(
- interaction: Interaction,
- viewRefs: ViewRefs,
- ): boolean {
- const {subviews, visibleArea} = this;
-
- if (visibleArea.size.height === 0) {
- return false;
- }
-
- // Pass the interaction to subviews first,
- // so they have the opportunity to claim it before it bubbles.
- //
- // Views are painted first to last,
- // so they should process interactions last to first,
- // so views in front (on top) can claim the interaction first.
- for (let i = subviews.length - 1; i >= 0; i--) {
- const subview = subviews[i];
- if (rectIntersectsRect(visibleArea, subview.visibleArea)) {
- const didSubviewHandle =
- subview.handleInteractionAndPropagateToSubviews(
- interaction,
- viewRefs,
- ) === true;
- if (didSubviewHandle) {
- return true;
- }
- }
- }
-
- const didSelfHandle =
- this.handleInteraction(interaction, viewRefs) === true;
- if (didSelfHandle) {
- return true;
- }
-
- return false;
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/__tests__/geometry-test.js b/packages/react-devtools-timeline/src/view-base/__tests__/geometry-test.js
deleted file mode 100644
index f49d634c8e5e..000000000000
--- a/packages/react-devtools-timeline/src/view-base/__tests__/geometry-test.js
+++ /dev/null
@@ -1,272 +0,0 @@
-/**
- * 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
- */
-
-import {
- pointEqualToPoint,
- sizeEqualToSize,
- rectEqualToRect,
- sizeIsValid,
- sizeIsEmpty,
- rectIntersectsRect,
- intersectionOfRects,
- rectContainsPoint,
- unionOfRects,
-} from '../geometry';
-
-describe('pointEqualToPoint', () => {
- it('should return true when 2 points have the same values', () => {
- expect(pointEqualToPoint({x: 1, y: 1}, {x: 1, y: 1})).toBe(true);
- expect(pointEqualToPoint({x: -1, y: 2}, {x: -1, y: 2})).toBe(true);
- expect(
- pointEqualToPoint({x: 3.14159, y: 0.26535}, {x: 3.14159, y: 0.26535}),
- ).toBe(true);
- });
-
- it('should return false when 2 points have different values', () => {
- expect(pointEqualToPoint({x: 1, y: 1}, {x: 1, y: 0})).toBe(false);
- expect(pointEqualToPoint({x: -1, y: 2}, {x: 0, y: 1})).toBe(false);
- expect(
- pointEqualToPoint({x: 3.1416, y: 0.26534}, {x: 3.14159, y: 0.26535}),
- ).toBe(false);
- });
-});
-
-describe('sizeEqualToSize', () => {
- it('should return true when 2 sizes have the same values', () => {
- expect(sizeEqualToSize({width: 1, height: 1}, {width: 1, height: 1})).toBe(
- true,
- );
- expect(
- sizeEqualToSize({width: -1, height: 2}, {width: -1, height: 2}),
- ).toBe(true);
- expect(
- sizeEqualToSize(
- {width: 3.14159, height: 0.26535},
- {width: 3.14159, height: 0.26535},
- ),
- ).toBe(true);
- });
-
- it('should return false when 2 sizes have different values', () => {
- expect(sizeEqualToSize({width: 1, height: 1}, {width: 1, height: 0})).toBe(
- false,
- );
- expect(sizeEqualToSize({width: -1, height: 2}, {width: 0, height: 1})).toBe(
- false,
- );
- expect(
- sizeEqualToSize(
- {width: 3.1416, height: 0.26534},
- {width: 3.14159, height: 0.26535},
- ),
- ).toBe(false);
- });
-});
-
-describe('rectEqualToRect', () => {
- it('should return true when 2 rects have the same values', () => {
- expect(
- rectEqualToRect(
- {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
- {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
- ),
- ).toBe(true);
- expect(
- rectEqualToRect(
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- ),
- ).toBe(true);
- });
-
- it('should return false when 2 rects have different values', () => {
- expect(
- rectEqualToRect(
- {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
- {origin: {x: 0, y: 1}, size: {width: 1, height: 1}},
- ),
- ).toBe(false);
- expect(
- rectEqualToRect(
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- {origin: {x: 1, y: 2}, size: {width: 3.15, height: 4}},
- ),
- ).toBe(false);
- });
-});
-
-describe('sizeIsValid', () => {
- it('should return true when the size has non-negative width and height', () => {
- expect(sizeIsValid({width: 1, height: 1})).toBe(true);
- expect(sizeIsValid({width: 0, height: 0})).toBe(true);
- });
-
- it('should return false when the size has negative width or height', () => {
- expect(sizeIsValid({width: 0, height: -1})).toBe(false);
- expect(sizeIsValid({width: -1, height: 0})).toBe(false);
- expect(sizeIsValid({width: -1, height: -1})).toBe(false);
- });
-});
-
-describe('sizeIsEmpty', () => {
- it('should return true when the size has negative area', () => {
- expect(sizeIsEmpty({width: 1, height: -1})).toBe(true);
- expect(sizeIsEmpty({width: -1, height: -1})).toBe(true);
- });
-
- it('should return true when the size has zero area', () => {
- expect(sizeIsEmpty({width: 0, height: 0})).toBe(true);
- expect(sizeIsEmpty({width: 0, height: 1})).toBe(true);
- expect(sizeIsEmpty({width: 1, height: 0})).toBe(true);
- });
-
- it('should return false when the size has positive area', () => {
- expect(sizeIsEmpty({width: 1, height: 1})).toBe(false);
- expect(sizeIsEmpty({width: 2, height: 1})).toBe(false);
- });
-});
-
-describe('rectIntersectsRect', () => {
- it('should return true when 2 rects intersect', () => {
- // Rects touch
- expect(
- rectIntersectsRect(
- {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
- {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
- ),
- ).toEqual(true);
-
- // Rects overlap
- expect(
- rectIntersectsRect(
- {origin: {x: 0, y: 0}, size: {width: 2, height: 1}},
- {origin: {x: 1, y: -2}, size: {width: 0.5, height: 5}},
- ),
- ).toEqual(true);
-
- // Rects are equal
- expect(
- rectIntersectsRect(
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- ),
- ).toEqual(true);
- });
-
- it('should return false when 2 rects do not intersect', () => {
- expect(
- rectIntersectsRect(
- {origin: {x: 0, y: 1}, size: {width: 1, height: 1}},
- {origin: {x: 0, y: 10}, size: {width: 1, height: 1}},
- ),
- ).toBe(false);
- expect(
- rectIntersectsRect(
- {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
- {origin: {x: -4, y: 2}, size: {width: 3.15, height: 4}},
- ),
- ).toBe(false);
- });
-});
-
-describe('intersectionOfRects', () => {
- // NOTE: Undefined behavior if rects do not intersect
-
- it('should return intersection when 2 rects intersect', () => {
- // Rects touch
- expect(
- intersectionOfRects(
- {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
- {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
- ),
- ).toEqual({origin: {x: 1, y: 1}, size: {width: 0, height: 0}});
-
- // Rects overlap
- expect(
- intersectionOfRects(
- {origin: {x: 0, y: 0}, size: {width: 2, height: 1}},
- {origin: {x: 1, y: -2}, size: {width: 0.5, height: 5}},
- ),
- ).toEqual({origin: {x: 1, y: 0}, size: {width: 0.5, height: 1}});
-
- // Rects are equal
- expect(
- intersectionOfRects(
- {origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}},
- {origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}},
- ),
- ).toEqual({origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}});
- });
-});
-
-describe('rectContainsPoint', () => {
- it("should return true if point is on the rect's edge", () => {
- expect(
- rectContainsPoint(
- {x: 0, y: 0},
- {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
- ),
- ).toBe(true);
- expect(
- rectContainsPoint(
- {x: 5, y: 0},
- {origin: {x: 0, y: 0}, size: {width: 10, height: 1}},
- ),
- ).toBe(true);
- expect(
- rectContainsPoint(
- {x: 1, y: 1},
- {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
- ),
- ).toBe(true);
- });
-
- it('should return true if point is in rect', () => {
- expect(
- rectContainsPoint(
- {x: 5, y: 50},
- {origin: {x: 0, y: 0}, size: {width: 10, height: 100}},
- ),
- ).toBe(true);
- });
-
- it('should return false if point is not in rect', () => {
- expect(
- rectContainsPoint(
- {x: -1, y: 0},
- {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
- ),
- ).toBe(false);
- });
-});
-
-describe('unionOfRects', () => {
- it('should return zero rect if no rects are provided', () => {
- expect(unionOfRects()).toEqual({
- origin: {x: 0, y: 0},
- size: {width: 0, height: 0},
- });
- });
-
- it('should return rect if 1 rect is provided', () => {
- expect(
- unionOfRects({origin: {x: 1, y: 2}, size: {width: 3, height: 4}}),
- ).toEqual({origin: {x: 1, y: 2}, size: {width: 3, height: 4}});
- });
-
- it('should return union of rects if more than one rect is provided', () => {
- expect(
- unionOfRects(
- {origin: {x: 1, y: 2}, size: {width: 3, height: 4}},
- {origin: {x: 100, y: 200}, size: {width: 3, height: 4}},
- {origin: {x: -10, y: -20}, size: {width: 50, height: 60}},
- ),
- ).toEqual({origin: {x: -10, y: -20}, size: {width: 113, height: 224}});
- });
-});
diff --git a/packages/react-devtools-timeline/src/view-base/constants.js b/packages/react-devtools-timeline/src/view-base/constants.js
deleted file mode 100644
index 845d1fcb96f0..000000000000
--- a/packages/react-devtools-timeline/src/view-base/constants.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * 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
- */
-
-export const MOVE_WHEEL_DELTA_THRESHOLD = 1;
-export const ZOOM_WHEEL_DELTA_THRESHOLD = 1;
-export const MIN_ZOOM_LEVEL = 0.25;
-export const MAX_ZOOM_LEVEL = 1000;
-export const DEFAULT_ZOOM_LEVEL = 0.25;
diff --git a/packages/react-devtools-timeline/src/view-base/geometry.js b/packages/react-devtools-timeline/src/view-base/geometry.js
deleted file mode 100644
index e298d7f2f66d..000000000000
--- a/packages/react-devtools-timeline/src/view-base/geometry.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * 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
- */
-
-export type Point = $ReadOnly<{x: number, y: number}>;
-export type Size = $ReadOnly<{width: number, height: number}>;
-export type IntrinsicSize = {
- ...Size,
-
- // If content is this height or less, hide the scrollbar entirely,
- // so that it doesn't take up vertical space unnecessarily (e.g. for a single row of content).
- hideScrollBarIfLessThanHeight?: number,
-
- // The initial height should be the height of the content, or this, whichever is less.
- maxInitialHeight?: number,
-};
-export type Rect = $ReadOnly<{origin: Point, size: Size}>;
-
-/**
- * Alternative representation of `Rect`.
- * A tuple of (`top`, `right`, `bottom`, `left`) coordinates.
- */
-type Box = [number, number, number, number];
-
-export const zeroPoint: Point = Object.freeze({x: 0, y: 0});
-export const zeroSize: Size = Object.freeze({width: 0, height: 0});
-export const zeroRect: Rect = Object.freeze({
- origin: zeroPoint,
- size: zeroSize,
-});
-
-export function pointEqualToPoint(point1: Point, point2: Point): boolean {
- return point1.x === point2.x && point1.y === point2.y;
-}
-
-export function sizeEqualToSize(size1: Size, size2: Size): boolean {
- return size1.width === size2.width && size1.height === size2.height;
-}
-
-export function rectEqualToRect(rect1: Rect, rect2: Rect): boolean {
- return (
- pointEqualToPoint(rect1.origin, rect2.origin) &&
- sizeEqualToSize(rect1.size, rect2.size)
- );
-}
-
-export function sizeIsValid({width, height}: Size): boolean {
- return width >= 0 && height >= 0;
-}
-
-export function sizeIsEmpty({width, height}: Size): boolean {
- return width <= 0 || height <= 0;
-}
-
-function rectToBox(rect: Rect): Box {
- const top = rect.origin.y;
- const right = rect.origin.x + rect.size.width;
- const bottom = rect.origin.y + rect.size.height;
- const left = rect.origin.x;
- return [top, right, bottom, left];
-}
-
-function boxToRect(box: Box): Rect {
- const [top, right, bottom, left] = box;
- return {
- origin: {
- x: left,
- y: top,
- },
- size: {
- width: right - left,
- height: bottom - top,
- },
- };
-}
-
-export function rectIntersectsRect(rect1: Rect, rect2: Rect): boolean {
- if (
- rect1.size.width === 0 ||
- rect1.size.height === 0 ||
- rect2.size.width === 0 ||
- rect2.size.height === 0
- ) {
- return false;
- }
-
- const [top1, right1, bottom1, left1] = rectToBox(rect1);
- const [top2, right2, bottom2, left2] = rectToBox(rect2);
- return !(
- right1 < left2 ||
- right2 < left1 ||
- bottom1 < top2 ||
- bottom2 < top1
- );
-}
-
-/**
- * Returns the intersection of the 2 rectangles.
- *
- * Prerequisite: `rect1` must intersect with `rect2`.
- */
-export function intersectionOfRects(rect1: Rect, rect2: Rect): Rect {
- const [top1, right1, bottom1, left1] = rectToBox(rect1);
- const [top2, right2, bottom2, left2] = rectToBox(rect2);
- return boxToRect([
- Math.max(top1, top2),
- Math.min(right1, right2),
- Math.min(bottom1, bottom2),
- Math.max(left1, left2),
- ]);
-}
-
-export function rectContainsPoint({x, y}: Point, rect: Rect): boolean {
- const [top, right, bottom, left] = rectToBox(rect);
- return left <= x && x <= right && top <= y && y <= bottom;
-}
-
-/**
- * Returns the smallest rectangle that contains all provided rects.
- *
- * @returns Union of `rects`. If `rects` is empty, returns `zeroRect`.
- */
-export function unionOfRects(...rects: Rect[]): Rect {
- if (rects.length === 0) {
- return zeroRect;
- }
-
- const [firstRect, ...remainingRects] = rects;
- const boxUnion = remainingRects
- .map(rectToBox)
- .reduce((intermediateUnion, nextBox): Box => {
- const [unionTop, unionRight, unionBottom, unionLeft] = intermediateUnion;
- const [nextTop, nextRight, nextBottom, nextLeft] = nextBox;
- return [
- Math.min(unionTop, nextTop),
- Math.max(unionRight, nextRight),
- Math.max(unionBottom, nextBottom),
- Math.min(unionLeft, nextLeft),
- ];
- }, rectToBox(firstRect));
- return boxToRect(boxUnion);
-}
diff --git a/packages/react-devtools-timeline/src/view-base/index.js b/packages/react-devtools-timeline/src/view-base/index.js
deleted file mode 100644
index 8990131793f7..000000000000
--- a/packages/react-devtools-timeline/src/view-base/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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
- */
-
-export * from './BackgroundColorView';
-export * from './HorizontalPanAndZoomView';
-export * from './Surface';
-export * from './VerticalScrollView';
-export * from './View';
-export * from './geometry';
-export * from './layouter';
-export * from './resizable';
-export * from './useCanvasInteraction';
-export * from './vertical-scroll-overflow';
diff --git a/packages/react-devtools-timeline/src/view-base/layouter.js b/packages/react-devtools-timeline/src/view-base/layouter.js
deleted file mode 100644
index 0a235a6cae3c..000000000000
--- a/packages/react-devtools-timeline/src/view-base/layouter.js
+++ /dev/null
@@ -1,225 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect} from './geometry';
-import type {View} from './View';
-
-export type LayoutInfo = {view: View, frame: Rect};
-export type Layout = LayoutInfo[];
-
-/**
- * A function that takes a list of subviews, currently laid out in
- * `existingLayout`, and lays them out into `containingFrame`.
- */
-export type Layouter = (
- existingLayout: Layout,
- containingFrame: Rect,
-) => Layout;
-
-function viewToLayoutInfo(view: View): LayoutInfo {
- return {view, frame: view.frame};
-}
-
-export function viewsToLayout(views: View[]): Layout {
- return views.map(viewToLayoutInfo);
-}
-
-/**
- * Applies `layout`'s `frame`s to its corresponding `view`.
- */
-export function collapseLayoutIntoViews(layout: Layout) {
- layout.forEach(({view, frame}) => view.setFrame(frame));
-}
-
-/**
- * A no-operation layout; does not modify the layout.
- */
-export const noopLayout: Layouter = layout => layout;
-
-/**
- * Layer views on top of each other. All views' frames will be set to `containerFrame`.
- *
- * Equivalent to composing:
- * - `alignToContainerXLayout`,
- * - `alignToContainerYLayout`,
- * - `containerWidthLayout`, and
- * - `containerHeightLayout`.
- */
-export const layeredLayout: Layouter = (layout, containerFrame) => {
- return layout.map(layoutInfo => ({...layoutInfo, frame: containerFrame}));
-};
-
-/**
- * Stacks `views` vertically in `frame`.
- * All views in `views` will have their widths set to the frame's width.
- */
-export const verticallyStackedLayout: Layouter = (layout, containerFrame) => {
- let currentY = containerFrame.origin.y;
- return layout.map(layoutInfo => {
- const desiredSize = layoutInfo.view.desiredSize();
- const height = desiredSize
- ? desiredSize.height
- : containerFrame.origin.y + containerFrame.size.height - currentY;
- const proposedFrame = {
- origin: {x: containerFrame.origin.x, y: currentY},
- size: {width: containerFrame.size.width, height},
- };
- currentY += height;
- return {
- ...layoutInfo,
- frame: proposedFrame,
- };
- });
-};
-
-/**
- * A layouter that aligns all frames' lefts to the container frame's left.
- */
-export const alignToContainerXLayout: Layouter = (layout, containerFrame) => {
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: {
- x: containerFrame.origin.x,
- y: layoutInfo.frame.origin.y,
- },
- size: layoutInfo.frame.size,
- },
- }));
-};
-
-/**
- * A layouter that aligns all frames' tops to the container frame's top.
- */
-export const alignToContainerYLayout: Layouter = (layout, containerFrame) => {
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: {
- x: layoutInfo.frame.origin.x,
- y: containerFrame.origin.y,
- },
- size: layoutInfo.frame.size,
- },
- }));
-};
-
-/**
- * A layouter that sets all frames' widths to `containerFrame.size.width`.
- */
-export const containerWidthLayout: Layouter = (layout, containerFrame) => {
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: layoutInfo.frame.origin,
- size: {
- width: containerFrame.size.width,
- height: layoutInfo.frame.size.height,
- },
- },
- }));
-};
-
-/**
- * A layouter that sets all frames' heights to `containerFrame.size.height`.
- */
-export const containerHeightLayout: Layouter = (layout, containerFrame) => {
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: layoutInfo.frame.origin,
- size: {
- width: layoutInfo.frame.size.width,
- height: containerFrame.size.height,
- },
- },
- }));
-};
-
-/**
- * A layouter that sets all frames' heights to the desired height of its view.
- * If the view has no desired size, the frame's height is set to 0.
- */
-export const desiredHeightLayout: Layouter = layout => {
- return layout.map(layoutInfo => {
- const desiredSize = layoutInfo.view.desiredSize();
- const height = desiredSize ? desiredSize.height : 0;
- return {
- ...layoutInfo,
- frame: {
- origin: layoutInfo.frame.origin,
- size: {
- width: layoutInfo.frame.size.width,
- height,
- },
- },
- };
- });
-};
-
-/**
- * A layouter that sets all frames' heights to the height of the tallest frame.
- */
-export const uniformMaxSubviewHeightLayout: Layouter = layout => {
- const maxHeight = Math.max(
- ...layout.map(layoutInfo => layoutInfo.frame.size.height),
- );
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: layoutInfo.frame.origin,
- size: {
- width: layoutInfo.frame.size.width,
- height: maxHeight,
- },
- },
- }));
-};
-
-/**
- * A layouter that sets heights in this fashion:
- * - If a frame's height >= `containerFrame.size.height`, the frame is left unchanged.
- * - Otherwise, sets the frame's height to `containerFrame.size.height`.
- */
-export const atLeastContainerHeightLayout: Layouter = (
- layout,
- containerFrame,
-) => {
- return layout.map(layoutInfo => ({
- ...layoutInfo,
- frame: {
- origin: layoutInfo.frame.origin,
- size: {
- width: layoutInfo.frame.size.width,
- height: Math.max(
- containerFrame.size.height,
- layoutInfo.frame.size.height,
- ),
- },
- },
- }));
-};
-
-/**
- * Create a layouter that applies each layouter in `layouters` in sequence.
- */
-export function createComposedLayout(...layouters: Layouter[]): Layouter {
- if (layouters.length === 0) {
- return noopLayout;
- }
-
- const composedLayout: Layouter = (layout, containerFrame) => {
- return layouters.reduce(
- (intermediateLayout, layouter) =>
- layouter(intermediateLayout, containerFrame),
- layout,
- );
- };
- return composedLayout;
-}
diff --git a/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js b/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js
deleted file mode 100644
index 024ff5056f3e..000000000000
--- a/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js
+++ /dev/null
@@ -1,299 +0,0 @@
-/**
- * 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
- */
-
-import type {
- ClickInteraction,
- DoubleClickInteraction,
- Interaction,
- MouseDownInteraction,
- MouseMoveInteraction,
- MouseUpInteraction,
-} from '../useCanvasInteraction';
-import type {Rect} from '../geometry';
-import type {ViewRefs} from '../Surface';
-import type {ViewState} from '../../types';
-
-import {ResizeBarView} from './ResizeBarView';
-import {Surface} from '../Surface';
-import {View} from '../View';
-import {rectContainsPoint} from '../geometry';
-import {noopLayout} from '../layouter';
-import {clamp} from '../utils/clamp';
-
-type ResizingState = $ReadOnly<{
- /** Distance between top of resize bar and mouseY */
- cursorOffsetInBarFrame: number,
- /** Mouse's vertical coordinates relative to canvas */
- mouseY: number,
-}>;
-
-type LayoutState = {
- /** Resize bar's vertical position relative to resize view's frame.origin.y */
- barOffsetY: number,
-};
-
-const RESIZE_BAR_HEIGHT = 8;
-const RESIZE_BAR_WITH_LABEL_HEIGHT = 16;
-
-const HIDDEN_RECT = {
- origin: {x: 0, y: 0},
- size: {width: 0, height: 0},
-};
-
-export class ResizableView extends View {
- _canvasRef: {current: HTMLCanvasElement | null};
- _layoutState: LayoutState;
- _mutableViewStateKey: string;
- _resizeBar: ResizeBarView;
- _resizingState: ResizingState | null = null;
- _subview: View;
- _viewState: ViewState;
-
- constructor(
- surface: Surface,
- frame: Rect,
- subview: View,
- viewState: ViewState,
- canvasRef: {current: HTMLCanvasElement | null},
- label: string,
- ) {
- super(surface, frame, noopLayout);
-
- this._canvasRef = canvasRef;
- this._layoutState = {barOffsetY: 0};
- this._mutableViewStateKey = label + ':ResizableView';
- this._subview = subview;
- this._resizeBar = new ResizeBarView(surface, frame, label);
- this._viewState = viewState;
-
- this.addSubview(this._subview);
- this.addSubview(this._resizeBar);
-
- this._restoreMutableViewState();
- }
-
- desiredSize(): {+height: number, +width: number} {
- const subviewDesiredSize = this._subview.desiredSize();
-
- if (this._shouldRenderResizeBar()) {
- const resizeBarDesiredSize = this._resizeBar.desiredSize();
-
- return {
- width: this.frame.size.width,
- height: this._layoutState.barOffsetY + resizeBarDesiredSize.height,
- };
- } else {
- return {
- width: this.frame.size.width,
- height: subviewDesiredSize.height,
- };
- }
- }
-
- layoutSubviews() {
- this._updateLayoutState();
- this._updateSubviewFrames();
-
- super.layoutSubviews();
- }
-
- _restoreMutableViewState() {
- if (
- this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
- ) {
- this._layoutState = this._viewState.viewToMutableViewStateMap.get(
- this._mutableViewStateKey,
- ) as any as LayoutState;
-
- this._updateLayoutStateAndResizeBar(this._layoutState.barOffsetY);
- } else {
- this._viewState.viewToMutableViewStateMap.set(
- this._mutableViewStateKey,
- this._layoutState,
- );
-
- const subviewDesiredSize = this._subview.desiredSize();
- this._updateLayoutStateAndResizeBar(
- subviewDesiredSize.maxInitialHeight != null
- ? Math.min(
- subviewDesiredSize.maxInitialHeight,
- subviewDesiredSize.height,
- )
- : subviewDesiredSize.height,
- );
- }
-
- this.setNeedsDisplay();
- }
-
- _shouldRenderResizeBar(): boolean {
- const subviewDesiredSize = this._subview.desiredSize();
- return subviewDesiredSize.hideScrollBarIfLessThanHeight != null
- ? subviewDesiredSize.height >
- subviewDesiredSize.hideScrollBarIfLessThanHeight
- : true;
- }
-
- _updateLayoutStateAndResizeBar(barOffsetY: number) {
- if (barOffsetY <= RESIZE_BAR_WITH_LABEL_HEIGHT - RESIZE_BAR_HEIGHT) {
- barOffsetY = 0;
- }
-
- this._layoutState.barOffsetY = barOffsetY;
-
- this._resizeBar.showLabel = barOffsetY === 0;
- }
-
- _updateLayoutState() {
- const {frame, _resizingState} = this;
-
- // Allow bar to travel to bottom of the visible area of this view but no further
- const subviewDesiredSize = this._subview.desiredSize();
- const maxBarOffset = subviewDesiredSize.height;
-
- let proposedBarOffsetY = this._layoutState.barOffsetY;
- // Update bar offset if dragging bar
- if (_resizingState) {
- const {mouseY, cursorOffsetInBarFrame} = _resizingState;
- proposedBarOffsetY = mouseY - frame.origin.y - cursorOffsetInBarFrame;
- }
-
- this._updateLayoutStateAndResizeBar(
- clamp(0, maxBarOffset, proposedBarOffsetY),
- );
- }
-
- _updateSubviewFrames() {
- const {
- frame: {
- origin: {x, y},
- size: {width},
- },
- _layoutState: {barOffsetY},
- } = this;
-
- const resizeBarDesiredSize = this._resizeBar.desiredSize();
-
- if (barOffsetY === 0) {
- this._subview.setFrame(HIDDEN_RECT);
- } else {
- this._subview.setFrame({
- origin: {x, y},
- size: {width, height: barOffsetY},
- });
- }
-
- this._resizeBar.setFrame({
- origin: {x, y: y + barOffsetY},
- size: {width, height: resizeBarDesiredSize.height},
- });
- }
-
- _handleClick(interaction: ClickInteraction): void | boolean {
- if (!this._shouldRenderResizeBar()) {
- return;
- }
-
- const cursorInView = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
- if (cursorInView) {
- if (this._layoutState.barOffsetY === 0) {
- // Clicking on the collapsed label should expand.
- const subviewDesiredSize = this._subview.desiredSize();
- this._updateLayoutStateAndResizeBar(subviewDesiredSize.height);
- this.setNeedsDisplay();
-
- return true;
- }
- }
- }
-
- _handleDoubleClick(interaction: DoubleClickInteraction): void | boolean {
- if (!this._shouldRenderResizeBar()) {
- return;
- }
-
- const cursorInView = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
- if (cursorInView) {
- if (this._layoutState.barOffsetY > 0) {
- // Double clicking on the expanded view should collapse.
- this._updateLayoutStateAndResizeBar(0);
- this.setNeedsDisplay();
-
- return true;
- }
- }
- }
-
- _handleMouseDown(interaction: MouseDownInteraction): void | boolean {
- const cursorLocation = interaction.payload.location;
- const resizeBarFrame = this._resizeBar.frame;
- if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
- const mouseY = cursorLocation.y;
- this._resizingState = {
- cursorOffsetInBarFrame: mouseY - resizeBarFrame.origin.y,
- mouseY,
- };
-
- return true;
- }
- }
-
- _handleMouseMove(interaction: MouseMoveInteraction): void | boolean {
- const {_resizingState} = this;
- if (_resizingState) {
- this._resizingState = {
- ..._resizingState,
- mouseY: interaction.payload.location.y,
- };
- this.setNeedsDisplay();
-
- return true;
- }
- }
-
- _handleMouseUp(interaction: MouseUpInteraction) {
- if (this._resizingState) {
- this._resizingState = null;
- }
- }
-
- getCursorActiveSubView(interaction: Interaction): View | null {
- const cursorLocation = interaction.payload.location;
- const resizeBarFrame = this._resizeBar.frame;
- if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
- return this;
- } else {
- return null;
- }
- }
-
- handleInteraction(
- interaction: Interaction,
- viewRefs: ViewRefs,
- ): void | boolean {
- switch (interaction.type) {
- case 'click':
- return this._handleClick(interaction);
- case 'double-click':
- return this._handleDoubleClick(interaction);
- case 'mousedown':
- return this._handleMouseDown(interaction);
- case 'mousemove':
- return this._handleMouseMove(interaction);
- case 'mouseup':
- return this._handleMouseUp(interaction);
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/resizable/ResizeBarView.js b/packages/react-devtools-timeline/src/view-base/resizable/ResizeBarView.js
deleted file mode 100644
index 21e49734037d..000000000000
--- a/packages/react-devtools-timeline/src/view-base/resizable/ResizeBarView.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * 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
- */
-
-import type {
- Interaction,
- MouseDownInteraction,
- MouseMoveInteraction,
- MouseUpInteraction,
-} from '../useCanvasInteraction';
-import type {Rect, Size} from '../geometry';
-import type {ViewRefs} from '../Surface';
-
-import {BORDER_SIZE, COLORS} from '../../content-views/constants';
-import {drawText} from '../../content-views/utils/text';
-import {Surface} from '../Surface';
-import {View} from '../View';
-import {rectContainsPoint} from '../geometry';
-import {noopLayout} from '../layouter';
-
-type ResizeBarState = 'normal' | 'hovered' | 'dragging';
-
-const RESIZE_BAR_DOT_RADIUS = 1;
-const RESIZE_BAR_DOT_SPACING = 4;
-const RESIZE_BAR_HEIGHT = 8;
-const RESIZE_BAR_WITH_LABEL_HEIGHT = 16;
-
-export class ResizeBarView extends View {
- _interactionState: ResizeBarState = 'normal';
- _label: string;
-
- showLabel: boolean = false;
-
- constructor(surface: Surface, frame: Rect, label: string) {
- super(surface, frame, noopLayout);
-
- this._label = label;
- }
-
- desiredSize(): Size {
- return this.showLabel
- ? {height: RESIZE_BAR_WITH_LABEL_HEIGHT, width: 0}
- : {height: RESIZE_BAR_HEIGHT, width: 0};
- }
-
- draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
- const {frame} = this;
- const {x, y} = frame.origin;
- const {width, height} = frame.size;
-
- const isActive =
- this._interactionState === 'dragging' ||
- (this._interactionState === 'hovered' && viewRefs.activeView === null);
-
- context.fillStyle = isActive
- ? COLORS.REACT_RESIZE_BAR_ACTIVE
- : COLORS.REACT_RESIZE_BAR;
- context.fillRect(x, y, width, height);
-
- context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
- context.fillRect(x, y, width, BORDER_SIZE);
- context.fillRect(x, y + height - BORDER_SIZE, width, BORDER_SIZE);
-
- const horizontalCenter = x + width / 2;
- const verticalCenter = y + height / 2;
-
- if (this.showLabel) {
- // When the resize view is collapsed entirely,
- // rather than showing a resize bar– this view displays a label.
- const labelRect: Rect = {
- origin: {
- x: 0,
- y: y + height - RESIZE_BAR_WITH_LABEL_HEIGHT,
- },
- size: {
- width: frame.size.width,
- height: RESIZE_BAR_WITH_LABEL_HEIGHT,
- },
- };
-
- drawText(this._label, context, labelRect, frame, {
- fillStyle: COLORS.REACT_RESIZE_BAR_DOT,
- textAlign: 'center',
- });
- } else {
- // Otherwise draw horizontally centered resize bar dots
- context.beginPath();
- context.fillStyle = COLORS.REACT_RESIZE_BAR_DOT;
- context.arc(
- horizontalCenter,
- verticalCenter,
- RESIZE_BAR_DOT_RADIUS,
- 0,
- 2 * Math.PI,
- );
- context.arc(
- horizontalCenter + RESIZE_BAR_DOT_SPACING,
- verticalCenter,
- RESIZE_BAR_DOT_RADIUS,
- 0,
- 2 * Math.PI,
- );
- context.arc(
- horizontalCenter - RESIZE_BAR_DOT_SPACING,
- verticalCenter,
- RESIZE_BAR_DOT_RADIUS,
- 0,
- 2 * Math.PI,
- );
- context.fill();
- }
- }
-
- _setInteractionState(state: ResizeBarState) {
- if (this._interactionState === state) {
- return;
- }
- this._interactionState = state;
- this.setNeedsDisplay();
- }
-
- _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
- const cursorInView = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
- if (cursorInView) {
- this._setInteractionState('dragging');
- viewRefs.activeView = this;
- }
- }
-
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const cursorInView = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
-
- if (viewRefs.activeView === this) {
- // If we're actively dragging this resize bar,
- // show the cursor even if the pointer isn't hovering over this view.
- this.currentCursor = 'ns-resize';
- } else if (cursorInView) {
- if (this.showLabel) {
- this.currentCursor = 'pointer';
- } else {
- this.currentCursor = 'ns-resize';
- }
- }
-
- if (cursorInView) {
- viewRefs.hoveredView = this;
- }
-
- if (this._interactionState === 'dragging') {
- return;
- }
- this._setInteractionState(cursorInView ? 'hovered' : 'normal');
- }
-
- _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
- const cursorInView = rectContainsPoint(
- interaction.payload.location,
- this.frame,
- );
- if (this._interactionState === 'dragging') {
- this._setInteractionState(cursorInView ? 'hovered' : 'normal');
- }
-
- if (viewRefs.activeView === this) {
- viewRefs.activeView = null;
- }
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'mousedown':
- this._handleMouseDown(interaction, viewRefs);
- break;
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- case 'mouseup':
- this._handleMouseUp(interaction, viewRefs);
- break;
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/resizable/index.js b/packages/react-devtools-timeline/src/view-base/resizable/index.js
deleted file mode 100644
index 0ea8018f66ef..000000000000
--- a/packages/react-devtools-timeline/src/view-base/resizable/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * 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
- */
-
-export * from './ResizableView';
-export * from './ResizeBarView';
diff --git a/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js b/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js
deleted file mode 100644
index 0f2bf0e87c46..000000000000
--- a/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/**
- * 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
- */
-
-import type {NormalizedWheelDelta} from './utils/normalizeWheel';
-import type {Point} from './geometry';
-
-import {useEffect, useRef} from 'react';
-import {normalizeWheel} from './utils/normalizeWheel';
-
-export type ClickInteraction = {
- type: 'click',
- payload: {
- event: MouseEvent,
- location: Point,
- },
-};
-export type DoubleClickInteraction = {
- type: 'double-click',
- payload: {
- event: MouseEvent,
- location: Point,
- },
-};
-export type MouseDownInteraction = {
- type: 'mousedown',
- payload: {
- event: MouseEvent,
- location: Point,
- },
-};
-export type MouseMoveInteraction = {
- type: 'mousemove',
- payload: {
- event: MouseEvent,
- location: Point,
- },
-};
-export type MouseUpInteraction = {
- type: 'mouseup',
- payload: {
- event: MouseEvent,
- location: Point,
- },
-};
-export type WheelPlainInteraction = {
- type: 'wheel-plain',
- payload: {
- event: WheelEvent,
- location: Point,
- delta: NormalizedWheelDelta,
- },
-};
-export type WheelWithShiftInteraction = {
- type: 'wheel-shift',
- payload: {
- event: WheelEvent,
- location: Point,
- delta: NormalizedWheelDelta,
- },
-};
-export type WheelWithControlInteraction = {
- type: 'wheel-control',
- payload: {
- event: WheelEvent,
- location: Point,
- delta: NormalizedWheelDelta,
- },
-};
-export type WheelWithMetaInteraction = {
- type: 'wheel-meta',
- payload: {
- event: WheelEvent,
- location: Point,
- delta: NormalizedWheelDelta,
- },
-};
-
-export type Interaction =
- | ClickInteraction
- | DoubleClickInteraction
- | MouseDownInteraction
- | MouseMoveInteraction
- | MouseUpInteraction
- | WheelPlainInteraction
- | WheelWithShiftInteraction
- | WheelWithControlInteraction
- | WheelWithMetaInteraction;
-
-let canvasBoundingRectCache = null;
-function cacheFirstGetCanvasBoundingRect(
- canvas: HTMLCanvasElement,
-): ClientRect {
- if (
- canvasBoundingRectCache &&
- canvas.width === canvasBoundingRectCache.width &&
- canvas.height === canvasBoundingRectCache.height
- ) {
- return canvasBoundingRectCache.rect;
- }
- canvasBoundingRectCache = {
- width: canvas.width,
- height: canvas.height,
- rect: canvas.getBoundingClientRect(),
- };
- return canvasBoundingRectCache.rect;
-}
-
-export function useCanvasInteraction(
- canvasRef: {current: HTMLCanvasElement | null},
- interactor: (interaction: Interaction) => void,
-) {
- const isMouseDownRef = useRef(false);
- const didMouseMoveWhileDownRef = useRef(false);
-
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) {
- return;
- }
-
- function localToCanvasCoordinates(localCoordinates: Point): Point {
- // $FlowFixMe[incompatible-type] found when upgrading Flow
- const canvasRect = cacheFirstGetCanvasBoundingRect(canvas);
- return {
- x: localCoordinates.x - canvasRect.left,
- y: localCoordinates.y - canvasRect.top,
- };
- }
-
- const onCanvasClick: MouseEventHandler = event => {
- if (didMouseMoveWhileDownRef.current) {
- return;
- }
-
- interactor({
- type: 'click',
- payload: {
- event,
- location: localToCanvasCoordinates({x: event.x, y: event.y}),
- },
- });
- };
-
- const onCanvasDoubleClick: MouseEventHandler = event => {
- if (didMouseMoveWhileDownRef.current) {
- return;
- }
-
- interactor({
- type: 'double-click',
- payload: {
- event,
- location: localToCanvasCoordinates({x: event.x, y: event.y}),
- },
- });
- };
-
- const onCanvasMouseDown: MouseEventHandler = event => {
- didMouseMoveWhileDownRef.current = false;
- isMouseDownRef.current = true;
-
- interactor({
- type: 'mousedown',
- payload: {
- event,
- location: localToCanvasCoordinates({x: event.x, y: event.y}),
- },
- });
- };
-
- const onDocumentMouseMove: MouseEventHandler = event => {
- if (isMouseDownRef.current) {
- didMouseMoveWhileDownRef.current = true;
- }
-
- interactor({
- type: 'mousemove',
- payload: {
- event,
- location: localToCanvasCoordinates({x: event.x, y: event.y}),
- },
- });
- };
-
- const onDocumentMouseUp: MouseEventHandler = event => {
- isMouseDownRef.current = false;
-
- interactor({
- type: 'mouseup',
- payload: {
- event,
- location: localToCanvasCoordinates({x: event.x, y: event.y}),
- },
- });
- };
-
- const onCanvasWheel: WheelEventHandler = event => {
- event.preventDefault();
- event.stopPropagation();
-
- const location = localToCanvasCoordinates({x: event.x, y: event.y});
- const delta = normalizeWheel(event);
-
- if (event.shiftKey) {
- interactor({
- type: 'wheel-shift',
- payload: {event, location, delta},
- });
- } else if (event.ctrlKey) {
- interactor({
- type: 'wheel-control',
- payload: {event, location, delta},
- });
- } else if (event.metaKey) {
- interactor({
- type: 'wheel-meta',
- payload: {event, location, delta},
- });
- } else {
- interactor({
- type: 'wheel-plain',
- payload: {event, location, delta},
- });
- }
-
- return false;
- };
-
- const ownerDocument = canvas.ownerDocument;
- ownerDocument.addEventListener('mousemove', onDocumentMouseMove);
- ownerDocument.addEventListener('mouseup', onDocumentMouseUp);
-
- canvas.addEventListener('click', onCanvasClick);
- canvas.addEventListener('dblclick', onCanvasDoubleClick);
- canvas.addEventListener('mousedown', onCanvasMouseDown);
- canvas.addEventListener('wheel', onCanvasWheel);
-
- return () => {
- ownerDocument.removeEventListener('mousemove', onDocumentMouseMove);
- ownerDocument.removeEventListener('mouseup', onDocumentMouseUp);
-
- canvas.removeEventListener('click', onCanvasClick);
- canvas.removeEventListener('dblclick', onCanvasDoubleClick);
- canvas.removeEventListener('mousedown', onCanvasMouseDown);
- canvas.removeEventListener('wheel', onCanvasWheel);
- };
- }, [canvasRef, interactor]);
-}
diff --git a/packages/react-devtools-timeline/src/view-base/utils/__tests__/clamp-test.js b/packages/react-devtools-timeline/src/view-base/utils/__tests__/clamp-test.js
deleted file mode 100644
index c8bcb84903b9..000000000000
--- a/packages/react-devtools-timeline/src/view-base/utils/__tests__/clamp-test.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * 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
- */
-
-import {clamp} from '../clamp';
-
-describe('clamp', () => {
- it('should return min if value < min', () => {
- expect(clamp(0, 1, -1)).toBe(0);
- expect(clamp(0.1, 1.1, 0.05)).toBe(0.1);
- });
-
- it('should return value if min <= value <= max', () => {
- expect(clamp(0, 1, 0)).toBe(0);
- expect(clamp(0, 1, 0.5)).toBe(0.5);
- expect(clamp(0, 1, 1)).toBe(1);
- expect(clamp(0.1, 1.1, 0.15)).toBe(0.15);
- });
-
- it('should return max if max < value', () => {
- expect(clamp(0, 1, 2)).toBe(1);
- expect(clamp(0.1, 1.1, 1.15)).toBe(1.1);
- });
-});
diff --git a/packages/react-devtools-timeline/src/view-base/utils/__tests__/scrollState-test.js b/packages/react-devtools-timeline/src/view-base/utils/__tests__/scrollState-test.js
deleted file mode 100644
index 8129ea5d4afd..000000000000
--- a/packages/react-devtools-timeline/src/view-base/utils/__tests__/scrollState-test.js
+++ /dev/null
@@ -1,261 +0,0 @@
-/**
- * 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
- */
-
-import {
- clampState,
- moveStateToRange,
- areScrollStatesEqual,
- translateState,
- zoomState,
-} from '../scrollState';
-
-describe('clampState', () => {
- it('should passthrough offset if state fits within container', () => {
- expect(
- clampState({
- state: {offset: 0, length: 50},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(0, 10);
- expect(
- clampState({
- state: {offset: -20, length: 100},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(-20, 10);
- });
-
- it('should clamp offset if offset causes content to go out of container', () => {
- expect(
- clampState({
- state: {offset: -1, length: 50},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(0, 10);
- expect(
- clampState({
- state: {offset: 1, length: 50},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(0, 10);
-
- expect(
- clampState({
- state: {offset: -51, length: 100},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(-50, 10);
- expect(
- clampState({
- state: {offset: 1, length: 100},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(0, 10);
- });
-
- it('should passthrough length if container fits in content', () => {
- expect(
- clampState({
- state: {offset: 0, length: 70},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).length,
- ).toBeCloseTo(70, 10);
- expect(
- clampState({
- state: {offset: 0, length: 50},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).length,
- ).toBeCloseTo(50, 10);
- expect(
- clampState({
- state: {offset: 0, length: 100},
- minContentLength: 0,
- maxContentLength: 100,
- containerLength: 50,
- }).length,
- ).toBeCloseTo(100, 10);
- });
-
- it('should clamp length to minimum of max(minContentLength, containerLength)', () => {
- expect(
- clampState({
- state: {offset: -20, length: 0},
- minContentLength: 20,
- maxContentLength: 100,
- containerLength: 50,
- }).length,
- ).toBeCloseTo(50, 10);
- expect(
- clampState({
- state: {offset: -20, length: 0},
- minContentLength: 50,
- maxContentLength: 100,
- containerLength: 20,
- }).length,
- ).toBeCloseTo(50, 10);
- });
-
- it('should clamp length to maximum of max(containerLength, maxContentLength)', () => {
- expect(
- clampState({
- state: {offset: -20, length: 100},
- minContentLength: 0,
- maxContentLength: 40,
- containerLength: 50,
- }).length,
- ).toBeCloseTo(50, 10);
- expect(
- clampState({
- state: {offset: -20, length: 100},
- minContentLength: 0,
- maxContentLength: 50,
- containerLength: 40,
- }).length,
- ).toBeCloseTo(50, 10);
- });
-});
-
-describe('translateState', () => {
- it('should translate state by delta and leave length unchanged', () => {
- expect(
- translateState({
- state: {offset: 0, length: 100},
- delta: -3.14,
- containerLength: 50,
- }),
- ).toEqual({offset: -3.14, length: 100});
- });
-
- it('should clamp resulting offset', () => {
- expect(
- translateState({
- state: {offset: 0, length: 50},
- delta: -3.14,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(0, 10);
- expect(
- translateState({
- state: {offset: 0, length: 53},
- delta: -100,
- containerLength: 50,
- }).offset,
- ).toBeCloseTo(-3, 10);
- });
-});
-
-describe('zoomState', () => {
- it('should scale width by multiplier', () => {
- expect(
- zoomState({
- state: {offset: 0, length: 100},
- multiplier: 1.5,
- fixedPoint: 0,
-
- minContentLength: 0,
- maxContentLength: 1000,
- containerLength: 50,
- }),
- ).toEqual({offset: 0, length: 150});
- });
-
- it('should clamp zoomed state', () => {
- const zoomedState = zoomState({
- state: {offset: -20, length: 100},
- multiplier: 0.1,
- fixedPoint: 5,
-
- minContentLength: 50,
- maxContentLength: 100,
- containerLength: 50,
- });
- expect(zoomedState.offset).toBeCloseTo(0, 10);
- expect(zoomedState.length).toBeCloseTo(50, 10);
- });
-
- it('should maintain containerStart<->fixedPoint distance', () => {
- const offset = -20;
- const fixedPointFromContainer = 10;
-
- const zoomedState = zoomState({
- state: {offset, length: 100},
- multiplier: 2,
- fixedPoint: fixedPointFromContainer - offset,
-
- minContentLength: 0,
- maxContentLength: 1000,
- containerLength: 50,
- });
-
- expect(zoomedState).toMatchInlineSnapshot(`
- {
- "length": 200,
- "offset": -50,
- }
- `);
- });
-});
-
-describe('moveStateToRange', () => {
- it('should set [rangeStart, rangeEnd] = container', () => {
- const movedState = moveStateToRange({
- state: {offset: -20, length: 100},
- rangeStart: 50,
- rangeEnd: 100,
- contentLength: 400,
-
- minContentLength: 10,
- maxContentLength: 1000,
- containerLength: 50,
- });
-
- expect(movedState).toMatchInlineSnapshot(`
- {
- "length": 400,
- "offset": -50,
- }
- `);
- });
-});
-
-describe('areScrollStatesEqual', () => {
- it('should return true if equal', () => {
- expect(
- areScrollStatesEqual({offset: 0, length: 0}, {offset: 0, length: 0}),
- ).toBe(true);
- expect(
- areScrollStatesEqual({offset: -1, length: 1}, {offset: -1, length: 1}),
- ).toBe(true);
- });
-
- it('should return false if not equal', () => {
- expect(
- areScrollStatesEqual({offset: 0, length: 0}, {offset: -1, length: 0}),
- ).toBe(false);
- expect(
- areScrollStatesEqual({offset: -1, length: 1}, {offset: -1, length: 0}),
- ).toBe(false);
- });
-});
diff --git a/packages/react-devtools-timeline/src/view-base/utils/clamp.js b/packages/react-devtools-timeline/src/view-base/utils/clamp.js
deleted file mode 100644
index ff9fdd74ba98..000000000000
--- a/packages/react-devtools-timeline/src/view-base/utils/clamp.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * 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
- */
-
-export function clamp(min: number, max: number, value: number): number {
- if (Number.isNaN(min) || Number.isNaN(max) || Number.isNaN(value)) {
- throw new Error(
- `Clamp was called with NaN. Args: min: ${min}, max: ${max}, value: ${value}.`,
- );
- }
- return Math.min(max, Math.max(min, value));
-}
diff --git a/packages/react-devtools-timeline/src/view-base/utils/normalizeWheel.js b/packages/react-devtools-timeline/src/view-base/utils/normalizeWheel.js
deleted file mode 100644
index 95f3db1aea2d..000000000000
--- a/packages/react-devtools-timeline/src/view-base/utils/normalizeWheel.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 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
- */
-
-// Adapted from: https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
-
-export type NormalizedWheelDelta = {
- deltaX: number,
- deltaY: number,
-};
-
-// Reasonable defaults
-const LINE_HEIGHT = 40;
-const PAGE_HEIGHT = 800;
-
-/**
- * Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
- * complicated, thus this doc is long and (hopefully) detailed enough to answer
- * your questions.
- *
- * If you need to react to the mouse wheel in a predictable way, this code is
- * like your bestest friend. * hugs *
- *
- * In your event callback, use this code to get sane interpretation of the
- * deltas. This code will return an object with properties:
- *
- * - deltaX -- normalized distance (to pixels) - x plane
- * - deltaY -- " - y plane
- *
- * Wheel values are provided by the browser assuming you are using the wheel to
- * scroll a web page by a number of lines or pixels (or pages). Values can vary
- * significantly on different platforms and browsers, forgetting that you can
- * scroll at different speeds. Some devices (like trackpads) emit more events
- * at smaller increments with fine granularity, and some emit massive jumps with
- * linear speed or acceleration.
- *
- * This code does its best to normalize the deltas for you:
- *
- * - delta* is normalizing the desired scroll delta in pixel units.
- *
- * - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT. This
- * should translate to positive value zooming IN, negative zooming OUT.
- * This matches the 'wheel' event.
- *
- * Implementation info:
- *
- * The basics of the standard 'wheel' event is that it includes a unit,
- * deltaMode (pixels, lines, pages), and deltaX, deltaY and deltaZ.
- * See: http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
- *
- * Examples of 'wheel' event if you scroll slowly (down) by one step with an
- * average mouse:
- *
- * OS X + Chrome (mouse) - 4 pixel delta (wheelDelta -120)
- * OS X + Safari (mouse) - N/A pixel delta (wheelDelta -12)
- * OS X + Firefox (mouse) - 0.1 line delta (wheelDelta N/A)
- * Win8 + Chrome (mouse) - 100 pixel delta (wheelDelta -120)
- * Win8 + Firefox (mouse) - 3 line delta (wheelDelta -120)
- *
- * On the trackpad:
- *
- * OS X + Chrome (trackpad) - 2 pixel delta (wheelDelta -6)
- * OS X + Firefox (trackpad) - 1 pixel delta (wheelDelta N/A)
- */
-export function normalizeWheel(event: WheelEvent): NormalizedWheelDelta {
- let deltaX = event.deltaX;
- let deltaY = event.deltaY;
-
- if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
- // delta in LINE units
- deltaX *= LINE_HEIGHT;
- deltaY *= LINE_HEIGHT;
- } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
- // delta in PAGE units
- deltaX *= PAGE_HEIGHT;
- deltaY *= PAGE_HEIGHT;
- }
-
- return {deltaX, deltaY};
-}
diff --git a/packages/react-devtools-timeline/src/view-base/utils/scrollState.js b/packages/react-devtools-timeline/src/view-base/utils/scrollState.js
deleted file mode 100644
index 9450b312e211..000000000000
--- a/packages/react-devtools-timeline/src/view-base/utils/scrollState.js
+++ /dev/null
@@ -1,206 +0,0 @@
-/**
- * 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
- */
-
-import {clamp} from './clamp';
-
-/**
- * Single-axis offset and length state.
- *
- * ```
- * contentStart containerStart containerEnd contentEnd
- * |<----------offset| | |
- * |<-------------------length------------------->|
- * ```
- */
-export type ScrollState = {
- offset: number,
- length: number,
-};
-
-function clampOffset(state: ScrollState, containerLength: number): ScrollState {
- return {
- offset: clamp(-(state.length - containerLength), 0, state.offset),
- length: state.length,
- };
-}
-
-function clampLength({
- state,
- minContentLength,
- maxContentLength,
- containerLength,
-}: {
- state: ScrollState,
- minContentLength: number,
- maxContentLength: number,
- containerLength: number,
-}): ScrollState {
- return {
- offset: state.offset,
- length: clamp(
- Math.max(minContentLength, containerLength),
- Math.max(containerLength, maxContentLength),
- state.length,
- ),
- };
-}
-
-/**
- * Returns `state` clamped such that:
- * - `length`: you won't be able to zoom in/out such that the content is
- * shorter than the `containerLength`.
- * - `offset`: content remains in `containerLength`.
- */
-export function clampState({
- state,
- minContentLength,
- maxContentLength,
- containerLength,
-}: {
- state: ScrollState,
- minContentLength: number,
- maxContentLength: number,
- containerLength: number,
-}): ScrollState {
- return clampOffset(
- clampLength({
- state,
- minContentLength,
- maxContentLength,
- containerLength,
- }),
- containerLength,
- );
-}
-
-export function translateState({
- state,
- delta,
- containerLength,
-}: {
- state: ScrollState,
- delta: number,
- containerLength: number,
-}): ScrollState {
- return clampOffset(
- {
- offset: state.offset + delta,
- length: state.length,
- },
- containerLength,
- );
-}
-
-/**
- * Returns a new clamped `state` zoomed by `multiplier`.
- *
- * The provided fixed point will also remain stationary relative to
- * `containerStart`.
- *
- * ```
- * contentStart containerStart fixedPoint containerEnd
- * |<---------offset-| x |
- * |-fixedPoint-------------------------------->x |
- * |-fixedPointFromContainer->x |
- * |<----------containerLength----------->|
- * ```
- */
-export function zoomState({
- state,
- multiplier,
- fixedPoint,
-
- minContentLength,
- maxContentLength,
- containerLength,
-}: {
- state: ScrollState,
- multiplier: number,
- fixedPoint: number,
-
- minContentLength: number,
- maxContentLength: number,
- containerLength: number,
-}): ScrollState {
- // Length and offset must be computed separately, so that if the length is
- // clamped the offset will still be correct (unless it gets clamped too).
-
- const zoomedState = clampLength({
- state: {
- offset: state.offset,
- length: state.length * multiplier,
- },
- minContentLength,
- maxContentLength,
- containerLength,
- });
-
- // Adjust offset so that distance between containerStart<->fixedPoint is fixed
- const fixedPointFromContainer = fixedPoint + state.offset;
- const scaledFixedPoint = fixedPoint * (zoomedState.length / state.length);
- const offsetAdjustedState = clampOffset(
- {
- offset: fixedPointFromContainer - scaledFixedPoint,
- length: zoomedState.length,
- },
- containerLength,
- );
-
- return offsetAdjustedState;
-}
-
-export function moveStateToRange({
- state,
- rangeStart,
- rangeEnd,
- contentLength,
-
- minContentLength,
- maxContentLength,
- containerLength,
-}: {
- state: ScrollState,
- rangeStart: number,
- rangeEnd: number,
- contentLength: number,
-
- minContentLength: number,
- maxContentLength: number,
- containerLength: number,
-}): ScrollState {
- // Length and offset must be computed separately, so that if the length is
- // clamped the offset will still be correct (unless it gets clamped too).
-
- const lengthClampedState = clampLength({
- state: {
- offset: state.offset,
- length: contentLength * (containerLength / (rangeEnd - rangeStart)),
- },
- minContentLength,
- maxContentLength,
- containerLength,
- });
-
- const offsetAdjustedState = clampOffset(
- {
- offset: -rangeStart * (lengthClampedState.length / contentLength),
- length: lengthClampedState.length,
- },
- containerLength,
- );
-
- return offsetAdjustedState;
-}
-
-export function areScrollStatesEqual(
- state1: ScrollState,
- state2: ScrollState,
-): boolean {
- return state1.offset === state2.offset && state1.length === state2.length;
-}
diff --git a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollBarView.js b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollBarView.js
deleted file mode 100644
index 1b542509f5e6..000000000000
--- a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollBarView.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect} from '../geometry';
-import type {Surface, ViewRefs} from '../Surface';
-import type {
- Interaction,
- ClickInteraction,
- MouseDownInteraction,
- MouseMoveInteraction,
- MouseUpInteraction,
-} from '../useCanvasInteraction';
-
-import {VerticalScrollOverflowView} from './VerticalScrollOverflowView';
-import {rectContainsPoint, rectEqualToRect} from '../geometry';
-import {View} from '../View';
-import {BORDER_SIZE, COLORS} from '../../content-views/constants';
-
-const SCROLL_BAR_SIZE = 14;
-
-const HIDDEN_RECT = {
- origin: {
- x: 0,
- y: 0,
- },
- size: {
- width: 0,
- height: 0,
- },
-};
-
-export class VerticalScrollBarView extends View {
- _contentHeight: number = 0;
- _isScrolling: boolean = false;
- _scrollBarRect: Rect = HIDDEN_RECT;
- _scrollThumbRect: Rect = HIDDEN_RECT;
- _verticalScrollOverflowView: VerticalScrollOverflowView;
-
- constructor(
- surface: Surface,
- frame: Rect,
- verticalScrollOverflowView: VerticalScrollOverflowView,
- ) {
- super(surface, frame);
-
- this._verticalScrollOverflowView = verticalScrollOverflowView;
- }
-
- desiredSize(): {+height: number, +width: number} {
- return {
- width: SCROLL_BAR_SIZE,
- height: 0, // No desired height
- };
- }
-
- getMaxScrollThumbY(): number {
- const {height} = this.frame.size;
-
- const maxScrollThumbY = height - this._scrollThumbRect.size.height;
-
- return maxScrollThumbY;
- }
-
- setContentHeight(contentHeight: number) {
- this._contentHeight = contentHeight;
-
- const {height, width} = this.frame.size;
-
- const proposedScrollThumbRect = {
- origin: {
- x: this.frame.origin.x,
- y: this._scrollThumbRect.origin.y,
- },
- size: {
- width,
- height: height * (height / contentHeight),
- },
- };
-
- if (!rectEqualToRect(this._scrollThumbRect, proposedScrollThumbRect)) {
- this._scrollThumbRect = proposedScrollThumbRect;
- this.setNeedsDisplay();
- }
- }
-
- setScrollThumbY(value: number) {
- const {height} = this.frame.size;
-
- const maxScrollThumbY = this.getMaxScrollThumbY();
- const newScrollThumbY = Math.max(0, Math.min(maxScrollThumbY, value));
-
- this._scrollThumbRect = {
- ...this._scrollThumbRect,
- origin: {
- x: this.frame.origin.x,
- y: newScrollThumbY,
- },
- };
-
- const maxContentOffset = this._contentHeight - height;
- const contentScrollOffset =
- (newScrollThumbY / maxScrollThumbY) * maxContentOffset * -1;
-
- this._verticalScrollOverflowView.setScrollOffset(
- contentScrollOffset,
- maxScrollThumbY,
- );
- }
-
- draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
- const {x, y} = this.frame.origin;
- const {width, height} = this.frame.size;
-
- // TODO Use real color
- context.fillStyle = COLORS.REACT_RESIZE_BAR;
- context.fillRect(x, y, width, height);
-
- // TODO Use real color
- context.fillStyle = COLORS.SCROLL_CARET;
- context.fillRect(
- this._scrollThumbRect.origin.x,
- this._scrollThumbRect.origin.y,
- this._scrollThumbRect.size.width,
- this._scrollThumbRect.size.height,
- );
-
- // TODO Use real color
- context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
- context.fillRect(x, y, BORDER_SIZE, height);
- }
-
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
- switch (interaction.type) {
- case 'click':
- this._handleClick(interaction, viewRefs);
- break;
- case 'mousedown':
- this._handleMouseDown(interaction, viewRefs);
- break;
- case 'mousemove':
- this._handleMouseMove(interaction, viewRefs);
- break;
- case 'mouseup':
- this._handleMouseUp(interaction, viewRefs);
- break;
- }
- }
-
- _handleClick(interaction: ClickInteraction, viewRefs: ViewRefs) {
- const {location} = interaction.payload;
- if (rectContainsPoint(location, this.frame)) {
- if (rectContainsPoint(location, this._scrollThumbRect)) {
- // Ignore clicks on the track thumb directly.
- return;
- }
-
- const currentScrollThumbY = this._scrollThumbRect.origin.y;
- const y = location.y;
-
- const {height} = this.frame.size;
-
- // Scroll up or down about one viewport worth of content:
- const deltaY = (height / this._contentHeight) * height * 0.8;
-
- this.setScrollThumbY(
- y > currentScrollThumbY
- ? this._scrollThumbRect.origin.y + deltaY
- : this._scrollThumbRect.origin.y - deltaY,
- );
- }
- }
-
- _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
- const {location} = interaction.payload;
- if (!rectContainsPoint(location, this._scrollThumbRect)) {
- return;
- }
- viewRefs.activeView = this;
-
- this.currentCursor = 'default';
-
- this._isScrolling = true;
- this.setNeedsDisplay();
- }
-
- _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
- const {event, location} = interaction.payload;
- if (rectContainsPoint(location, this.frame)) {
- if (viewRefs.hoveredView !== this) {
- viewRefs.hoveredView = this;
- }
-
- this.currentCursor = 'default';
- }
-
- if (viewRefs.activeView === this) {
- this.currentCursor = 'default';
-
- this.setScrollThumbY(this._scrollThumbRect.origin.y + event.movementY);
- }
- }
-
- _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
- if (viewRefs.activeView === this) {
- viewRefs.activeView = null;
- }
-
- if (this._isScrolling) {
- this._isScrolling = false;
- this.setNeedsDisplay();
- }
- }
-}
diff --git a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollOverflowView.js b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollOverflowView.js
deleted file mode 100644
index 55e86bbb08eb..000000000000
--- a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollOverflowView.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * 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
- */
-
-import type {Rect} from '../geometry';
-import type {ScrollState} from '../utils/scrollState';
-import type {Surface} from '../Surface';
-import type {ViewState} from '../../types';
-
-import {VerticalScrollBarView} from './VerticalScrollBarView';
-import {withVerticalScrollbarLayout} from './withVerticalScrollbarLayout';
-import {View} from '../View';
-import {VerticalScrollView} from '../VerticalScrollView';
-
-export class VerticalScrollOverflowView extends View {
- _contentView: View;
- _isProcessingOnChange: boolean = false;
- _isScrolling: boolean = false;
- _scrollOffset: number = 0;
- _scrollBarView: VerticalScrollBarView;
- _verticalScrollView: VerticalScrollView;
-
- constructor(
- surface: Surface,
- frame: Rect,
- contentView: View,
- viewState: ViewState,
- ) {
- super(surface, frame, withVerticalScrollbarLayout);
-
- this._contentView = contentView;
- this._verticalScrollView = new VerticalScrollView(
- surface,
- frame,
- contentView,
- viewState,
- 'VerticalScrollOverflowView',
- );
- this._verticalScrollView.onChange(this._onVerticalScrollViewChange);
-
- this._scrollBarView = new VerticalScrollBarView(surface, frame, this);
-
- this.addSubview(this._verticalScrollView);
- this.addSubview(this._scrollBarView);
- }
-
- layoutSubviews() {
- super.layoutSubviews();
-
- const contentSize = this._contentView.desiredSize();
-
- // This should be done after calling super.layoutSubviews() – calling it
- // before somehow causes _contentView to need display on every mousemove
- // event when the scroll bar is shown.
- this._scrollBarView.setContentHeight(contentSize.height);
- }
-
- setScrollOffset(newScrollOffset: number, maxScrollOffset: number) {
- const deltaY = newScrollOffset - this._scrollOffset;
-
- if (!this._isProcessingOnChange) {
- this._verticalScrollView.scrollBy(-deltaY);
- }
-
- this._scrollOffset = newScrollOffset;
-
- this.setNeedsDisplay();
- }
-
- _onVerticalScrollViewChange: (
- scrollState: ScrollState,
- containerLength: number,
- ) => void = (scrollState: ScrollState, containerLength: number) => {
- const maxOffset = scrollState.length - containerLength;
- if (maxOffset === 0) {
- return;
- }
-
- const percentage = Math.abs(scrollState.offset) / maxOffset;
- const maxScrollThumbY = this._scrollBarView.getMaxScrollThumbY();
-
- this._isProcessingOnChange = true;
- this._scrollBarView.setScrollThumbY(percentage * maxScrollThumbY);
- this._isProcessingOnChange = false;
- };
-}
diff --git a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/index.js b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/index.js
deleted file mode 100644
index 47628fe47e09..000000000000
--- a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * 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
- */
-
-export * from './VerticalScrollBarView';
-export * from './VerticalScrollOverflowView';
diff --git a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js
deleted file mode 100644
index 2e3a8fa8d225..000000000000
--- a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * 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
- */
-
-import type {LayoutInfo, Layouter} from '../layouter';
-
-/**
- * Assumes {@param layout} will only contain 2 views.
- */
-export const withVerticalScrollbarLayout: Layouter = (
- layout,
- containerFrame,
-) => {
- const [contentLayoutInfo, scrollbarLayoutInfo] = layout;
-
- const desiredContentSize = contentLayoutInfo.view.desiredSize();
- const shouldShowScrollbar =
- desiredContentSize.height > containerFrame.size.height;
- const scrollbarWidth = shouldShowScrollbar
- ? scrollbarLayoutInfo.view.desiredSize().width
- : 0;
-
- const laidOutContentLayoutInfo: LayoutInfo = {
- ...contentLayoutInfo,
- frame: {
- origin: contentLayoutInfo.view.frame.origin,
- size: {
- width: containerFrame.size.width - scrollbarWidth,
- height: containerFrame.size.height,
- },
- },
- };
- const laidOutScrollbarLayoutInfo: LayoutInfo = {
- ...scrollbarLayoutInfo,
- frame: {
- origin: {
- x:
- laidOutContentLayoutInfo.frame.origin.x +
- laidOutContentLayoutInfo.frame.size.width,
- y: containerFrame.origin.y,
- },
- size: {
- width: scrollbarWidth,
- height: containerFrame.size.height,
- },
- },
- };
-
- return [laidOutContentLayoutInfo, laidOutScrollbarLayoutInfo];
-};
diff --git a/packages/react-devtools/OVERVIEW.md b/packages/react-devtools/OVERVIEW.md
index 7f705e4c3f34..aec6fae3200c 100644
--- a/packages/react-devtools/OVERVIEW.md
+++ b/packages/react-devtools/OVERVIEW.md
@@ -293,13 +293,13 @@ To mitigate the performance impact of re-rendering a component, DevTools does th
## Profiler
-DevTools provides a suite of profiling tools for identifying and fixing performance problems. React 16.9+ supports a "legacy" profiler and React 18+ adds the ["timeline" profiler](https://github.com/facebook/react/tree/main/packages/react-devtools-timeline/src) support. These profilers are explained below, but at a high level– the architecture of each profiler aims to minimize the impact (CPU usage) while profiling is active. This can be accomplished by:
+DevTools provides profiling tools for identifying and fixing performance problems in React 16.9+. The profiler is explained below, but at a high level– its architecture aims to minimize the impact (CPU usage) while profiling is active. This can be accomplished by:
* Minimizing bridge traffic.
* Making expensive computations lazy.
The majority of profiling information is stored in the DevTools backend. The backend push-notifies the frontend of when profiling starts or stops by sending a "_profilingStatus_" message. The frontend also asks for the current status after mounting by sending a "_getProfilingStatus_" message. (This is done to support the reload-and-profile functionality.)
-### Legacy profiler
+### Commit profiler
When profiling begins, the frontend takes a snapshot/copy of each root. This snapshot includes the id, name, key, and child IDs for each node in the tree. (This information is already present on the frontend, so it does not require any additional bridge traffic.) While profiling is active, each time React commits– the frontend also stores a copy of the "_operations_" message (described above). Once profiling has finished, the frontend can use the original snapshot along with each of the stored "_operations_" messages to reconstruct the tree for each of the profiled commits.
@@ -310,12 +310,6 @@ When profiling begins, the backend records the base durations of each fiber curr
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 been completed (to minimize the performance impact of profiling).
-### Timeline profiler
-
-Timeline profiling data can come from one of two places:
-* The React DevTools backend, which injects a [set of profiling hooks](https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/profilingHooks.js) that React calls while rendering. When profiling, these hooks store information in memory which gets passed to DevTools when profiling is stopped.
-* A Chrome performance export (JSON) containing React data (as User Timing marks) and other browser data like CPU samples, Network traffic, and native commits. (This method is not as convenient but provides more detailed browser performance data.)
-
### Combining profiling data
Once profiling is finished, the frontend requests profiling data from the backend one renderer at a time by sending a "_getProfilingData_" message. The backend responds with a "_profilingData_" message that contains per-root commit timing and duration information. The frontend then combines this information with its own snapshots to form a complete picture of the profiling session. Using this data, charts and graphs are lazily computed (and incrementally cached) on-demand, based on which commits and views are selected in the Profiler UI.
diff --git a/scripts/devtools/configuration.js b/scripts/devtools/configuration.js
index 98d65baf946a..6f7443a54da1 100644
--- a/scripts/devtools/configuration.js
+++ b/scripts/devtools/configuration.js
@@ -6,7 +6,6 @@ const PACKAGE_PATHS = [
'packages/react-devtools/package.json',
'packages/react-devtools-core/package.json',
'packages/react-devtools-inline/package.json',
- 'packages/react-devtools-timeline/package.json',
];
const MANIFEST_PATHS = [
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index 3f03b50c3a82..05ee10692fe3 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -85,9 +85,6 @@ module.exports = Object.assign({}, baseConfig, {
require.resolve(
'../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
),
- require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js'
- ),
require.resolve(
'../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
),
diff --git a/yarn.lock b/yarn.lock
index e8ddcce91c95..e047451cd08f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2628,14 +2628,6 @@
optionalDependencies:
global-agent "^3.0.0"
-"@elg/speedscope@1.9.0-a6f84db":
- version "1.9.0-a6f84db"
- resolved "https://registry.yarnpkg.com/@elg/speedscope/-/speedscope-1.9.0-a6f84db.tgz#36079096390b9b396dfec5aeac12e971feb46084"
- integrity sha512-AoGBS1H5s8jrqIh51P8tZnO9i02w7jBJsd0W6+nrF2tLce502zb2rOyot4iv4fnvhmjnM6VFPBLjE2DfElg8Dw==
- dependencies:
- opn "5.3.0"
- react "^16.13.1"
-
"@esbuild/aix-ppc64@0.25.1":
version "0.25.1"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz#c33cf6bbee34975626b01b80451cbb72b4c6c91d"
@@ -3446,18 +3438,6 @@
dependencies:
playwright "1.51.1"
-"@pmmmwh/react-refresh-webpack-plugin@^0.4.1":
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.1.tgz#a4db0ed8e58c2f8566161c9a8cdf1d095c9a891b"
- integrity sha512-MzM87WdX2r2KRFfhEho7oGyK1XRE/J9WwjB3v6oLQHN0dzBypBZxSWjnoYx+RWneRCsg8Sin1myf+EjX1fqIbQ==
- dependencies:
- ansi-html "^0.0.7"
- error-stack-parser "^2.0.6"
- html-entities "^1.2.1"
- native-url "^0.2.6"
- schema-utils "^2.6.5"
- source-map "^0.7.3"
-
"@pnpm/config.env-replace@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
@@ -4030,11 +4010,6 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
-"@types/json-schema@^7.0.4":
- version "7.0.5"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
- integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
-
"@types/json-schema@^7.0.8":
version "7.0.8"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
@@ -4466,35 +4441,6 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
-"@vercel/build-utils@2.5.1":
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-2.5.1.tgz#2f687c2d82464dd85e0ed8130bc01e5dac9b11a4"
- integrity sha512-689ov8fGrgVnLwPbJUvgUKdSTseZiOMobR4XoZjmSmeF8+gaOpycsdGuVFzNtdFneUiQWO/k5AkY5diZj8fNgg==
-
-"@vercel/go@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@vercel/go/-/go-1.1.6.tgz#45ac3a6bd98a15b8bf1028b8c141a51fd971ac15"
- integrity sha512-swA2crS08fkPmw4UkR9yjmoL8FOCzuNHLFDqj8oM1V9ni610ibJ7Xk57jI8uyS7bTecQVh8VUxihb+SF0GT+aw==
-
-"@vercel/node@1.8.1":
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.8.1.tgz#aa0a04f425638874d19be64f7905018ef6f9b1c5"
- integrity sha512-vQsYMrulghMaHEKbqxJCQPfHrGEBmYmQMLYrx+m8kolKDq1LQDWFx2MRh4DyMnGfYSoYoW5PI2HP7NAfbEjzmg==
- dependencies:
- "@types/node" "*"
- ts-node "8.9.1"
- typescript "3.9.3"
-
-"@vercel/python@1.2.3":
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/@vercel/python/-/python-1.2.3.tgz#23ebb71c753fe1cc75fe186c89fc0af04c950191"
- integrity sha512-DJRvL6bmt4m0xrkzSKUbP8mK57YSDdTBWoo0JYyXq/o2golQrv/wQTalbNchd4P8NhVL3mZuk/1JNYuv5u1rKQ==
-
-"@vercel/ruby@1.2.4":
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/@vercel/ruby/-/ruby-1.2.4.tgz#60e0a91d7a1a730a7ecdbc6e095acb95f28f24be"
- integrity sha512-g19vrrmJ4MTJCRB/bvx8DahIsml1iPn7wsdHf5k3QVN6lT0dlDILSBwpERC4hqzndimaApsmWOfjYtY9/L6+tQ==
-
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
@@ -4811,7 +4757,7 @@ ajv-formats@^2.1.1:
dependencies:
ajv "^8.0.0"
-ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
+ajv-keywords@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
@@ -4848,7 +4794,7 @@ ajv@^6.1.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ajv@^6.10.0, ajv@^6.12.2:
+ajv@^6.10.0:
version "6.12.4"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
@@ -4940,11 +4886,6 @@ ansi-html-community@^0.0.8:
resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
-ansi-html@^0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
- integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
-
ansi-red@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c"
@@ -5094,11 +5035,6 @@ archiver@~2.1.0:
tar-stream "^1.5.0"
zip-stream "^1.2.0"
-arg@^4.1.0:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
- integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
-
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -5369,17 +5305,6 @@ babel-loader@^8.0.4:
mkdirp "^0.5.1"
pify "^4.0.1"
-babel-loader@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3"
- integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==
- dependencies:
- find-cache-dir "^2.1.0"
- loader-utils "^1.4.0"
- mkdirp "^0.5.3"
- pify "^4.0.1"
- schema-utils "^2.6.5"
-
babel-plugin-dynamic-import-node@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
@@ -5847,20 +5772,6 @@ boolean@^3.0.0, boolean@^3.0.1:
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==
-boxen@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
- integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
- dependencies:
- ansi-align "^3.0.0"
- camelcase "^5.3.1"
- chalk "^3.0.0"
- cli-boxes "^2.2.0"
- string-width "^4.1.0"
- term-size "^2.1.0"
- type-fest "^0.8.1"
- widest-line "^3.1.0"
-
boxen@^5.0.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
@@ -6238,11 +6149,6 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-camelcase@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
- integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
-
camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
@@ -6422,6 +6328,11 @@ chownr@^1.0.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+chrome-devtools-mcp@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.3.0.tgz#7aeb4c8dab5d8dc536ef683b75e7a81b3989ad0e"
+ integrity sha512-52NVUwWSL4eW7W9nsDrzYJF96IKVuxEwAn4O7ZfdNRtopS954P9nryJbdYwg7vdqxhLrvioGFlm5e4P41WXsiw==
+
chrome-launch@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chrome-launch/-/chrome-launch-1.1.4.tgz#c8985bd022635a5cc897099bafd19831838a7252"
@@ -6432,11 +6343,6 @@ chrome-launch@^1.1.4:
rimraf "^2.2.8"
shallow-copy "0.0.1"
-chrome-devtools-mcp@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.3.0.tgz#7aeb4c8dab5d8dc536ef683b75e7a81b3989ad0e"
- integrity sha512-52NVUwWSL4eW7W9nsDrzYJF96IKVuxEwAn4O7ZfdNRtopS954P9nryJbdYwg7vdqxhLrvioGFlm5e4P41WXsiw==
-
chrome-launcher@0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.1.tgz#0a0208037063641e2b3613b7e42b0fcb3fa2d399"
@@ -6487,11 +6393,6 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
-cli-boxes@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
- integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
-
cli-boxes@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
@@ -7117,25 +7018,6 @@ css-loader@^1.0.1:
postcss-value-parser "^3.3.0"
source-list-map "^2.0.0"
-css-loader@^4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.1.tgz#9f48fd7eae1219d629a3f085ba9a9102ca1141a7"
- integrity sha512-MoqmF1if7Z0pZIEXA4ZF9PgtCXxWbfzfJM+3p+OYfhcrwcqhaCRb74DSnfzRl7e024xEiCRn5hCvfUbTf2sgFA==
- dependencies:
- camelcase "^6.0.0"
- cssesc "^3.0.0"
- icss-utils "^4.1.1"
- loader-utils "^2.0.0"
- normalize-path "^3.0.0"
- postcss "^7.0.32"
- postcss-modules-extract-imports "^2.0.0"
- postcss-modules-local-by-default "^3.0.3"
- postcss-modules-scope "^2.2.0"
- postcss-modules-values "^3.0.0"
- postcss-value-parser "^4.1.0"
- schema-utils "^2.7.0"
- semver "^7.3.2"
-
css-loader@^6.9.1:
version "6.10.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7"
@@ -7600,11 +7482,6 @@ diff@5.1.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
-diff@^4.0.1:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
- integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -9019,14 +8896,6 @@ file-entry-cache@^8.0.0:
dependencies:
flat-cache "^4.0.0"
-file-loader@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f"
- integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==
- dependencies:
- loader-utils "^2.0.0"
- schema-utils "^2.6.5"
-
file-loader@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@@ -9134,7 +9003,7 @@ finalhandler@1.3.1:
statuses "2.0.1"
unpipe "~1.0.0"
-find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+find-cache-dir@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@@ -9729,13 +9598,6 @@ global-agent@^3.0.0:
semver "^7.3.2"
serialize-error "^7.0.1"
-global-dirs@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201"
- integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==
- dependencies:
- ini "^1.3.5"
-
global-dirs@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485"
@@ -10192,11 +10054,6 @@ html-encoding-sniffer@^3.0.0:
dependencies:
whatwg-encoding "^2.0.0"
-html-entities@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
- integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
-
html-entities@^2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
@@ -10393,13 +10250,6 @@ icss-utils@^2.1.0:
dependencies:
postcss "^6.0.1"
-icss-utils@^4.0.0, icss-utils@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
- integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
- dependencies:
- postcss "^7.0.14"
-
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
@@ -10902,14 +10752,6 @@ is-inside-container@^1.0.0:
dependencies:
is-docker "^3.0.0"
-is-installed-globally@^0.3.1:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
- integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
- dependencies:
- global-dirs "^2.0.1"
- is-path-inside "^3.0.1"
-
is-installed-globally@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
@@ -10955,11 +10797,6 @@ is-negative-zero@^2.0.1:
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
-is-npm@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
- integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
-
is-npm@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
@@ -11002,11 +10839,6 @@ is-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
-is-path-inside@^3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
- integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
-
is-path-inside@^3.0.2, is-path-inside@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
@@ -11182,11 +11014,6 @@ is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-is-wsl@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
- integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
-
is-wsl@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
@@ -12065,7 +11892,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-latest-version@^5.0.0, latest-version@^5.1.0:
+latest-version@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
@@ -12186,15 +12013,6 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
emojis-list "^2.0.0"
json5 "^1.0.1"
-loader-utils@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
- integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
- dependencies:
- big.js "^5.2.2"
- emojis-list "^3.0.0"
- json5 "^1.0.1"
-
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
@@ -12485,7 +12303,7 @@ make-dir@^4.0.0:
dependencies:
semver "^7.5.3"
-make-error@^1.1.1, make-error@^1.3.2:
+make-error@^1.3.2:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
@@ -12585,11 +12403,6 @@ memfs@^3.4.3:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
-memoize-one@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
- integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
-
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -12666,11 +12479,6 @@ mime-db@1.40.0, "mime-db@>= 1.40.0 < 2":
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
-mime-db@1.44.0:
- version "1.44.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
- integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
-
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
@@ -12688,13 +12496,6 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.34:
dependencies:
mime-db "1.52.0"
-mime-types@^2.1.26:
- version "2.1.27"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
- integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
- dependencies:
- mime-db "1.44.0"
-
mime-types@~2.1.17, mime-types@~2.1.24:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
@@ -12820,7 +12621,7 @@ mkdirp@3.0.1:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
-mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
+mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@@ -12920,13 +12721,6 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-native-url@^0.2.6:
- version "0.2.6"
- resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae"
- integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==
- dependencies:
- querystring "^0.2.0"
-
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -13200,7 +12994,7 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
-nullthrows@^1.0.0, nullthrows@^1.1.1:
+nullthrows@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
@@ -13374,13 +13168,6 @@ open@^8.0.9:
is-docker "^2.1.1"
is-wsl "^2.2.0"
-opn@5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
- integrity sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==
- dependencies:
- is-wsl "^1.1.0"
-
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@@ -13730,11 +13517,6 @@ parse-link-header@^2.0.0:
dependencies:
xtend "~4.0.1"
-parse-ms@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
- integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
-
parse-node-version@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
@@ -14066,13 +13848,6 @@ postcss-modules-extract-imports@^1.2.0:
dependencies:
postcss "^6.0.1"
-postcss-modules-extract-imports@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
- integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==
- dependencies:
- postcss "^7.0.5"
-
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
@@ -14086,16 +13861,6 @@ postcss-modules-local-by-default@^1.2.0:
css-selector-tokenizer "^0.7.0"
postcss "^6.0.1"
-postcss-modules-local-by-default@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
- integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==
- dependencies:
- icss-utils "^4.1.1"
- postcss "^7.0.32"
- postcss-selector-parser "^6.0.2"
- postcss-value-parser "^4.1.0"
-
postcss-modules-local-by-default@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6"
@@ -14113,14 +13878,6 @@ postcss-modules-scope@^1.1.0:
css-selector-tokenizer "^0.7.0"
postcss "^6.0.1"
-postcss-modules-scope@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee"
- integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==
- dependencies:
- postcss "^7.0.6"
- postcss-selector-parser "^6.0.0"
-
postcss-modules-scope@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134"
@@ -14136,14 +13893,6 @@ postcss-modules-values@^1.3.0:
icss-replace-symbols "^1.1.0"
postcss "^6.0.1"
-postcss-modules-values@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
- integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
- dependencies:
- icss-utils "^4.0.0"
- postcss "^7.0.6"
-
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
@@ -14151,7 +13900,7 @@ postcss-modules-values@^4.0.0:
dependencies:
icss-utils "^5.0.0"
-postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
+postcss-selector-parser@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
@@ -14192,15 +13941,6 @@ postcss@^6.0.1, postcss@^6.0.23:
source-map "^0.6.1"
supports-color "^5.4.0"
-postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
- version "7.0.32"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
- integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
- dependencies:
- chalk "^2.4.2"
- source-map "^0.6.1"
- supports-color "^6.1.0"
-
postcss@^8.4.33:
version "8.4.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7"
@@ -14268,13 +14008,6 @@ pretty-format@^29.7.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
-pretty-ms@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.0.tgz#45781273110caf35f55cab21a8a9bd403a233dc0"
- integrity sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==
- dependencies:
- parse-ms "^2.1.0"
-
prettyjson@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prettyjson/-/prettyjson-1.2.1.tgz#fcffab41d19cab4dfae5e575e64246619b12d289"
@@ -14425,13 +14158,6 @@ punycode@^2.1.1, punycode@^2.3.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
-pupa@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726"
- integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==
- dependencies:
- escape-goat "^2.0.0"
-
pupa@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
@@ -14487,7 +14213,7 @@ querystring-es3@~0.2.0:
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
-querystring@0.2.0, querystring@^0.2.0:
+querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
@@ -14683,15 +14409,6 @@ react-window@^1.8.10:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
-react@^16.13.1:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
- integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
- prop-types "^15.6.2"
-
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -14861,11 +14578,6 @@ regenerator-runtime@^0.13.4:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
-regenerator-runtime@^0.13.7:
- version "0.13.7"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
- integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
-
regenerator-runtime@^0.13.9:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
@@ -15414,15 +15126,6 @@ schema-utils@^2.0.1:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
-schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
- version "2.7.0"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
- integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
- dependencies:
- "@types/json-schema" "^7.0.4"
- ajv "^6.12.2"
- ajv-keywords "^3.4.1"
-
schema-utils@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
@@ -15905,7 +15608,7 @@ source-map-support@0.5.13:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-support@0.5.21, source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@~0.5.20:
+source-map-support@0.5.21, source-map-support@^0.5.16, source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -15935,11 +15638,6 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@^0.7.3:
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
- integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@@ -16365,14 +16063,6 @@ style-loader@^0.23.1:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
-style-loader@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
- integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==
- dependencies:
- loader-utils "^2.0.0"
- schema-utils "^2.6.6"
-
sucrase@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
@@ -16405,13 +16095,6 @@ supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
-supports-color@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
- integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
- dependencies:
- has-flag "^3.0.0"
-
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -16554,11 +16237,6 @@ tempfile@^2.0.0:
temp-dir "^1.0.0"
uuid "^3.0.1"
-term-size@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
- integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==
-
terser-webpack-plugin@^5.3.7:
version "5.3.8"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz#415e03d2508f7de63d59eca85c5d102838f06610"
@@ -16848,17 +16526,6 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
-ts-node@8.9.1:
- version "8.9.1"
- resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
- integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
- dependencies:
- arg "^4.1.0"
- diff "^4.0.1"
- make-error "^1.1.1"
- source-map-support "^0.5.17"
- yn "3.1.1"
-
tslib@^1.8.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
@@ -16949,11 +16616,6 @@ type-fest@^0.21.3:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
-type-fest@^0.8.1:
- version "0.8.1"
- resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
- integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
-
type-fest@^1.0.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
@@ -16994,11 +16656,6 @@ typescript-compiler@^1.4.1-2:
resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
integrity sha512-EMopKmoAEJqA4XXRFGOb7eSBhmQMbBahW6P1Koayeatp0b4AW2q/bBqYWkpG7QVQc9HGQUiS4trx2ZHcnAaZUg==
-typescript@3.9.3:
- version "3.9.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
- integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
-
typescript@^5.4.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
@@ -17211,25 +16868,6 @@ update-browserslist-db@^1.1.1:
escalade "^3.2.0"
picocolors "^1.1.1"
-update-notifier@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3"
- integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==
- dependencies:
- boxen "^4.2.0"
- chalk "^3.0.0"
- configstore "^5.0.1"
- has-yarn "^2.1.0"
- import-lazy "^2.1.0"
- is-ci "^2.0.0"
- is-installed-globally "^0.3.1"
- is-npm "^4.0.0"
- is-yarn-global "^0.3.0"
- latest-version "^5.0.0"
- pupa "^2.0.1"
- semver-diff "^3.1.1"
- xdg-basedir "^4.0.0"
-
update-notifier@6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60"
@@ -17289,15 +16927,6 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
-url-loader@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.0.tgz#c7d6b0d6b0fccd51ab3ffc58a78d32b8d89a7be2"
- integrity sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==
- dependencies:
- loader-utils "^2.0.0"
- mime-types "^2.1.26"
- schema-utils "^2.6.5"
-
url-parse-lax@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
@@ -17421,18 +17050,6 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-vercel@^20.1.0:
- version "20.1.0"
- resolved "https://registry.yarnpkg.com/vercel/-/vercel-20.1.0.tgz#366c39892455f5fff9b1d31db6e6ac1dff436453"
- integrity sha512-fOYq2X6o157hOBbyxU9VcM2pCHa6cAxvkA731N9FEw8MyMnPC7zJRgFQ/kYJ0oisjlKsmm2A1ryrLEz3KLIDFw==
- dependencies:
- "@vercel/build-utils" "2.5.1"
- "@vercel/go" "1.1.6"
- "@vercel/node" "1.8.1"
- "@vercel/python" "1.2.3"
- "@vercel/ruby" "1.2.4"
- update-notifier "4.1.0"
-
vinyl-sourcemaps-apply@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
@@ -18072,11 +17689,6 @@ yauzl@2.10.0, yauzl@^2.10.0, yauzl@^2.4.2:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
-yn@3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
- integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
-
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"