diff --git a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js index 6fcd1efd31eaf..cae3ff41a5300 100644 --- a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js @@ -79,4 +79,44 @@ describe('ProfilerStore', () => { store.profilerStore.profilingData = fauxProfilingData; expect(store.profilerStore.profilingData).toBe(fauxProfilingData); }); + + // This test covers current broken behavior (arguably) with the synthetic event system. + it('should filter empty commits', () => { + const inputRef = React.createRef(); + const ControlledInput = () => { + const [name, setName] = React.useState('foo'); + const handleChange = event => setName(event.target.value); + return ; + }; + + const container = document.createElement('div'); + + // This element has to be in the for the event system to work. + document.body.appendChild(container); + + // It's important that this test uses legacy sync mode. + // The root API does not trigger this particular failing case. + ReactDOM.render(, container); + + utils.act(() => store.profilerStore.startProfiling()); + + // Sets a value in a way that React doesn't see, + // so that a subsequent "change" event will trigger the event handler. + const setUntrackedValue = Object.getOwnPropertyDescriptor( + HTMLInputElement.prototype, + 'value', + ).set; + + const target = inputRef.current; + setUntrackedValue.call(target, 'bar'); + target.dispatchEvent(new Event('input', {bubbles: true, cancelable: true})); + expect(target.value).toBe('bar'); + + utils.act(() => store.profilerStore.stopProfiling()); + + // Only one commit should have been recorded (in response to the "change" event). + const root = store.roots[0]; + const data = store.profilerStore.getDataForRoot(root); + expect(data.commitData).toHaveLength(1); + }); }); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 287ec898e3f87..761c7893505e2 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -1643,6 +1643,7 @@ export function attach( } } } + if (shouldIncludeInTree) { const isProfilingSupported = nextFiber.hasOwnProperty('treeBaseDuration'); if (isProfilingSupported) { @@ -1742,6 +1743,14 @@ export function attach( const current = root.current; const alternate = current.alternate; + // Certain types of updates bail out at the root without doing any actual render work. + // React should probably not call the DevTools commit hook in this case, + // but if it does- we can detect it and filter them out from the profiler. + const didBailoutAtRoot = + alternate !== null && + alternate.expirationTime === 0 && + alternate.childExpirationTime === 0; + currentRootID = getFiberID(getPrimaryFiber(current)); // Before the traversals, remember to start tracking @@ -1758,7 +1767,7 @@ export function attach( // where some v16 renderers support profiling and others don't. const isProfilingSupported = root.memoizedInteractions != null; - if (isProfiling && isProfilingSupported) { + if (isProfiling && isProfilingSupported && !didBailoutAtRoot) { // If profiling is active, store commit time and duration, and the current interactions. // The frontend may request this information after profiling has stopped. currentCommitProfilingMetadata = { @@ -1802,7 +1811,7 @@ export function attach( mountFiberRecursively(current, null, false, false); } - if (isProfiling && isProfilingSupported) { + if (isProfiling && isProfilingSupported && !didBailoutAtRoot) { const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get( currentRootID, ); diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 118049abc2d87..044c153cceeab 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -284,6 +284,9 @@ export type DevToolsHook = { onCommitFiberRoot: ( rendererID: RendererID, fiber: Object, + // Added in v16.9 to support Profiler priority labels commitPriority?: number, + // Added in v16.9 to support Fast Refresh + didError?: boolean, ) => void, };