Skip to content

Commit cdccdbe

Browse files
author
Brian Vaughn
authored
Display warnings in tooltips for native events that render sync updates (#21975)
1 parent d954340 commit cdccdbe

File tree

6 files changed

+53
-16
lines changed

6 files changed

+53
-16
lines changed

packages/react-devtools-scheduling-profiler/src/CanvasPage.js

+1
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
509509
</ContextMenu>
510510
{!isContextMenuShown && !surfaceRef.current.hasActiveView() && (
511511
<EventTooltip
512+
canvasRef={canvasRef}
512513
data={data}
513514
hoveredEvent={hoveredEvent}
514515
origin={mouseLocation}

packages/react-devtools-scheduling-profiler/src/EventTooltip.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import useSmartTooltip from './utils/useSmartTooltip';
2828
import styles from './EventTooltip.css';
2929

3030
type Props = {|
31+
canvasRef: {|current: HTMLCanvasElement | null|},
3132
data: ReactProfilerData,
3233
hoveredEvent: ReactHoverContextInfo | null,
3334
origin: Point,
@@ -102,8 +103,14 @@ function getReactMeasureLabel(type): string | null {
102103
}
103104
}
104105

105-
export default function EventTooltip({data, hoveredEvent, origin}: Props) {
106+
export default function EventTooltip({
107+
canvasRef,
108+
data,
109+
hoveredEvent,
110+
origin,
111+
}: Props) {
106112
const tooltipRef = useSmartTooltip({
113+
canvasRef,
107114
mouseX: origin.x,
108115
mouseY: origin.y,
109116
});
@@ -209,7 +216,19 @@ const TooltipNativeEvent = ({
209216
nativeEvent: NativeEvent,
210217
tooltipRef: Return<typeof useRef>,
211218
}) => {
212-
const {duration, timestamp, type} = nativeEvent;
219+
const {duration, timestamp, type, warnings} = nativeEvent;
220+
221+
const warningElements = [];
222+
if (warnings !== null) {
223+
warnings.forEach((warning, index) => {
224+
warningElements.push(
225+
<Fragment key={index}>
226+
<div className={styles.DetailsGridLabel}>Warning:</div>
227+
<div>{warning}</div>
228+
</Fragment>,
229+
);
230+
});
231+
}
213232

214233
return (
215234
<div className={styles.Tooltip} ref={tooltipRef}>
@@ -221,6 +240,7 @@ const TooltipNativeEvent = ({
221240
<div>{formatTimestamp(timestamp)}</div>
222241
<div className={styles.DetailsGridLabel}>Duration:</div>
223242
<div>{formatDuration(duration)}</div>
243+
{warningElements}
224244
</div>
225245
</div>
226246
);

packages/react-devtools-scheduling-profiler/src/content-views/NativeEventsView.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class NativeEventsView extends View {
128128
showHoverHighlight: boolean,
129129
) {
130130
const {frame} = this;
131-
const {depth, duration, highlight, timestamp, type} = event;
131+
const {depth, duration, timestamp, type, warnings} = event;
132132

133133
baseY += depth * ROW_WITH_BORDER_HEIGHT;
134134

@@ -152,7 +152,7 @@ export class NativeEventsView extends View {
152152

153153
const drawableRect = intersectionOfRects(eventRect, rect);
154154
context.beginPath();
155-
if (highlight) {
155+
if (warnings !== null) {
156156
context.fillStyle = showHoverHighlight
157157
? COLORS.NATIVE_EVENT_WARNING_HOVER
158158
: COLORS.NATIVE_EVENT_WARNING;
@@ -182,9 +182,10 @@ export class NativeEventsView extends View {
182182
);
183183

184184
if (trimmedName !== null) {
185-
context.fillStyle = highlight
186-
? COLORS.NATIVE_EVENT_WARNING_TEXT
187-
: COLORS.TEXT_COLOR;
185+
context.fillStyle =
186+
warnings !== null
187+
? COLORS.NATIVE_EVENT_WARNING_TEXT
188+
: COLORS.TEXT_COLOR;
188189

189190
context.fillText(
190191
trimmedName,

packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,9 @@ function processTimelineEvent(
202202
const nativeEvent = {
203203
depth,
204204
duration,
205-
highlight: false,
206205
timestamp,
207206
type,
207+
warnings: null,
208208
};
209209

210210
currentProfilerData.nativeEvents.push(nativeEvent);
@@ -339,8 +339,13 @@ function processTimelineEvent(
339339
const nativeEvent = nativeEventStack[i];
340340
const stopTime = nativeEvent.timestamp + nativeEvent.duration;
341341
if (stopTime > startTime) {
342-
// Warn about sync updates that happen an event handler.
343-
nativeEvent.highlight = true;
342+
const warning =
343+
'An event handler scheduled a synchronous update with React.';
344+
if (nativeEvent.warnings === null) {
345+
nativeEvent.warnings = new Set([warning]);
346+
} else {
347+
nativeEvent.warnings.add(warning);
348+
}
344349
}
345350
}
346351
} else if (

packages/react-devtools-scheduling-profiler/src/types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ export type ReactLane = number;
2424
export type NativeEvent = {|
2525
+depth: number,
2626
+duration: Milliseconds,
27-
highlight: boolean,
2827
+timestamp: Milliseconds,
2928
+type: string,
29+
warnings: Set<string> | null,
3030
|};
3131

3232
type BaseReactEvent = {|

packages/react-devtools-scheduling-profiler/src/utils/useSmartTooltip.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,32 @@ import {useLayoutEffect, useRef} from 'react';
1212
const TOOLTIP_OFFSET = 4;
1313

1414
export default function useSmartTooltip({
15+
canvasRef,
1516
mouseX,
1617
mouseY,
1718
}: {
19+
canvasRef: {|current: HTMLCanvasElement | null|},
1820
mouseX: number,
1921
mouseY: number,
2022
}) {
2123
const ref = useRef<HTMLElement | null>(null);
2224

25+
// HACK: Browser extension reports window.innerHeight of 0,
26+
// so we fallback to using the tooltip target element.
27+
let height = window.innerHeight;
28+
let width = window.innerWidth;
29+
const target = canvasRef.current;
30+
if (target !== null) {
31+
const rect = target.getBoundingClientRect();
32+
height = rect.top + rect.height;
33+
width = rect.left + rect.width;
34+
}
35+
2336
useLayoutEffect(() => {
2437
const element = ref.current;
2538
if (element !== null) {
2639
// Let's check the vertical position.
27-
if (
28-
mouseY + TOOLTIP_OFFSET + element.offsetHeight >=
29-
window.innerHeight
30-
) {
40+
if (mouseY + TOOLTIP_OFFSET + element.offsetHeight >= height) {
3141
// The tooltip doesn't fit below the mouse cursor (which is our
3242
// default strategy). Therefore we try to position it either above the
3343
// mouse cursor or finally aligned with the window's top edge.
@@ -45,7 +55,7 @@ export default function useSmartTooltip({
4555
}
4656

4757
// Now let's check the horizontal position.
48-
if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= window.innerWidth) {
58+
if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= width) {
4959
// The tooltip doesn't fit at the right of the mouse cursor (which is
5060
// our default strategy). Therefore we try to position it either at the
5161
// left of the mouse cursor or finally aligned with the window's left

0 commit comments

Comments
 (0)