mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
6b30832666
The old version of prettier we were using didn't support the Flow syntax to access properties in a type using `SomeType['prop']`. This updates `prettier` and `rollup-plugin-prettier` to the latest versions. I added the prettier config `arrowParens: "avoid"` to reduce the diff size as the default has changed in Prettier 2.0. The largest amount of changes comes from function expressions now having a space. This doesn't have an option to preserve the old behavior, so we have to update this.
302 lines
8.3 KiB
JavaScript
302 lines
8.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactComponentMeasure, TimelineData, ViewState} from '../types';
|
|
import type {
|
|
Interaction,
|
|
IntrinsicSize,
|
|
MouseMoveInteraction,
|
|
Rect,
|
|
ViewRefs,
|
|
} from '../view-base';
|
|
|
|
import {
|
|
durationToWidth,
|
|
positioningScaleFactor,
|
|
positionToTimestamp,
|
|
timestampToPosition,
|
|
} from './utils/positioning';
|
|
import {drawText} from './utils/text';
|
|
import {formatDuration} from '../utils/formatting';
|
|
import {
|
|
View,
|
|
Surface,
|
|
rectContainsPoint,
|
|
rectIntersectsRect,
|
|
intersectionOfRects,
|
|
} from '../view-base';
|
|
import {BORDER_SIZE, COLORS, NATIVE_EVENT_HEIGHT} from './constants';
|
|
|
|
const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
|
|
|
|
export class ComponentMeasuresView extends View {
|
|
_cachedSearchMatches: Map<string, boolean>;
|
|
_cachedSearchRegExp: RegExp | null = null;
|
|
_hoveredComponentMeasure: ReactComponentMeasure | null = null;
|
|
_intrinsicSize: IntrinsicSize;
|
|
_profilerData: TimelineData;
|
|
_viewState: ViewState;
|
|
|
|
onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
|
|
|
|
constructor(
|
|
surface: Surface,
|
|
frame: Rect,
|
|
profilerData: TimelineData,
|
|
viewState: ViewState,
|
|
) {
|
|
super(surface, frame);
|
|
|
|
this._profilerData = profilerData;
|
|
this._viewState = viewState;
|
|
|
|
this._cachedSearchMatches = new Map();
|
|
this._cachedSearchRegExp = null;
|
|
|
|
viewState.onSearchRegExpStateChange(() => {
|
|
this.setNeedsDisplay();
|
|
});
|
|
|
|
this._intrinsicSize = {
|
|
width: profilerData.duration,
|
|
height: ROW_WITH_BORDER_HEIGHT,
|
|
};
|
|
}
|
|
|
|
desiredSize(): IntrinsicSize {
|
|
return this._intrinsicSize;
|
|
}
|
|
|
|
setHoveredEvent(hoveredEvent: ReactComponentMeasure | null) {
|
|
if (this._hoveredComponentMeasure === hoveredEvent) {
|
|
return;
|
|
}
|
|
this._hoveredComponentMeasure = hoveredEvent;
|
|
this.setNeedsDisplay();
|
|
}
|
|
|
|
/**
|
|
* Draw a single `ReactComponentMeasure` as a box/span with text inside of it.
|
|
*/
|
|
_drawSingleReactComponentMeasure(
|
|
context: CanvasRenderingContext2D,
|
|
rect: Rect,
|
|
componentMeasure: ReactComponentMeasure,
|
|
scaleFactor: number,
|
|
showHoverHighlight: boolean,
|
|
): boolean {
|
|
const {frame} = this;
|
|
const {componentName, duration, timestamp, type, warning} =
|
|
componentMeasure;
|
|
|
|
const xStart = timestampToPosition(timestamp, scaleFactor, frame);
|
|
const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
|
|
const componentMeasureRect: Rect = {
|
|
origin: {
|
|
x: xStart,
|
|
y: frame.origin.y,
|
|
},
|
|
size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
|
|
};
|
|
if (!rectIntersectsRect(componentMeasureRect, rect)) {
|
|
return false; // Not in view
|
|
}
|
|
|
|
const width = durationToWidth(duration, scaleFactor);
|
|
if (width < 1) {
|
|
return false; // Too small to render at this zoom level
|
|
}
|
|
|
|
let textFillStyle = ((null: any): string);
|
|
let typeLabel = ((null: any): string);
|
|
|
|
const drawableRect = intersectionOfRects(componentMeasureRect, rect);
|
|
context.beginPath();
|
|
if (warning !== null) {
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.WARNING_BACKGROUND_HOVER
|
|
: COLORS.WARNING_BACKGROUND;
|
|
} else {
|
|
switch (type) {
|
|
case 'render':
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.REACT_RENDER_HOVER
|
|
: COLORS.REACT_RENDER;
|
|
textFillStyle = COLORS.REACT_RENDER_TEXT;
|
|
typeLabel = 'rendered';
|
|
break;
|
|
case 'layout-effect-mount':
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.REACT_LAYOUT_EFFECTS_HOVER
|
|
: COLORS.REACT_LAYOUT_EFFECTS;
|
|
textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
|
|
typeLabel = 'mounted layout effect';
|
|
break;
|
|
case 'layout-effect-unmount':
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.REACT_LAYOUT_EFFECTS_HOVER
|
|
: COLORS.REACT_LAYOUT_EFFECTS;
|
|
textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
|
|
typeLabel = 'unmounted layout effect';
|
|
break;
|
|
case 'passive-effect-mount':
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.REACT_PASSIVE_EFFECTS_HOVER
|
|
: COLORS.REACT_PASSIVE_EFFECTS;
|
|
textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
|
|
typeLabel = 'mounted passive effect';
|
|
break;
|
|
case 'passive-effect-unmount':
|
|
context.fillStyle = showHoverHighlight
|
|
? COLORS.REACT_PASSIVE_EFFECTS_HOVER
|
|
: COLORS.REACT_PASSIVE_EFFECTS;
|
|
textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
|
|
typeLabel = 'unmounted passive effect';
|
|
break;
|
|
}
|
|
}
|
|
|
|
let isMatch = false;
|
|
const cachedSearchRegExp = this._cachedSearchRegExp;
|
|
if (cachedSearchRegExp !== null) {
|
|
const cachedSearchMatches = this._cachedSearchMatches;
|
|
const cachedValue = cachedSearchMatches.get(componentName);
|
|
if (cachedValue != null) {
|
|
isMatch = cachedValue;
|
|
} else {
|
|
isMatch = componentName.match(cachedSearchRegExp) !== null;
|
|
cachedSearchMatches.set(componentName, isMatch);
|
|
}
|
|
}
|
|
|
|
if (isMatch) {
|
|
context.fillStyle = COLORS.SEARCH_RESULT_FILL;
|
|
}
|
|
|
|
context.fillRect(
|
|
drawableRect.origin.x,
|
|
drawableRect.origin.y,
|
|
drawableRect.size.width,
|
|
drawableRect.size.height,
|
|
);
|
|
|
|
const label = `${componentName} ${typeLabel} - ${formatDuration(duration)}`;
|
|
|
|
drawText(label, context, componentMeasureRect, drawableRect, {
|
|
fillStyle: textFillStyle,
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
draw(context: CanvasRenderingContext2D) {
|
|
const {
|
|
frame,
|
|
_profilerData: {componentMeasures},
|
|
_hoveredComponentMeasure,
|
|
visibleArea,
|
|
} = this;
|
|
|
|
const searchRegExp = this._viewState.searchRegExp;
|
|
if (this._cachedSearchRegExp !== searchRegExp) {
|
|
this._cachedSearchMatches = new Map();
|
|
this._cachedSearchRegExp = searchRegExp;
|
|
}
|
|
|
|
context.fillStyle = COLORS.BACKGROUND;
|
|
context.fillRect(
|
|
visibleArea.origin.x,
|
|
visibleArea.origin.y,
|
|
visibleArea.size.width,
|
|
visibleArea.size.height,
|
|
);
|
|
|
|
// Draw events
|
|
const scaleFactor = positioningScaleFactor(
|
|
this._intrinsicSize.width,
|
|
frame,
|
|
);
|
|
|
|
let didDrawMeasure = false;
|
|
componentMeasures.forEach(componentMeasure => {
|
|
didDrawMeasure =
|
|
this._drawSingleReactComponentMeasure(
|
|
context,
|
|
visibleArea,
|
|
componentMeasure,
|
|
scaleFactor,
|
|
componentMeasure === _hoveredComponentMeasure,
|
|
) || didDrawMeasure;
|
|
});
|
|
|
|
if (!didDrawMeasure) {
|
|
drawText(
|
|
'(zoom or pan to see React components)',
|
|
context,
|
|
visibleArea,
|
|
visibleArea,
|
|
{fillStyle: COLORS.TEXT_DIM_COLOR, textAlign: 'center'},
|
|
);
|
|
}
|
|
|
|
context.fillStyle = COLORS.PRIORITY_BORDER;
|
|
context.fillRect(
|
|
visibleArea.origin.x,
|
|
visibleArea.origin.y + ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
|
|
visibleArea.size.width,
|
|
BORDER_SIZE,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
|
|
const {frame, _intrinsicSize, onHover, visibleArea} = this;
|
|
if (!onHover) {
|
|
return;
|
|
}
|
|
|
|
const {location} = interaction.payload;
|
|
if (!rectContainsPoint(location, visibleArea)) {
|
|
onHover(null);
|
|
return;
|
|
}
|
|
|
|
const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
|
|
const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
|
|
|
|
const componentMeasures = this._profilerData.componentMeasures;
|
|
for (let index = componentMeasures.length - 1; index >= 0; index--) {
|
|
const componentMeasure = componentMeasures[index];
|
|
const {duration, timestamp} = componentMeasure;
|
|
|
|
if (
|
|
hoverTimestamp >= timestamp &&
|
|
hoverTimestamp <= timestamp + duration
|
|
) {
|
|
this.currentCursor = 'context-menu';
|
|
viewRefs.hoveredView = this;
|
|
onHover(componentMeasure);
|
|
return;
|
|
}
|
|
}
|
|
|
|
onHover(null);
|
|
}
|
|
|
|
handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
|
|
switch (interaction.type) {
|
|
case 'mousemove':
|
|
this._handleMouseMove(interaction, viewRefs);
|
|
break;
|
|
}
|
|
}
|
|
}
|