ui: Backport fix for Track outliving its state
Bug: 196965537
Change-Id: Ia83cbec491a5cac69f3858f2468ecff33157115b
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 5ad8bdf..30a0d4c 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -13,6 +13,7 @@
// limitations under the License.
import * as m from 'mithril';
+import {assertExists} from '../base/logging';
import {TrackState} from '../common/state';
import {TrackData} from '../common/track_data';
import {checkerboard} from './checkerboard';
@@ -54,19 +55,37 @@
* The abstract class that needs to be implemented by all tracks.
*/
export abstract class Track<Config = {}, Data extends TrackData = TrackData> {
+ // The UI-generated track ID (not to be confused with the SQL track.id).
private trackId: string;
+
+ // Caches the last state.track[this.trackId]. This is to deal with track
+ // deletion, see comments in trackState() below.
+ private lastTrackState: TrackState;
+
constructor(trackId: string) {
this.trackId = trackId;
+ this.lastTrackState = assertExists(globals.state.tracks[this.trackId]);
}
protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
protected get trackState(): TrackState {
- return globals.state.tracks[this.trackId];
+ // We can end up in a state where a Track is still in the mithril renderer
+ // tree but its corresponding state has been deleted. This can happen in the
+ // interval of time between a track being removed from the state and the
+ // next animation frame that would remove the Track object. If a mouse event
+ // is dispatched in the meanwhile (or a promise is resolved), we need to be
+ // able to access the state. Hence the caching logic here.
+ const trackState = globals.state.tracks[this.trackId];
+ if (trackState === undefined) {
+ return this.lastTrackState;
+ }
+ this.lastTrackState = trackState;
+ return trackState;
}
get config(): Config {
- return globals.state.tracks[this.trackId].config as Config;
+ return this.trackState.config as Config;
}
data(): Data|undefined {