Prioritise and reduce the amount of events decrypted on application startup (#5980)

This commit is contained in:
Germain 2021-05-12 12:19:53 +01:00 committed by GitHub
commit 91133ed35b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 24 deletions

View file

@ -908,6 +908,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
let presentedId = roomInfo.room_alias || roomInfo.room_id; let presentedId = roomInfo.room_alias || roomInfo.room_id;
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id); const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
if (room) { if (room) {
// Not all timeline events are decrypted ahead of time anymore
// Only the critical ones for a typical UI are
// This will start the decryption process for all events when a
// user views a room
room.decryptAllEvents();
const theAlias = Rooms.getDisplayAliasForRoom(room); const theAlias = Rooms.getDisplayAliasForRoom(room);
if (theAlias) { if (theAlias) {
presentedId = theAlias; presentedId = theAlias;

View file

@ -38,6 +38,7 @@ import {haveTileForEvent} from "../views/rooms/EventTile";
import {UIFeature} from "../../settings/UIFeature"; import {UIFeature} from "../../settings/UIFeature";
import {objectHasDiff} from "../../utils/objects"; import {objectHasDiff} from "../../utils/objects";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import { arrayFastClone } from "../../utils/arrays";
const PAGINATE_SIZE = 20; const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20; const INITIAL_SIZE = 20;
@ -1141,6 +1142,18 @@ class TimelinePanel extends React.Component {
// get the list of events from the timeline window and the pending event list // get the list of events from the timeline window and the pending event list
_getEvents() { _getEvents() {
const events = this._timelineWindow.getEvents(); const events = this._timelineWindow.getEvents();
// `arrayFastClone` performs a shallow copy of the array
// we want the last event to be decrypted first but displayed last
// `reverse` is destructive and unfortunately mutates the "events" array
arrayFastClone(events)
.reverse()
.forEach(event => {
if (event.shouldAttemptDecryption()) {
event.attemptDecryption(MatrixClientPeg.get()._crypto);
}
});
const firstVisibleEventIndex = this._checkForPreJoinUISI(events); const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
// Hold onto the live events separately. The read receipt and read marker // Hold onto the live events separately. The read receipt and read marker

View file

@ -38,7 +38,6 @@ export default class EventIndex extends EventEmitter {
this._eventsPerCrawl = 100; this._eventsPerCrawl = 100;
this._crawler = null; this._crawler = null;
this._currentCheckpoint = null; this._currentCheckpoint = null;
this.liveEventsForIndex = new Set();
} }
async init() { async init() {
@ -188,16 +187,12 @@ export default class EventIndex extends EventEmitter {
return; return;
} }
// If the event is not yet decrypted mark it for the
// Event.decrypted callback.
if (ev.isBeingDecrypted()) { if (ev.isBeingDecrypted()) {
const eventId = ev.getId(); // XXX: Private member access
this.liveEventsForIndex.add(eventId); await ev._decryptionPromise;
} else {
// If the event is decrypted or is unencrypted add it to the
// index now.
await this.addLiveEventToIndex(ev);
} }
await this.addLiveEventToIndex(ev);
} }
onRoomStateEvent = async (ev, state) => { onRoomStateEvent = async (ev, state) => {
@ -216,10 +211,7 @@ export default class EventIndex extends EventEmitter {
* listener, if so queues it up to be added to the index. * listener, if so queues it up to be added to the index.
*/ */
onEventDecrypted = async (ev, err) => { onEventDecrypted = async (ev, err) => {
const eventId = ev.getId();
// If the event isn't in our live event set, ignore it. // If the event isn't in our live event set, ignore it.
if (!this.liveEventsForIndex.delete(eventId)) return;
if (err) return; if (err) return;
await this.addLiveEventToIndex(ev); await this.addLiveEventToIndex(ev);
} }
@ -523,16 +515,21 @@ export default class EventIndex extends EventEmitter {
} }
}); });
const decryptionPromises = []; const decryptionPromises = matrixEvents
.filter(event => event.isEncrypted())
matrixEvents.forEach(ev => { .map(event => {
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) { if (event.shouldAttemptDecryption()) {
return event.attemptDecryption(client._crypto, {
isRetry: true,
emit: false,
});
} else {
// TODO the decryption promise is a private property, this // TODO the decryption promise is a private property, this
// should either be made public or we should convert the // should either be made public or we should convert the
// event that gets fired when decryption is done into a // event that gets fired when decryption is done into a
// promise using the once event emitter method: // promise using the once event emitter method:
// https://nodejs.org/api/events.html#events_events_once_emitter_name // https://nodejs.org/api/events.html#events_events_once_emitter_name
decryptionPromises.push(ev._decryptionPromise); return event._decryptionPromise;
} }
}); });

View file

@ -16,12 +16,14 @@ limitations under the License.
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { ActionPayload } from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher"; import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays"; import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { SettingLevel } from "../settings/SettingLevel"; import { SettingLevel } from "../settings/SettingLevel";
import { MatrixClientPeg } from '../MatrixClientPeg';
const MAX_ROOMS = 20; // arbitrary const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
@ -59,6 +61,30 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
return this.matrixClient && this.matrixClient.getVisibleRooms().length >= 20; return this.matrixClient && this.matrixClient.getVisibleRooms().length >= 20;
} }
componentDidUpdate(prevProps, prevState) {
const prevRoomCount = (prevState.rooms?.length || 0);
const currentRoomCount = (this.state.rooms?.length || 0)
// Only decrypting the breadcrumb rooms events on app initialisation
// when room count transitions from 0 to the number of rooms it contains
if (prevRoomCount === 0 && currentRoomCount > prevRoomCount) {
const client = MatrixClientPeg.get();
// Rooms in the breadcrumb have a good chance to be interacted with
// again by a user. Decrypting the messages ahead of time will help
// reduce content shift on first render
this.state.rooms?.forEach(async room => {
const [cryptoEvent] = room.currentState.getStateEvents(EventType.RoomEncryption);
if (cryptoEvent) {
if (!client.isRoomEncrypted(room.roomId)) {
// XXX: Private member access
await client._crypto.onCryptoEvent(cryptoEvent);
}
room?.decryptAllEvents();
}
});
}
}
protected async onAction(payload: ActionPayload) { protected async onAction(payload: ActionPayload) {
if (!this.matrixClient) return; if (!this.matrixClient) return;