@samitouri / QOS-React / commits / c21e8fccad

Scheduling profiler: Improve native events UI (#21966)

Also highlight events that have synchronous updates inside of them. (We may want to relax this highlighting later to not warn about event handlers that are still fast enough.)

Brian Vaughn committed Jul 26, 2021 at 19:30 UTC c21e8fccadc0e0351bc45c2df729c92807a647ef
11 files changed +265 -123
packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
+7 -7
@@ -32,9 +32,9 @@ import {
32 } from './utils/positioning';
33 import {
34 COLORS,
35 - FLAMECHART_FONT_SIZE,
35 + FONT_SIZE,
36 FLAMECHART_FRAME_HEIGHT,
37 - FLAMECHART_TEXT_PADDING,
37 + TEXT_PADDING,
38 COLOR_HOVER_DIM_DELTA,
39 BORDER_SIZE,
40 } from './constants';
@@ -157,7 +157,7 @@ class FlamechartStackLayerView extends View {
157
158 context.textAlign = 'left';
159 context.textBaseline = 'middle';
160 - context.font = `${FLAMECHART_FONT_SIZE}px sans-serif`;
160 + context.font = `${FONT_SIZE}px sans-serif`;
161
162 const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
163
@@ -195,15 +195,15 @@ class FlamechartStackLayerView extends View {
195 drawableRect.size.height,
196 );
197
198 - if (width > FLAMECHART_TEXT_PADDING * 2) {
198 + if (width > TEXT_PADDING * 2) {
199 const trimmedName = trimFlamechartText(
200 context,
201 name,
202 - width - FLAMECHART_TEXT_PADDING * 2 + (x < 0 ? x : 0),
202 + width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
203 );
204
205 if (trimmedName !== null) {
206 - context.fillStyle = COLORS.FLAME_GRAPH_LABEL;
206 + context.fillStyle = COLORS.TEXT_COLOR;
207
208 // Prevent text from being drawn outside `viewableArea`
209 const textOverflowsViewableArea = !rectEqualToRect(
@@ -225,7 +225,7 @@ class FlamechartStackLayerView extends View {
225
226 context.fillText(
227 trimmedName,
228 - nodeRect.origin.x + FLAMECHART_TEXT_PADDING - (x < 0 ? x : 0),
228 + nodeRect.origin.x + TEXT_PADDING - (x < 0 ? x : 0),
229 nodeRect.origin.y + FLAMECHART_FRAME_HEIGHT / 2,
230 );
231
packages/react-devtools-scheduling-profiler/src/content-views/NativeEventsView.js
+149 -75
@@ -11,9 +11,10 @@ import type {NativeEvent, ReactProfilerData} from '../types';
11 import type {Interaction, MouseMoveInteraction, Rect, Size} from '../view-base';
12
13 import {
14 + durationToWidth,
15 positioningScaleFactor,
15 - timestampToPosition,
16 positionToTimestamp,
17 + timestampToPosition,
18 } from './utils/positioning';
19 import {
20 View,
@@ -24,28 +25,77 @@ import {
25 } from '../view-base';
26 import {
27 COLORS,
27 - EVENT_ROW_PADDING,
28 - EVENT_DIAMETER,
28 + TEXT_PADDING,
29 + NATIVE_EVENT_HEIGHT,
30 + FONT_SIZE,
31 BORDER_SIZE,
32 } from './constants';
33
32 -const EVENT_ROW_HEIGHT_FIXED =
33 - EVENT_ROW_PADDING + EVENT_DIAMETER + EVENT_ROW_PADDING;
34 +const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
35 +
36 +// TODO (scheduling profiler) Make this a reusable util
37 +const cachedFlamechartTextWidths = new Map();
38 +const trimFlamechartText = (
39 + context: CanvasRenderingContext2D,
40 + text: string,
41 + width: number,
42 +) => {
43 + for (let i = text.length - 1; i >= 0; i--) {
44 + const trimmedText = i === text.length - 1 ? text : text.substr(0, i) + '…';
45 +
46 + let measuredWidth = cachedFlamechartTextWidths.get(trimmedText);
47 + if (measuredWidth == null) {
48 + measuredWidth = context.measureText(trimmedText).width;
49 + cachedFlamechartTextWidths.set(trimmedText, measuredWidth);
50 + }
51 +
52 + if (measuredWidth <= width) {
53 + return trimmedText;
54 + }
55 + }
56 +
57 + return null;
58 +};
59
60 export class NativeEventsView extends View {
36 - _profilerData: ReactProfilerData;
61 + _depthToNativeEvent: Map<number, NativeEvent[]>;
62 + _hoveredEvent: NativeEvent | null = null;
63 _intrinsicSize: Size;
64 + _maxDepth: number = 0;
65 + _profilerData: ReactProfilerData;
66
39 - _hoveredEvent: NativeEvent | null = null;
67 onHover: ((event: NativeEvent | null) => void) | null = null;
68
69 constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
70 super(surface, frame);
71 +
72 this._profilerData = profilerData;
73
74 + this._performPreflightComputations();
75 + console.log(this._depthToNativeEvent);
76 + }
77 +
78 + _performPreflightComputations() {
79 + this._depthToNativeEvent = new Map();
80 +
81 + const {duration, nativeEvents} = this._profilerData;
82 +
83 + nativeEvents.forEach(event => {
84 + const depth = event.depth;
85 +
86 + this._maxDepth = Math.max(this._maxDepth, depth);
87 +
88 + if (!this._depthToNativeEvent.has(depth)) {
89 + this._depthToNativeEvent.set(depth, [event]);
90 + } else {
91 + // $FlowFixMe This is unnecessary.
92 + this._depthToNativeEvent.get(depth).push(event);
93 + }
94 + });
95 +
96 this._intrinsicSize = {
47 - width: this._profilerData.duration,
48 - height: EVENT_ROW_HEIGHT_FIXED,
97 + width: duration,
98 + height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
99 };
100 }
101
@@ -73,7 +123,9 @@ export class NativeEventsView extends View {
123 showHoverHighlight: boolean,
124 ) {
125 const {frame} = this;
76 - const {duration, timestamp} = event;
126 + const {depth, duration, highlight, timestamp, type} = event;
127 +
128 + baseY += depth * ROW_WITH_BORDER_HEIGHT;
129
130 const xStart = timestampToPosition(timestamp, scaleFactor, frame);
131 const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
@@ -82,25 +134,60 @@ export class NativeEventsView extends View {
134 x: xStart,
135 y: baseY,
136 },
85 - size: {width: xStop - xStart, height: EVENT_DIAMETER},
137 + size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
138 };
139 if (!rectIntersectsRect(eventRect, rect)) {
140 return; // Not in view
141 }
142
91 - const fillStyle = showHoverHighlight
92 - ? COLORS.NATIVE_EVENT_HOVER
93 - : COLORS.NATIVE_EVENT;
143 + const width = durationToWidth(duration, scaleFactor);
144 + if (width < 1) {
145 + return; // Too small to render at this zoom level
146 + }
147
148 const drawableRect = intersectionOfRects(eventRect, rect);
149 context.beginPath();
97 - context.fillStyle = fillStyle;
150 + if (highlight) {
151 + context.fillStyle = showHoverHighlight
152 + ? COLORS.NATIVE_EVENT_WARNING_HOVER
153 + : COLORS.NATIVE_EVENT_WARNING;
154 + } else {
155 + context.fillStyle = showHoverHighlight
156 + ? COLORS.NATIVE_EVENT_HOVER
157 + : COLORS.NATIVE_EVENT;
158 + }
159 context.fillRect(
160 drawableRect.origin.x,
161 drawableRect.origin.y,
162 drawableRect.size.width,
163 drawableRect.size.height,
164 );
165 +
166 + // Render event type label
167 + context.textAlign = 'left';
168 + context.textBaseline = 'middle';
169 + context.font = `${FONT_SIZE}px sans-serif`;
170 +
171 + if (width > TEXT_PADDING * 2) {
172 + const x = Math.floor(timestampToPosition(timestamp, scaleFactor, frame));
173 + const trimmedName = trimFlamechartText(
174 + context,
175 + type,
176 + width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
177 + );
178 +
179 + if (trimmedName !== null) {
180 + context.fillStyle = highlight
181 + ? COLORS.NATIVE_EVENT_WARNING_TEXT
182 + : COLORS.TEXT_COLOR;
183 +
184 + context.fillText(
185 + trimmedName,
186 + eventRect.origin.x + TEXT_PADDING - (x < 0 ? x : 0),
187 + eventRect.origin.y + NATIVE_EVENT_HEIGHT / 2,
188 + );
189 + }
190 + }
191 }
192
193 draw(context: CanvasRenderingContext2D) {
@@ -111,7 +198,7 @@ export class NativeEventsView extends View {
198 visibleArea,
199 } = this;
200
114 - context.fillStyle = COLORS.BACKGROUND;
201 + context.fillStyle = COLORS.PRIORITY_BACKGROUND;
202 context.fillRect(
203 visibleArea.origin.x,
204 visibleArea.origin.y,
@@ -120,57 +207,43 @@ export class NativeEventsView extends View {
207 );
208
209 // Draw events
123 - const baseY = frame.origin.y + EVENT_ROW_PADDING;
210 const scaleFactor = positioningScaleFactor(
211 this._intrinsicSize.width,
212 frame,
213 );
214
215 nativeEvents.forEach(event => {
130 - if (event === _hoveredEvent) {
131 - // Draw the highlighted items on top so they stand out.
132 - // This is helpful if there are multiple (overlapping) items close to each other.
133 - this._drawSingleNativeEvent(
134 - context,
135 - visibleArea,
136 - event,
137 - baseY,
138 - scaleFactor,
139 - true,
140 - );
141 - } else {
142 - this._drawSingleNativeEvent(
143 - context,
144 - visibleArea,
145 - event,
146 - baseY,
147 - scaleFactor,
148 - false,
149 - );
150 - }
216 + this._drawSingleNativeEvent(
217 + context,
218 + visibleArea,
219 + event,
220 + frame.origin.y,
221 + scaleFactor,
222 + event === _hoveredEvent,
223 + );
224 });
225
153 - // Render bottom border.
154 - // Propose border rect, check if intersects with `rect`, draw intersection.
155 - const borderFrame: Rect = {
156 - origin: {
157 - x: frame.origin.x,
158 - y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE,
159 - },
160 - size: {
161 - width: frame.size.width,
162 - height: BORDER_SIZE,
163 - },
164 - };
165 - if (rectIntersectsRect(borderFrame, visibleArea)) {
166 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
167 - context.fillStyle = COLORS.PRIORITY_BORDER;
168 - context.fillRect(
169 - borderDrawableRect.origin.x,
170 - borderDrawableRect.origin.y,
171 - borderDrawableRect.size.width,
172 - borderDrawableRect.size.height,
173 - );
226 + // Render bottom borders.
227 + for (let i = 0; i <= this._maxDepth; i++) {
228 + const borderFrame: Rect = {
229 + origin: {
230 + x: frame.origin.x,
231 + y: frame.origin.y + NATIVE_EVENT_HEIGHT,
232 + },
233 + size: {
234 + width: frame.size.width,
235 + height: BORDER_SIZE,
236 + },
237 + };
238 + if (rectIntersectsRect(borderFrame, visibleArea)) {
239 + context.fillStyle = COLORS.PRIORITY_BORDER;
240 + context.fillRect(
241 + visibleArea.origin.x,
242 + frame.origin.y + (i + 1) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
243 + visibleArea.size.width,
244 + BORDER_SIZE,
245 + );
246 + }
247 }
248 }
249
@@ -189,25 +262,26 @@ export class NativeEventsView extends View {
262 return;
263 }
264
192 - const {nativeEvents} = this._profilerData;
193 -
265 const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
266 const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
267
197 - // Find the event being hovered over.
198 - //
199 - // Because data ranges may overlap, we want to find the last intersecting item.
200 - // This will always be the one on "top" (the one the user is hovering over).
201 - for (let index = nativeEvents.length - 1; index >= 0; index--) {
202 - const nativeEvent = nativeEvents[index];
203 - const {duration, timestamp} = nativeEvent;
204 -
205 - if (
206 - hoverTimestamp >= timestamp &&
207 - hoverTimestamp <= timestamp + duration
208 - ) {
209 - onHover(nativeEvent);
210 - return;
268 + const adjustedCanvasMouseY = location.y - frame.origin.y;
269 + const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
270 + const nativeEventsAtDepth = this._depthToNativeEvent.get(depth);
271 +
272 + if (nativeEventsAtDepth) {
273 + // Find the event being hovered over.
274 + for (let index = nativeEventsAtDepth.length - 1; index >= 0; index--) {
275 + const nativeEvent = nativeEventsAtDepth[index];
276 + const {duration, timestamp} = nativeEvent;
277 +
278 + if (
279 + hoverTimestamp >= timestamp &&
280 + hoverTimestamp <= timestamp + duration
281 + ) {
282 + onHover(nativeEvent);
283 + return;
284 + }
285 }
286 }
287
packages/react-devtools-scheduling-profiler/src/content-views/ReactEventsView.js
+7 -7
@@ -25,13 +25,13 @@ import {
25 } from '../view-base';
26 import {
27 COLORS,
28 - EVENT_ROW_PADDING,
29 - EVENT_DIAMETER,
28 + TOP_ROW_PADDING,
29 + REACT_EVENT_DIAMETER,
30 BORDER_SIZE,
31 } from './constants';
32
33 const EVENT_ROW_HEIGHT_FIXED =
34 - EVENT_ROW_PADDING + EVENT_DIAMETER + EVENT_ROW_PADDING;
34 + TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
35
36 function isSuspenseEvent(event: ReactEvent): boolean %checks {
37 return (
@@ -85,13 +85,13 @@ export class ReactEventsView extends View {
85 const {timestamp, type} = event;
86
87 const x = timestampToPosition(timestamp, scaleFactor, frame);
88 - const radius = EVENT_DIAMETER / 2;
88 + const radius = REACT_EVENT_DIAMETER / 2;
89 const eventRect: Rect = {
90 origin: {
91 x: x - radius,
92 y: baseY,
93 },
94 - size: {width: EVENT_DIAMETER, height: EVENT_DIAMETER},
94 + size: {width: REACT_EVENT_DIAMETER, height: REACT_EVENT_DIAMETER},
95 };
96 if (!rectIntersectsRect(eventRect, rect)) {
97 return; // Not in view
@@ -156,7 +156,7 @@ export class ReactEventsView extends View {
156 );
157
158 // Draw events
159 - const baseY = frame.origin.y + EVENT_ROW_PADDING;
159 + const baseY = frame.origin.y + TOP_ROW_PADDING;
160 const scaleFactor = positioningScaleFactor(
161 this._intrinsicSize.width,
162 frame,
@@ -246,7 +246,7 @@ export class ReactEventsView extends View {
246 );
247 const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
248 const eventTimestampAllowance = widthToDuration(
249 - EVENT_DIAMETER / 2,
249 + REACT_EVENT_DIAMETER / 2,
250 scaleFactor,
251 );
252
packages/react-devtools-scheduling-profiler/src/content-views/ReactMeasuresView.js
+1 -1
@@ -54,7 +54,7 @@ export class ReactMeasuresView extends View {
54
55 _performPreflightComputations() {
56 this._lanesToRender = [];
57 - this._laneToMeasures = new Map<ReactLane, ReactMeasure[]>();
57 + this._laneToMeasures = new Map();
58
59 for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
60 const measuresForLane = getMeasuresForLane(
packages/react-devtools-scheduling-profiler/src/content-views/TimeAxisMarkersView.js
+2 -2
@@ -25,7 +25,7 @@ import {
25 COLORS,
26 INTERVAL_TIMES,
27 LABEL_SIZE,
28 - MARKER_FONT_SIZE,
28 + FONT_SIZE,
29 MARKER_HEIGHT,
30 MARKER_TEXT_PADDING,
31 MARKER_TICK_HEIGHT,
@@ -129,7 +129,7 @@ export class TimeAxisMarkersView extends View {
129 context.fillStyle = COLORS.TIME_MARKER_LABEL;
130 context.textAlign = 'right';
131 context.textBaseline = 'middle';
132 - context.font = `${MARKER_FONT_SIZE}px sans-serif`;
132 + context.font = `${FONT_SIZE}px sans-serif`;
133 context.fillText(
134 `${markerLabel}ms`,
135 x - MARKER_TEXT_PADDING,
packages/react-devtools-scheduling-profiler/src/content-views/UserTimingMarksView.js
+3 -3
@@ -25,13 +25,13 @@ import {
25 } from '../view-base';
26 import {
27 COLORS,
28 - EVENT_ROW_PADDING,
28 + TOP_ROW_PADDING,
29 USER_TIMING_MARK_SIZE,
30 BORDER_SIZE,
31 } from './constants';
32
33 const ROW_HEIGHT_FIXED =
34 - EVENT_ROW_PADDING + USER_TIMING_MARK_SIZE + EVENT_ROW_PADDING;
34 + TOP_ROW_PADDING + USER_TIMING_MARK_SIZE + TOP_ROW_PADDING;
35
36 export class UserTimingMarksView extends View {
37 _marks: UserTimingMark[];
@@ -125,7 +125,7 @@ export class UserTimingMarksView extends View {
125 );
126
127 // Draw marks
128 - const baseY = frame.origin.y + EVENT_ROW_PADDING;
128 + const baseY = frame.origin.y + TOP_ROW_PADDING;
129 const scaleFactor = positioningScaleFactor(
130 this._intrinsicSize.width,
131 frame,
packages/react-devtools-scheduling-profiler/src/content-views/constants.js
+25 -16
@@ -8,12 +8,19 @@
8 */
9
10 export const LABEL_SIZE = 80;
11 -export const LABEL_FONT_SIZE = 11;
11 export const MARKER_HEIGHT = 20;
12 export const MARKER_TICK_HEIGHT = 8;
14 -export const MARKER_FONT_SIZE = 10;
13 +export const FONT_SIZE = 10;
14 export const MARKER_TEXT_PADDING = 8;
15 export const COLOR_HOVER_DIM_DELTA = 5;
16 +export const TOP_ROW_PADDING = 4;
17 +export const NATIVE_EVENT_HEIGHT = 14;
18 +export const REACT_EVENT_DIAMETER = 6;
19 +export const USER_TIMING_MARK_SIZE = 8;
20 +export const REACT_MEASURE_HEIGHT = 9;
21 +export const BORDER_SIZE = 1;
22 +export const FLAMECHART_FRAME_HEIGHT = 14;
23 +export const TEXT_PADDING = 3;
24
25 export const INTERVAL_TIMES = [
26 1,
@@ -31,22 +38,14 @@ export const INTERVAL_TIMES = [
38 ];
39 export const MIN_INTERVAL_SIZE_PX = 70;
40
34 -export const EVENT_ROW_PADDING = 4;
35 -export const EVENT_DIAMETER = 6;
36 -export const USER_TIMING_MARK_SIZE = 8;
37 -export const REACT_MEASURE_HEIGHT = 9;
38 -export const BORDER_SIZE = 1;
39 -
40 -export const FLAMECHART_FONT_SIZE = 10;
41 -export const FLAMECHART_FRAME_HEIGHT = 16;
42 -export const FLAMECHART_TEXT_PADDING = 3;
43 -
41 // TODO Replace this with "export let" vars
42 export let COLORS = {
43 BACKGROUND: '',
47 - FLAME_GRAPH_LABEL: '',
44 NATIVE_EVENT: '',
45 NATIVE_EVENT_HOVER: '',
46 + NATIVE_EVENT_WARNING: '',
47 + NATIVE_EVENT_WARNING_HOVER: '',
48 + NATIVE_EVENT_WARNING_TEXT: '',
49 PRIORITY_BACKGROUND: '',
50 PRIORITY_BORDER: '',
51 PRIORITY_LABEL: '',
@@ -75,6 +74,7 @@ export let COLORS = {
74 REACT_SUSPEND: '',
75 REACT_SUSPEND_HOVER: '',
76 REACT_WORK_BORDER: '',
77 + TEXT_COLOR: '',
78 TIME_MARKER_LABEL: '',
79 };
80
@@ -83,15 +83,21 @@ export function updateColorsToMatchTheme(): void {
83
84 COLORS = {
85 BACKGROUND: computedStyle.getPropertyValue('--color-background'),
86 - FLAME_GRAPH_LABEL: computedStyle.getPropertyValue(
87 - '--color-scheduling-profiler-flame-graph-label',
88 - ),
86 NATIVE_EVENT: computedStyle.getPropertyValue(
87 '--color-scheduling-profiler-native-event',
88 ),
89 NATIVE_EVENT_HOVER: computedStyle.getPropertyValue(
90 '--color-scheduling-profiler-native-event-hover',
91 ),
92 + NATIVE_EVENT_WARNING: computedStyle.getPropertyValue(
93 + '--color-scheduling-profiler-native-event-warning',
94 + ),
95 + NATIVE_EVENT_WARNING_HOVER: computedStyle.getPropertyValue(
96 + '--color-scheduling-profiler-native-event-warning-hover',
97 + ),
98 + NATIVE_EVENT_WARNING_TEXT: computedStyle.getPropertyValue(
99 + '--color-scheduling-profiler-native-event-warning-text',
100 + ),
101 PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
102 '--color-scheduling-profiler-priority-background',
103 ),
@@ -172,6 +178,9 @@ export function updateColorsToMatchTheme(): void {
178 REACT_WORK_BORDER: computedStyle.getPropertyValue(
179 '--color-scheduling-profiler-react-work-border',
180 ),
181 + TEXT_COLOR: computedStyle.getPropertyValue(
182 + '--color-scheduling-profiler-text-color',
183 + ),
184 TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
185 };
186 }
packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js
+41 -5
@@ -16,6 +16,7 @@ import type {
16 Milliseconds,
17 BatchUID,
18 Flamechart,
19 + NativeEvent,
20 ReactLane,
21 ReactMeasureType,
22 ReactProfilerData,
@@ -39,6 +40,8 @@ type ProcessorState = {|
40 measureStack: MeasureStackElement[],
41 |};
42
43 +let nativeEventStack: Array<NativeEvent> = [];
44 +
45 // Exported for tests
46 export function getLanesFromTransportDecimalBitmask(
47 laneBitmaskString: string,
@@ -178,15 +181,37 @@ function processTimelineEvent(
181 }
182 }
183
181 - const startTime = (ts - currentProfilerData.startTime) / 1000;
184 + const timestamp = (ts - currentProfilerData.startTime) / 1000;
185 const duration = event.dur / 1000;
186
184 - // TODO (scheduling profiler) Should we filter out certain event types?
185 - currentProfilerData.nativeEvents.push({
187 + let depth = 0;
188 +
189 + while (nativeEventStack.length > 0) {
190 + const prevNativeEvent = nativeEventStack[nativeEventStack.length - 1];
191 + const prevStopTime =
192 + prevNativeEvent.timestamp + prevNativeEvent.duration;
193 +
194 + if (timestamp < prevStopTime) {
195 + depth = prevNativeEvent.depth + 1;
196 + break;
197 + } else {
198 + nativeEventStack.pop();
199 + }
200 + }
201 +
202 + const nativeEvent = {
203 + depth,
204 duration,
187 - timestamp: startTime,
205 + highlight: false,
206 + timestamp,
207 type,
189 - });
208 + };
209 +
210 + currentProfilerData.nativeEvents.push(nativeEvent);
211 +
212 + // Keep track of curent event in case future ones overlap.
213 + // We separate them into different vertical lanes in this case.
214 + nativeEventStack.push(nativeEvent);
215 }
216 break;
217 case 'blink.user_timing':
@@ -309,6 +334,15 @@ function processTimelineEvent(
334 currentProfilerData,
335 state,
336 );
337 +
338 + for (let i = 0; i < nativeEventStack.length; i++) {
339 + const nativeEvent = nativeEventStack[i];
340 + const stopTime = nativeEvent.timestamp + nativeEvent.duration;
341 + if (stopTime > startTime) {
342 + // Warn about sync updates that happen an event handler.
343 + nativeEvent.highlight = true;
344 + }
345 + }
346 } else if (
347 name.startsWith('--render-stop') ||
348 name.startsWith('--render-yield')
@@ -470,6 +504,8 @@ function preprocessFlamechart(rawData: TimelineEvent[]): Flamechart {
504 export default function preprocessData(
505 timeline: TimelineEvent[],
506 ): ReactProfilerData {
507 + nativeEventStack = [];
508 +
509 const flamechart = preprocessFlamechart(timeline);
510
511 const profilerData: ReactProfilerData = {
packages/react-devtools-scheduling-profiler/src/types.js
+2
@@ -22,7 +22,9 @@ export type Milliseconds = number;
22 export type ReactLane = number;
23
24 export type NativeEvent = {|
25 + +depth: number,
26 +duration: Milliseconds,
27 + highlight: boolean,
28 +timestamp: Milliseconds,
29 +type: string,
30 |};
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+16 -1
@@ -418,7 +418,7 @@ export function updateThemeVariables(
418 updateStyleHelper(theme, 'color-search-match-current', documentElements);
419 updateStyleHelper(
420 theme,
421 - 'color-scheduling-profiler-flame-graph-label',
421 + 'color-scheduling-profiler-text-color',
422 documentElements,
423 );
424 updateStyleHelper(
@@ -431,6 +431,21 @@ export function updateThemeVariables(
431 'color-scheduling-profiler-native-event-hover',
432 documentElements,
433 );
434 + updateStyleHelper(
435 + theme,
436 + 'color-scheduling-profiler-native-event-warning',
437 + documentElements,
438 + );
439 + updateStyleHelper(
440 + theme,
441 + 'color-scheduling-profiler-native-event-warning-hover',
442 + documentElements,
443 + );
444 + updateStyleHelper(
445 + theme,
446 + 'color-scheduling-profiler-native-event-warning-text',
447 + documentElements,
448 + );
449 updateStyleHelper(
450 theme,
451 'color-selected-tree-highlight-active',
packages/react-devtools-shared/src/devtools/views/root.css
+12 -6
@@ -78,9 +78,11 @@
78 --light-color-record-hover: #3578e5;
79 --light-color-record-inactive: #0088fa;
80 --light-color-resize-bar: #cccccc;
81 - --light-color-scheduling-profiler-flame-graph-label: #000000;
82 - --light-color-scheduling-profiler-native-event: #aaaaaa;
83 - --light-color-scheduling-profiler-native-event-hover: #888888;
81 + --light-color-scheduling-profiler-native-event: #ccc;
82 + --light-color-scheduling-profiler-native-event-hover: #aaa;
83 + --light-color-scheduling-profiler-native-event-warning: #ee1638;
84 + --light-color-scheduling-profiler-native-event-warning-hover: #da1030;
85 + --light-color-scheduling-profiler-native-event-warning-text: #fff;
86 --light-color-scheduling-profiler-priority-background: #f6f6f6;
87 --light-color-scheduling-profiler-priority-border: #eeeeee;
88 --light-color-scheduling-profiler-user-timing: #c9cacd;
@@ -106,6 +108,7 @@
108 --light-color-scheduling-profiler-react-schedule-cascading-hover:#ed0030;
109 --light-color-scheduling-profiler-react-suspend: #a6e59f;
110 --light-color-scheduling-profiler-react-suspend-hover:#13bc00;
111 + --light-color-scheduling-profiler-text-color: #000000;
112 --light-color-scheduling-profiler-react-work-border:#ffffff;
113 --light-color-scroll-thumb: #c2c2c2;
114 --light-color-scroll-track: #fafafa;
@@ -200,9 +203,11 @@
203 --dark-color-record-hover: #a2e9fc;
204 --dark-color-record-inactive: #61dafb;
205 --dark-color-resize-bar: #3d424a;
203 - --dark-color-scheduling-profiler-flame-graph-label: #000000;
204 - --dark-color-scheduling-profiler-native-event: #aaaaaa;
205 - --dark-color-scheduling-profiler-native-event-hover: #888888;
206 + --dark-color-scheduling-profiler-native-event: #b2b2b2;
207 + --dark-color-scheduling-profiler-native-event-hover: #949494;
208 + --dark-color-scheduling-profiler-native-event-warning: #ee1638;
209 + --dark-color-scheduling-profiler-native-event-warning-hover: #da1030;
210 + --dark-color-scheduling-profiler-native-event-warning-text: #fff;
211 --dark-color-scheduling-profiler-priority-background: #1d2129;
212 --dark-color-scheduling-profiler-priority-border: #282c34;
213 --dark-color-scheduling-profiler-user-timing: #c9cacd;
@@ -228,6 +233,7 @@
233 --dark-color-scheduling-profiler-react-schedule-cascading-hover:#ed0030;
234 --dark-color-scheduling-profiler-react-suspend: #a6e59f;
235 --dark-color-scheduling-profiler-react-suspend-hover:#13bc00;
236 + --dark-color-scheduling-profiler-text-color: #000000;
237 --dark-color-scheduling-profiler-react-work-border:#ffffff;
238 --dark-color-scroll-thumb: #afb3b9;
239 --dark-color-scroll-track: #313640;