Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions locales/en-US/app.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,20 @@ AssemblyView--show-button =
AssemblyView--hide-button =
.title = Hide the assembly view
# The "◀" button above the assembly view.
AssemblyView--prev-button =
.title = Previous
# The "▶" button above the assembly view.
AssemblyView--next-button =
.title = Next
# The label showing the current position and total count above the assembly view.
# Variables:
# $current (Number) - The current position (1-indexed).
# $total (Number) - The total count.
AssemblyView--position-label = { $current } of { $total }
## UploadedRecordingsHome
## This is the page that displays all the profiles that user has uploaded.
## See: https://profiler.firefox.com/uploaded-recordings/
Expand Down
9 changes: 9 additions & 0 deletions src/actions/profile-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,15 @@ export function openAssemblyView(): Action {
};
}

export function changeAssemblyViewNativeSymbolEntryIndex(
entryIndex: number
): Action {
return {
type: 'CHANGE_ASSEMBLY_VIEW_NATIVE_SYMBOL_ENTRY_INDEX',
entryIndex,
};
}

export function closeAssemblyView(): Action {
return {
type: 'CLOSE_ASSEMBLY_VIEW',
Expand Down
119 changes: 119 additions & 0 deletions src/components/app/AssemblyViewNativeSymbolNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import React from 'react';
import classNames from 'classnames';

import {
getAssemblyViewCurrentNativeSymbolEntryIndex,
getAssemblyViewNativeSymbolEntryCount,
} from 'firefox-profiler/selectors/url-state';
import { changeAssemblyViewNativeSymbolEntryIndex } from 'firefox-profiler/actions/profile-view';
import explicitConnect from 'firefox-profiler/utils/connect';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';

import { Localized } from '@fluent/react';

type StateProps = {
readonly assemblyViewCurrentNativeSymbolEntryIndex: number | null;
readonly assemblyViewNativeSymbolEntryCount: number;
};

type DispatchProps = {
readonly changeAssemblyViewNativeSymbolEntryIndex: typeof changeAssemblyViewNativeSymbolEntryIndex;
};

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

class AssemblyViewNativeSymbolNavigatorImpl extends React.PureComponent<Props> {
_onPreviousClick = () => {
this._changeIndexBy(-1);
};

_onNextClick = () => {
this._changeIndexBy(1);
};

_changeIndexBy(delta: number) {
const {
assemblyViewCurrentNativeSymbolEntryIndex: index,
assemblyViewNativeSymbolEntryCount: count,
changeAssemblyViewNativeSymbolEntryIndex,
} = this.props;
const newIndex = (index ?? 0) + delta;
if (newIndex >= 0 && newIndex < count) {
changeAssemblyViewNativeSymbolEntryIndex(newIndex);
}
}

override render() {
const {
assemblyViewCurrentNativeSymbolEntryIndex: index,
assemblyViewNativeSymbolEntryCount: count,
} = this.props;

if (index === null || count <= 1) {
return null;
}

return (
<>
<Localized
id="AssemblyView--position-label"
vars={{ current: index + 1, total: count }}
>
<h3 className="bottom-box-title-trailer" />
</Localized>
<Localized id="AssemblyView--prev-button" attrs={{ title: true }}>
<button
className={classNames(
'bottom-prev-button',
'photon-button',
'photon-button-ghost'
)}
title="Previous"
type="button"
disabled={index <= 0}
onClick={this._onPreviousClick}
>
</button>
</Localized>
<Localized id="AssemblyView--next-button" attrs={{ title: true }}>
<button
className={classNames(
'bottom-next-button',
'photon-button',
'photon-button-ghost'
)}
title="Next"
type="button"
disabled={index >= count - 1}
onClick={this._onNextClick}
>
</button>
</Localized>
</>
);
}
}

export const AssemblyViewNativeSymbolNavigator = explicitConnect<
{},
StateProps,
DispatchProps
>({
mapStateToProps: (state) => ({
assemblyViewCurrentNativeSymbolEntryIndex:
getAssemblyViewCurrentNativeSymbolEntryIndex(state),
assemblyViewNativeSymbolEntryCount:
getAssemblyViewNativeSymbolEntryCount(state),
}),
mapDispatchToProps: {
changeAssemblyViewNativeSymbolEntryIndex,
},
component: AssemblyViewNativeSymbolNavigatorImpl,
});
21 changes: 19 additions & 2 deletions src/components/app/BottomBox.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,19 @@
line-height: 18px;
}

.bottom-box-title {
.bottom-box-title,
.bottom-box-title-trailer {
overflow: hidden;
margin: 0 8px;
font: inherit;
text-overflow: ellipsis;
white-space: nowrap;
}

.bottom-box-title-trailer {
margin: 0;
}

.bottom-box-header-trailing-buttons {
display: flex;
height: 100%;
Expand All @@ -67,7 +72,9 @@
}

.bottom-close-button,
.bottom-assembly-button {
.bottom-assembly-button,
.bottom-prev-button,
.bottom-next-button {
width: 24px;
height: 24px;
flex-shrink: 0;
Expand All @@ -76,6 +83,16 @@
background-size: 16px 16px;
}

.bottom-prev-button,
.bottom-next-button {
width: 16px;
font-size: 9px;
}

.bottom-next-button {
margin-right: 8px;
}

.bottom-close-button {
background-image: var(--internal-close-icon);
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/app/BottomBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import classNames from 'classnames';
import { SourceView } from '../shared/SourceView';
import { AssemblyView } from '../shared/AssemblyView';
import { AssemblyViewToggleButton } from './AssemblyViewToggleButton';
import { AssemblyViewNativeSymbolNavigator } from './AssemblyViewNativeSymbolNavigator';
import { IonGraphView } from '../shared/IonGraphView';
import { CodeLoadingOverlay } from './CodeLoadingOverlay';
import { CodeErrorOverlay } from './CodeErrorOverlay';
Expand Down Expand Up @@ -192,6 +193,7 @@ class BottomBoxImpl extends React.PureComponent<Props> {
// These trailing header buttons go into the bottom-box-bar of the last pane.
const trailingHeaderButtons = (
<div className="bottom-box-header-trailing-buttons">
{assemblyViewIsOpen ? <AssemblyViewNativeSymbolNavigator /> : null}
<AssemblyViewToggleButton />
<Localized id="SourceView--close-button" attrs={{ title: true }}>
<button
Expand Down
6 changes: 6 additions & 0 deletions src/reducers/url-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,12 @@ const assemblyView: Reducer<AssemblyViewState> = (
isOpen: true,
};
}
case 'CHANGE_ASSEMBLY_VIEW_NATIVE_SYMBOL_ENTRY_INDEX': {
return {
...state,
currentNativeSymbol: action.entryIndex,
};
}
case 'CLOSE_ASSEMBLY_VIEW': {
return {
...state,
Expand Down
6 changes: 6 additions & 0 deletions src/selectors/url-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export const getAssemblyViewNativeSymbol: Selector<NativeSymbolInfo | null> = (
? nativeSymbols[currentNativeSymbol]
: null;
};
export const getAssemblyViewCurrentNativeSymbolEntryIndex: Selector<
number | null
> = (state) => getProfileSpecificState(state).assemblyView.currentNativeSymbol;
export const getAssemblyViewNativeSymbolEntryCount: Selector<number> = (
state
) => getProfileSpecificState(state).assemblyView.nativeSymbols.length;
export const getAssemblyViewScrollGeneration: Selector<number> = (state) =>
getProfileSpecificState(state).assemblyView.scrollGeneration;
export const getAssemblyViewScrollToInstructionAddress: Selector<
Expand Down
59 changes: 58 additions & 1 deletion src/test/components/BottomBox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('BottomBox', () => {
);

const { profile } = getProfileFromTextSamples(`
A[file:hg:hg.mozilla.org/mozilla-central:${filepath}:${revision}][line:4][address:30][sym:Asym:20:1a][lib:libA.so]
A[file:hg:hg.mozilla.org/mozilla-central:${filepath}:${revision}][line:4][address:30][sym:Asym:20:1a][lib:libA.so] A[file:hg:hg.mozilla.org/mozilla-central:${filepath}:${revision}][line:4][address:70][sym:A2sym:60:1a][lib:libA.so]
B[file:git:github.com/rust-lang/rust:library/std/src/sys/unix/thread.rs:53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b]
C[lib:libC.so][file:s3:gecko-generated-sources:a5d3747707d6877b0e5cb0a364e3cb9fea8aa4feb6ead138952c2ba46d41045297286385f0e0470146f49403e46bd266e654dfca986de48c230f3a71c2aafed4/ipc/ipdl/PBackgroundChild.cpp:]
D[lib:libD.so]
Expand Down Expand Up @@ -180,4 +180,61 @@ describe('BottomBox', () => {

expect(assemblyView()).not.toBeInTheDocument();
});

it('should navigate between symbols using prev/next buttons', async () => {
const { sourceView, assemblyView } = setup();

const frameElement = screen.getByRole('treeitem', { name: /^A/ });

fireFullClick(frameElement);
fireFullClick(frameElement, { detail: 2 });
expect(sourceView()).toBeInTheDocument();

const asmViewShowButton = ensureExists(
document.querySelector('.bottom-assembly-button')
) as HTMLElement;
fireFullClick(asmViewShowButton);

expect(assemblyView()).toBeInTheDocument();

// Verify we're showing "1 of 2"
const titleTrailer = await screen.findByText(
'\u20681\u2069 of \u20682\u2069'
);
expect(titleTrailer).toBeInTheDocument();

// Find the prev and next buttons
const prevButton = ensureExists(
document.querySelector('.bottom-prev-button')
) as HTMLButtonElement;
const nextButton = ensureExists(
document.querySelector('.bottom-next-button')
) as HTMLButtonElement;

// Initially, prev should be disabled and next should be enabled
expect(prevButton).toBeDisabled();
expect(nextButton).toBeEnabled();

// Click next to go to symbol 2
fireFullClick(nextButton);

// Now we should see "2 of 2"
expect(
await screen.findByText('\u20682\u2069 of \u20682\u2069')
).toBeInTheDocument();

// Now prev should be enabled and next should be disabled
expect(prevButton).toBeEnabled();
expect(nextButton).toBeDisabled();

// Click prev to go back to symbol 1
fireFullClick(prevButton);

// We should be back to "1 of 2"
expect(
await screen.findByText('\u20681\u2069 of \u20682\u2069')
).toBeInTheDocument();
expect(prevButton).toBeDisabled();
expect(nextButton).toBeEnabled();
});
});
4 changes: 4 additions & 0 deletions src/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ type ProfileAction =
| {
readonly type: 'OPEN_ASSEMBLY_VIEW';
}
| {
readonly type: 'CHANGE_ASSEMBLY_VIEW_NATIVE_SYMBOL_ENTRY_INDEX';
readonly entryIndex: number;
}
| {
readonly type: 'CLOSE_ASSEMBLY_VIEW';
}
Expand Down