Skip to content

Commit df3e3bd

Browse files
committed
Add those buttons for the self wing too.
1 parent 6362631 commit df3e3bd

9 files changed

Lines changed: 184 additions & 21 deletions

File tree

src/actions/profile-view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1799,7 +1799,7 @@ export function changeFunctionListSectionOpen(
17991799
}
18001800

18011801
export function changeWingView(
1802-
wing: 'upper' | 'lower',
1802+
wing: 'upper' | 'lower' | 'self',
18031803
view: 'flame-graph' | 'call-tree'
18041804
): Action {
18051805
return {

src/app-logic/url-handling.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -853,23 +853,28 @@ function convertFunctionListSectionsOpenFromString(
853853
return result;
854854
}
855855

856-
// WingView visualization choice for the Descendants ("upper") and Ancestors
857-
// ("lower") wings of the FunctionList tab. The URL stores a comma-separated
858-
// list of "wing:view" pairs for wings whose view differs from the default
859-
// (flame-graph), e.g. "upper:call-tree". The param is omitted when both wings
860-
// use the default.
856+
// WingView visualization choice for the Descendants ("upper"), Ancestors
857+
// ("lower"), and Self wings of the FunctionList tab. The URL stores a
858+
// comma-separated list of "wing:view" pairs for wings whose view differs from
859+
// the default (flame-graph), e.g. "upper:call-tree". The param is omitted
860+
// when all wings use the default.
861861
const WING_VIEWS_DEFAULT: WingViewsState = {
862862
upper: 'flame-graph',
863863
lower: 'flame-graph',
864+
self: 'flame-graph',
864865
};
866+
const WING_VIEW_NAMES: ReadonlyArray<keyof WingViewsState> = [
867+
'upper',
868+
'lower',
869+
'self',
870+
];
865871

866872
function convertWingViewsToString(state: WingViewsState): string | undefined {
867873
const parts: string[] = [];
868-
if (state.upper !== WING_VIEWS_DEFAULT.upper) {
869-
parts.push(`upper:${state.upper}`);
870-
}
871-
if (state.lower !== WING_VIEWS_DEFAULT.lower) {
872-
parts.push(`lower:${state.lower}`);
874+
for (const wing of WING_VIEW_NAMES) {
875+
if (state[wing] !== WING_VIEWS_DEFAULT[wing]) {
876+
parts.push(`${wing}:${state[wing]}`);
877+
}
873878
}
874879
return parts.length === 0 ? undefined : parts.join(',');
875880
}
@@ -882,7 +887,7 @@ function convertWingViewsFromString(raw: string | null | void): WingViewsState {
882887
for (const part of raw.split(',')) {
883888
const [wing, view] = part.split(':');
884889
if (
885-
(wing === 'upper' || wing === 'lower') &&
890+
(wing === 'upper' || wing === 'lower' || wing === 'self') &&
886891
(view === 'flame-graph' || view === 'call-tree')
887892
) {
888893
result[wing] = view;

src/components/calltree/ProfileFunctionListView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class ProfileFunctionListViewImpl extends React.PureComponent<Props> {
8383
label="Self"
8484
isOpen={sectionsOpen.self}
8585
onToggle={this._onSelfToggle}
86+
headerActions={<WingViewToggle wing="self" />}
8687
>
8788
<SelfWing />
8889
</DisclosureBox>

src/components/calltree/SelfWing.tsx

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@
44
// @flow
55

66
import * as React from 'react';
7+
import memoize from 'memoize-immutable';
78

89
import explicitConnect from 'firefox-profiler/utils/connect';
910
import { FlameGraph } from 'firefox-profiler/components/flame-graph/FlameGraph';
11+
import { TreeView } from 'firefox-profiler/components/shared/TreeView';
12+
import { CallTreeEmptyReasons } from './CallTreeEmptyReasons';
13+
import {
14+
treeColumnsForTracingMs,
15+
treeColumnsForSamples,
16+
treeColumnsForBytes,
17+
} from './columns';
1018

1119
import {
1220
getCategories,
@@ -16,16 +24,22 @@ import {
1624
getProfileInterval,
1725
getInnerWindowIDToPageMap,
1826
getProfileUsesMultipleStackTypes,
27+
getCurrentTableViewOptions,
28+
getPreviewSelectionIsBeingModified,
1929
} from 'firefox-profiler/selectors/profile';
2030
import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread';
2131
import {
2232
getSelectedThreadsKey,
2333
getInvertCallstack,
34+
getSearchStringsAsRegExp,
35+
getSelfWingView,
2436
} from 'firefox-profiler/selectors/url-state';
2537
import {
2638
updateBottomBoxContentsAndMaybeOpen,
2739
changeRightClickedFunctionIndex,
40+
changeTableViewOptions,
2841
} from 'firefox-profiler/actions/profile-view';
42+
import { assertExhaustiveCheck } from 'firefox-profiler/utils/types';
2943

3044
import type {
3145
Thread,
@@ -41,12 +55,19 @@ import type {
4155
InnerWindowID,
4256
Page,
4357
SampleCategoriesAndSubcategories,
58+
CallNodeDisplayData,
59+
TableViewOptions,
60+
WingViewType,
4461
} from 'firefox-profiler/types';
4562

4663
import type { FlameGraphTiming } from 'firefox-profiler/profile-logic/flame-graph';
4764
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';
4865
import type { CallTree } from 'firefox-profiler/profile-logic/call-tree';
4966

67+
import type {
68+
Column,
69+
MaybeResizableColumn,
70+
} from 'firefox-profiler/components/shared/TreeView';
5071
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
5172

5273
type StateProps = {
@@ -68,26 +89,62 @@ type StateProps = {
6889
readonly ctssSamples: SamplesLikeTable;
6990
readonly ctssSampleCategoriesAndSubcategories: SampleCategoriesAndSubcategories;
7091
readonly displayStackType: boolean;
92+
readonly searchStringsRegExp: RegExp | null;
93+
readonly disableOverscan: boolean;
94+
readonly tableViewOptions: TableViewOptions;
95+
readonly view: WingViewType;
7196
};
7297

7398
type DispatchProps = {
7499
readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen;
75100
readonly changeRightClickedFunctionIndex: typeof changeRightClickedFunctionIndex;
101+
readonly onTableViewOptionsChange: (options: TableViewOptions) => any;
76102
};
77103

78104
type Props = ConnectedProps<{}, StateProps, DispatchProps>;
79105

80106
type LocalState = {
81107
selectedCallNodeIndex: IndexIntoCallNodeTable | null;
82108
rightClickedCallNodeIndex: IndexIntoCallNodeTable | null;
109+
expandedCallNodeIndexes: Array<IndexIntoCallNodeTable | null>;
83110
};
84111

85112
class SelfWingImpl extends React.PureComponent<Props, LocalState> {
86113
override state: LocalState = {
87114
selectedCallNodeIndex: null,
88115
rightClickedCallNodeIndex: null,
116+
expandedCallNodeIndexes: [],
89117
};
90118

119+
_mainColumn: Column<CallNodeDisplayData> = {
120+
propName: 'name',
121+
titleL10nId: '',
122+
};
123+
_appendageColumn: Column<CallNodeDisplayData> = {
124+
propName: 'lib',
125+
titleL10nId: '',
126+
};
127+
_treeView: TreeView<CallNodeDisplayData> | null = null;
128+
_takeTreeViewRef = (treeView: TreeView<CallNodeDisplayData> | null) => {
129+
this._treeView = treeView;
130+
};
131+
132+
_weightTypeToColumns = memoize(
133+
(weightType: WeightType): MaybeResizableColumn<CallNodeDisplayData>[] => {
134+
switch (weightType) {
135+
case 'tracing-ms':
136+
return treeColumnsForTracingMs;
137+
case 'samples':
138+
return treeColumnsForSamples;
139+
case 'bytes':
140+
return treeColumnsForBytes;
141+
default:
142+
throw assertExhaustiveCheck(weightType, 'Unhandled WeightType.');
143+
}
144+
},
145+
{ cache: new Map() }
146+
);
147+
91148
override componentDidUpdate(prevProps: Props, _prevState: LocalState) {
92149
// Reset local selection when the call node info changes (e.g. different
93150
// function selected) since old call node indices are no longer valid.
@@ -98,6 +155,7 @@ class SelfWingImpl extends React.PureComponent<Props, LocalState> {
98155
this.setState({
99156
selectedCallNodeIndex: null,
100157
rightClickedCallNodeIndex: null,
158+
expandedCallNodeIndexes: [],
101159
});
102160
}
103161
}
@@ -119,6 +177,30 @@ class SelfWingImpl extends React.PureComponent<Props, LocalState> {
119177
changeRightClickedFunctionIndex(threadsKey, funcIndex);
120178
};
121179

180+
_onTreeViewSelectionChange = (
181+
callNodeIndex: IndexIntoCallNodeTable,
182+
context: { source: 'keyboard' | 'pointer' }
183+
) => {
184+
this.setState({ selectedCallNodeIndex: callNodeIndex }, () => {
185+
// Selection in this wing is local state, so the Redux-driven
186+
// scrollToSelectionGeneration mechanism used by the other wings does not
187+
// apply. Scroll directly when keyboard navigation moves the selection.
188+
if (context.source === 'keyboard' && this._treeView) {
189+
this._treeView.scrollSelectionIntoView();
190+
}
191+
});
192+
};
193+
194+
_onTreeViewRightClickSelection = (callNodeIndex: IndexIntoCallNodeTable) => {
195+
this._onRightClickedCallNodeChange(callNodeIndex);
196+
};
197+
198+
_onTreeViewExpandedNodesChange = (
199+
expandedCallNodeIndexes: Array<IndexIntoCallNodeTable | null>
200+
) => {
201+
this.setState({ expandedCallNodeIndexes });
202+
};
203+
122204
_onCallNodeEnterOrDoubleClick = (
123205
callNodeIndex: IndexIntoCallNodeTable | null
124206
) => {
@@ -137,7 +219,7 @@ class SelfWingImpl extends React.PureComponent<Props, LocalState> {
137219
_nodeIndex: IndexIntoCallNodeTable
138220
) => {};
139221

140-
override render() {
222+
_renderFlameGraph() {
141223
const {
142224
thread,
143225
threadsKey,
@@ -194,6 +276,58 @@ class SelfWingImpl extends React.PureComponent<Props, LocalState> {
194276
/>
195277
);
196278
}
279+
280+
_renderCallTree() {
281+
const {
282+
callTree,
283+
searchStringsRegExp,
284+
disableOverscan,
285+
maxStackDepthPlusOne,
286+
weightType,
287+
tableViewOptions,
288+
onTableViewOptionsChange,
289+
} = this.props;
290+
const {
291+
selectedCallNodeIndex,
292+
rightClickedCallNodeIndex,
293+
expandedCallNodeIndexes,
294+
} = this.state;
295+
if (callTree.getRoots().length === 0) {
296+
return <CallTreeEmptyReasons />;
297+
}
298+
return (
299+
<TreeView
300+
tree={callTree}
301+
fixedColumns={this._weightTypeToColumns(weightType)}
302+
mainColumn={this._mainColumn}
303+
appendageColumn={this._appendageColumn}
304+
onSelectionChange={this._onTreeViewSelectionChange}
305+
onRightClickSelection={this._onTreeViewRightClickSelection}
306+
onExpandedNodesChange={this._onTreeViewExpandedNodesChange}
307+
ref={this._takeTreeViewRef}
308+
selectedNodeId={selectedCallNodeIndex}
309+
rightClickedNodeId={rightClickedCallNodeIndex}
310+
expandedNodeIds={expandedCallNodeIndexes}
311+
highlightRegExp={searchStringsRegExp}
312+
disableOverscan={disableOverscan}
313+
contextMenuId="FunctionListContextMenu"
314+
maxNodeDepth={maxStackDepthPlusOne}
315+
rowHeight={16}
316+
indentWidth={10}
317+
onEnterKey={this._onCallNodeEnterOrDoubleClick}
318+
onDoubleClick={this._onCallNodeEnterOrDoubleClick}
319+
viewOptions={tableViewOptions}
320+
onViewOptionsChange={onTableViewOptionsChange}
321+
/>
322+
);
323+
}
324+
325+
override render() {
326+
if (this.props.view === 'call-tree') {
327+
return this._renderCallTree();
328+
}
329+
return this._renderFlameGraph();
330+
}
197331
}
198332

199333
export const SelfWing = explicitConnect<{}, StateProps, DispatchProps>({
@@ -222,10 +356,16 @@ export const SelfWing = explicitConnect<{}, StateProps, DispatchProps>({
222356
state
223357
),
224358
displayStackType: getProfileUsesMultipleStackTypes(state),
359+
searchStringsRegExp: getSearchStringsAsRegExp(state),
360+
disableOverscan: getPreviewSelectionIsBeingModified(state),
361+
tableViewOptions: getCurrentTableViewOptions(state),
362+
view: getSelfWingView(state),
225363
}),
226364
mapDispatchToProps: {
227365
updateBottomBoxContentsAndMaybeOpen,
228366
changeRightClickedFunctionIndex,
367+
onTableViewOptionsChange: (options: TableViewOptions) =>
368+
changeTableViewOptions('calltree', options),
229369
},
230370
component: SelfWingImpl,
231371
});

src/components/calltree/WingViewToggle.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import explicitConnect from 'firefox-profiler/utils/connect';
88
import {
99
getUpperWingView,
1010
getLowerWingView,
11+
getSelfWingView,
1112
} from 'firefox-profiler/selectors/url-state';
1213
import { changeWingView } from 'firefox-profiler/actions/profile-view';
14+
import { assertExhaustiveCheck } from 'firefox-profiler/utils/types';
1315

1416
import type { State, WingViewType } from 'firefox-profiler/types';
1517
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
1618

1719
import './WingViewToggle.css';
1820

1921
type OwnProps = {
20-
readonly wing: 'upper' | 'lower';
22+
readonly wing: 'upper' | 'lower' | 'self';
2123
};
2224

2325
type StateProps = {
@@ -97,12 +99,23 @@ export const WingViewToggle = explicitConnect<
9799
StateProps,
98100
DispatchProps
99101
>({
100-
mapStateToProps: (state: State, ownProps: OwnProps) => ({
101-
view:
102-
ownProps.wing === 'upper'
103-
? getUpperWingView(state)
104-
: getLowerWingView(state),
105-
}),
102+
mapStateToProps: (state: State, ownProps: OwnProps) => {
103+
let view;
104+
switch (ownProps.wing) {
105+
case 'upper':
106+
view = getUpperWingView(state);
107+
break;
108+
case 'lower':
109+
view = getLowerWingView(state);
110+
break;
111+
case 'self':
112+
view = getSelfWingView(state);
113+
break;
114+
default:
115+
throw assertExhaustiveCheck(ownProps.wing, 'Unhandled wing.');
116+
}
117+
return { view };
118+
},
106119
mapDispatchToProps: {
107120
changeWingView,
108121
},

src/reducers/url-state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ const functionListSectionsOpen: Reducer<FunctionListSectionsOpenState> = (
242242
const WING_VIEWS_DEFAULT: WingViewsState = {
243243
upper: 'flame-graph',
244244
lower: 'flame-graph',
245+
self: 'flame-graph',
245246
};
246247

247248
const wingViews: Reducer<WingViewsState> = (

src/selectors/url-state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ export const getUpperWingView: Selector<WingViewType> = (state) =>
135135
getWingViews(state).upper;
136136
export const getLowerWingView: Selector<WingViewType> = (state) =>
137137
getWingViews(state).lower;
138+
export const getSelfWingView: Selector<WingViewType> = (state) =>
139+
getWingViews(state).self;
138140
export const getNetworkSearchString: Selector<string> = (state) =>
139141
getProfileSpecificState(state).networkSearchString;
140142
export const getSelectedTab: Selector<TabSlug> = (state) =>

src/types/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ type UrlStateAction =
588588
}
589589
| {
590590
readonly type: 'CHANGE_WING_VIEW';
591-
readonly wing: 'upper' | 'lower';
591+
readonly wing: 'upper' | 'lower' | 'self';
592592
readonly view: 'flame-graph' | 'call-tree';
593593
}
594594
| {

src/types/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ export type WingViewType = 'flame-graph' | 'call-tree';
425425
export type WingViewsState = {
426426
upper: WingViewType;
427427
lower: WingViewType;
428+
self: WingViewType;
428429
};
429430

430431
export type UrlState = {

0 commit comments

Comments
 (0)