From f2249b3e375e4218e0ce2f0f786824d4a3302626 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Jan 2022 11:08:28 +0000 Subject: [PATCH] Fix CallEventGrouper map building to not occur during a Render phase (#7638) --- src/components/structures/CallEventGrouper.ts | 1 + src/components/structures/MessagePanel.tsx | 21 ++---------- src/components/structures/TimelinePanel.tsx | 34 ++++++++++++++++++- .../structures/MessagePanel-test.js | 12 ++++--- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/components/structures/CallEventGrouper.ts b/src/components/structures/CallEventGrouper.ts index cf5109c634..c439e69a26 100644 --- a/src/components/structures/CallEventGrouper.ts +++ b/src/components/structures/CallEventGrouper.ts @@ -170,6 +170,7 @@ export default class CallEventGrouper extends EventEmitter { }; public add(event: MatrixEvent) { + if (this.events.has(event)) return; // nothing to do this.events.add(event); this.setCall(); } diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index ca34dffa7d..5c3696ffb1 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -183,6 +183,8 @@ interface IProps { hideThreadedMessages?: boolean; disableGrouping?: boolean; + + callEventGroupers: Map; } interface IState { @@ -254,9 +256,6 @@ export default class MessagePanel extends React.Component { private readonly showTypingNotificationsWatcherRef: string; private eventTiles: Record = {}; - // A map of - private callEventGroupers = new Map(); - constructor(props, context) { super(props, context); @@ -650,20 +649,6 @@ export default class MessagePanel extends React.Component { const last = (mxEv === lastShownEvent); const { nextEvent, nextTile } = this.getNextEventInfo(this.props.events, i); - if ( - mxEv.getType().indexOf("m.call.") === 0 || - mxEv.getType().indexOf("org.matrix.call.") === 0 - ) { - const callId = mxEv.getContent().call_id; - if (this.callEventGroupers.has(callId)) { - this.callEventGroupers.get(callId).add(mxEv); - } else { - const callEventGrouper = new CallEventGrouper(); - callEventGrouper.add(mxEv); - this.callEventGroupers.set(callId, callEventGrouper); - } - } - if (grouper) { if (grouper.shouldGroup(mxEv)) { grouper.add(mxEv, this.showHiddenEvents); @@ -784,7 +769,7 @@ export default class MessagePanel extends React.Component { // it's successful: we received it. isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId(); - const callEventGrouper = this.callEventGroupers.get(mxEv.getContent().call_id); + const callEventGrouper = this.props.callEventGroupers.get(mxEv.getContent().call_id); // use txnId as key if available so that we don't remount during sending ret.push( diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index e9ed951f42..7ef0c4c44d 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -51,6 +51,7 @@ import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import Spinner from "../views/elements/Spinner"; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import ErrorDialog from '../views/dialogs/ErrorDialog'; +import CallEventGrouper from "./CallEventGrouper"; const PAGINATE_SIZE = 20; const INITIAL_SIZE = 20; @@ -237,6 +238,9 @@ class TimelinePanel extends React.Component { private readReceiptActivityTimer: Timer; private readMarkerActivityTimer: Timer; + // A map of + private callEventGroupers = new Map(); + constructor(props, context) { super(props, context); @@ -388,6 +392,7 @@ class TimelinePanel extends React.Component { this.timelineWindow.unpaginate(count, backwards); const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); + this.buildCallEventGroupers(events); const newState: Partial = { events, liveEvents, @@ -449,6 +454,7 @@ class TimelinePanel extends React.Component { debuglog("paginate complete backwards:"+backwards+"; success:"+r); const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); + this.buildCallEventGroupers(events); const newState: Partial = { [paginatingKey]: false, [canPaginateKey]: r, @@ -561,6 +567,7 @@ class TimelinePanel extends React.Component { if (this.unmounted) { return; } const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); + this.buildCallEventGroupers(events); const lastLiveEvent = liveEvents[liveEvents.length - 1]; const updatedState: Partial = { @@ -1231,6 +1238,7 @@ class TimelinePanel extends React.Component { canForwardPaginate: false, timelineLoading: true, }); + this.buildCallEventGroupers(); prom.then(onLoaded, onError); } } @@ -1243,7 +1251,9 @@ class TimelinePanel extends React.Component { // the results if so. if (this.unmounted) return; - this.setState(this.getEvents()); + const state = this.getEvents(); + this.buildCallEventGroupers(state.events); + this.setState(state); } // Force refresh the timeline before threads support pending events @@ -1512,6 +1522,27 @@ class TimelinePanel extends React.Component { eventType: EventType | string, ) => this.props.timelineSet.getRelationsForEvent(eventId, relationType, eventType); + private buildCallEventGroupers(events?: MatrixEvent[]): void { + const oldCallEventGroupers = this.callEventGroupers; + this.callEventGroupers = new Map(); + events?.forEach(ev => { + if (!ev.getType().startsWith("m.call.") && !ev.getType().startsWith("org.matrix.call.")) { + return; + } + + const callId = ev.getContent().call_id; + if (!this.callEventGroupers.has(callId)) { + if (oldCallEventGroupers.has(callId)) { + // reuse the CallEventGrouper object where possible + this.callEventGroupers.set(callId, oldCallEventGroupers.get(callId)); + } else { + this.callEventGroupers.set(callId, new CallEventGrouper()); + } + } + this.callEventGroupers.get(callId).add(ev); + }); + } + render() { // just show a spinner while the timeline loads. // @@ -1596,6 +1627,7 @@ class TimelinePanel extends React.Component { enableFlair={SettingsStore.getValue(UIFeature.Flair)} hideThreadedMessages={this.props.hideThreadedMessages} disableGrouping={this.props.disableGrouping} + callEventGroupers={this.callEventGroupers} /> ); } diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index f17c657d7b..0e4042368b 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -41,9 +41,8 @@ const room = new Matrix.Room("!roomId:server_name"); // wrap MessagePanel with a component which provides the MatrixClient in the context. class WrappedMessagePanel extends React.Component { - state = { - resizeNotifier: new EventEmitter(), - }; + resizeNotifier = new EventEmitter(); + callEventGroupers = new Map(); render() { const roomContext = { @@ -60,7 +59,12 @@ class WrappedMessagePanel extends React.Component { return - + ; }