Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 85f70ea

Browse files
committed
Move subscriptions out of RoomView constructor
Components are not 'cleaned up' (i.e. `componentWillUnmount` called) unless `componentDidMount` was called. React doesn't expect to have to simulate an unmount if the component was never mounted to begin with. In the case of RoomView, that means that the subscriptions created in the constructor are never removed, i.e. a leak. This moves the subscriptions to the `componentDidMount` method. See element-hq/element-web#24236 (comment) Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
1 parent 5cba710 commit 85f70ea

File tree

1 file changed

+73
-67
lines changed

1 file changed

+73
-67
lines changed

src/components/structures/RoomView.tsx

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
/*
2-
Copyright 2015, 2016 OpenMarket Ltd
3-
Copyright 2017 Vector Creations Ltd
4-
Copyright 2018, 2019 New Vector Ltd
5-
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
2+
Copyright 2015, 2017 - 2023 The Matrix.org Foundation C.I.C.
63
74
Licensed under the Apache License, Version 2.0 (the "License");
85
you may not use this file except in compliance with the License.
@@ -33,6 +30,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto";
3330
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
3431
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
3532
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
33+
import { IRoomTimelineData } from "matrix-js-sdk/src/matrix";
3634

3735
import shouldHideEvent from "../../shouldHideEvent";
3836
import { _t } from "../../languageHandler";
@@ -360,8 +358,8 @@ function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement
360358
}
361359

362360
export class RoomView extends React.Component<IRoomProps, IRoomState> {
363-
private readonly dispatcherRef: string;
364-
private settingWatchers: string[];
361+
private dispatcherRef: string | undefined = undefined;
362+
private settingWatchers: string[] = [];
365363

366364
private unmounted = false;
367365
private permalinkCreators: Record<string, RoomPermalinkCreator> = {};
@@ -417,57 +415,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
417415
narrow: false,
418416
visibleDecryptionFailures: [],
419417
};
420-
421-
this.dispatcherRef = dis.register(this.onAction);
422-
context.client.on(ClientEvent.Room, this.onRoom);
423-
context.client.on(RoomEvent.Timeline, this.onRoomTimeline);
424-
context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
425-
context.client.on(RoomEvent.Name, this.onRoomName);
426-
context.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
427-
context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
428-
context.client.on(RoomEvent.MyMembership, this.onMyMembership);
429-
context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
430-
context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
431-
context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
432-
context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
433-
context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
434-
// Start listening for RoomViewStore updates
435-
context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
436-
437-
context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
438-
439-
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
440-
context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
441-
442-
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
443-
444-
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
445-
446-
this.settingWatchers = [
447-
SettingsStore.watchSetting("layout", null, (...[, , , value]) =>
448-
this.setState({ layout: value as Layout }),
449-
),
450-
SettingsStore.watchSetting("lowBandwidth", null, (...[, , , value]) =>
451-
this.setState({ lowBandwidth: value as boolean }),
452-
),
453-
SettingsStore.watchSetting("alwaysShowTimestamps", null, (...[, , , value]) =>
454-
this.setState({ alwaysShowTimestamps: value as boolean }),
455-
),
456-
SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) =>
457-
this.setState({ showTwelveHourTimestamps: value as boolean }),
458-
),
459-
SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) =>
460-
this.setState({ readMarkerInViewThresholdMs: value as number }),
461-
),
462-
SettingsStore.watchSetting("readMarkerOutOfViewThresholdMs", null, (...[, , , value]) =>
463-
this.setState({ readMarkerOutOfViewThresholdMs: value as number }),
464-
),
465-
SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[, , , value]) =>
466-
this.setState({ showHiddenEvents: value as boolean }),
467-
),
468-
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
469-
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
470-
];
471418
}
472419

473420
private onIsResizing = (resizing: boolean): void => {
@@ -621,7 +568,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
621568
}
622569

623570
// Add watchers for each of the settings we just looked up
624-
this.settingWatchers = this.settingWatchers.concat([
571+
this.settingWatchers.push(
625572
SettingsStore.watchSetting("showReadReceipts", roomId, (...[, , , value]) =>
626573
this.setState({ showReadReceipts: value as boolean }),
627574
),
@@ -637,7 +584,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
637584
SettingsStore.watchSetting("showDisplaynameChanges", roomId, (...[, , , value]) =>
638585
this.setState({ showDisplaynameChanges: value as boolean }),
639586
),
640-
]);
587+
);
641588

642589
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
643590
// Stop peeking because we have joined this room now
@@ -765,8 +712,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
765712
peekLoading: true,
766713
isPeeking: true, // this will change to false if peeking fails
767714
});
768-
this.context.client
769-
.peekInRoom(roomId)
715+
this.context
716+
.client!.peekInRoom(roomId)
770717
.then((room) => {
771718
if (this.unmounted) {
772719
return;
@@ -801,7 +748,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
801748
});
802749
} else if (room) {
803750
// Stop peeking because we have joined this room previously
804-
this.context.client.stopPeeking();
751+
this.context.client!.stopPeeking();
805752
this.setState({ isPeeking: false });
806753
}
807754
}
@@ -824,6 +771,59 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
824771
}
825772

826773
public componentDidMount(): void {
774+
const context = this.context;
775+
const client = context.client!;
776+
this.dispatcherRef = dis.register(this.onAction);
777+
client.on(ClientEvent.Room, this.onRoom);
778+
client.on(RoomEvent.Timeline, this.onRoomTimeline);
779+
client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
780+
client.on(RoomEvent.Name, this.onRoomName);
781+
client.on(RoomStateEvent.Events, this.onRoomStateEvents);
782+
client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
783+
client.on(RoomEvent.MyMembership, this.onMyMembership);
784+
client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
785+
client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
786+
client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
787+
client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
788+
client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
789+
// Start listening for RoomViewStore updates
790+
context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
791+
792+
context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
793+
794+
WidgetEchoStore!.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
795+
context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
796+
797+
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
798+
799+
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
800+
801+
this.settingWatchers = [
802+
SettingsStore.watchSetting("layout", null, (...[, , , value]) =>
803+
this.setState({ layout: value as Layout }),
804+
),
805+
SettingsStore.watchSetting("lowBandwidth", null, (...[, , , value]) =>
806+
this.setState({ lowBandwidth: value as boolean }),
807+
),
808+
SettingsStore.watchSetting("alwaysShowTimestamps", null, (...[, , , value]) =>
809+
this.setState({ alwaysShowTimestamps: value as boolean }),
810+
),
811+
SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) =>
812+
this.setState({ showTwelveHourTimestamps: value as boolean }),
813+
),
814+
SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) =>
815+
this.setState({ readMarkerInViewThresholdMs: value as number }),
816+
),
817+
SettingsStore.watchSetting("readMarkerOutOfViewThresholdMs", null, (...[, , , value]) =>
818+
this.setState({ readMarkerOutOfViewThresholdMs: value as number }),
819+
),
820+
SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[, , , value]) =>
821+
this.setState({ showHiddenEvents: value as boolean }),
822+
),
823+
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
824+
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
825+
];
826+
827827
this.onRoomViewStoreUpdate(true);
828828

829829
const call = this.getCallForRoom();
@@ -883,7 +883,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
883883
// stop tracking room changes to format permalinks
884884
this.stopAllPermalinkCreators();
885885

886-
dis.unregister(this.dispatcherRef);
886+
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
887887
if (this.context.client) {
888888
this.context.client.removeListener(ClientEvent.Room, this.onRoom);
889889
this.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
@@ -1106,11 +1106,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
11061106
};
11071107

11081108
private onLocalRoomEvent(roomId: string): void {
1109-
if (roomId !== this.state.room.roomId) return;
1110-
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
1109+
if (roomId !== this.state.room?.roomId) return;
1110+
createRoomFromLocalRoom(this.context.client!, this.state.room as LocalRoom);
11111111
}
11121112

1113-
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => {
1113+
private onRoomTimeline = (
1114+
ev: MatrixEvent,
1115+
room: Room | undefined,
1116+
toStartOfTimeline: boolean | undefined,
1117+
_removed: boolean,
1118+
data: IRoomTimelineData,
1119+
): void => {
11141120
if (this.unmounted) return;
11151121

11161122
// ignore events for other rooms or the notification timeline set
@@ -1371,7 +1377,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
13711377
this.updateRoomMembers();
13721378
};
13731379

1374-
private onMyMembership = (room: Room, membership: string, oldMembership: string): void => {
1380+
private onMyMembership = (room: Room): void => {
13751381
if (room.roomId === this.state.roomId) {
13761382
this.forceUpdate();
13771383
this.loadMembersIfJoined(room);

0 commit comments

Comments
 (0)