-
+
@@ -175,37 +175,6 @@ const tabsWithTimeline = [
title: 'Timeline',
},
];
-
-const NoProfilingData = () => (
-
-
No profiling data has been recorded.
-
- Click the record button to start recording.
-
-
-);
-
-const ProfilingNotSupported = () => (
-
-
Profiling not supported.
-
- Profiling support requires either a development or production-profiling
- build of React v16.5+.
-
-
- Learn more at{' '}
-
- reactjs.org/link/profiling
-
- .
-
-
-);
-
const ProcessingData = () => (
Processing data...
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 6e486091e1419..8b08e73328432 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
@@ -96,18 +96,18 @@ function ProfilerContextController({children}: Props) {
isProcessingData: profilerStore.isProcessingData,
isProfiling: profilerStore.isProfiling,
profilingData: profilerStore.profilingData,
- supportsProfiling: store.supportsProfiling,
+ supportsProfiling: store.rootSupportsBasicProfiling,
}),
subscribe: (callback: Function) => {
profilerStore.addListener('profilingData', callback);
profilerStore.addListener('isProcessingData', callback);
profilerStore.addListener('isProfiling', callback);
- store.addListener('supportsProfiling', callback);
+ store.addListener('rootSupportsBasicProfiling', callback);
return () => {
profilerStore.removeListener('profilingData', callback);
profilerStore.removeListener('isProcessingData', callback);
profilerStore.removeListener('isProfiling', callback);
- store.removeListener('supportsProfiling', callback);
+ store.removeListener('rootSupportsBasicProfiling', callback);
};
},
}),
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js
new file mode 100644
index 0000000000000..4dcbe64cef069
--- /dev/null
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+
+import styles from './Profiler.css';
+
+export default function ProfilingNotSupported() {
+ return (
+
+
Profiling not supported.
+
+ Profiling support requires either a development or profiling build of
+ React v16.5+.
+
+
+ Learn more at{' '}
+
+ reactjs.org/link/profiling
+
+ .
+
+
+ );
+}
diff --git a/packages/react-devtools-timeline/src/Timeline.js b/packages/react-devtools-timeline/src/Timeline.js
index 290830840356e..20feb8eb4fc65 100644
--- a/packages/react-devtools-timeline/src/Timeline.js
+++ b/packages/react-devtools-timeline/src/Timeline.js
@@ -9,7 +9,6 @@
import type {ViewState} from './types';
-import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
import * as React from 'react';
import {
Suspense,
@@ -20,18 +19,22 @@ import {
useState,
} from 'react';
import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
+import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
import {updateColorsToMatchTheme} from './content-views/constants';
import {TimelineContext} from './TimelineContext';
import ImportButton from './ImportButton';
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(_: {||}) {
- const {file, setFile, viewState} = useContext(TimelineContext);
+ const {file, isTimelineSupported, setFile, viewState} = useContext(
+ TimelineContext,
+ );
const ref = useRef(null);
@@ -71,55 +74,15 @@ export function Timeline(_: {||}) {
viewState={viewState}
/>
+ ) : isTimelineSupported ? (
+
) : (
-
+
)}
);
}
-const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
-
- {isInternalFacebookBuild && (
- -
- Enable the
-
-
react_enable_scheduling_profiler
GK
-
- .
-
- )}
- -
- Open a website that's built with the
-
- profiling build of ReactDOM
-
- (version 18 or newer).
-
- -
- Open the "Performance" tab in Chrome and record some performance data.
-
- -
- Click the "Save profile..." button in Chrome to export the data.
-
- -
- Import the data into the profiler:
-
-
- Import
-
-
-
-);
-
const ProcessingData = () => (
Processing data...
diff --git a/packages/react-devtools-timeline/src/TimelineContext.js b/packages/react-devtools-timeline/src/TimelineContext.js
index cbf57a275c0c5..fbf52b5802479 100644
--- a/packages/react-devtools-timeline/src/TimelineContext.js
+++ b/packages/react-devtools-timeline/src/TimelineContext.js
@@ -8,7 +8,15 @@
*/
import * as React from 'react';
-import {createContext, useMemo, useRef, useState} from 'react';
+import {
+ createContext,
+ useContext,
+ useMemo,
+ useRef,
+ useState,
+ useSyncExternalStore,
+} from 'react';
+import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
import type {
HorizontalScrollStateChangeCallback,
@@ -19,6 +27,7 @@ import type {RefObject} from 'shared/ReactTypes';
export type Context = {|
file: File | null,
+ isTimelineSupported: boolean,
searchInputContainerRef: RefObject,
setFile: (file: File | null) => void,
viewState: ViewState,
@@ -35,6 +44,20 @@ function TimelineContextController({children}: Props) {
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;
+ },
+ );
+
// Recreate view state any time new profiling data is imported.
const viewState = useMemo(() => {
const horizontalScrollStateChangeCallbacks: Set = new Set();
@@ -85,11 +108,12 @@ function TimelineContextController({children}: Props) {
const value = useMemo(
() => ({
file,
+ isTimelineSupported,
searchInputContainerRef,
setFile,
viewState,
}),
- [file, setFile, viewState],
+ [file, isTimelineSupported, setFile, viewState],
);
return (
diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.css b/packages/react-devtools-timeline/src/TimelineNotSupported.css
new file mode 100644
index 0000000000000..971723520a456
--- /dev/null
+++ b/packages/react-devtools-timeline/src/TimelineNotSupported.css
@@ -0,0 +1,38 @@
+.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
new file mode 100644
index 0000000000000..9a21df8ab8cb3
--- /dev/null
+++ b/packages/react-devtools-timeline/src/TimelineNotSupported.js
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
+
+import styles from './TimelineNotSupported.css';
+
+export default function TimelineNotSupported() {
+ return (
+
+
Timeline profiling not supported.
+
+
+ Timeline profiler requires a development or profiling build of{' '}
+ react-dom@^18
.
+
+
+
+ Click{' '}
+
+ here
+ {' '}
+ to learn more about profiling.
+
+
+ {isInternalFacebookBuild && (
+
+ )}
+
+ );
+}
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index e14ecb48c3485..276d209054745 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -62,19 +62,22 @@ module.exports = Object.assign({}, baseConfig, {
testRegex: 'packages/react-devtools-shared/.+/__tests__/[^]+.test.js$',
snapshotSerializers: [
require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js'
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js'
),
require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/hookSerializer.js'
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js'
),
require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js'
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js'
),
require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/storeSerializer.js'
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
),
require.resolve(
- '../../packages/react-devtools-shared/src/__tests__/treeContextStateSerializer.js'
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
+ ),
+ require.resolve(
+ '../../packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js'
),
],
setupFiles: [