@samitouri / QOS-React / commits / 42251331d8

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;