diff --git a/.eslintrc.js b/.eslintrc.js
index 86ec975d87..e1f1b2bcfd 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -46,11 +46,6 @@ module.exports = {
// See https://github.com/yannickcr/eslint-plugin-react/issues/1561
// 'react/no-unused-prop-types': 'error',
'react/no-unused-state': 'error',
- 'react/prefer-stateless-function': 'error',
- 'react/prefer-stateless-function': [
- 'error',
- { ignorePureComponents: true },
- ],
'react/jsx-no-bind': 'error',
'flowtype/require-valid-file-annotation': [ 'error', 'always', { annotationStyle: 'line' } ],
// no-dupe-keys crashes with recent eslint. See
diff --git a/res/css/categories.css b/res/css/categories.css
new file mode 100644
index 0000000000..64559a8954
--- /dev/null
+++ b/res/css/categories.css
@@ -0,0 +1,52 @@
+/* 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/. */
+
+/**
+ * This class should be used to create a small color swatch to describe a
+ * category. It is a global css class as it can be used across the project.
+ */
+.category-swatch {
+ display: inline-block;
+ box-sizing: border-box;
+ width: 9px;
+ height: 9px;
+ border: 0.5px solid rgba(0, 0, 0, 0.1);
+ margin-right: 3px;
+}
+
+.category-swatch.category-color-transparent {
+ background-color: transparent;
+}
+
+.category-swatch.category-color-purple {
+ background-color: var(--purple-70);
+}
+
+.category-swatch.category-color-green {
+ background-color: var(--green-60);
+}
+
+.category-swatch.category-color-orange {
+ background-color: var(--orange-50);
+}
+
+.category-swatch.category-color-yellow {
+ background-color: var(--yellow-50);
+}
+
+.category-swatch.category-color-lightblue {
+ background-color: var(--blue-40);
+}
+
+.category-swatch.category-color-grey {
+ background-color: var(--grey-30);
+}
+
+.category-swatch.category-color-blue {
+ background-color: var(--blue-60);
+}
+
+.category-swatch.category-color-brown {
+ background-color: var(--magenta-60);
+}
diff --git a/res/css/style.css b/res/css/style.css
index 40d8b99d2f..bce4edda0c 100644
--- a/res/css/style.css
+++ b/res/css/style.css
@@ -197,42 +197,6 @@ body,
color: highlighttext;
}
-.treeViewCategoryKnob {
- display: inline-block;
- box-sizing: border-box;
- width: 9px;
- height: 9px;
- border: 0.5px solid rgba(0, 0, 0, 0.1);
- margin-right: 3px;
-}
-.treeViewCategoryKnob.category-color-transparent {
- background-color: transparent;
-}
-.treeViewCategoryKnob.category-color-purple {
- background-color: var(--purple-70);
-}
-.treeViewCategoryKnob.category-color-green {
- background-color: var(--green-60);
-}
-.treeViewCategoryKnob.category-color-orange {
- background-color: var(--orange-50);
-}
-.treeViewCategoryKnob.category-color-yellow {
- background-color: var(--yellow-50);
-}
-.treeViewCategoryKnob.category-color-lightblue {
- background-color: var(--blue-40);
-}
-.treeViewCategoryKnob.category-color-grey {
- background-color: var(--grey-30);
-}
-.treeViewCategoryKnob.category-color-blue {
- background-color: var(--blue-60);
-}
-.treeViewCategoryKnob.category-color-brown {
- background-color: var(--magenta-60);
-}
-
.treeViewHighlighting {
/* This negative margin enlarges the background to the top, so that it fully
* covers the underlying background. There's an underlying background when the
diff --git a/src/components/shared/Backtrace.css b/src/components/shared/Backtrace.css
index 01ee5e1a70..7c8ebfb0a8 100644
--- a/src/components/shared/Backtrace.css
+++ b/src/components/shared/Backtrace.css
@@ -1,5 +1,4 @@
.backtrace {
- --max-height: 30em;
--gradient-height: calc(var(--max-height) - 5em);
margin: 5px;
diff --git a/src/components/shared/Backtrace.js b/src/components/shared/Backtrace.js
index e9b1061dd6..01d259fcf4 100644
--- a/src/components/shared/Backtrace.js
+++ b/src/components/shared/Backtrace.js
@@ -11,29 +11,29 @@ import {
convertStackToCallNodePath,
} from '../../profile-logic/profile-data';
-import type { Thread } from '../../types/profile';
-import type { CauseBacktrace } from '../../types/markers';
+import type { Thread, IndexIntoStackTable } from '../../types/profile';
import type { ImplementationFilter } from '../../types/actions';
require('./Backtrace.css');
type Props = {|
+thread: Thread,
- +cause: CauseBacktrace,
+ +maxHeight: string | number,
+ +stackIndex: IndexIntoStackTable,
+implementationFilter: ImplementationFilter,
|};
function Backtrace(props: Props) {
- const { cause, thread, implementationFilter } = props;
+ const { stackIndex, thread, implementationFilter, maxHeight } = props;
const { funcTable, stringTable } = thread;
const callNodePath = filterCallNodePathByImplementation(
thread,
implementationFilter,
- convertStackToCallNodePath(thread, cause.stack)
+ convertStackToCallNodePath(thread, stackIndex)
);
return (
-
+
{callNodePath.length > 0 ? (
callNodePath.map((func, i) => (
-
diff --git a/src/components/shared/MarkerTooltipContents.js b/src/components/shared/MarkerTooltipContents.js
index c604c56239..8f6b4f3eb6 100644
--- a/src/components/shared/MarkerTooltipContents.js
+++ b/src/components/shared/MarkerTooltipContents.js
@@ -344,7 +344,8 @@ function _markerBacktrace(
First invalidated {formatNumber(causeAge)}ms before the flush, at:
diff --git a/src/components/shared/SampleTooltipContents.js b/src/components/shared/SampleTooltipContents.js
new file mode 100644
index 0000000000..6495df81f3
--- /dev/null
+++ b/src/components/shared/SampleTooltipContents.js
@@ -0,0 +1,58 @@
+/* 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/. */
+// @flow
+
+import * as React from 'react';
+import type {
+ IndexIntoSamplesTable,
+ CategoryList,
+ Thread,
+} from '../../types/profile';
+import Backtrace from './Backtrace';
+
+type Props = {|
+ +sampleIndex: IndexIntoSamplesTable,
+ +categories: CategoryList,
+ +fullThread: Thread,
+|};
+
+/**
+ * This class displays the tooltip contents for a given sample. Typically the user
+ * will want to know what the function is, and its category.
+ */
+export default class SampleTooltipContents extends React.PureComponent {
+ render() {
+ const { sampleIndex, fullThread, categories } = this.props;
+ const { samples, stackTable } = fullThread;
+ const stackIndex = samples.stack[sampleIndex];
+ if (stackIndex === null) {
+ return 'No stack information';
+ }
+ const categoryIndex = stackTable.category[stackIndex];
+ const category = categories[categoryIndex];
+
+ return (
+ <>
+
+
Category:
+
+
+ {category.name}
+
+
+
+
+ >
+ );
+ }
+}
diff --git a/src/components/shared/TreeView.js b/src/components/shared/TreeView.js
index eb58f11af8..fba9381a3d 100644
--- a/src/components/shared/TreeView.js
+++ b/src/components/shared/TreeView.js
@@ -247,7 +247,7 @@ class TreeViewRowScrolledColumns<
>
{displayData.categoryColor && displayData.categoryName ? (
number,
|};
-class ThreadActivityGraph extends PureComponent {
+type State = {
+ hoveredSample: null | IndexIntoSamplesTable,
+ mouseX: CssPixels,
+ mouseY: CssPixels,
+};
+
+class ThreadActivityGraph extends React.PureComponent {
_canvas: null | HTMLCanvasElement = null;
_resizeListener = () => this.forceUpdate();
_categoryDrawStyles: null | CategoryDrawStyles = null;
_fillsQuerier: null | ActivityFillGraphQuerier = null;
+ state = {
+ hoveredSample: null,
+ mouseX: 0,
+ mouseY: 0,
+ };
+
+ _onMouseLeave = () => {
+ this.setState({ hoveredSample: null });
+ };
+
+ _onMouseMove = (event: SyntheticMouseEvent) => {
+ const canvas = this._canvas;
+ if (!canvas) {
+ return;
+ }
+ const rect = canvas.getBoundingClientRect();
+ this.setState({
+ hoveredSample: this._getSampleAtMouseEvent(event),
+ mouseX: event.pageX,
+ // Have the tooltip align to the bottom of the track.
+ mouseY: rect.bottom - MOUSE_OFFSET,
+ });
+ };
+
_takeCanvasRef = (canvas: HTMLCanvasElement | null) => {
this._canvas = canvas;
};
@@ -186,21 +218,27 @@ class ThreadActivityGraph extends PureComponent {
}
}
- _onMouseUp = (e: SyntheticMouseEvent<>) => {
+ _getSampleAtMouseEvent(
+ event: SyntheticMouseEvent<>
+ ): null | IndexIntoSamplesTable {
// Create local variables so that Flow can refine the following to be non-null.
const fillsQuerier = this._fillsQuerier;
const canvas = this._canvas;
if (!canvas || !fillsQuerier) {
- return;
+ return null;
}
// Re-measure the canvas and get the coordinates and time for the click.
const { rangeStart, rangeEnd } = this.props;
const rect = canvas.getBoundingClientRect();
- const x = e.pageX - rect.left;
- const y = e.pageY - rect.top;
+ const x = event.pageX - rect.left;
+ const y = event.pageY - rect.top;
const time = rangeStart + x / rect.width * (rangeEnd - rangeStart);
- const sample = fillsQuerier.getSampleAtClick(x, y, time, rect);
+ return fillsQuerier.getSampleAtClick(x, y, time, rect);
+ }
+
+ _onMouseUp = (event: SyntheticMouseEvent<>) => {
+ const sample = this._getSampleAtMouseEvent(event);
if (sample !== null) {
this.props.onSampleClick(sample);
}
@@ -208,8 +246,14 @@ class ThreadActivityGraph extends PureComponent {
render() {
this._renderCanvas();
+ const { fullThread, categories } = this.props;
+ const { hoveredSample, mouseX, mouseY } = this.state;
return (
-
+
);
}
diff --git a/src/index.js b/src/index.js
index 27c073843a..a11e30f8e5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,6 +9,7 @@ import Root from './components/app/Root';
import createStore from './app-logic/create-store';
import 'photon-colors/photon-colors.css';
import '../res/css/style.css';
+import '../res/css/categories.css';
import {
addDataToWindowObject,
logFriendlyPreamble,
diff --git a/src/test/components/__snapshots__/GlobalTrack.test.js.snap b/src/test/components/__snapshots__/GlobalTrack.test.js.snap
index 525109284d..5d14e21cd7 100644
--- a/src/test/components/__snapshots__/GlobalTrack.test.js.snap
+++ b/src/test/components/__snapshots__/GlobalTrack.test.js.snap
@@ -59,6 +59,8 @@ process: \\"tab\\" (222)"