Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.Component {
margin-bottom: 1rem;
}

.Item {
margin-top: 0.25rem;
}

.Key {
font-family: var(--font-family-monospace);
font-size: var(--font-size-monospace-small);
line-height: 1;
}

.Key:first-of-type::before {
content: ' (';
}

.Key::after {
content: ', ';
}

.Key:last-of-type::after {
content: ')';
}

.Label {
font-weight: bold;
margin-bottom: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright (c) Facebook, Inc. and its 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 React, {useContext} from 'react';
import {ProfilerContext} from '../Profiler/ProfilerContext';
import {StoreContext} from '../context';

import styles from './ProfilerWhatChanged.css';

type ProfilerWhatChangedProps = {|
fiberID: number,
|};

export default function ProfilerWhatChanged({
fiberID,
}: ProfilerWhatChangedProps) {
const {profilerStore} = useContext(StoreContext);
const {rootID, selectedCommitIndex} = useContext(ProfilerContext);

// TRICKY
// Handle edge case where no commit is selected because of a min-duration filter update.
// If the commit index is null, suspending for data below would throw an error.
// TODO (ProfilerContext) This check should not be necessary.
if (selectedCommitIndex === null) {
return null;
}

const {changeDescriptions} = profilerStore.getCommitData(
((rootID: any): number),
selectedCommitIndex,
);

if (changeDescriptions === null) {
return null;
}

const changeDescription = changeDescriptions.get(fiberID);
if (changeDescription == null) {
return null;
}

if (changeDescription.isFirstMount) {
return (
<div className={styles.Component}>
<label className={styles.Label}>Why did this render?</label>
<div className={styles.Item}>
This is the first time the component rendered.
</div>
</div>
);
}

const changes = [];

if (changeDescription.context === true) {
changes.push(
<div key="context" className={styles.Item}>
• Context changed
</div>,
);
} else if (
typeof changeDescription.context === 'object' &&
changeDescription.context !== null &&
changeDescription.context.length !== 0
) {
changes.push(
<div key="context" className={styles.Item}>
• Context changed:
{changeDescription.context.map(key => (
<span key={key} className={styles.Key}>
{key}
</span>
))}
</div>,
);
}

if (changeDescription.didHooksChange) {
changes.push(
<div key="hooks" className={styles.Item}>
• Hooks changed
</div>,
);
}

if (
changeDescription.props !== null &&
changeDescription.props.length !== 0
) {
changes.push(
<div key="props" className={styles.Item}>
• Props changed:
{changeDescription.props.map(key => (
<span key={key} className={styles.Key}>
{key}
</span>
))}
</div>,
);
}

if (
changeDescription.state !== null &&
changeDescription.state.length !== 0
) {
changes.push(
<div key="state" className={styles.Item}>
• State changed:
{changeDescription.state.map(key => (
<span key={key} className={styles.Key}>
{key}
</span>
))}
</div>,
);
}

if (changes.length === 0) {
changes.push(
<div key="nothing" className={styles.Item}>
The parent component rendered.
</div>,
);
}

return (
<div className={styles.Component}>
<label className={styles.Label}>Why did this render?</label>
{changes}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.Tooltip {
position: absolute;
pointer-events: none;
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-family: var(--font-family-sans);
font-size: 12px;
background-color: var(--color-tooltip-background);
color: var(--color-tooltip-text);

/* Make sure this is above the DevTools, which are above the Overlay */
z-index: 10000002;
}

.Container {
width: -moz-max-content;
width: -webkit-max-content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @flow */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit, and I'm happy to do this post-merge- BUT! The new Tooltip and WhatChanged components are only used by views in the "Profiler" tab, so they should be in views/Profiler rather than views/Components (which is where views for the "Components" tab live). I would be happy to sort this out with a git mv after the PR merge though!


import React, {useCallback, useRef, useState} from 'react';
import {useSmartTooltip} from '../hooks';

import styles from './Tooltip.css';

const initialTooltipState = {height: 0, mouseX: 0, mouseY: 0, width: 0};

export default function Tooltip({children, label}: any) {
const containerRef = useRef(null);
const [tooltipState, setTooltipState] = useState(initialTooltipState);
const tooltipRef = useSmartTooltip(tooltipState);

const onMouseMove = useCallback(
(event: SyntheticMouseEvent<*>) => {
setTooltipState(getTooltipPosition(containerRef.current, event));
},
[setTooltipState],
);

return (
<div
className={styles.Container}
onMouseMove={label !== null ? onMouseMove : undefined}
ref={containerRef}>
{label !== null && (
<div ref={tooltipRef} className={styles.Tooltip}>
{label}
</div>
)}
{children}
</div>
);
}

function getTooltipPosition(
relativeContainer,
mouseEvent: SyntheticMouseEvent<*>,
) {
if (relativeContainer !== null) {
const {height, top, width} = relativeContainer.getBoundingClientRect();

const mouseX = mouseEvent.clientX;
const mouseY = mouseEvent.clientY - top;

return {height, mouseX, mouseY, width};
} else {
return initialTooltipState;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type Props = {|
label: string,
onClick: (event: SyntheticMouseEvent<*>) => mixed,
onDoubleClick?: (event: SyntheticMouseEvent<*>) => mixed,
onMouseOver: (event: SyntheticMouseEvent<*>) => mixed,
onMouseOut: (event: SyntheticMouseEvent<*>) => mixed,
placeLabelAboveNode?: boolean,
textStyle?: Object,
width: number,
Expand All @@ -33,6 +35,8 @@ export default function ChartNode({
isDimmed = false,
label,
onClick,
onMouseOver,
onMouseOut,
onDoubleClick,
textStyle,
width,
Expand All @@ -41,12 +45,13 @@ export default function ChartNode({
}: Props) {
return (
<g className={styles.Group} transform={`translate(${x},${y})`}>
<title>{label}</title>
<rect
width={width}
height={height}
fill={color}
onClick={onClick}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
onDoubleClick={onDoubleClick}
className={styles.Rect}
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@
* @flow
*/

import React, {forwardRef, useCallback, useContext, useMemo} from 'react';
import React, {
forwardRef,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import {FixedSizeList} from 'react-window';
import {ProfilerContext} from './ProfilerContext';
import NoCommitData from './NoCommitData';
import CommitFlamegraphListItem from './CommitFlamegraphListItem';
import HoveredFiberInfo from './HoveredFiberInfo';
import {scale} from './utils';
import {StoreContext} from '../context';
import {SettingsContext} from '../Settings/SettingsContext';
import Tooltip from '../Components/Tooltip';

import styles from './CommitFlamegraph.css';

import type {TooltipFiberData} from './HoveredFiberInfo';
import type {ChartData, ChartNode} from './FlamegraphChartBuilder';
import type {CommitTree} from './types';

export type ItemData = {|
chartData: ChartData,
hoverFiber: (fiberData: TooltipFiberData | null) => void,
scaleX: (value: number, fallbackValue: number) => number,
selectedChartNode: ChartNode | null,
selectedChartNodeIndex: number,
Expand Down Expand Up @@ -91,6 +101,7 @@ type Props = {|
|};

function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
const [hoveredFiberData, hoverFiber] = useState<number | null>(null);
const {lineHeight} = useContext(SettingsContext);
const {selectFiber, selectedFiberID} = useContext(ProfilerContext);

Expand Down Expand Up @@ -118,6 +129,7 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
const itemData = useMemo<ItemData>(
() => ({
chartData,
hoverFiber,
scaleX: scale(
0,
selectedChartNode !== null
Expand All @@ -131,19 +143,37 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
selectFiber,
width,
}),
[chartData, selectedChartNode, selectedChartNodeIndex, selectFiber, width],
[
chartData,
hoverFiber,
selectedChartNode,
selectedChartNodeIndex,
selectFiber,
width,
],
);

// Tooltip used to show summary of fiber info on hover
const tooltipLabel = useMemo(
() =>
hoveredFiberData !== null ? (
<HoveredFiberInfo fiberData={hoveredFiberData} />
) : null,
[hoveredFiberData],
);

return (
<FixedSizeList
height={height}
innerElementType={InnerElementType}
itemCount={chartData.depth}
itemData={itemData}
itemSize={lineHeight}
width={width}>
{CommitFlamegraphListItem}
</FixedSizeList>
<Tooltip label={tooltipLabel}>
<FixedSizeList
height={height}
innerElementType={InnerElementType}
itemCount={chartData.depth}
itemData={itemData}
itemSize={lineHeight}
width={width}>
{CommitFlamegraphListItem}
</FixedSizeList>
</Tooltip>
);
}

Expand Down
Loading