[DevTools] Explicitly say which id to scroll to and only once (#34823)

This ensures that we don't scroll on changes to the timeline such as
when loading a new page or while the timeline is still loading.

We only auto scroll to a boundary when we perform an explicit operation
from the user.
This commit is contained in:
Sebastian Markbåge 2025-10-13 12:09:45 -04:00 committed by GitHub
parent 93d4458fdc
commit b467c6e949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 19 additions and 15 deletions

View File

@ -8,7 +8,7 @@
*/
import * as React from 'react';
import {useContext, useEffect, useRef} from 'react';
import {useContext, useEffect} from 'react';
import {BridgeContext} from '../context';
import {TreeDispatcherContext} from '../Components/TreeContext';
import {useHighlightHostInstance, useScrollToHostInstance} from '../hooks';
@ -29,9 +29,8 @@ function SuspenseTimelineInput() {
useHighlightHostInstance();
const scrollToHostInstance = useScrollToHostInstance();
const {timeline, timelineIndex, hoveredTimelineIndex, playing} = useContext(
SuspenseTreeStateContext,
);
const {timeline, timelineIndex, hoveredTimelineIndex, playing, autoScroll} =
useContext(SuspenseTreeStateContext);
const min = 0;
const max = timeline.length > 0 ? timeline.length - 1 : 0;
@ -102,7 +101,6 @@ function SuspenseTimelineInput() {
});
}
const isInitialMount = useRef(true);
// TODO: useEffectEvent here once it's supported in all versions DevTools supports.
// For now we just exclude it from deps since we don't lint those anyway.
function changeTimelineIndex(newIndex: number) {
@ -115,22 +113,21 @@ function SuspenseTimelineInput() {
bridge.send('overrideSuspenseMilestone', {
suspendedSet,
});
if (isInitialMount.current) {
// Skip scrolling on initial mount. Only when we're changing the timeline.
isInitialMount.current = false;
} else {
// When we're scrubbing through the timeline, scroll the current boundary
// into view as it was just revealed. This is after we override the milestone
// to reveal it.
const selectedSuspenseID = timeline[timelineIndex];
scrollToHostInstance(selectedSuspenseID);
}
}
useEffect(() => {
changeTimelineIndex(timelineIndex);
}, [timelineIndex]);
useEffect(() => {
if (autoScroll.id > 0) {
const scrollToId = autoScroll.id;
// Consume the scroll ref so that we only trigger this scroll once.
autoScroll.id = 0;
scrollToHostInstance(scrollToId);
}
}, [autoScroll]);
useEffect(() => {
if (!playing) {
return undefined;

View File

@ -31,6 +31,7 @@ export type SuspenseTreeState = {
uniqueSuspendersOnly: boolean,
playing: boolean,
autoSelect: boolean,
autoScroll: {id: number}, // Ref that's set to 0 after scrolling once.
};
type ACTION_SUSPENSE_TREE_MUTATION = {
@ -125,6 +126,7 @@ function getInitialState(store: Store): SuspenseTreeState {
uniqueSuspendersOnly,
playing: false,
autoSelect: true,
autoScroll: {id: 0}, // Don't auto-scroll initially
};
return initialState;
@ -218,6 +220,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
selectedSuspenseID,
playing: false, // pause
autoSelect: false,
autoScroll: {id: selectedSuspenseID}, // scroll
};
}
case 'SET_SUSPENSE_LINEAGE': {
@ -285,6 +288,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
timelineIndex: nextTimelineIndex,
playing: false, // pause
autoSelect: false,
autoScroll: {id: nextSelectedSuspenseID}, // scroll
};
}
case 'SUSPENSE_SKIP_TIMELINE_INDEX': {
@ -308,6 +312,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
timelineIndex: nextTimelineIndex,
playing: false, // pause
autoSelect: false,
autoScroll: {id: nextSelectedSuspenseID}, // scroll
};
}
case 'SUSPENSE_PLAY_PAUSE': {
@ -359,6 +364,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
selectedSuspenseID: nextSelectedSuspenseID,
timelineIndex: nextTimelineIndex,
playing: nextPlaying,
autoScroll: {id: nextSelectedSuspenseID}, // scroll
};
}
case 'TOGGLE_TIMELINE_FOR_ID': {
@ -392,6 +398,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
timelineIndex: nextTimelineIndex,
playing: false, // pause
autoSelect: false,
autoScroll: {id: nextSelectedSuspenseID},
};
}
case 'HOVER_TIMELINE_FOR_ID': {