mirror of
https://github.com/facebook/react.git
synced 2026-02-27 03:07:57 +00:00
* skip reading element for imported data * rename nodes & enable store lookup for components tab * replace names * Added some more test coverage; reverted rename Co-authored-by: Brian Vaughn <bvaughn@fb.com>
310 lines
8.9 KiB
JavaScript
310 lines
8.9 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 * as React from 'react';
|
|
import {createContext, useCallback, useContext, useMemo, useState} from 'react';
|
|
import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
|
|
import {useLocalStorage, useSubscription} from '../hooks';
|
|
import {
|
|
TreeDispatcherContext,
|
|
TreeStateContext,
|
|
} from '../Components/TreeContext';
|
|
import {StoreContext} from '../context';
|
|
|
|
import type {ProfilingDataFrontend} from './types';
|
|
|
|
export type TabID = 'flame-chart' | 'ranked-chart' | 'interactions';
|
|
|
|
export type Context = {|
|
|
// Which tab is selected in the Profiler UI?
|
|
selectedTabID: TabID,
|
|
selectTab(id: TabID): void,
|
|
|
|
// Store subscription based values.
|
|
// The isProfiling value may be modified by the record button in the Profiler toolbar,
|
|
// or from the backend itself (after a reload-and-profile action).
|
|
// It is synced between the backend and frontend via a Store subscription.
|
|
didRecordCommits: boolean,
|
|
isProcessingData: boolean,
|
|
isProfiling: boolean,
|
|
profilingData: ProfilingDataFrontend | null,
|
|
startProfiling(): void,
|
|
stopProfiling(): void,
|
|
supportsProfiling: boolean,
|
|
|
|
// Which root should profiling data be shown for?
|
|
// This value should be initialized to either:
|
|
// 1. The selected root in the Components tree (if it has any profiling data) or
|
|
// 2. The first root in the list with profiling data.
|
|
rootID: number | null,
|
|
setRootID: (id: number) => void,
|
|
|
|
// Controls whether commits are filtered by duration.
|
|
// This value is controlled by a filter toggle UI in the Profiler toolbar.
|
|
// It impacts the commit selector UI as well as the fiber commits bar chart.
|
|
isCommitFilterEnabled: boolean,
|
|
setIsCommitFilterEnabled: (value: boolean) => void,
|
|
minCommitDuration: number,
|
|
setMinCommitDuration: (value: number) => void,
|
|
|
|
// Which commit is currently selected in the commit selector UI.
|
|
// Note that this is the index of the commit in all commits (non-filtered) that were profiled.
|
|
// This value is controlled by the commit selector UI in the Profiler toolbar.
|
|
// It impacts the flame graph and ranked charts.
|
|
selectedCommitIndex: number | null,
|
|
selectCommitIndex: (value: number | null) => void,
|
|
|
|
// Which fiber is currently selected in the Ranked or Flamegraph charts?
|
|
selectedFiberID: number | null,
|
|
selectedFiberName: string | null,
|
|
selectFiber: (id: number | null, name: string | null) => void,
|
|
|
|
// Which interaction is currently selected in the Interactions graph?
|
|
selectedInteractionID: number | null,
|
|
selectInteraction: (id: number | null) => void,
|
|
|};
|
|
|
|
const ProfilerContext = createContext<Context>(((null: any): Context));
|
|
ProfilerContext.displayName = 'ProfilerContext';
|
|
|
|
type StoreProfilingState = {|
|
|
didRecordCommits: boolean,
|
|
isProcessingData: boolean,
|
|
isProfiling: boolean,
|
|
profilingData: ProfilingDataFrontend | null,
|
|
supportsProfiling: boolean,
|
|
|};
|
|
|
|
type Props = {|
|
|
children: React$Node,
|
|
|};
|
|
|
|
function ProfilerContextController({children}: Props) {
|
|
const store = useContext(StoreContext);
|
|
const {selectedElementID} = useContext(TreeStateContext);
|
|
const dispatch = useContext(TreeDispatcherContext);
|
|
|
|
const {profilerStore} = store;
|
|
|
|
const subscription = useMemo(
|
|
() => ({
|
|
getCurrentValue: () => ({
|
|
didRecordCommits: profilerStore.didRecordCommits,
|
|
isProcessingData: profilerStore.isProcessingData,
|
|
isProfiling: profilerStore.isProfiling,
|
|
profilingData: profilerStore.profilingData,
|
|
supportsProfiling: store.supportsProfiling,
|
|
}),
|
|
subscribe: (callback: Function) => {
|
|
profilerStore.addListener('profilingData', callback);
|
|
profilerStore.addListener('isProcessingData', callback);
|
|
profilerStore.addListener('isProfiling', callback);
|
|
store.addListener('supportsProfiling', callback);
|
|
return () => {
|
|
profilerStore.removeListener('profilingData', callback);
|
|
profilerStore.removeListener('isProcessingData', callback);
|
|
profilerStore.removeListener('isProfiling', callback);
|
|
store.removeListener('supportsProfiling', callback);
|
|
};
|
|
},
|
|
}),
|
|
[profilerStore, store],
|
|
);
|
|
const {
|
|
didRecordCommits,
|
|
isProcessingData,
|
|
isProfiling,
|
|
profilingData,
|
|
supportsProfiling,
|
|
} = useSubscription<StoreProfilingState>(subscription);
|
|
|
|
const [
|
|
prevProfilingData,
|
|
setPrevProfilingData,
|
|
] = useState<ProfilingDataFrontend | null>(null);
|
|
const [rootID, setRootID] = useState<number | null>(null);
|
|
const [selectedFiberID, selectFiberID] = useState<number | null>(null);
|
|
const [selectedFiberName, selectFiberName] = useState<string | null>(null);
|
|
|
|
const selectFiber = useCallback(
|
|
(id: number | null, name: string | null) => {
|
|
selectFiberID(id);
|
|
selectFiberName(name);
|
|
|
|
// Sync selection to the Components tab for convenience.
|
|
// Keep in mind that profiling data may be from a previous session.
|
|
// If data has been imported, we should skip the selection sync.
|
|
if (
|
|
id !== null &&
|
|
profilingData !== null &&
|
|
profilingData.imported === false
|
|
) {
|
|
// We should still check to see if this element is still in the store.
|
|
// It may have been removed during profiling.
|
|
if (store.containsElement(id)) {
|
|
dispatch({
|
|
type: 'SELECT_ELEMENT_BY_ID',
|
|
payload: id,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
[dispatch, selectFiberID, selectFiberName, store, profilingData],
|
|
);
|
|
|
|
const setRootIDAndClearFiber = useCallback(
|
|
(id: number | null) => {
|
|
selectFiber(null, null);
|
|
setRootID(id);
|
|
},
|
|
[setRootID, selectFiber],
|
|
);
|
|
|
|
if (prevProfilingData !== profilingData) {
|
|
batchedUpdates(() => {
|
|
setPrevProfilingData(profilingData);
|
|
|
|
const dataForRoots =
|
|
profilingData !== null ? profilingData.dataForRoots : null;
|
|
if (dataForRoots != null) {
|
|
const firstRootID = dataForRoots.keys().next().value || null;
|
|
|
|
if (rootID === null || !dataForRoots.has(rootID)) {
|
|
let selectedElementRootID = null;
|
|
if (selectedElementID !== null) {
|
|
selectedElementRootID = store.getRootIDForElement(
|
|
selectedElementID,
|
|
);
|
|
}
|
|
if (
|
|
selectedElementRootID !== null &&
|
|
dataForRoots.has(selectedElementRootID)
|
|
) {
|
|
setRootIDAndClearFiber(selectedElementRootID);
|
|
} else {
|
|
setRootIDAndClearFiber(firstRootID);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const startProfiling = useCallback(
|
|
() => store.profilerStore.startProfiling(),
|
|
[store],
|
|
);
|
|
const stopProfiling = useCallback(() => store.profilerStore.stopProfiling(), [
|
|
store,
|
|
]);
|
|
|
|
const [
|
|
isCommitFilterEnabled,
|
|
setIsCommitFilterEnabled,
|
|
] = useLocalStorage<boolean>('React::DevTools::isCommitFilterEnabled', false);
|
|
const [minCommitDuration, setMinCommitDuration] = useLocalStorage<number>(
|
|
'minCommitDuration',
|
|
0,
|
|
);
|
|
|
|
const [selectedCommitIndex, selectCommitIndex] = useState<number | null>(
|
|
null,
|
|
);
|
|
const [selectedTabID, selectTab] = useState<TabID>('flame-chart');
|
|
const [selectedInteractionID, selectInteraction] = useState<number | null>(
|
|
null,
|
|
);
|
|
|
|
if (isProfiling) {
|
|
batchedUpdates(() => {
|
|
if (selectedCommitIndex !== null) {
|
|
selectCommitIndex(null);
|
|
}
|
|
if (selectedFiberID !== null) {
|
|
selectFiberID(null);
|
|
selectFiberName(null);
|
|
}
|
|
if (selectedInteractionID !== null) {
|
|
selectInteraction(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
selectedTabID,
|
|
selectTab,
|
|
|
|
didRecordCommits,
|
|
isProcessingData,
|
|
isProfiling,
|
|
profilingData,
|
|
startProfiling,
|
|
stopProfiling,
|
|
supportsProfiling,
|
|
|
|
rootID,
|
|
setRootID: setRootIDAndClearFiber,
|
|
|
|
isCommitFilterEnabled,
|
|
setIsCommitFilterEnabled,
|
|
minCommitDuration,
|
|
setMinCommitDuration,
|
|
|
|
selectedCommitIndex,
|
|
selectCommitIndex,
|
|
|
|
selectedFiberID,
|
|
selectedFiberName,
|
|
selectFiber,
|
|
|
|
selectedInteractionID,
|
|
selectInteraction,
|
|
}),
|
|
[
|
|
selectedTabID,
|
|
selectTab,
|
|
|
|
didRecordCommits,
|
|
isProcessingData,
|
|
isProfiling,
|
|
profilingData,
|
|
startProfiling,
|
|
stopProfiling,
|
|
supportsProfiling,
|
|
|
|
rootID,
|
|
setRootID,
|
|
setRootIDAndClearFiber,
|
|
|
|
isCommitFilterEnabled,
|
|
setIsCommitFilterEnabled,
|
|
minCommitDuration,
|
|
setMinCommitDuration,
|
|
|
|
selectedCommitIndex,
|
|
selectCommitIndex,
|
|
|
|
selectedFiberID,
|
|
selectedFiberName,
|
|
selectFiber,
|
|
|
|
selectedInteractionID,
|
|
selectInteraction,
|
|
],
|
|
);
|
|
|
|
return (
|
|
<ProfilerContext.Provider value={value}>
|
|
{children}
|
|
</ProfilerContext.Provider>
|
|
);
|
|
}
|
|
|
|
export {ProfilerContext, ProfilerContextController};
|