diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
index 5a183f1b85..0e5d8c3daf 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
@@ -198,6 +198,7 @@ import {
getWorkInProgressRoot,
pushRenderLanes,
} from './ReactFiberWorkLoop.new';
+import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
@@ -2336,10 +2337,11 @@ function updateDehydratedSuspenseComponent(
// Leave the child in place. I.e. the dehydrated fragment.
workInProgress.child = current.child;
// Register a callback to retry this boundary once the server has sent the result.
- registerSuspenseInstanceRetry(
- suspenseInstance,
- retryDehydratedSuspenseBoundary.bind(null, current),
- );
+ let retry = retryDehydratedSuspenseBoundary.bind(null, current);
+ if (enableSchedulerTracing) {
+ retry = Schedule_tracing_wrap(retry);
+ }
+ registerSuspenseInstanceRetry(suspenseInstance, retry);
return null;
} else {
// This is the first attempt.
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
index d9212d8ea7..80a8162725 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
@@ -180,6 +180,7 @@ import {
markUnprocessedUpdateTime,
getWorkInProgressRoot,
} from './ReactFiberWorkLoop.old';
+import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
@@ -2303,10 +2304,11 @@ function updateDehydratedSuspenseComponent(
// Leave the child in place. I.e. the dehydrated fragment.
workInProgress.child = current.child;
// Register a callback to retry this boundary once the server has sent the result.
- registerSuspenseInstanceRetry(
- suspenseInstance,
- retryDehydratedSuspenseBoundary.bind(null, current),
- );
+ let retry = retryDehydratedSuspenseBoundary.bind(null, current);
+ if (enableSchedulerTracing) {
+ retry = Schedule_tracing_wrap(retry);
+ }
+ registerSuspenseInstanceRetry(suspenseInstance, retry);
return null;
} else {
// This is the first attempt.
diff --git a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js
index 6a79960e38..643436c80e 100644
--- a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+++ b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js
@@ -719,6 +719,76 @@ describe('ReactDOMTracing', () => {
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
+ // @gate experimental
+ it('traces interaction across suspended hydration from server', async () => {
+ // Copied from ReactDOMHostConfig.js
+ const SUSPENSE_START_DATA = '$';
+ const SUSPENSE_PENDING_START_DATA = '$?';
+
+ const ref = React.createRef();
+
+ function App() {
+ return (
+
+ Hello
+
+ );
+ }
+
+ const container = document.createElement('div');
+
+ // Render the final HTML.
+ const finalHTML = ReactDOMServer.renderToString();
+
+ // Replace the marker with a pending state.
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
+ const escapedMarker = SUSPENSE_START_DATA.replace(
+ /[.*+\-?^${}()|[\]\\]/g,
+ '\\$&',
+ );
+ container.innerHTML = finalHTML.replace(
+ new RegExp(escapedMarker, 'g'),
+ SUSPENSE_PENDING_START_DATA,
+ );
+
+ let interaction;
+
+ const root = ReactDOM.createRoot(container, {hydrate: true});
+
+ // Start hydrating but simulate blocking for suspense data from the server.
+ SchedulerTracing.unstable_trace('initialization', 0, () => {
+ interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
+
+ root.render();
+ });
+ Scheduler.unstable_flushAll();
+ jest.runAllTimers();
+
+ expect(ref.current).toBe(null);
+ expect(onInteractionTraced).toHaveBeenCalledTimes(1);
+ expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
+ interaction,
+ );
+ expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
+
+ // Unblock rendering, pretend the content is injected by the server.
+ const startNode = container.childNodes[0];
+ expect(startNode).not.toBe(null);
+ expect(startNode.nodeType).toBe(Node.COMMENT_NODE);
+
+ startNode.textContent = SUSPENSE_START_DATA;
+ startNode._reactRetry();
+
+ Scheduler.unstable_flushAll();
+ jest.runAllTimers();
+
+ expect(ref.current).not.toBe(null);
+ expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
+ expect(
+ onInteractionScheduledWorkCompleted,
+ ).toHaveBeenLastNotifiedOfInteraction(interaction);
+ });
+
// @gate experimental
it('traces interaction across client-rendered hydration', () => {
let suspend = false;