Pause refresh when page is hidden (#177693)

## Summary

close https://github.com/elastic/kibana/issues/1878

Pauses auto-refresh when a page is not visible. 
I tested and didn't notice any issues, but looking for more testing help


## Release Notes

Auto-refresh pauses when the page is not visible.
This commit is contained in:
Anton Dosov 2024-03-04 14:44:52 +01:00 committed by GitHub
parent c970b395d5
commit b635674a67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 2 deletions

View file

@ -203,3 +203,39 @@ test('calling older done() is ignored', () => {
jest.advanceTimersByTime(501);
expect(fn1).toHaveBeenCalledTimes(2);
});
test('pauses if page is not visible', () => {
let mockPageVisibility: DocumentVisibilityState = 'visible';
jest.spyOn(document, 'visibilityState', 'get').mockImplementation(() => mockPageVisibility);
const { loop$, start, stop } = createAutoRefreshLoop();
const fn = jest.fn((done) => done());
loop$.subscribe(fn);
jest.advanceTimersByTime(5000);
expect(fn).not.toBeCalled();
start(1000);
jest.advanceTimersByTime(1001);
expect(fn).toHaveBeenCalledTimes(1);
mockPageVisibility = 'hidden';
document.dispatchEvent(new Event('visibilitychange'));
jest.advanceTimersByTime(1001);
expect(fn).toHaveBeenCalledTimes(1);
mockPageVisibility = 'visible';
document.dispatchEvent(new Event('visibilitychange'));
expect(fn).toHaveBeenCalledTimes(2);
jest.advanceTimersByTime(1001);
expect(fn).toHaveBeenCalledTimes(3);
stop();
jest.advanceTimersByTime(5000);
expect(fn).toHaveBeenCalledTimes(3);
});

View file

@ -7,8 +7,9 @@
*/
import { defer, Subject } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { finalize, map, delayWhen, filter } from 'rxjs/operators';
import { once } from 'lodash';
import { createPageVisibility$ } from './page_visibility';
export type AutoRefreshDoneFn = () => void;
@ -18,6 +19,8 @@ export type AutoRefreshDoneFn = () => void;
* When auto refresh loop emits, it won't continue automatically,
* until each subscriber calls received `done` function.
*
* Also, it will pause when the page is not visible.
*
* @internal
*/
export const createAutoRefreshLoop = () => {
@ -51,6 +54,12 @@ export const createAutoRefreshLoop = () => {
_timeoutHandle = -1;
}
const pageVisible$ = createPageVisibility$().pipe(
filter((visibility) => visibility === 'visible')
);
const tickWhenVisible$ = tick.pipe(delayWhen(() => pageVisible$));
return {
stop: () => {
_timeout = 0;
@ -65,7 +74,7 @@ export const createAutoRefreshLoop = () => {
loop$: defer(() => {
subscribersCount++;
start(); // restart the loop on a new subscriber
return tick.pipe(map((doneCb) => once(doneCb))); // each subscriber allowed to call done only once
return tickWhenVisible$.pipe(map((doneCb) => once(doneCb))); // each subscriber allowed to call done only once
}).pipe(
finalize(() => {
subscribersCount--;

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createPageVisibility$ } from './page_visibility';
let mockPageVisibility: DocumentVisibilityState = 'visible';
jest.spyOn(document, 'visibilityState', 'get').mockImplementation(() => mockPageVisibility);
test('createPageVisibility$ returns an observable that emits visibility state', () => {
const pageVisibility$ = createPageVisibility$();
const fn = jest.fn();
pageVisibility$.subscribe(fn);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenLastCalledWith('visible');
mockPageVisibility = 'hidden';
document.dispatchEvent(new Event('visibilitychange'));
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenLastCalledWith('hidden');
});

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { fromEvent, Observable } from 'rxjs';
import { map, shareReplay, startWith } from 'rxjs/operators';
export function createPageVisibility$(): Observable<DocumentVisibilityState> {
return fromEvent(document, 'visibilitychange').pipe(
startWith(document.visibilityState),
map(() => document.visibilityState),
shareReplay({ refCount: true, bufferSize: 1 })
);
}