Fix infinite pagination loop when offline

I'm not really sure how this is meant to work - and I'm not sure
how it was working before, but this is causing fairly bad infinite
loops if I start element with no homeserver connection. This is
a fairly crude fix, only thing I can think that would be better is
some awareness of when network requests were failing and intentionally
backing off.

Fixes https://github.com/vector-im/element-web/issues/18242
pull/21833/head
David Baker 2021-07-26 22:05:35 +01:00
parent 405fe436a6
commit f20da6d3de
1 changed files with 21 additions and 6 deletions

View File

@ -183,8 +183,14 @@ export default class ScrollPanel extends React.Component<IProps> {
private readonly itemlist = createRef<HTMLOListElement>(); private readonly itemlist = createRef<HTMLOListElement>();
private unmounted = false; private unmounted = false;
private scrollTimeout: Timer; private scrollTimeout: Timer;
// Are we currently trying to backfill?
private isFilling: boolean; private isFilling: boolean;
// Is the current fill request caused by a props update?
private isFillingDueToPropsUpdate = false;
// Did another request to check the fill state arrive while we were trying to backfill?
private fillRequestWhileRunning: boolean; private fillRequestWhileRunning: boolean;
// Is that next fill request scheduled because of a props update?
private pendingFillDueToPropsUpdate: boolean;
private scrollState: IScrollState; private scrollState: IScrollState;
private preventShrinkingState: IPreventShrinkingState; private preventShrinkingState: IPreventShrinkingState;
private unfillDebouncer: number; private unfillDebouncer: number;
@ -213,7 +219,7 @@ export default class ScrollPanel extends React.Component<IProps> {
// adding events to the top). // adding events to the top).
// //
// This will also re-check the fill state, in case the paginate was inadequate // This will also re-check the fill state, in case the paginate was inadequate
this.checkScroll(); this.checkScroll(true);
this.updatePreventShrinking(); this.updatePreventShrinking();
} }
@ -251,12 +257,12 @@ export default class ScrollPanel extends React.Component<IProps> {
// after an update to the contents of the panel, check that the scroll is // after an update to the contents of the panel, check that the scroll is
// where it ought to be, and set off pagination requests if necessary. // where it ought to be, and set off pagination requests if necessary.
public checkScroll = () => { public checkScroll = (isFromPropsUpdate = false) => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
this.restoreSavedScrollState(); this.restoreSavedScrollState();
this.checkFillState(); this.checkFillState(0, isFromPropsUpdate);
}; };
// return true if the content is fully scrolled down right now; else false. // return true if the content is fully scrolled down right now; else false.
@ -319,7 +325,7 @@ export default class ScrollPanel extends React.Component<IProps> {
} }
// check the scroll state and send out backfill requests if necessary. // check the scroll state and send out backfill requests if necessary.
public checkFillState = async (depth = 0): Promise<void> => { public checkFillState = async (depth = 0, isFromPropsUpdate = false): Promise<void> => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
@ -355,14 +361,20 @@ export default class ScrollPanel extends React.Component<IProps> {
// don't allow more than 1 chain of calls concurrently // don't allow more than 1 chain of calls concurrently
// do make a note when a new request comes in while already running one, // do make a note when a new request comes in while already running one,
// so we can trigger a new chain of calls once done. // so we can trigger a new chain of calls once done.
// However, we make an exception for when we're already filling due to a
// props (or children) update, because very often the children include
// spinners to say whether we're pagainating or not, so this would cause
// infinite paginating.
if (isFirstCall) { if (isFirstCall) {
if (this.isFilling) { if (this.isFilling && !this.isFillingDueToPropsUpdate) {
debuglog("isFilling: not entering while request is ongoing, marking for a subsequent request"); debuglog("isFilling: not entering while request is ongoing, marking for a subsequent request");
this.fillRequestWhileRunning = true; this.fillRequestWhileRunning = true;
this.pendingFillDueToPropsUpdate = isFromPropsUpdate;
return; return;
} }
debuglog("isFilling: setting"); debuglog("isFilling: setting");
this.isFilling = true; this.isFilling = true;
this.isFillingDueToPropsUpdate = isFromPropsUpdate;
} }
const itemlist = this.itemlist.current; const itemlist = this.itemlist.current;
@ -393,11 +405,14 @@ export default class ScrollPanel extends React.Component<IProps> {
if (isFirstCall) { if (isFirstCall) {
debuglog("isFilling: clearing"); debuglog("isFilling: clearing");
this.isFilling = false; this.isFilling = false;
this.isFillingDueToPropsUpdate = false;
} }
if (this.fillRequestWhileRunning) { if (this.fillRequestWhileRunning) {
const refillDueToPropsUpdate = this.pendingFillDueToPropsUpdate;
this.fillRequestWhileRunning = false; this.fillRequestWhileRunning = false;
this.checkFillState(); this.pendingFillDueToPropsUpdate = false;
this.checkFillState(0, refillDueToPropsUpdate);
} }
}; };