From 62d3daf9ab396acfffe68eafdf77a78014e1762f Mon Sep 17 00:00:00 2001 From: Julien Wajsberg Date: Tue, 17 Jan 2023 17:44:30 +0100 Subject: [PATCH 1/4] Use createSelector for the getCounter selector, so that the result is memoized instead of being recomputed at each call --- src/selectors/profile.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/selectors/profile.js b/src/selectors/profile.js index 1d6614acd9..62a52b8bce 100644 --- a/src/selectors/profile.js +++ b/src/selectors/profile.js @@ -282,13 +282,14 @@ export const getCounterSelectors = (index: CounterIndex): CounterSelectors => { * type of the function. */ function _createCounterSelectors(counterIndex: CounterIndex) { - const getCounter: Selector = (state) => + const getCounter: Selector = createSelector(getProfile, (profile) => processCounter( ensureExists( - getProfile(state).counters, + profile.counters, 'Attempting to get a counter by index, but no counters exist.' )[counterIndex] - ); + ) + ); const getDescription: Selector = (state) => getCounter(state).description; From 638902b9f16bcf60fb2ed3e75fd52b305cd12180 Mon Sep 17 00:00:00 2001 From: Julien Wajsberg Date: Tue, 17 Jan 2023 17:44:57 +0100 Subject: [PATCH 2/4] Add a simple tool to log prop changes triggering an update --- src/utils/react.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/utils/react.js diff --git a/src/utils/react.js b/src/utils/react.js new file mode 100644 index 0000000000..54065c8c7e --- /dev/null +++ b/src/utils/react.js @@ -0,0 +1,37 @@ +/* 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 + +// This file contains some functions that might be interesting when debugging +// react code. + +// This function keeps track of the props of a React component and logs the +// changes between two calls. This is best used in render() or +// componentDidUpdate() like this: +// import { logPropChanges } from 'firefox-profiler/utils/react'; +// ... +// render() { +// logPropChanges(this); +// } +export function logPropChanges(component: any) { + if (!component.__DEBUG__oldProps) { + component.__DEBUG__oldProps = component.props; + return; + } + + for (const [prop, value] of Object.entries(component.props)) { + const oldValue = component.__DEBUG__oldProps[prop]; + if (oldValue !== value) { + console.log( + `prop "${prop}" changed: old value is`, + oldValue, + ', new value is', + value + ); + } + } + + component.__DEBUG__oldProps = component.props; +} From 5748c54c851b17dc76f3043097b367deaaa760be Mon Sep 17 00:00:00 2001 From: Julien Wajsberg Date: Tue, 17 Jan 2023 17:51:43 +0100 Subject: [PATCH 3/4] Use an Intersection Observer for the power graphs --- src/components/timeline/TrackPowerGraph.js | 32 ++++++++++++++++++- .../__snapshots__/TrackPower.test.js.snap | 12 ++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/components/timeline/TrackPowerGraph.js b/src/components/timeline/TrackPowerGraph.js index fb37584271..2428192311 100644 --- a/src/components/timeline/TrackPowerGraph.js +++ b/src/components/timeline/TrackPowerGraph.js @@ -5,6 +5,7 @@ // @flow import * as React from 'react'; +import { InView } from 'react-intersection-observer'; import { withSize } from 'firefox-profiler/components/shared/WithSize'; import explicitConnect from 'firefox-profiler/utils/connect'; import { bisectionRight } from 'firefox-profiler/utils/bisect'; @@ -58,6 +59,10 @@ type CanvasProps = {| class TrackPowerCanvas extends React.PureComponent { _canvas: null | HTMLCanvasElement = null; _requestedAnimationFrame: boolean = false; + _canvasState: {| renderScheduled: boolean, inView: boolean |} = { + renderScheduled: false, + inView: false, + }; drawCanvas(canvas: HTMLCanvasElement): void { const { @@ -175,6 +180,16 @@ class TrackPowerCanvas extends React.PureComponent { } _scheduleDraw() { + if (!this._canvasState.inView) { + // Canvas is not in the view. Schedule the render for a later intersection + // observer callback. + this._canvasState.renderScheduled = true; + return; + } + + // Canvas is in the view. Render the canvas and reset the schedule state. + this._canvasState.renderScheduled = false; + if (!this._requestedAnimationFrame) { this._requestedAnimationFrame = true; window.requestAnimationFrame(() => { @@ -191,11 +206,26 @@ class TrackPowerCanvas extends React.PureComponent { this._canvas = canvas; }; + _observerCallback = (inView: boolean, _entry: IntersectionObserverEntry) => { + this._canvasState.inView = inView; + if (!this._canvasState.renderScheduled) { + // Skip if render is not scheduled. + return; + } + + this._scheduleDraw(); + }; + render() { this._scheduleDraw(); return ( - + + + ); } } diff --git a/src/test/components/__snapshots__/TrackPower.test.js.snap b/src/test/components/__snapshots__/TrackPower.test.js.snap index 9c8b7ca90f..ff26e785a8 100644 --- a/src/test/components/__snapshots__/TrackPower.test.js.snap +++ b/src/test/components/__snapshots__/TrackPower.test.js.snap @@ -128,11 +128,13 @@ exports[`TrackPower matches the component snapshot 1`] = `
- +
+ +
From 73cd01bc448e355b51215569652da68b3a772853 Mon Sep 17 00:00:00 2001 From: Julien Wajsberg Date: Tue, 17 Jan 2023 18:50:29 +0100 Subject: [PATCH 4/4] Remove the requestAnimationFrame call --- src/components/timeline/TrackPowerGraph.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/timeline/TrackPowerGraph.js b/src/components/timeline/TrackPowerGraph.js index 2428192311..b930e1424c 100644 --- a/src/components/timeline/TrackPowerGraph.js +++ b/src/components/timeline/TrackPowerGraph.js @@ -58,7 +58,6 @@ type CanvasProps = {| */ class TrackPowerCanvas extends React.PureComponent { _canvas: null | HTMLCanvasElement = null; - _requestedAnimationFrame: boolean = false; _canvasState: {| renderScheduled: boolean, inView: boolean |} = { renderScheduled: false, inView: false, @@ -179,7 +178,7 @@ class TrackPowerCanvas extends React.PureComponent { } } - _scheduleDraw() { + _renderCanvas() { if (!this._canvasState.inView) { // Canvas is not in the view. Schedule the render for a later intersection // observer callback. @@ -190,15 +189,9 @@ class TrackPowerCanvas extends React.PureComponent { // Canvas is in the view. Render the canvas and reset the schedule state. this._canvasState.renderScheduled = false; - if (!this._requestedAnimationFrame) { - this._requestedAnimationFrame = true; - window.requestAnimationFrame(() => { - this._requestedAnimationFrame = false; - const canvas = this._canvas; - if (canvas) { - this.drawCanvas(canvas); - } - }); + const canvas = this._canvas; + if (canvas) { + this.drawCanvas(canvas); } } @@ -213,11 +206,11 @@ class TrackPowerCanvas extends React.PureComponent { return; } - this._scheduleDraw(); + this._renderCanvas(); }; render() { - this._scheduleDraw(); + this._renderCanvas(); return (