mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Display warnings in tooltips for native events that render sync updates (#21975)
This commit is contained in:
@@ -509,6 +509,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
||||
</ContextMenu>
|
||||
{!isContextMenuShown && !surfaceRef.current.hasActiveView() && (
|
||||
<EventTooltip
|
||||
canvasRef={canvasRef}
|
||||
data={data}
|
||||
hoveredEvent={hoveredEvent}
|
||||
origin={mouseLocation}
|
||||
|
||||
@@ -28,6 +28,7 @@ import useSmartTooltip from './utils/useSmartTooltip';
|
||||
import styles from './EventTooltip.css';
|
||||
|
||||
type Props = {|
|
||||
canvasRef: {|current: HTMLCanvasElement | null|},
|
||||
data: ReactProfilerData,
|
||||
hoveredEvent: ReactHoverContextInfo | null,
|
||||
origin: Point,
|
||||
@@ -102,8 +103,14 @@ function getReactMeasureLabel(type): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export default function EventTooltip({data, hoveredEvent, origin}: Props) {
|
||||
export default function EventTooltip({
|
||||
canvasRef,
|
||||
data,
|
||||
hoveredEvent,
|
||||
origin,
|
||||
}: Props) {
|
||||
const tooltipRef = useSmartTooltip({
|
||||
canvasRef,
|
||||
mouseX: origin.x,
|
||||
mouseY: origin.y,
|
||||
});
|
||||
@@ -209,7 +216,19 @@ const TooltipNativeEvent = ({
|
||||
nativeEvent: NativeEvent,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
const {duration, timestamp, type} = nativeEvent;
|
||||
const {duration, timestamp, type, warnings} = nativeEvent;
|
||||
|
||||
const warningElements = [];
|
||||
if (warnings !== null) {
|
||||
warnings.forEach((warning, index) => {
|
||||
warningElements.push(
|
||||
<Fragment key={index}>
|
||||
<div className={styles.DetailsGridLabel}>Warning:</div>
|
||||
<div>{warning}</div>
|
||||
</Fragment>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
@@ -221,6 +240,7 @@ const TooltipNativeEvent = ({
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
{warningElements}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+6
-5
@@ -128,7 +128,7 @@ export class NativeEventsView extends View {
|
||||
showHoverHighlight: boolean,
|
||||
) {
|
||||
const {frame} = this;
|
||||
const {depth, duration, highlight, timestamp, type} = event;
|
||||
const {depth, duration, timestamp, type, warnings} = event;
|
||||
|
||||
baseY += depth * ROW_WITH_BORDER_HEIGHT;
|
||||
|
||||
@@ -152,7 +152,7 @@ export class NativeEventsView extends View {
|
||||
|
||||
const drawableRect = intersectionOfRects(eventRect, rect);
|
||||
context.beginPath();
|
||||
if (highlight) {
|
||||
if (warnings !== null) {
|
||||
context.fillStyle = showHoverHighlight
|
||||
? COLORS.NATIVE_EVENT_WARNING_HOVER
|
||||
: COLORS.NATIVE_EVENT_WARNING;
|
||||
@@ -182,9 +182,10 @@ export class NativeEventsView extends View {
|
||||
);
|
||||
|
||||
if (trimmedName !== null) {
|
||||
context.fillStyle = highlight
|
||||
? COLORS.NATIVE_EVENT_WARNING_TEXT
|
||||
: COLORS.TEXT_COLOR;
|
||||
context.fillStyle =
|
||||
warnings !== null
|
||||
? COLORS.NATIVE_EVENT_WARNING_TEXT
|
||||
: COLORS.TEXT_COLOR;
|
||||
|
||||
context.fillText(
|
||||
trimmedName,
|
||||
|
||||
+8
-3
@@ -202,9 +202,9 @@ function processTimelineEvent(
|
||||
const nativeEvent = {
|
||||
depth,
|
||||
duration,
|
||||
highlight: false,
|
||||
timestamp,
|
||||
type,
|
||||
warnings: null,
|
||||
};
|
||||
|
||||
currentProfilerData.nativeEvents.push(nativeEvent);
|
||||
@@ -339,8 +339,13 @@ function processTimelineEvent(
|
||||
const nativeEvent = nativeEventStack[i];
|
||||
const stopTime = nativeEvent.timestamp + nativeEvent.duration;
|
||||
if (stopTime > startTime) {
|
||||
// Warn about sync updates that happen an event handler.
|
||||
nativeEvent.highlight = true;
|
||||
const warning =
|
||||
'An event handler scheduled a synchronous update with React.';
|
||||
if (nativeEvent.warnings === null) {
|
||||
nativeEvent.warnings = new Set([warning]);
|
||||
} else {
|
||||
nativeEvent.warnings.add(warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
|
||||
@@ -24,9 +24,9 @@ export type ReactLane = number;
|
||||
export type NativeEvent = {|
|
||||
+depth: number,
|
||||
+duration: Milliseconds,
|
||||
highlight: boolean,
|
||||
+timestamp: Milliseconds,
|
||||
+type: string,
|
||||
warnings: Set<string> | null,
|
||||
|};
|
||||
|
||||
type BaseReactEvent = {|
|
||||
|
||||
@@ -12,22 +12,32 @@ import {useLayoutEffect, useRef} from 'react';
|
||||
const TOOLTIP_OFFSET = 4;
|
||||
|
||||
export default function useSmartTooltip({
|
||||
canvasRef,
|
||||
mouseX,
|
||||
mouseY,
|
||||
}: {
|
||||
canvasRef: {|current: HTMLCanvasElement | null|},
|
||||
mouseX: number,
|
||||
mouseY: number,
|
||||
}) {
|
||||
const ref = useRef<HTMLElement | null>(null);
|
||||
|
||||
// HACK: Browser extension reports window.innerHeight of 0,
|
||||
// so we fallback to using the tooltip target element.
|
||||
let height = window.innerHeight;
|
||||
let width = window.innerWidth;
|
||||
const target = canvasRef.current;
|
||||
if (target !== null) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
height = rect.top + rect.height;
|
||||
width = rect.left + rect.width;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = ref.current;
|
||||
if (element !== null) {
|
||||
// Let's check the vertical position.
|
||||
if (
|
||||
mouseY + TOOLTIP_OFFSET + element.offsetHeight >=
|
||||
window.innerHeight
|
||||
) {
|
||||
if (mouseY + TOOLTIP_OFFSET + element.offsetHeight >= height) {
|
||||
// The tooltip doesn't fit below the mouse cursor (which is our
|
||||
// default strategy). Therefore we try to position it either above the
|
||||
// mouse cursor or finally aligned with the window's top edge.
|
||||
@@ -45,7 +55,7 @@ export default function useSmartTooltip({
|
||||
}
|
||||
|
||||
// Now let's check the horizontal position.
|
||||
if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= window.innerWidth) {
|
||||
if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= width) {
|
||||
// The tooltip doesn't fit at the right of the mouse cursor (which is
|
||||
// our default strategy). Therefore we try to position it either at the
|
||||
// left of the mouse cursor or finally aligned with the window's left
|
||||
|
||||
Reference in New Issue
Block a user