DevTools: Scheduling profiler (#22006)
Brian Vaughn committed
Aug 2, 2021 at 14:30 UTC
42251331d8f7a997a018d75370f5216bb7552819
13 files changed
+100
-44
packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+18
-13
@@ -230,18 +230,21 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
230
schedulingEventsViewRef.current = schedulingEventsView;
231
const schedulingEventsViewWrapper = createViewHelper(schedulingEventsView);
232
233
- const suspenseEventsView = new SuspenseEventsView(
234
- surface,
235
- defaultFrame,
236
- data,
237
- );
238
- suspenseEventsViewRef.current = suspenseEventsView;
239
- const suspenseEventsViewWrapper = createViewHelper(
240
- suspenseEventsView,
241
- 'suspense',
242
- true,
243
- true,
244
- );
233
+ let suspenseEventsViewWrapper = null;
234
+ if (data.suspenseEvents.length > 0) {
235
+ const suspenseEventsView = new SuspenseEventsView(
236
+ surface,
237
+ defaultFrame,
238
+ data,
239
+ );
240
+ suspenseEventsViewRef.current = suspenseEventsView;
241
+ suspenseEventsViewWrapper = createViewHelper(
242
+ suspenseEventsView,
243
+ 'suspense',
244
+ true,
245
+ true,
246
+ );
247
+ }
248
249
const reactMeasuresView = new ReactMeasuresView(
250
surface,
@@ -286,7 +289,9 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
289
}
290
rootView.addSubview(nativeEventsViewWrapper);
291
rootView.addSubview(schedulingEventsViewWrapper);
289
- rootView.addSubview(suspenseEventsViewWrapper);
292
+ if (suspenseEventsViewWrapper !== null) {
293
+ rootView.addSubview(suspenseEventsViewWrapper);
294
+ }
295
rootView.addSubview(reactMeasuresViewWrapper);
296
rootView.addSubview(flamechartViewWrapper);
297
packages/react-devtools-scheduling-profiler/src/content-views/NativeEventsView.js
+3
-3
@@ -10,9 +10,9 @@
10
import type {NativeEvent, ReactProfilerData} from '../types';
11
import type {
12
Interaction,
13
+ IntrinsicSize,
14
MouseMoveInteraction,
15
Rect,
15
- Size,
16
ViewRefs,
17
} from '../view-base';
18
@@ -38,7 +38,7 @@ const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
38
export class NativeEventsView extends View {
39
_depthToNativeEvent: Map<number, NativeEvent[]>;
40
_hoveredEvent: NativeEvent | null = null;
41
- _intrinsicSize: Size;
41
+ _intrinsicSize: IntrinsicSize;
42
_maxDepth: number = 0;
43
_profilerData: ReactProfilerData;
44
@@ -73,6 +73,7 @@ export class NativeEventsView extends View {
73
this._intrinsicSize = {
74
width: duration,
75
height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
76
+ hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
77
};
78
}
79
@@ -239,7 +240,6 @@ export class NativeEventsView extends View {
240
hoverTimestamp <= timestamp + duration
241
) {
242
viewRefs.hoveredView = this;
242
-
243
onHover(nativeEvent);
244
return;
245
}
packages/react-devtools-scheduling-profiler/src/content-views/ReactMeasuresView.js
+3
-2
@@ -10,9 +10,9 @@
10
import type {ReactLane, ReactMeasure, ReactProfilerData} from '../types';
11
import type {
12
Interaction,
13
+ IntrinsicSize,
14
MouseMoveInteraction,
15
Rect,
15
- SizeWithMaxHeight,
16
ViewRefs,
17
} from '../view-base';
18
@@ -45,7 +45,7 @@ function getMeasuresForLane(
45
46
export class ReactMeasuresView extends View {
47
_profilerData: ReactProfilerData;
48
- _intrinsicSize: SizeWithMaxHeight;
48
+ _intrinsicSize: IntrinsicSize;
49
50
_lanesToRender: ReactLane[];
51
_laneToMeasures: Map<ReactLane, ReactMeasure[]>;
@@ -78,6 +78,7 @@ export class ReactMeasuresView extends View {
78
this._intrinsicSize = {
79
width: this._profilerData.duration,
80
height: this._lanesToRender.length * REACT_LANE_HEIGHT,
81
+ hideScrollBarIfLessThanHeight: REACT_LANE_HEIGHT,
82
maxInitialHeight: MAX_ROWS_TO_SHOW_INITIALLY * REACT_LANE_HEIGHT,
83
};
84
}
packages/react-devtools-scheduling-profiler/src/content-views/SuspenseEventsView.js
+2
-2
@@ -10,9 +10,9 @@
10
import type {SuspenseEvent, ReactProfilerData} from '../types';
11
import type {
12
Interaction,
13
+ IntrinsicSize,
14
MouseMoveInteraction,
15
Rect,
15
- SizeWithMaxHeight,
16
ViewRefs,
17
} from '../view-base';
18
@@ -45,7 +45,7 @@ const MAX_ROWS_TO_SHOW_INITIALLY = 3;
45
export class SuspenseEventsView extends View {
46
_depthToSuspenseEvent: Map<number, SuspenseEvent[]>;
47
_hoveredEvent: SuspenseEvent | null = null;
48
- _intrinsicSize: SizeWithMaxHeight;
48
+ _intrinsicSize: IntrinsicSize;
49
_maxDepth: number = 0;
50
_profilerData: ReactProfilerData;
51
packages/react-devtools-scheduling-profiler/src/content-views/UserTimingMarksView.js
-1
@@ -224,7 +224,6 @@ export class UserTimingMarksView extends View {
224
timestamp - timestampAllowance <= hoverTimestamp &&
225
hoverTimestamp <= timestamp + timestampAllowance
226
) {
227
- this.currentCursor = 'context-menu';
227
viewRefs.hoveredView = this;
228
onHover(mark);
229
return;
packages/react-devtools-scheduling-profiler/src/view-base/ResizableView.js
+22
-5
@@ -255,12 +255,21 @@ export class ResizableView extends View {
255
}
256
257
desiredSize() {
258
- const resizeBarDesiredSize = this._resizeBar.desiredSize();
258
+ const subviewDesiredSize = this._subview.desiredSize();
259
260
- return {
261
- width: this.frame.size.width,
262
- height: this._layoutState.barOffsetY + resizeBarDesiredSize.height,
263
- };
260
+ if (this._shouldRenderResizeBar()) {
261
+ const resizeBarDesiredSize = this._resizeBar.desiredSize();
262
+
263
+ return {
264
+ width: this.frame.size.width,
265
+ height: this._layoutState.barOffsetY + resizeBarDesiredSize.height,
266
+ };
267
+ } else {
268
+ return {
269
+ width: this.frame.size.width,
270
+ height: subviewDesiredSize.height,
271
+ };
272
+ }
273
}
274
275
layoutSubviews() {
@@ -270,6 +279,14 @@ export class ResizableView extends View {
279
super.layoutSubviews();
280
}
281
282
+ _shouldRenderResizeBar() {
283
+ const subviewDesiredSize = this._subview.desiredSize();
284
+ return subviewDesiredSize.hideScrollBarIfLessThanHeight != null
285
+ ? subviewDesiredSize.height >
286
+ subviewDesiredSize.hideScrollBarIfLessThanHeight
287
+ : true;
288
+ }
289
+
290
_updateLayoutStateAndResizeBar(barOffsetY: number) {
291
if (barOffsetY <= RESIZE_BAR_WITH_LABEL_HEIGHT - RESIZE_BAR_HEIGHT) {
292
barOffsetY = 0;
packages/react-devtools-scheduling-profiler/src/view-base/View.js
+2
-2
@@ -8,7 +8,7 @@
8
*/
9
10
import type {Interaction} from './useCanvasInteraction';
11
-import type {Rect, Size, SizeWithMaxHeight} from './geometry';
11
+import type {IntrinsicSize, Rect, Size} from './geometry';
12
import type {Layouter} from './layouter';
13
import type {ViewRefs} from './Surface';
14
@@ -140,7 +140,7 @@ export class View {
140
*
141
* Can be overridden by subclasses.
142
*/
143
- desiredSize(): Size | SizeWithMaxHeight {
143
+ desiredSize(): Size | IntrinsicSize {
144
if (this._needsDisplay) {
145
this.layoutSubviews();
146
}
packages/react-devtools-scheduling-profiler/src/view-base/geometry.js
+7
-1
@@ -9,8 +9,14 @@
9
10
export type Point = $ReadOnly<{|x: number, y: number|}>;
11
export type Size = $ReadOnly<{|width: number, height: number|}>;
12
-export type SizeWithMaxHeight = {|
12
+export type IntrinsicSize = {|
13
...Size,
14
+
15
+ // If content is this height or less, hide the scrollbar entirely,
16
+ // so that it doesn't take up vertical space unnecessarily (e.g. for a single row of content).
17
+ hideScrollBarIfLessThanHeight?: number,
18
+
19
+ // The initial height should be the height of the content, or this, whichever is less.
20
maxInitialHeight?: number,
21
|};
22
export type Rect = $ReadOnly<{|origin: Point, size: Size|}>;
packages/react-devtools-shared/src/constants.js
+16
-8
@@ -165,8 +165,6 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
165
'--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
166
'--color-scheduling-profiler-text-color': '#000000',
167
'--color-scheduling-profiler-react-work-border': '#ffffff',
168
- '--color-scroll-thumb': '#c2c2c2',
169
- '--color-scroll-track': '#fafafa',
168
'--color-search-match': 'yellow',
169
'--color-search-match-current': '#f7923b',
170
'--color-selected-tree-highlight-active': 'rgba(0, 136, 250, 0.1)',
@@ -180,12 +178,18 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
178
'--color-toggle-background-on': '#0088fa',
179
'--color-toggle-background-off': '#cfd1d5',
180
'--color-toggle-text': '#ffffff',
183
- '--color-tooltip-background': 'rgba(0, 0, 0, 0.9)',
184
- '--color-tooltip-text': '#ffffff',
181
'--color-warning-background': '#fb3655',
182
'--color-warning-background-hover': '#f82042',
183
'--color-warning-text-color': '#ffffff',
184
'--color-warning-text-color-inverted': '#fd4d69',
185
+
186
+ // The styles below should be kept in sync with 'root.css'
187
+ // They are repeated there because they're used by e.g. tooltips or context menus
188
+ // which get rendered outside of the DOM subtree (where normal theme/styles are written).
189
+ '--color-scroll-thumb': '#c2c2c2',
190
+ '--color-scroll-track': '#fafafa',
191
+ '--color-tooltip-background': 'rgba(0, 0, 0, 0.9)',
192
+ '--color-tooltip-text': '#ffffff',
193
},
194
dark: {
195
'--color-attribute-name': '#9d87d2',
@@ -291,8 +295,6 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
295
'--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
296
'--color-scheduling-profiler-text-color': '#000000',
297
'--color-scheduling-profiler-react-work-border': '#ffffff',
294
- '--color-scroll-thumb': '#afb3b9',
295
- '--color-scroll-track': '#313640',
298
'--color-search-match': 'yellow',
299
'--color-search-match-current': '#f7923b',
300
'--color-selected-tree-highlight-active': 'rgba(23, 143, 185, 0.15)',
@@ -307,12 +309,18 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
309
'--color-toggle-background-on': '#178fb9',
310
'--color-toggle-background-off': '#777d88',
311
'--color-toggle-text': '#ffffff',
310
- '--color-tooltip-background': 'rgba(255, 255, 255, 0.95)',
311
- '--color-tooltip-text': '#000000',
312
'--color-warning-background': '#ee1638',
313
'--color-warning-background-hover': '#da1030',
314
'--color-warning-text-color': '#ffffff',
315
'--color-warning-text-color-inverted': '#ee1638',
316
+
317
+ // The styles below should be kept in sync with 'root.css'
318
+ // They are repeated there because they're used by e.g. tooltips or context menus
319
+ // which get rendered outside of the DOM subtree (where normal theme/styles are written).
320
+ '--color-scroll-thumb': '#afb3b9',
321
+ '--color-scroll-track': '#313640',
322
+ '--color-tooltip-background': 'rgba(255, 255, 255, 0.95)',
323
+ '--color-tooltip-text': '#000000',
324
},
325
compact: {
326
'--font-size-monospace-small': '9px',
packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js
+9
-5
@@ -68,11 +68,15 @@ export default function ContextMenu({children, id}: Props) {
68
const element = bodyAccessorRef.current;
69
if (element !== null) {
70
const ownerDocument = element.ownerDocument;
71
- containerRef.current = ownerDocument.createElement('div');
72
- ownerDocument.body.appendChild(containerRef.current);
73
- return () => {
74
- ownerDocument.body.removeChild(containerRef.current);
75
- };
71
+ containerRef.current = ownerDocument.querySelector(
72
+ '[data-react-devtools-portal-root]',
73
+ );
74
+
75
+ if (containerRef.current == null) {
76
+ console.warn(
77
+ 'DevTools tooltip root node not found; context menus will be disabled.',
78
+ );
79
+ }
80
}
81
}, []);
82
packages/react-devtools-shared/src/devtools/views/DevTools.js
+4
-1
@@ -222,7 +222,10 @@ export default function DevTools({
222
<ProfilerContextController>
223
<SchedulingProfilerContextController>
224
<ThemeProvider>
225
- <div className={styles.DevTools} ref={devToolsRef}>
225
+ <div
226
+ className={styles.DevTools}
227
+ ref={devToolsRef}
228
+ data-react-devtools-portal-root={true}>
229
{showTabBar && (
230
<div className={styles.TabBar}>
231
<ReactLogo />
packages/react-devtools-shared/src/devtools/views/portaledContent.js
+9
-1
@@ -32,7 +32,15 @@ export default function portaledContent(
32
// The ThemeProvider works by writing DOM style variables to an HTMLDivElement.
33
// Because Portals render in a different DOM subtree, these variables don't propagate.
34
// So in this case, we need to re-wrap portaled content in a second ThemeProvider.
35
- children = <ThemeProvider>{children}</ThemeProvider>;
35
+ children = (
36
+ <ThemeProvider>
37
+ <div
38
+ data-react-devtools-portal-root={true}
39
+ style={{width: '100vw', height: '100vh'}}>
40
+ {children}
41
+ </div>
42
+ </ThemeProvider>
43
+ );
44
}
45
46
return portalContainer != null
packages/react-devtools-shared/src/devtools/views/root.css
+5
@@ -1,4 +1,9 @@
1
:root {
2
+ /**
3
+ * The light and dark theme styles below should be kept in sync with 'react-devtools-shared/src/constants'
4
+ * They are repeated here because they're used by e.g. tooltips or context menus
5
+ * which get rendered outside of the DOM subtree (where normal theme/styles are written).
6
+ */
7
8
/* Light theme */
9
--light-color-scroll-thumb: #c2c2c2;