From dfa844a0352fc561cee73123132492842c4afdbe Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 22 Jul 2022 11:38:44 -0500 Subject: [PATCH] Error resistant data dump from `TimelinePanel` (#9079) Error resistant data dump from `TimelinePanel` so we still have some data to dump if one of the pieces errors out. Example error that can happen (as seen in https://github.com/matrix-org/element-web-rageshakes/issues/14197): ``` Uncaught TypeError: Cannot read properties of null (reading 'getEvents') at TimelineWindow.getEvents (timeline-window.ts:378:37) at TimelinePanel_TimelinePanel.onDumpDebugLogs (TimelinePanel.tsx:434:60) at Object.ID_727 (TimelinePanel.tsx:609:22) at MatrixDispatcher._invokeCallback (Dispatcher.js:198:1) at MatrixDispatcher.dispatch (Dispatcher.js:174:1) at sentryWrapped (helpers.js:77:1) ``` --- src/components/structures/TimelinePanel.tsx | 78 +++++++++++++-------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index d6dfabe71a..3c787748ba 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -384,24 +384,28 @@ class TimelinePanel extends React.Component { * every message change so instead we only log it out when asked. */ private onDumpDebugLogs = (): void => { - const room = this.props.timelineSet.room; + const room = this.props.timelineSet?.room; // Get a list of the event IDs used in this TimelinePanel. // This includes state and hidden events which we don't render - const eventIdList = this.state.events.map((ev) => ev.getId()); + const eventIdList = this.state?.events?.map((ev) => ev.getId()); // Get the list of actually rendered events seen in the DOM. // This is useful to know for sure what's being shown on screen. // And we can suss out any corrupted React `key` problems. let renderedEventIds: string[]; - const messagePanel = this.messagePanel.current; - if (messagePanel) { - const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element; - if (messagePanelNode) { - const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]'); - renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => { - return renderedEvent.getAttribute('data-event-id'); - }); + try { + const messagePanel = this.messagePanel.current; + if (messagePanel) { + const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element; + if (messagePanelNode) { + const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]'); + renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => { + return renderedEvent.getAttribute('data-event-id'); + }); + } } + } catch (err) { + logger.error(`onDumpDebugLogs: Failed to get the actual event ID's in the DOM`, err); } // Get the list of events and threads for the room as seen by the @@ -413,26 +417,44 @@ class TimelinePanel extends React.Component { const timelineSets = room.getTimelineSets(); const threadsTimelineSets = room.threadsTimelineSets; - // Serialize all of the timelineSets and timelines in each set to their event IDs - serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets); - serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets); + try { + // Serialize all of the timelineSets and timelines in each set to their event IDs + serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets); + serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets); + } catch (err) { + logger.error(`onDumpDebugLogs: Failed to serialize event IDs from timelinesets`, err); + } - // Serialize all threads in the room from theadId -> event IDs in the thread - room.getThreads().forEach((thread) => { - serializedThreadsMap[thread.id] = { - events: thread.events.map(ev => ev.getId()), - numTimelines: thread.timelineSet.getTimelines().length, - liveTimeline: thread.timelineSet.getLiveTimeline().getEvents().length, - prevTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Backward) - ?.getEvents().length, - nextTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Forward) - ?.getEvents().length, - }; - }); + try { + // Serialize all threads in the room from theadId -> event IDs in the thread + room.getThreads().forEach((thread) => { + serializedThreadsMap[thread.id] = { + events: thread.events.map(ev => ev.getId()), + numTimelines: thread.timelineSet.getTimelines().length, + liveTimeline: thread.timelineSet.getLiveTimeline().getEvents().length, + prevTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Backward) + ?.getEvents().length, + nextTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Forward) + ?.getEvents().length, + }; + }); + } catch (err) { + logger.error(`onDumpDebugLogs: Failed to serialize event IDs from the threads`, err); + } } - const timelineWindowEventIds = this.timelineWindow.getEvents().map(ev => ev.getId()); - const pendingEvents = this.props.timelineSet.getPendingEvents().map(ev => ev.getId()); + let timelineWindowEventIds: string[]; + try { + timelineWindowEventIds = this.timelineWindow.getEvents().map(ev => ev.getId()); + } catch (err) { + logger.error(`onDumpDebugLogs: Failed to get event IDs from the timelineWindow`, err); + } + let pendingEventIds: string[]; + try { + pendingEventIds = this.props.timelineSet.getPendingEvents().map(ev => ev.getId()); + } catch (err) { + logger.error(`onDumpDebugLogs: Failed to get pending event IDs`, err); + } logger.debug( `TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` + @@ -444,7 +466,7 @@ class TimelinePanel extends React.Component { `${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` + `\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` + `\ttimelineWindowEventIds(${timelineWindowEventIds.length})=${JSON.stringify(timelineWindowEventIds)}\n` + - `\tpendingEvents(${pendingEvents.length})=${JSON.stringify(pendingEvents)}`, + `\tpendingEventIds(${pendingEventIds.length})=${JSON.stringify(pendingEventIds)}`, ); };