diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js
index 8f5b4e6dcac93..199e6e2c46620 100644
--- a/packages/react-devtools-core/webpack.standalone.js
+++ b/packages/react-devtools-core/webpack.standalone.js
@@ -18,6 +18,15 @@ const __DEV__ = NODE_ENV === 'development';
const DEVTOOLS_VERSION = getVersionString();
+const babelOptions = {
+ configFile: resolve(
+ __dirname,
+ '..',
+ 'react-devtools-shared',
+ 'babel.config.js',
+ ),
+};
+
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : 'source-map',
@@ -62,17 +71,25 @@ module.exports = {
],
module: {
rules: [
+ {
+ test: /\.worker\.js$/,
+ use: [
+ {
+ loader: 'worker-loader',
+ options: {
+ inline: 'no-fallback',
+ },
+ },
+ {
+ loader: 'babel-loader',
+ options: babelOptions,
+ },
+ ],
+ },
{
test: /\.js$/,
loader: 'babel-loader',
- options: {
- configFile: resolve(
- __dirname,
- '..',
- 'react-devtools-shared',
- 'babel.config.js',
- ),
- },
+ options: babelOptions,
},
{
test: /\.css$/,
diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json
index a97422cedf86d..44327c8566f74 100644
--- a/packages/react-devtools-extensions/firefox/manifest.json
+++ b/packages/react-devtools-extensions/firefox/manifest.json
@@ -32,7 +32,7 @@
"devtools_page": "main.html",
- "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
+ "content_security_policy": "script-src 'self' 'unsafe-eval' blob:; object-src 'self'",
"web_accessible_resources": [
"main.html",
"panel.html",
diff --git a/packages/react-devtools-extensions/package.json b/packages/react-devtools-extensions/package.json
index da473f7613a68..d18f205c7c8a7 100644
--- a/packages/react-devtools-extensions/package.json
+++ b/packages/react-devtools-extensions/package.json
@@ -37,6 +37,7 @@
"chrome-launch": "^1.1.4",
"crx": "^5.0.0",
"css-loader": "^1.0.1",
+ "file-loader": "^6.1.0",
"firefox-profile": "^1.0.2",
"fs-extra": "^4.0.2",
"jest-fetch-mock": "^3.0.3",
@@ -55,7 +56,8 @@
"web-ext": "^3.0.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
- "webpack-dev-server": "^3.10.3"
+ "webpack-dev-server": "^3.10.3",
+ "worker-loader": "^3.0.3"
},
"dependencies": {
"web-ext": "^4"
diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js
index 94a12ebdd1118..45ea0c3ab767d 100644
--- a/packages/react-devtools-extensions/webpack.config.js
+++ b/packages/react-devtools-extensions/webpack.config.js
@@ -19,6 +19,15 @@ const DEVTOOLS_VERSION = getVersionString();
const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
+const babelOptions = {
+ configFile: resolve(
+ __dirname,
+ '..',
+ 'react-devtools-shared',
+ 'babel.config.js',
+ ),
+};
+
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
@@ -81,17 +90,25 @@ module.exports = {
],
rules: [
+ {
+ test: /\.worker\.js$/,
+ use: [
+ {
+ loader: 'worker-loader',
+ options: {
+ inline: 'no-fallback',
+ },
+ },
+ {
+ loader: 'babel-loader',
+ options: babelOptions,
+ },
+ ],
+ },
{
test: /\.js$/,
loader: 'babel-loader',
- options: {
- configFile: resolve(
- __dirname,
- '..',
- 'react-devtools-shared',
- 'babel.config.js',
- ),
- },
+ options: babelOptions,
},
{
test: /\.css$/,
diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json
index a661fdf839957..21efaf2bbd9a3 100644
--- a/packages/react-devtools-inline/package.json
+++ b/packages/react-devtools-inline/package.json
@@ -34,10 +34,12 @@
"babel-loader": "^8.0.4",
"cross-env": "^3.1.4",
"css-loader": "^1.0.1",
+ "file-loader": "^6.1.0",
"raw-loader": "^3.1.0",
"style-loader": "^0.23.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
- "webpack-dev-server": "^3.10.3"
+ "webpack-dev-server": "^3.10.3",
+ "worker-loader": "^3.0.3"
}
}
diff --git a/packages/react-devtools-inline/webpack.config.js b/packages/react-devtools-inline/webpack.config.js
index 7011e7151c760..0397d4358c567 100644
--- a/packages/react-devtools-inline/webpack.config.js
+++ b/packages/react-devtools-inline/webpack.config.js
@@ -16,6 +16,15 @@ const __DEV__ = NODE_ENV === 'development';
const DEVTOOLS_VERSION = getVersionString();
+const babelOptions = {
+ configFile: resolve(
+ __dirname,
+ '..',
+ 'react-devtools-shared',
+ 'babel.config.js',
+ ),
+};
+
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'eval-cheap-source-map' : 'source-map',
@@ -65,17 +74,25 @@ module.exports = {
],
module: {
rules: [
+ {
+ test: /\.worker\.js$/,
+ use: [
+ {
+ loader: 'worker-loader',
+ options: {
+ inline: 'no-fallback',
+ },
+ },
+ {
+ loader: 'babel-loader',
+ options: babelOptions,
+ },
+ ],
+ },
{
test: /\.js$/,
loader: 'babel-loader',
- options: {
- configFile: resolve(
- __dirname,
- '..',
- 'react-devtools-shared',
- 'babel.config.js',
- ),
- },
+ options: babelOptions,
},
{
test: /\.css$/,
diff --git a/packages/react-devtools-scheduling-profiler/README.md b/packages/react-devtools-scheduling-profiler/README.md
index 5ce54a3d7ea20..457bec25efcf2 100644
--- a/packages/react-devtools-scheduling-profiler/README.md
+++ b/packages/react-devtools-scheduling-profiler/README.md
@@ -1,15 +1,3 @@
-# Experimental React Concurrent Mode Profiler
+# React Concurrent Mode Profiler
-https://react-devtools-scheduling-profiler.vercel.app/
-
-## Setting up continuous deployment with CircleCI and Vercel
-
-These instructions are intended for internal use, but may be useful if you are setting up a custom production deployment of the scheduling profiler.
-
-1. Create a Vercel token at https://vercel.com/account/tokens.
-2. Configure CircleCI:
- 1. In CircleCI, navigate to the repository's Project Settings.
- 2. In the Advanced tab, ensure that "Pass secrets to builds from forked pull requests" is set to false.
- 3. In the Environment Variables tab, add the Vercel token as a new `SCHEDULING_PROFILER_DEPLOY_VERCEL_TOKEN` environment variable.
-
-The Vercel project will be created when the deploy job runs.
+This package contains the new/experimental "scheduling profiler" 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-scheduling-profiler/buildUtils.js b/packages/react-devtools-scheduling-profiler/buildUtils.js
deleted file mode 100644
index b0971c4861112..0000000000000
--- a/packages/react-devtools-scheduling-profiler/buildUtils.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-const {execSync} = require('child_process');
-const {readFileSync} = require('fs');
-const {resolve} = require('path');
-
-function getGitCommit() {
- try {
- return execSync('git show -s --format=%h')
- .toString()
- .trim();
- } catch (error) {
- // Mozilla runs this command from a git archive.
- // In that context, there is no Git revision.
- return null;
- }
-}
-
-function getVersionString() {
- const packageVersion = JSON.parse(
- readFileSync(resolve(__dirname, './package.json')),
- ).version;
-
- const commit = getGitCommit();
-
- return `${packageVersion}-${commit}`;
-}
-
-module.exports = {
- getVersionString,
-};
diff --git a/packages/react-devtools-scheduling-profiler/package.json b/packages/react-devtools-scheduling-profiler/package.json
index 705261279e304..e3b87c664fafd 100644
--- a/packages/react-devtools-scheduling-profiler/package.json
+++ b/packages/react-devtools-scheduling-profiler/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "react-devtools-scheduling-profiler",
- "version": "0.0.0",
+ "version": "4.14.0",
"license": "MIT",
"scripts": {
"build": "cross-env NODE_ENV=production cross-env TARGET=remote webpack --config webpack.config.js",
diff --git a/packages/react-devtools-scheduling-profiler/src/App.css b/packages/react-devtools-scheduling-profiler/src/App.css
deleted file mode 100644
index 1ea3d75fcc595..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/App.css
+++ /dev/null
@@ -1,19 +0,0 @@
-.DevTools {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- background-color: var(--color-background);
- color: var(--color-text);
-}
-
-.TabContent {
- flex: 1 1 100%;
- overflow: auto;
- -webkit-app-region: no-drag;
-}
-
-.DevTools, .DevTools * {
- box-sizing: border-box;
- -webkit-font-smoothing: var(--font-smoothing);
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/App.js b/packages/react-devtools-scheduling-profiler/src/App.js
deleted file mode 100644
index 9a27253b6c032..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/App.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-// Reach styles need to come before any component styles.
-// This makes overriding the styles simpler.
-import '@reach/menu-button/styles.css';
-import '@reach/tooltip/styles.css';
-
-import * as React from 'react';
-
-import {SchedulingProfiler} from './SchedulingProfiler';
-import {useBrowserTheme, useDisplayDensity} from './hooks';
-
-import styles from './App.css';
-import 'react-devtools-shared/src/devtools/views/root.css';
-
-export default function App() {
- useBrowserTheme();
- useDisplayDensity();
-
- return (
-
- );
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/CanvasPage.css b/packages/react-devtools-scheduling-profiler/src/CanvasPage.css
index e5d238a0d9d2c..8c7633a1b8977 100644
--- a/packages/react-devtools-scheduling-profiler/src/CanvasPage.css
+++ b/packages/react-devtools-scheduling-profiler/src/CanvasPage.css
@@ -1,7 +1,7 @@
.CanvasPage {
position: absolute;
- top: 0.5rem;
- bottom: 0.5rem;
- left: 0.5rem;
- right: 0.5rem;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
}
diff --git a/packages/react-devtools-scheduling-profiler/src/CanvasPage.js b/packages/react-devtools-scheduling-profiler/src/CanvasPage.js
index 9b20cd2fb80d7..cd6a0d072f501 100644
--- a/packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+++ b/packages/react-devtools-scheduling-profiler/src/CanvasPage.js
@@ -52,9 +52,9 @@ import {
import {COLORS} from './content-views/constants';
import EventTooltip from './EventTooltip';
-import ContextMenu from './context/ContextMenu';
-import ContextMenuItem from './context/ContextMenuItem';
-import useContextMenu from './context/useContextMenu';
+import ContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenu';
+import ContextMenuItem from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem';
+import useContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/useContextMenu';
import {getBatchRange} from './utils/getBatchRange';
import styles from './CanvasPage.css';
@@ -243,6 +243,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
defaultFrame,
reactMeasuresHorizontalPanAndZoomView,
flamechartHorizontalPanAndZoomView,
+ canvasRef,
);
const rootView = new View(
diff --git a/packages/react-devtools-scheduling-profiler/src/EventTooltip.css b/packages/react-devtools-scheduling-profiler/src/EventTooltip.css
index f721295b2f2ba..b6503b7338c35 100644
--- a/packages/react-devtools-scheduling-profiler/src/EventTooltip.css
+++ b/packages/react-devtools-scheduling-profiler/src/EventTooltip.css
@@ -6,9 +6,10 @@
padding: 0.25rem;
user-select: none;
pointer-events: none;
- background-color: #ffffff;
- border: 1px solid #ccc;
- box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
+ background-color: var(--color-tooltip-background);
+ border: 1px solid var(border);
+ box-shadow: 1px 1px 2px var(--color-shadow);
+ color: var(--color-tooltip-text);
font-size: 11px;
}
@@ -26,7 +27,7 @@
}
.DetailsGridLabel {
- color: #666;
+ color: var(--color-dim);
text-align: right;
}
@@ -56,14 +57,14 @@
line-height: 1.5;
-webkit-mask-image: linear-gradient(
180deg,
- #fff,
- #fff 5em,
+ var(--color-tooltip-background),
+ var(--color-tooltip-background) 5em,
transparent
);
mask-image: linear-gradient(
180deg,
- #fff,
- #fff 5em,
+ var(--color-tooltip-background),
+ var(--color-tooltip-background) 5em,
transparent
);
white-space: pre;
diff --git a/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css b/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css
index e16279192b83e..b5666e6bfca1e 100644
--- a/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css
+++ b/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css
@@ -1,21 +1,5 @@
-.SchedulingProfiler {
- width: 100%;
- height: 100%;
- position: relative;
- display: flex;
- flex-direction: column;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-sans-normal);
- background-color: var(--color-background);
- color: var(--color-text);
-}
-
-.SchedulingProfiler, .SchedulingProfiler * {
- box-sizing: border-box;
- -webkit-font-smoothing: var(--font-smoothing);
-}
-
.Content {
+ width: 100%;
position: relative;
flex: 1 1 auto;
display: flex;
@@ -51,58 +35,3 @@
font-size: var(--font-size-sans-large);
margin-bottom: 0.5rem;
}
-
-.Toolbar {
- height: 2.25rem;
- padding: 0 0.25rem;
- flex: 0 0 auto;
- display: flex;
- align-items: center;
- border-bottom: 1px solid var(--color-border);
-}
-
-.VRule {
- height: 20px;
- width: 1px;
- border-left: 1px solid var(--color-border);
- padding-left: 0.25rem;
- margin-left: 0.25rem;
-}
-
-.Spacer {
- flex: 1;
-}
-
-.Link {
- color: var(--color-button);
-}
-
-.ScreenshotWrapper {
- max-width: 30rem;
- padding: 0 1rem;
- margin-bottom: 2rem;
-}
-
-.Screenshot {
- width: 100%;
- border-radius: 0.4em;
- border: 2px solid var(--color-border);
-}
-
-.AppName {
- font-size: var(--font-size-sans-large);
- margin-right: 0.5rem;
- user-select: none;
-}
-
-@media screen and (max-width: 350px) {
- .AppName {
- display: none;
- }
-}
-
-@media screen and (max-height: 600px) {
- .ScreenshotWrapper {
- display: none;
- }
-}
\ No newline at end of file
diff --git a/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js b/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
index 2f232b6bbe183..a23f528258c00 100644
--- a/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
+++ b/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
@@ -7,95 +7,65 @@
* @flow
*/
-import type {Resource} from 'react-devtools-shared/src/devtools/cache';
-import type {ReactProfilerData} from './types';
-import type {ImportWorkerOutputData} from './import-worker/import.worker';
+import type {DataResource} from './createDataResourceFromImportedFile';
import * as React from 'react';
-import {Suspense, useCallback, useState} from 'react';
-import {createResource} from 'react-devtools-shared/src/devtools/cache';
-import ReactLogo from 'react-devtools-shared/src/devtools/views/ReactLogo';
+import {
+ Suspense,
+ useCallback,
+ useContext,
+ useDeferredValue,
+ useLayoutEffect,
+ useState,
+} from 'react';
+import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
+import {updateColorsToMatchTheme} from './content-views/constants';
+import {SchedulingProfilerContext} from './SchedulingProfilerContext';
import ImportButton from './ImportButton';
import CanvasPage from './CanvasPage';
-import ImportWorker from './import-worker/import.worker';
-import profilerBrowser from './assets/profilerBrowser.png';
import styles from './SchedulingProfiler.css';
-type DataResource = Resource;
-
-function createDataResourceFromImportedFile(file: File): DataResource {
- return createResource(
- () => {
- return new Promise((resolve, reject) => {
- const worker: Worker = new (ImportWorker: any)();
-
- worker.onmessage = function(event) {
- const data = ((event.data: any): ImportWorkerOutputData);
- switch (data.status) {
- case 'SUCCESS':
- resolve(data.processedData);
- break;
- case 'INVALID_PROFILE_ERROR':
- resolve(data.error);
- break;
- case 'UNEXPECTED_ERROR':
- reject(data.error);
- break;
- }
- worker.terminate();
- };
-
- worker.postMessage({file});
- });
- },
- () => file,
- {useWeakMap: true},
- );
-}
-
export function SchedulingProfiler(_: {||}) {
- const [dataResource, setDataResource] = useState(null);
+ const {importSchedulingProfilerData, schedulingProfilerData} = useContext(
+ SchedulingProfilerContext,
+ );
- const handleFileSelect = useCallback((file: File) => {
- setDataResource(createDataResourceFromImportedFile(file));
- }, []);
+ // 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(() => {
+ updateColorsToMatchTheme();
+ setKey(deferredTheme);
+ }, [deferredTheme]);
return (
-
-
-
-
Concurrent Mode Profiler
-
-
-
-
-
- {dataResource ? (
- }>
-
-
- ) : (
-
- )}
-
+
+ {schedulingProfilerData ? (
+ }>
+
+
+ ) : (
+
+ )}
);
}
const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
-
-
-
Welcome!
Click the import button
diff --git a/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js b/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js
new file mode 100644
index 0000000000000..11f66b7f16dc4
--- /dev/null
+++ b/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js
@@ -0,0 +1,60 @@
+/**
+ * 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 {createContext, useCallback, useContext, useMemo, useState} from 'react';
+import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
+import createDataResourceFromImportedFile from './createDataResourceFromImportedFile';
+
+import type {DataResource} from './createDataResourceFromImportedFile';
+
+export type Context = {|
+ importSchedulingProfilerData: (file: File) => void,
+ schedulingProfilerData: DataResource | null,
+|};
+
+const SchedulingProfilerContext = createContext
(
+ ((null: any): Context),
+);
+SchedulingProfilerContext.displayName = 'SchedulingProfilerContext';
+
+type State = {||};
+
+type Props = {|
+ children: React$Node,
+|};
+
+function SchedulingProfilerContextController({children}: Props) {
+ const [
+ schedulingProfilerData,
+ setSchedulingProfilerData,
+ ] = useState(null);
+
+ const importSchedulingProfilerData = useCallback((file: File) => {
+ setSchedulingProfilerData(createDataResourceFromImportedFile(file));
+ }, []);
+
+ // TODO (scheduling profiler) Move more stuff into this context, like scroll and zoom.
+
+ const value = useMemo(
+ () => ({
+ importSchedulingProfilerData,
+ schedulingProfilerData,
+ }),
+ [importSchedulingProfilerData, schedulingProfilerData],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export {SchedulingProfilerContext, SchedulingProfilerContextController};
diff --git a/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js b/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js
deleted file mode 100644
index 7558576c31781..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-export const enableDarkMode = false;
diff --git a/packages/react-devtools-scheduling-profiler/src/assets/logo.svg b/packages/react-devtools-scheduling-profiler/src/assets/logo.svg
deleted file mode 100644
index 2e5df0d3ab2f2..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/assets/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/react-devtools-scheduling-profiler/src/assets/profilerBrowser.png b/packages/react-devtools-scheduling-profiler/src/assets/profilerBrowser.png
deleted file mode 100644
index b0282be2f6828..0000000000000
Binary files a/packages/react-devtools-scheduling-profiler/src/assets/profilerBrowser.png and /dev/null differ
diff --git a/packages/react-devtools-scheduling-profiler/src/assets/reactlogo.svg b/packages/react-devtools-scheduling-profiler/src/assets/reactlogo.svg
deleted file mode 100644
index 6b60c1042f58d..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/assets/reactlogo.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js b/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
index 00b022899f657..8b79a30916fed 100644
--- a/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
+++ b/packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
@@ -203,7 +203,7 @@ class FlamechartStackLayerView extends View {
);
if (trimmedName !== null) {
- context.fillStyle = COLORS.PRIORITY_LABEL;
+ context.fillStyle = COLORS.FLAME_GRAPH_LABEL;
// Prevent text from being drawn outside `viewableArea`
const textOverflowsViewableArea = !rectEqualToRect(
diff --git a/packages/react-devtools-scheduling-profiler/src/content-views/constants.js b/packages/react-devtools-scheduling-profiler/src/content-views/constants.js
index 7b0fb87260769..baaedebc64ca7 100644
--- a/packages/react-devtools-scheduling-profiler/src/content-views/constants.js
+++ b/packages/react-devtools-scheduling-profiler/src/content-views/constants.js
@@ -41,34 +41,129 @@ export const FLAMECHART_FONT_SIZE = 10;
export const FLAMECHART_FRAME_HEIGHT = 16;
export const FLAMECHART_TEXT_PADDING = 3;
-export const COLORS = Object.freeze({
- BACKGROUND: '#ffffff',
- PRIORITY_BACKGROUND: '#ededf0',
- PRIORITY_BORDER: '#d7d7db',
- PRIORITY_LABEL: '#272727',
- USER_TIMING: '#c9cacd',
- USER_TIMING_HOVER: '#93959a',
- REACT_IDLE: '#edf6ff',
- REACT_IDLE_SELECTED: '#EDF6FF',
- REACT_IDLE_HOVER: '#EDF6FF',
- REACT_RENDER: '#9fc3f3',
- REACT_RENDER_SELECTED: '#64A9F5',
- REACT_RENDER_HOVER: '#2683E2',
- REACT_COMMIT: '#ff718e',
- REACT_COMMIT_SELECTED: '#FF5277',
- REACT_COMMIT_HOVER: '#ed0030',
- REACT_LAYOUT_EFFECTS: '#c88ff0',
- REACT_LAYOUT_EFFECTS_SELECTED: '#934FC1',
- REACT_LAYOUT_EFFECTS_HOVER: '#601593',
- REACT_PASSIVE_EFFECTS: '#c88ff0',
- REACT_PASSIVE_EFFECTS_SELECTED: '#934FC1',
- REACT_PASSIVE_EFFECTS_HOVER: '#601593',
- REACT_SCHEDULE: '#9fc3f3',
- REACT_SCHEDULE_HOVER: '#2683E2',
- REACT_SCHEDULE_CASCADING: '#ff718e',
- REACT_SCHEDULE_CASCADING_HOVER: '#ed0030',
- REACT_SUSPEND: '#a6e59f',
- REACT_SUSPEND_HOVER: '#13bc00',
- REACT_WORK_BORDER: '#ffffff',
- TIME_MARKER_LABEL: '#18212b',
-});
+// TODO Replace this with "export let" vars
+export let COLORS = {
+ BACKGROUND: '',
+ PRIORITY_BACKGROUND: '',
+ PRIORITY_BORDER: '',
+ PRIORITY_LABEL: '',
+ FLAME_GRAPH_LABEL: '',
+ USER_TIMING: '',
+ USER_TIMING_HOVER: '',
+ REACT_IDLE: '',
+ REACT_IDLE_SELECTED: '',
+ REACT_IDLE_HOVER: '',
+ REACT_RENDER: '',
+ REACT_RENDER_SELECTED: '',
+ REACT_RENDER_HOVER: '',
+ REACT_COMMIT: '',
+ REACT_COMMIT_SELECTED: '',
+ REACT_COMMIT_HOVER: '',
+ REACT_LAYOUT_EFFECTS: '',
+ REACT_LAYOUT_EFFECTS_SELECTED: '',
+ REACT_LAYOUT_EFFECTS_HOVER: '',
+ REACT_PASSIVE_EFFECTS: '',
+ REACT_PASSIVE_EFFECTS_SELECTED: '',
+ REACT_PASSIVE_EFFECTS_HOVER: '',
+ REACT_RESIZE_BAR: '',
+ REACT_SCHEDULE: '',
+ REACT_SCHEDULE_HOVER: '',
+ REACT_SCHEDULE_CASCADING: '',
+ REACT_SCHEDULE_CASCADING_HOVER: '',
+ REACT_SUSPEND: '',
+ REACT_SUSPEND_HOVER: '',
+ REACT_WORK_BORDER: '',
+ TIME_MARKER_LABEL: '',
+};
+
+export function updateColorsToMatchTheme(): void {
+ const computedStyle = getComputedStyle((document.body: any));
+
+ COLORS = {
+ BACKGROUND: computedStyle.getPropertyValue('--color-background'),
+ PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-priority-background',
+ ),
+ PRIORITY_BORDER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-priority-border',
+ ),
+ PRIORITY_LABEL: computedStyle.getPropertyValue('--color-text'),
+ FLAME_GRAPH_LABEL: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-flame-graph-label',
+ ),
+ USER_TIMING: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-user-timing',
+ ),
+ USER_TIMING_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-user-timing-hover',
+ ),
+ REACT_IDLE: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-idle',
+ ),
+ REACT_IDLE_SELECTED: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-idle-selected',
+ ),
+ REACT_IDLE_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-idle-hover',
+ ),
+ REACT_RENDER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-render',
+ ),
+ REACT_RENDER_SELECTED: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-render-selected',
+ ),
+ REACT_RENDER_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-render-hover',
+ ),
+ REACT_COMMIT: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-commit',
+ ),
+ REACT_COMMIT_SELECTED: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-commit-selected',
+ ),
+ REACT_COMMIT_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-commit-hover',
+ ),
+ REACT_LAYOUT_EFFECTS: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-layout-effects',
+ ),
+ REACT_LAYOUT_EFFECTS_SELECTED: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-layout-effects-selected',
+ ),
+ REACT_LAYOUT_EFFECTS_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-layout-effects-hover',
+ ),
+ REACT_PASSIVE_EFFECTS: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-passive-effects',
+ ),
+ REACT_PASSIVE_EFFECTS_SELECTED: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-passive-effects-selected',
+ ),
+ REACT_PASSIVE_EFFECTS_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-passive-effects-hover',
+ ),
+ REACT_RESIZE_BAR: computedStyle.getPropertyValue('--color-resize-bar'),
+ REACT_SCHEDULE: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-schedule',
+ ),
+ REACT_SCHEDULE_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-schedule-hover',
+ ),
+ REACT_SCHEDULE_CASCADING: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-schedule-cascading',
+ ),
+ REACT_SCHEDULE_CASCADING_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-schedule-cascading-hover',
+ ),
+ REACT_SUSPEND: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-suspend',
+ ),
+ REACT_SUSPEND_HOVER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-suspend-hover',
+ ),
+ REACT_WORK_BORDER: computedStyle.getPropertyValue(
+ '--color-scheduling-profiler-react-work-border',
+ ),
+ TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
+ };
+}
diff --git a/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.css b/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.css
deleted file mode 100644
index 60848641f4949..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.ContextMenu {
- position: absolute;
- border-radius: 0.125rem;
- background-color: #ffffff;
- border: 1px solid #ccc;
- box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
- font-size: 11px;
- overflow: hidden;
- z-index: 10000002;
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.js b/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.js
deleted file mode 100644
index 8b09ef1510dcf..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/ContextMenu.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {RegistryContextType} from './Contexts';
-
-import * as React from 'react';
-import {useContext, useEffect, useLayoutEffect, useRef, useState} from 'react';
-import {createPortal} from 'react-dom';
-import {RegistryContext} from './Contexts';
-
-import styles from './ContextMenu.css';
-
-function repositionToFit(element: HTMLElement, pageX: number, pageY: number) {
- const ownerWindow = element.ownerDocument.defaultView;
- if (element !== null) {
- if (pageY + element.offsetHeight >= ownerWindow.innerHeight) {
- if (pageY - element.offsetHeight > 0) {
- element.style.top = `${pageY - element.offsetHeight}px`;
- } else {
- element.style.top = '0px';
- }
- } else {
- element.style.top = `${pageY}px`;
- }
-
- if (pageX + element.offsetWidth >= ownerWindow.innerWidth) {
- if (pageX - element.offsetWidth > 0) {
- element.style.left = `${pageX - element.offsetWidth}px`;
- } else {
- element.style.left = '0px';
- }
- } else {
- element.style.left = `${pageX}px`;
- }
- }
-}
-
-const HIDDEN_STATE = {
- data: null,
- isVisible: false,
- pageX: 0,
- pageY: 0,
-};
-
-type Props = {|
- children: (data: Object) => React$Node,
- id: string,
-|};
-
-export default function ContextMenu({children, id}: Props) {
- const {hideMenu, registerMenu} = useContext(
- RegistryContext,
- );
-
- const [state, setState] = useState(HIDDEN_STATE);
-
- const bodyAccessorRef = useRef(null);
- const containerRef = useRef(null);
- const menuRef = useRef(null);
-
- useEffect(() => {
- if (!bodyAccessorRef.current) {
- return;
- }
- const ownerDocument = bodyAccessorRef.current.ownerDocument;
- containerRef.current = ownerDocument.createElement('div');
- if (ownerDocument.body) {
- ownerDocument.body.appendChild(containerRef.current);
- }
- return () => {
- if (ownerDocument.body && containerRef.current) {
- ownerDocument.body.removeChild(containerRef.current);
- }
- };
- }, [bodyAccessorRef, containerRef]);
-
- useEffect(() => {
- const showMenuFn = ({data, pageX, pageY}) => {
- setState({data, isVisible: true, pageX, pageY});
- };
- const hideMenuFn = () => setState(HIDDEN_STATE);
- return registerMenu(id, showMenuFn, hideMenuFn);
- }, [id]);
-
- useLayoutEffect(() => {
- if (!state.isVisible || !containerRef.current) {
- return;
- }
-
- const menu = menuRef.current;
- if (!menu) {
- return;
- }
-
- const hideUnlessContains: MouseEventHandler &
- TouchEventHandler &
- KeyboardEventHandler = event => {
- if (event.target instanceof HTMLElement && !menu.contains(event.target)) {
- hideMenu();
- }
- };
-
- const ownerDocument = containerRef.current.ownerDocument;
- ownerDocument.addEventListener('mousedown', hideUnlessContains);
- ownerDocument.addEventListener('touchstart', hideUnlessContains);
- ownerDocument.addEventListener('keydown', hideUnlessContains);
-
- const ownerWindow = ownerDocument.defaultView;
- ownerWindow.addEventListener('resize', hideMenu);
-
- repositionToFit(menu, state.pageX, state.pageY);
-
- return () => {
- ownerDocument.removeEventListener('mousedown', hideUnlessContains);
- ownerDocument.removeEventListener('touchstart', hideUnlessContains);
- ownerDocument.removeEventListener('keydown', hideUnlessContains);
-
- ownerWindow.removeEventListener('resize', hideMenu);
- };
- }, [state]);
-
- if (!state.isVisible) {
- return ;
- } else {
- const container = containerRef.current;
- if (container !== null) {
- return createPortal(
-
- {children(state.data)}
-
,
- container,
- );
- } else {
- return null;
- }
- }
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.css b/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.css
deleted file mode 100644
index 19fd8284a47cb..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.ContextMenuItem {
- display: flex;
- align-items: center;
- color: #333;
- padding: 0.5rem 0.75rem;
- cursor: default;
- border-top: 1px solid #ccc;
-}
-.ContextMenuItem:first-of-type {
- border-top: none;
-}
-.ContextMenuItem:hover,
-.ContextMenuItem:focus {
- outline: 0;
- background-color: rgba(0, 136, 250, 0.1);
-}
-.ContextMenuItem:active {
- background-color: #0088fa;
- color: #fff;
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.js b/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.js
deleted file mode 100644
index 5750bd90cd18f..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {RegistryContextType} from './Contexts';
-
-import * as React from 'react';
-import {useContext} from 'react';
-import {RegistryContext} from './Contexts';
-
-import styles from './ContextMenuItem.css';
-
-type Props = {|
- children: React$Node,
- onClick: () => void,
- title: string,
-|};
-
-export default function ContextMenuItem({children, onClick, title}: Props) {
- const {hideMenu} = useContext(RegistryContext);
-
- const handleClick: MouseEventHandler = event => {
- onClick();
- hideMenu();
- };
-
- return (
-
- {children}
-
- );
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/context/Contexts.js b/packages/react-devtools-scheduling-profiler/src/context/Contexts.js
deleted file mode 100644
index 46c742e06d0b8..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/Contexts.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import {createContext} from 'react';
-
-export type ShowFn = ({|data: Object, pageX: number, pageY: number|}) => void;
-export type HideFn = () => void;
-export type OnChangeFn = boolean => void;
-
-const idToShowFnMap = new Map();
-const idToHideFnMap = new Map();
-
-let currentHideFn: ?HideFn = null;
-let currentOnChange: ?OnChangeFn = null;
-
-function hideMenu() {
- if (typeof currentHideFn === 'function') {
- currentHideFn();
-
- if (typeof currentOnChange === 'function') {
- currentOnChange(false);
- }
- }
-
- currentHideFn = null;
- currentOnChange = null;
-}
-
-function showMenu({
- data,
- id,
- onChange,
- pageX,
- pageY,
-}: {|
- data: Object,
- id: string,
- onChange?: OnChangeFn,
- pageX: number,
- pageY: number,
-|}) {
- const showFn = idToShowFnMap.get(id);
- if (typeof showFn === 'function') {
- // Prevent open menus from being left hanging.
- hideMenu();
-
- currentHideFn = idToHideFnMap.get(id);
- showFn({data, pageX, pageY});
-
- if (typeof onChange === 'function') {
- currentOnChange = onChange;
- onChange(true);
- }
- }
-}
-
-function registerMenu(id: string, showFn: ShowFn, hideFn: HideFn) {
- if (idToShowFnMap.has(id)) {
- throw Error(`Context menu with id "${id}" already registered.`);
- }
-
- idToShowFnMap.set(id, showFn);
- idToHideFnMap.set(id, hideFn);
-
- return function unregisterMenu() {
- idToShowFnMap.delete(id);
- idToHideFnMap.delete(id);
- };
-}
-
-export type RegistryContextType = {|
- hideMenu: typeof hideMenu,
- showMenu: typeof showMenu,
- registerMenu: typeof registerMenu,
-|};
-
-export const RegistryContext = createContext({
- hideMenu,
- showMenu,
- registerMenu,
-});
diff --git a/packages/react-devtools-scheduling-profiler/src/context/index.js b/packages/react-devtools-scheduling-profiler/src/context/index.js
deleted file mode 100644
index c903d4f886409..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import {RegistryContext} from './Contexts';
-import ContextMenu from './ContextMenu';
-import ContextMenuItem from './ContextMenuItem';
-import useContextMenu from './useContextMenu';
-
-export {RegistryContext, ContextMenu, ContextMenuItem, useContextMenu};
diff --git a/packages/react-devtools-scheduling-profiler/src/context/useContextMenu.js b/packages/react-devtools-scheduling-profiler/src/context/useContextMenu.js
deleted file mode 100644
index 467c138f62d87..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/context/useContextMenu.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {OnChangeFn, RegistryContextType} from './Contexts';
-
-import {useContext, useEffect} from 'react';
-import {RegistryContext} from './Contexts';
-
-export default function useContextMenu({
- data,
- id,
- onChange,
- ref,
-}: {|
- data: T,
- id: string,
- onChange: OnChangeFn,
- ref: {+current: HTMLElement | null},
-|}) {
- const {showMenu} = useContext(RegistryContext);
-
- useEffect(() => {
- if (ref.current !== null) {
- const handleContextMenu = (event: MouseEvent | TouchEvent) => {
- event.preventDefault();
- event.stopPropagation();
-
- const pageX =
- (event: any).pageX ||
- (event.touches && (event: any).touches[0].pageX);
- const pageY =
- (event: any).pageY ||
- (event.touches && (event: any).touches[0].pageY);
-
- showMenu({data, id, onChange, pageX, pageY});
- };
-
- const trigger = ref.current;
- trigger.addEventListener('contextmenu', handleContextMenu);
-
- return () => {
- trigger.removeEventListener('contextmenu', handleContextMenu);
- };
- }
- }, [data, id, showMenu]);
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/createDataResourceFromImportedFile.js b/packages/react-devtools-scheduling-profiler/src/createDataResourceFromImportedFile.js
new file mode 100644
index 0000000000000..5d5d7939d4cab
--- /dev/null
+++ b/packages/react-devtools-scheduling-profiler/src/createDataResourceFromImportedFile.js
@@ -0,0 +1,48 @@
+/**
+ * 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 {createResource} from 'react-devtools-shared/src/devtools/cache';
+import ImportWorker from './import-worker/import.worker';
+
+import type {Resource} from 'react-devtools-shared/src/devtools/cache';
+import type {ReactProfilerData} from './types';
+import type {ImportWorkerOutputData} from './import-worker/import.worker';
+
+export type DataResource = Resource;
+
+export default function createDataResourceFromImportedFile(
+ file: File,
+): DataResource {
+ return createResource(
+ () => {
+ return new Promise((resolve, reject) => {
+ const worker: Worker = new (ImportWorker: any)();
+ worker.onmessage = function(event) {
+ const data = ((event.data: any): ImportWorkerOutputData);
+ switch (data.status) {
+ case 'SUCCESS':
+ resolve(data.processedData);
+ break;
+ case 'INVALID_PROFILE_ERROR':
+ resolve(data.error);
+ break;
+ case 'UNEXPECTED_ERROR':
+ reject(data.error);
+ break;
+ }
+ worker.terminate();
+ };
+
+ worker.postMessage({file});
+ });
+ },
+ () => file,
+ {useWeakMap: true},
+ );
+}
diff --git a/packages/react-devtools-scheduling-profiler/src/hooks.js b/packages/react-devtools-scheduling-profiler/src/hooks.js
deleted file mode 100644
index a9692010bed2c..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/hooks.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import {
- // $FlowFixMe
- unstable_createMutableSource as createMutableSource,
- useLayoutEffect,
- // $FlowFixMe
- unstable_useMutableSource as useMutableSource,
-} from 'react';
-
-import {
- updateDisplayDensity,
- updateThemeVariables,
-} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
-import {enableDarkMode} from './SchedulingProfilerFeatureFlags';
-
-export type BrowserTheme = 'dark' | 'light';
-
-const DARK_MODE_QUERY = '(prefers-color-scheme: dark)';
-
-const getSnapshot = window =>
- window.matchMedia(DARK_MODE_QUERY).matches ? 'dark' : 'light';
-
-const darkModeMutableSource = createMutableSource(
- window,
- () => window.matchMedia(DARK_MODE_QUERY).matches,
-);
-
-const subscribe = (window, callback) => {
- const mediaQueryList = window.matchMedia(DARK_MODE_QUERY);
- mediaQueryList.addEventListener('change', callback);
- return () => {
- mediaQueryList.removeEventListener('change', callback);
- };
-};
-
-export function useBrowserTheme(): void {
- const theme = useMutableSource(darkModeMutableSource, getSnapshot, subscribe);
-
- useLayoutEffect(() => {
- const documentElements = [((document.documentElement: any): HTMLElement)];
- if (enableDarkMode) {
- switch (theme) {
- case 'light':
- updateThemeVariables('light', documentElements);
- break;
- case 'dark':
- updateThemeVariables('dark', documentElements);
- break;
- default:
- throw Error(`Unsupported theme value "${theme}"`);
- }
- } else {
- updateThemeVariables('light', documentElements);
- }
- }, [theme]);
-}
-
-export function useDisplayDensity(): void {
- useLayoutEffect(() => {
- const documentElements = [((document.documentElement: any): HTMLElement)];
- updateDisplayDensity('comfortable', documentElements);
- }, []);
-}
diff --git a/packages/react-devtools-scheduling-profiler/src/index.css b/packages/react-devtools-scheduling-profiler/src/index.css
deleted file mode 100644
index 0e798eef50713..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/index.css
+++ /dev/null
@@ -1,21 +0,0 @@
-html {
- height: 100%;
-}
-
-body {
- height: 100%;
- margin: 0;
- font-family: var(--font-family-sans);
- font-size: var(--font-size-sans-normal);
- background-color: var(--color-background);
- color: var(--color-text);
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
-
-.Container {
- height: 100%;
-}
\ No newline at end of file
diff --git a/packages/react-devtools-scheduling-profiler/src/index.js b/packages/react-devtools-scheduling-profiler/src/index.js
deleted file mode 100644
index b10a2b07efd6f..0000000000000
--- a/packages/react-devtools-scheduling-profiler/src/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import 'regenerator-runtime/runtime';
-
-import * as React from 'react';
-// $FlowFixMe Flow does not yet know about createRoot()
-import {createRoot} from 'react-dom';
-import nullthrows from 'nullthrows';
-import App from './App';
-
-import styles from './index.css';
-
-const container = document.createElement('div');
-container.className = styles.Container;
-container.id = 'root';
-
-const body = nullthrows(document.body, 'Expect document.body to exist');
-body.appendChild(container);
-
-createRoot(container).render(
-
-
- ,
-);
diff --git a/packages/react-devtools-scheduling-profiler/src/view-base/ResizableSplitView.js b/packages/react-devtools-scheduling-profiler/src/view-base/ResizableSplitView.js
index 314be68e2888b..c69b982c47cdc 100644
--- a/packages/react-devtools-scheduling-profiler/src/view-base/ResizableSplitView.js
+++ b/packages/react-devtools-scheduling-profiler/src/view-base/ResizableSplitView.js
@@ -15,6 +15,7 @@ import type {
} from './useCanvasInteraction';
import type {Rect, Size} from './geometry';
+import {COLORS} from '../content-views/constants';
import nullthrows from 'nullthrows';
import {Surface} from './Surface';
import {View} from './View';
@@ -38,14 +39,11 @@ type LayoutState = $ReadOnly<{|
|}>;
function getColorForBarState(state: ResizeBarState): string {
- // Colors obtained from Firefox Profiler
switch (state) {
case 'normal':
- return '#ccc';
case 'hovered':
- return '#bbb';
case 'dragging':
- return '#aaa';
+ return COLORS.REACT_RESIZE_BAR;
}
throw new Error(`Unknown resize bar state ${state}`);
}
@@ -131,6 +129,7 @@ class ResizeBar extends View {
}
export class ResizableSplitView extends View {
+ _canvasRef: {current: HTMLCanvasElement | null};
_resizingState: ResizingState | null = null;
_layoutState: LayoutState;
@@ -139,9 +138,12 @@ export class ResizableSplitView extends View {
frame: Rect,
topSubview: View,
bottomSubview: View,
+ canvasRef: {current: HTMLCanvasElement | null},
) {
super(surface, frame, noopLayout);
+ this._canvasRef = canvasRef;
+
this.addSubview(topSubview);
this.addSubview(new ResizeBar(surface, frame));
this.addSubview(bottomSubview);
@@ -279,6 +281,18 @@ export class ResizableSplitView extends View {
}
_handleMouseMove(interaction: MouseMoveInteraction) {
+ const cursorLocation = interaction.payload.location;
+ const resizeBarFrame = this._getResizeBar().frame;
+
+ const canvas = this._canvasRef.current;
+ if (canvas !== null) {
+ if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
+ canvas.style.cursor = 'ns-resize';
+ } else {
+ canvas.style.cursor = 'default';
+ }
+ }
+
const {_resizingState} = this;
if (_resizingState) {
this._resizingState = {
diff --git a/packages/react-devtools-scheduling-profiler/src/view-base/useCanvasInteraction.js b/packages/react-devtools-scheduling-profiler/src/view-base/useCanvasInteraction.js
index b08d9bbbbb466..3b374aebbee79 100644
--- a/packages/react-devtools-scheduling-profiler/src/view-base/useCanvasInteraction.js
+++ b/packages/react-devtools-scheduling-profiler/src/view-base/useCanvasInteraction.js
@@ -175,15 +175,16 @@ export function useCanvasInteraction(
return false;
};
- document.addEventListener('mousemove', onDocumentMouseMove);
- document.addEventListener('mouseup', onDocumentMouseUp);
+ const ownerDocument = canvas.ownerDocument;
+ ownerDocument.addEventListener('mousemove', onDocumentMouseMove);
+ ownerDocument.addEventListener('mouseup', onDocumentMouseUp);
canvas.addEventListener('mousedown', onCanvasMouseDown);
canvas.addEventListener('wheel', onCanvasWheel);
return () => {
- document.removeEventListener('mousemove', onDocumentMouseMove);
- document.removeEventListener('mouseup', onDocumentMouseUp);
+ ownerDocument.removeEventListener('mousemove', onDocumentMouseMove);
+ ownerDocument.removeEventListener('mouseup', onDocumentMouseUp);
canvas.removeEventListener('mousedown', onCanvasMouseDown);
canvas.removeEventListener('wheel', onCanvasWheel);
diff --git a/packages/react-devtools-scheduling-profiler/webpack.config.js b/packages/react-devtools-scheduling-profiler/webpack.config.js
index e30d4fda13db2..683e4bb547b3e 100644
--- a/packages/react-devtools-scheduling-profiler/webpack.config.js
+++ b/packages/react-devtools-scheduling-profiler/webpack.config.js
@@ -4,7 +4,6 @@ const {resolve} = require('path');
const {DefinePlugin} = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
-const {getVersionString} = require('./buildUtils');
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
@@ -22,8 +21,6 @@ const shouldUseDevServer = TARGET === 'local';
const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
-const DEVTOOLS_VERSION = getVersionString();
-
const imageInlineSizeLimit = 10000;
const babelOptions = {
@@ -58,7 +55,6 @@ const config = {
__PROFILE__: false,
__EXPERIMENTAL__: true,
__VARIANT__: false,
- 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
}),
new HtmlWebpackPlugin({
title: 'React Concurrent Mode Profiler',
diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.css b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.css
index 20af7c096f059..674346621b102 100644
--- a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.css
+++ b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.css
@@ -4,4 +4,5 @@
border-radius: 0.25rem;
overflow: hidden;
z-index: 10000002;
+ user-select: none;
}
\ No newline at end of file
diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/Contexts.js b/packages/react-devtools-shared/src/devtools/ContextMenu/Contexts.js
index 0d2e55106c89f..46c742e06d0b8 100644
--- a/packages/react-devtools-shared/src/devtools/ContextMenu/Contexts.js
+++ b/packages/react-devtools-shared/src/devtools/ContextMenu/Contexts.js
@@ -11,33 +11,52 @@ import {createContext} from 'react';
export type ShowFn = ({|data: Object, pageX: number, pageY: number|}) => void;
export type HideFn = () => void;
+export type OnChangeFn = boolean => void;
const idToShowFnMap = new Map();
const idToHideFnMap = new Map();
-let currentHideFn = null;
+let currentHideFn: ?HideFn = null;
+let currentOnChange: ?OnChangeFn = null;
function hideMenu() {
if (typeof currentHideFn === 'function') {
currentHideFn();
+
+ if (typeof currentOnChange === 'function') {
+ currentOnChange(false);
+ }
}
+
+ currentHideFn = null;
+ currentOnChange = null;
}
function showMenu({
data,
id,
+ onChange,
pageX,
pageY,
}: {|
data: Object,
id: string,
+ onChange?: OnChangeFn,
pageX: number,
pageY: number,
|}) {
const showFn = idToShowFnMap.get(id);
if (typeof showFn === 'function') {
+ // Prevent open menus from being left hanging.
+ hideMenu();
+
currentHideFn = idToHideFnMap.get(id);
showFn({data, pageX, pageY});
+
+ if (typeof onChange === 'function') {
+ currentOnChange = onChange;
+ onChange(true);
+ }
}
}
@@ -56,14 +75,9 @@ function registerMenu(id: string, showFn: ShowFn, hideFn: HideFn) {
}
export type RegistryContextType = {|
- hideMenu: () => void,
- showMenu: ({|
- data: Object,
- id: string,
- pageX: number,
- pageY: number,
- |}) => void,
- registerMenu: (string, ShowFn, HideFn) => Function,
+ hideMenu: typeof hideMenu,
+ showMenu: typeof showMenu,
+ registerMenu: typeof registerMenu,
|};
export const RegistryContext = createContext({
diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js b/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js
index 1c713bae73dd5..f61cde4b0b535 100644
--- a/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js
+++ b/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js
@@ -10,17 +10,19 @@
import {useContext, useEffect} from 'react';
import {RegistryContext} from './Contexts';
-import type {RegistryContextType} from './Contexts';
+import type {OnChangeFn, RegistryContextType} from './Contexts';
import type {ElementRef} from 'react';
export default function useContextMenu({
data,
id,
+ onChange,
ref,
}: {|
data: Object,
id: string,
- ref: {current: ElementRef<'div'> | null},
+ onChange?: OnChangeFn,
+ ref: {current: ElementRef<*> | null},
|}) {
const {showMenu} = useContext(RegistryContext);
diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js
index 812ca916d8ebe..3c768c11ab97e 100644
--- a/packages/react-devtools-shared/src/devtools/views/DevTools.js
+++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js
@@ -24,6 +24,7 @@ import {TreeContextController} from './Components/TreeContext';
import ViewElementSourceContext from './Components/ViewElementSourceContext';
import HookNamesContext from './Components/HookNamesContext';
import {ProfilerContextController} from './Profiler/ProfilerContext';
+import {SchedulingProfilerContextController} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
import {ModalDialogContextController} from './ModalDialog';
import ReactLogo from './ReactLogo';
import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog';
@@ -218,36 +219,40 @@ export default function DevTools({
-
- {showTabBar && (
-
-
-
- {process.env.DEVTOOLS_VERSION}
-
-
-
+
+ {showTabBar && (
+
+
+
+ {process.env.DEVTOOLS_VERSION}
+
+
+
+
+ )}
+
+
+
+
- )}
-
-
-
-
-
+
diff --git a/packages/react-devtools-shared/src/devtools/views/Icon.js b/packages/react-devtools-shared/src/devtools/views/Icon.js
index ffa297610bdf5..c9ae931f5ee74 100644
--- a/packages/react-devtools-shared/src/devtools/views/Icon.js
+++ b/packages/react-devtools-shared/src/devtools/views/Icon.js
@@ -21,6 +21,7 @@ export type IconType =
| 'flame-chart'
| 'profiler'
| 'ranked-chart'
+ | 'scheduling-profiler'
| 'search'
| 'settings'
| 'store-as-global-variable'
@@ -64,6 +65,9 @@ export default function Icon({className = '', type}: Props) {
case 'ranked-chart':
pathData = PATH_RANKED_CHART;
break;
+ case 'scheduling-profiler':
+ pathData = PATH_SCHEDULING_PROFILER;
+ break;
case 'search':
pathData = PATH_SEARCH;
break;
@@ -136,6 +140,11 @@ 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/Profiler.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
index 9fda41499871e..70c87e618eb0d 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
@@ -16,6 +16,7 @@ import ClearProfilingDataButton from './ClearProfilingDataButton';
import CommitFlamegraph from './CommitFlamegraph';
import CommitRanked from './CommitRanked';
import RootSelector from './RootSelector';
+import {SchedulingProfiler} from 'react-devtools-scheduling-profiler/src/SchedulingProfiler';
import RecordToggle from './RecordToggle';
import ReloadAndProfileButton from './ReloadAndProfileButton';
import ProfilingImportExportButtons from './ProfilingImportExportButtons';
@@ -41,8 +42,10 @@ function Profiler(_: {||}) {
supportsProfiling,
} = useContext(ProfilerContext);
+ let showRightColumn = true;
+
let view = null;
- if (didRecordCommits) {
+ if (didRecordCommits || selectedTabID === 'scheduling-profiler') {
switch (selectedTabID) {
case 'flame-chart':
view = ;
@@ -50,6 +53,10 @@ function Profiler(_: {||}) {
case 'ranked-chart':
view = ;
break;
+ case 'scheduling-profiler':
+ view = ;
+ showRightColumn = false;
+ break;
default:
break;
}
@@ -119,7 +126,7 @@ function Profiler(_: {||}) {
- {sidebar}
+ {showRightColumn && {sidebar}
}
@@ -139,6 +146,13 @@ const tabs = [
label: 'Ranked',
title: 'Ranked chart',
},
+ null, // Divider/separator
+ {
+ id: 'scheduling-profiler',
+ icon: 'scheduling-profiler',
+ label: 'Scheduling',
+ title: 'Scheduling Profiler',
+ },
];
const NoProfilingData = () => (
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 48f9aa11eefdb..3206fcb28f74a 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
@@ -19,7 +19,8 @@ import {StoreContext} from '../context';
import type {ProfilingDataFrontend} from './types';
-export type TabID = 'flame-chart' | 'ranked-chart';
+// TODO (scheduling profiler) Should this be its own context?
+export type TabID = 'flame-chart' | 'ranked-chart' | 'scheduling-profiler';
export type Context = {|
// Which tab is selected in the Profiler UI?
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 d6570dd5ab34b..420bdbbd39021 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
@@ -19,13 +19,17 @@ import {
prepareProfilingDataFrontendFromExport,
} from './utils';
import {downloadFile} from '../utils';
+import {SchedulingProfilerContext} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
import styles from './ProfilingImportExportButtons.css';
import type {ProfilingDataExport} from './types';
export default function ProfilingImportExportButtons() {
- const {isProfiling, profilingData, rootID} = useContext(ProfilerContext);
+ const {isProfiling, profilingData, rootID, selectedTabID} = useContext(
+ ProfilerContext,
+ );
+ const {importSchedulingProfilerData} = useContext(SchedulingProfilerContext);
const store = useContext(StoreContext);
const {profilerStore} = store;
@@ -64,13 +68,13 @@ export default function ProfilingImportExportButtons() {
}
}, [rootID, profilingData]);
- const uploadData = useCallback(() => {
+ const clickInputElement = useCallback(() => {
if (inputRef.current !== null) {
inputRef.current.click();
}
}, []);
- const handleFiles = useCallback(() => {
+ const importProfilerData = useCallback(() => {
const input = inputRef.current;
if (input !== null && input.files.length > 0) {
const fileReader = new FileReader();
@@ -104,6 +108,11 @@ export default function ProfilingImportExportButtons() {
}
}, [modalDialogDispatch, profilerStore]);
+ const importSchedulingProfilerDataWrapper = event => {
+ const input = inputRef.current;
+ importSchedulingProfilerData(input.files[0]);
+ };
+
return (
@@ -111,18 +120,26 @@ export default function ProfilingImportExportButtons() {
ref={inputRef}
className={styles.Input}
type="file"
- onChange={handleFiles}
+ onChange={
+ selectedTabID === 'scheduling-profiler'
+ ? importSchedulingProfilerDataWrapper
+ : importProfilerData
+ }
tabIndex={-1}
/>