mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Scheduling profiler: Fix tooltip wheel event regression (#22130)
Panning horizontally via mouse wheel used to allow you to scrub over snapshot images. This was accidentally broken by a recent change. The core of the fix for this was to update `useSmartTooltip()` to remove the dependencies array so that a newly rendered tooltip is positioned even if the mouseX/mouseY coordinates don't change (as they don't when panning via wheel). I also cleaned a couple of unrelated things up while doing this: * Consolidated hover reset logic formerly split between `CanvasPage` and `Surface` into the `Surface` `handleInteraction()` function. * Cleaned up redundant ref setting code in EventTooltip.
This commit is contained in:
@@ -419,44 +419,6 @@ function AutoSizedCanvas({
|
||||
return;
|
||||
}
|
||||
|
||||
// Wheel events should always hide the current tooltip.
|
||||
switch (interaction.type) {
|
||||
case 'wheel-control':
|
||||
case 'wheel-meta':
|
||||
case 'wheel-plain':
|
||||
case 'wheel-shift':
|
||||
setHoveredEvent(prevHoverEvent => {
|
||||
if (prevHoverEvent === null) {
|
||||
return prevHoverEvent;
|
||||
} else if (
|
||||
prevHoverEvent.componentMeasure !== null ||
|
||||
prevHoverEvent.flamechartStackFrame !== null ||
|
||||
prevHoverEvent.measure !== null ||
|
||||
prevHoverEvent.nativeEvent !== null ||
|
||||
prevHoverEvent.networkMeasure !== null ||
|
||||
prevHoverEvent.schedulingEvent !== null ||
|
||||
prevHoverEvent.snapshot !== null ||
|
||||
prevHoverEvent.suspenseEvent !== null ||
|
||||
prevHoverEvent.userTimingMark !== null
|
||||
) {
|
||||
return {
|
||||
componentMeasure: null,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
nativeEvent: null,
|
||||
networkMeasure: null,
|
||||
schedulingEvent: null,
|
||||
snapshot: null,
|
||||
suspenseEvent: null,
|
||||
userTimingMark: null,
|
||||
};
|
||||
} else {
|
||||
return prevHoverEvent;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const surface = surfaceRef.current;
|
||||
surface.handleInteraction(interaction);
|
||||
|
||||
|
||||
+96
-160
@@ -16,7 +16,6 @@ import type {
|
||||
ReactHoverContextInfo,
|
||||
ReactMeasure,
|
||||
ReactProfilerData,
|
||||
Return,
|
||||
SchedulingEvent,
|
||||
Snapshot,
|
||||
SuspenseEvent,
|
||||
@@ -24,7 +23,6 @@ import type {
|
||||
} from './types';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useRef} from 'react';
|
||||
import {formatDuration, formatTimestamp, trimString} from './utils/formatting';
|
||||
import {getBatchRange} from './utils/getBatchRange';
|
||||
import useSmartTooltip from './utils/useSmartTooltip';
|
||||
@@ -75,7 +73,7 @@ export default function EventTooltip({
|
||||
hoveredEvent,
|
||||
origin,
|
||||
}: Props) {
|
||||
const tooltipRef = useSmartTooltip({
|
||||
const ref = useSmartTooltip({
|
||||
canvasRef,
|
||||
mouseX: origin.x,
|
||||
mouseY: origin.y,
|
||||
@@ -97,77 +95,53 @@ export default function EventTooltip({
|
||||
userTimingMark,
|
||||
} = hoveredEvent;
|
||||
|
||||
let content = null;
|
||||
if (componentMeasure !== null) {
|
||||
return (
|
||||
<TooltipReactComponentMeasure
|
||||
componentMeasure={componentMeasure}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
content = (
|
||||
<TooltipReactComponentMeasure componentMeasure={componentMeasure} />
|
||||
);
|
||||
} else if (nativeEvent !== null) {
|
||||
return (
|
||||
<TooltipNativeEvent nativeEvent={nativeEvent} tooltipRef={tooltipRef} />
|
||||
);
|
||||
content = <TooltipNativeEvent nativeEvent={nativeEvent} />;
|
||||
} else if (networkMeasure !== null) {
|
||||
return (
|
||||
<TooltipNetworkMeasure
|
||||
networkMeasure={networkMeasure}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
);
|
||||
content = <TooltipNetworkMeasure networkMeasure={networkMeasure} />;
|
||||
} else if (schedulingEvent !== null) {
|
||||
return (
|
||||
<TooltipSchedulingEvent
|
||||
data={data}
|
||||
schedulingEvent={schedulingEvent}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
content = (
|
||||
<TooltipSchedulingEvent data={data} schedulingEvent={schedulingEvent} />
|
||||
);
|
||||
} else if (snapshot !== null) {
|
||||
return <TooltipSnapshot snapshot={snapshot} tooltipRef={tooltipRef} />;
|
||||
content = <TooltipSnapshot snapshot={snapshot} />;
|
||||
} else if (suspenseEvent !== null) {
|
||||
return (
|
||||
<TooltipSuspenseEvent
|
||||
suspenseEvent={suspenseEvent}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
);
|
||||
content = <TooltipSuspenseEvent suspenseEvent={suspenseEvent} />;
|
||||
} else if (measure !== null) {
|
||||
return (
|
||||
<TooltipReactMeasure
|
||||
data={data}
|
||||
measure={measure}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
);
|
||||
content = <TooltipReactMeasure data={data} measure={measure} />;
|
||||
} else if (flamechartStackFrame !== null) {
|
||||
return (
|
||||
<TooltipFlamechartNode
|
||||
stackFrame={flamechartStackFrame}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
);
|
||||
content = <TooltipFlamechartNode stackFrame={flamechartStackFrame} />;
|
||||
} else if (userTimingMark !== null) {
|
||||
return (
|
||||
<TooltipUserTimingMark mark={userTimingMark} tooltipRef={tooltipRef} />
|
||||
);
|
||||
content = <TooltipUserTimingMark mark={userTimingMark} />;
|
||||
}
|
||||
|
||||
if (content !== null) {
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={ref}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const TooltipReactComponentMeasure = ({
|
||||
componentMeasure,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
componentMeasure: ReactComponentMeasure,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const {componentName, duration, timestamp, warning} = componentMeasure;
|
||||
|
||||
const label = `${componentName} rendered`;
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<>
|
||||
<div className={styles.TooltipSection}>
|
||||
{trimString(label, 768)}
|
||||
<div className={styles.Divider} />
|
||||
@@ -183,52 +157,42 @@ const TooltipReactComponentMeasure = ({
|
||||
<div className={styles.WarningText}>{warning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipFlamechartNode = ({
|
||||
stackFrame,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
stackFrame: FlamechartStackFrame,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const {name, timestamp, duration, locationLine, locationColumn} = stackFrame;
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.FlamechartStackFrameName}>{name}</span>
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
{(locationLine !== undefined || locationColumn !== undefined) && (
|
||||
<>
|
||||
<div className={styles.DetailsGridLabel}>Location:</div>
|
||||
<div>
|
||||
line {locationLine}, column {locationColumn}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.FlamechartStackFrameName}>{name}</span>
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
{(locationLine !== undefined || locationColumn !== undefined) && (
|
||||
<>
|
||||
<div className={styles.DetailsGridLabel}>Location:</div>
|
||||
<div>
|
||||
line {locationLine}, column {locationColumn}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipNativeEvent = ({
|
||||
nativeEvent,
|
||||
tooltipRef,
|
||||
}: {
|
||||
nativeEvent: NativeEvent,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
const TooltipNativeEvent = ({nativeEvent}: {|nativeEvent: NativeEvent|}) => {
|
||||
const {duration, timestamp, type, warning} = nativeEvent;
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.NativeEventName}>{trimString(type, 768)}</span>
|
||||
event
|
||||
@@ -245,17 +209,15 @@ const TooltipNativeEvent = ({
|
||||
<div className={styles.WarningText}>{warning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipNetworkMeasure = ({
|
||||
networkMeasure,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
networkMeasure: NetworkMeasure,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const {
|
||||
finishTimestamp,
|
||||
lastReceivedDataTimestamp,
|
||||
@@ -278,11 +240,9 @@ const TooltipNetworkMeasure = ({
|
||||
: '(incomplete)';
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<div className={styles.SingleLineTextSection}>
|
||||
{duration} <span className={styles.DimText}>{priority}</span>{' '}
|
||||
{urlToDisplay}
|
||||
</div>
|
||||
<div className={styles.SingleLineTextSection}>
|
||||
{duration} <span className={styles.DimText}>{priority}</span>{' '}
|
||||
{urlToDisplay}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -290,12 +250,10 @@ const TooltipNetworkMeasure = ({
|
||||
const TooltipSchedulingEvent = ({
|
||||
data,
|
||||
schedulingEvent,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
data: ReactProfilerData,
|
||||
schedulingEvent: SchedulingEvent,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const label = getSchedulingEventLabel(schedulingEvent);
|
||||
if (!label) {
|
||||
if (__DEV__) {
|
||||
@@ -323,7 +281,7 @@ const TooltipSchedulingEvent = ({
|
||||
const {componentName, timestamp, warning} = schedulingEvent;
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<>
|
||||
<div className={styles.TooltipSection}>
|
||||
{componentName && (
|
||||
<span className={styles.ComponentName}>
|
||||
@@ -350,35 +308,25 @@ const TooltipSchedulingEvent = ({
|
||||
<div className={styles.WarningText}>{warning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipSnapshot = ({
|
||||
snapshot,
|
||||
tooltipRef,
|
||||
}: {
|
||||
snapshot: Snapshot,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
const TooltipSnapshot = ({snapshot}: {|snapshot: Snapshot|}) => {
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<img
|
||||
className={styles.Image}
|
||||
src={snapshot.imageSource}
|
||||
style={{width: snapshot.width / 2, height: snapshot.height / 2}}
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
className={styles.Image}
|
||||
src={snapshot.imageSource}
|
||||
style={{width: snapshot.width / 2, height: snapshot.height / 2}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipSuspenseEvent = ({
|
||||
suspenseEvent,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
suspenseEvent: SuspenseEvent,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const {
|
||||
componentName,
|
||||
duration,
|
||||
@@ -394,7 +342,7 @@ const TooltipSuspenseEvent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<>
|
||||
<div className={styles.TooltipSection}>
|
||||
{componentName && (
|
||||
<span className={styles.ComponentName}>
|
||||
@@ -421,19 +369,17 @@ const TooltipSuspenseEvent = ({
|
||||
<div className={styles.WarningText}>{warning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipReactMeasure = ({
|
||||
data,
|
||||
measure,
|
||||
tooltipRef,
|
||||
}: {
|
||||
}: {|
|
||||
data: ReactProfilerData,
|
||||
measure: ReactMeasure,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
|}) => {
|
||||
const label = getReactMeasureLabel(measure.type);
|
||||
if (!label) {
|
||||
if (__DEV__) {
|
||||
@@ -450,52 +396,42 @@ const TooltipReactMeasure = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.ReactMeasureLabel}>{label}</span>
|
||||
<div className={styles.Divider} />
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
{measure.type !== 'render-idle' && (
|
||||
<>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
</>
|
||||
)}
|
||||
<div className={styles.DetailsGridLabel}>Batch duration:</div>
|
||||
<div>{formatDuration(stopTime - startTime)}</div>
|
||||
<div className={styles.DetailsGridLabel}>
|
||||
Lane{lanes.length === 1 ? '' : 's'}:
|
||||
</div>
|
||||
<div>
|
||||
{laneLabels.length > 0
|
||||
? `${laneLabels.join(', ')} (${lanes.join(', ')})`
|
||||
: lanes.join(', ')}
|
||||
</div>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.ReactMeasureLabel}>{label}</span>
|
||||
<div className={styles.Divider} />
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
{measure.type !== 'render-idle' && (
|
||||
<>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
</>
|
||||
)}
|
||||
<div className={styles.DetailsGridLabel}>Batch duration:</div>
|
||||
<div>{formatDuration(stopTime - startTime)}</div>
|
||||
<div className={styles.DetailsGridLabel}>
|
||||
Lane{lanes.length === 1 ? '' : 's'}:
|
||||
</div>
|
||||
<div>
|
||||
{laneLabels.length > 0
|
||||
? `${laneLabels.join(', ')} (${lanes.join(', ')})`
|
||||
: lanes.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipUserTimingMark = ({
|
||||
mark,
|
||||
tooltipRef,
|
||||
}: {
|
||||
mark: UserTimingMark,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
const TooltipUserTimingMark = ({mark}: {|mark: UserTimingMark|}) => {
|
||||
const {name, timestamp} = mark;
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.UserTimingLabel}>{name}</span>
|
||||
<div className={styles.Divider} />
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
</div>
|
||||
<div className={styles.TooltipSection}>
|
||||
<span className={styles.UserTimingLabel}>{name}</span>
|
||||
<div className={styles.Divider} />
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function useSmartTooltip({
|
||||
element.style.left = `${mouseX + TOOLTIP_OFFSET_BOTTOM}px`;
|
||||
}
|
||||
}
|
||||
}, [mouseX, mouseY, ref]);
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactHoverContextInfo} from '../types';
|
||||
import type {Interaction} from './useCanvasInteraction';
|
||||
import type {Size} from './geometry';
|
||||
|
||||
@@ -48,9 +47,7 @@ const getCanvasContext = memoize(
|
||||
},
|
||||
);
|
||||
|
||||
type ResetHoveredEventFn = (
|
||||
partialState: $Shape<ReactHoverContextInfo>,
|
||||
) => void;
|
||||
type ResetHoveredEventFn = () => void;
|
||||
|
||||
/**
|
||||
* Represents the canvas surface and a view heirarchy. A surface is also the
|
||||
@@ -123,7 +120,11 @@ export class Surface {
|
||||
const viewRefs = this._viewRefs;
|
||||
switch (interaction.type) {
|
||||
case 'mousemove':
|
||||
// Clean out the hovered view before processing a new mouse move interaction.
|
||||
case 'wheel-control':
|
||||
case 'wheel-meta':
|
||||
case 'wheel-plain':
|
||||
case 'wheel-shift':
|
||||
// Clean out the hovered view before processing this type of interaction.
|
||||
const hoveredView = viewRefs.hoveredView;
|
||||
viewRefs.hoveredView = null;
|
||||
|
||||
@@ -134,7 +135,7 @@ export class Surface {
|
||||
|
||||
// If a previously hovered view is no longer hovered, update the outer state.
|
||||
if (hoveredView !== null && viewRefs.hoveredView === null) {
|
||||
this._resetHoveredEvent({});
|
||||
this._resetHoveredEvent();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user