Files
react/packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
E-Liang Tan eabd18c73f Scheduling Profiler: Move preprocessing to web worker and add loading indicator (#19759)
* Move preprocessData into a web worker
* Add UI feedback for loading/import error states
* Terminate worker when done handling profile
* Add display density CSS variables
2020-09-04 10:57:32 -04:00

146 lines
4.3 KiB
JavaScript

/**
* 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 {Resource} from 'react-devtools-shared/src/devtools/cache';
import type {ReactProfilerData} from './types';
import type {ImportWorkerOutputData} from './import-worker/import.worker';
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 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<void, File, ReactProfilerData | Error>;
function createDataResourceFromImportedFile(file: File): DataResource {
return createResource(
() => {
return new Promise<ReactProfilerData | Error>((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<DataResource | null>(null);
const handleFileSelect = useCallback((file: File) => {
setDataResource(createDataResourceFromImportedFile(file));
}, []);
return (
<div className={styles.SchedulingProfiler}>
<div className={styles.Toolbar}>
<ReactLogo />
<span className={styles.AppName}>Concurrent Mode Profiler</span>
<div className={styles.VRule} />
<ImportButton onFileSelect={handleFileSelect} />
<div className={styles.Spacer} />
</div>
<div className={styles.Content}>
{dataResource ? (
<Suspense fallback={<ProcessingData />}>
<DataResourceComponent
dataResource={dataResource}
onFileSelect={handleFileSelect}
/>
</Suspense>
) : (
<Welcome onFileSelect={handleFileSelect} />
)}
</div>
</div>
);
}
const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
<div className={styles.EmptyStateContainer}>
<div className={styles.ScreenshotWrapper}>
<img
src={profilerBrowser}
className={styles.Screenshot}
alt="Profiler screenshot"
/>
</div>
<div className={styles.Header}>Welcome!</div>
<div className={styles.Row}>
Click the import button
<ImportButton onFileSelect={onFileSelect} /> to import a Chrome
performance profile.
</div>
</div>
);
const ProcessingData = () => (
<div className={styles.EmptyStateContainer}>
<div className={styles.Header}>Processing data...</div>
<div className={styles.Row}>This should only take a minute.</div>
</div>
);
const CouldNotLoadProfile = ({error, onFileSelect}) => (
<div className={styles.EmptyStateContainer}>
<div className={styles.Header}>Could not load profile</div>
{error.message && (
<div className={styles.Row}>
<div className={styles.ErrorMessage}>{error.message}</div>
</div>
)}
<div className={styles.Row}>
Try importing
<ImportButton onFileSelect={onFileSelect} />
another Chrome performance profile.
</div>
</div>
);
const DataResourceComponent = ({
dataResource,
onFileSelect,
}: {|
dataResource: DataResource,
onFileSelect: (file: File) => void,
|}) => {
const dataOrError = dataResource.read();
if (dataOrError instanceof Error) {
return (
<CouldNotLoadProfile error={dataOrError} onFileSelect={onFileSelect} />
);
}
return <CanvasPage profilerData={dataOrError} />;
};