Skip to content

Commit

Permalink
Display-observer: a hidden tab should still be displayable for playba…
Browse files Browse the repository at this point in the history
…ck (#32478)

* Display-observer: a hidden tab should still be displayable for playback

* review fixes
  • Loading branch information
Dima Voytenko committed Feb 8, 2021
1 parent 541c3e2 commit 4a43478
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 12 deletions.
38 changes: 27 additions & 11 deletions src/utils/display-observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import {VisibilityState} from '../visibility-state';
import {getServiceForDoc, registerServiceBuilderForDoc} from '../service';
import {pushIfNotExist, removeItem} from './array';
import {rethrowAsync} from '../log';
Expand Down Expand Up @@ -96,12 +97,13 @@ export class DisplayObserver {
);

/** @private {boolean} */
this.isDocVisible_ = ampdoc.isVisible();
this.isDocDisplay_ = computeDocIsDisplayed(ampdoc.getVisibilityState());

/** @private {?UnlistenDef} */
this.visibilityUnlisten_ = ampdoc.onVisibilityChanged(() => {
if (ampdoc.isVisible() !== this.isDocVisible_) {
this.isDocVisible_ = ampdoc.isVisible();
const display = computeDocIsDisplayed(ampdoc.getVisibilityState());
if (display !== this.isDocDisplay_) {
this.isDocDisplay_ = display;
this.docVisibilityChanged_();
}
});
Expand Down Expand Up @@ -141,7 +143,7 @@ export class DisplayObserver {
setTimeout(() => {
const display = computeDisplay(
this.targetObservations_.get(target),
this.isDocVisible_
this.isDocDisplay_
);
if (display != null) {
callCallbackNoInline(callback, display);
Expand Down Expand Up @@ -174,8 +176,8 @@ export class DisplayObserver {
docVisibilityChanged_() {
this.targetObserverCallbacks_.forEach((callbacks, target) => {
const observations = this.targetObservations_.get(target);
const oldDisplay = computeDisplay(observations, !this.isDocVisible_);
const newDisplay = computeDisplay(observations, this.isDocVisible_);
const oldDisplay = computeDisplay(observations, !this.isDocDisplay_);
const newDisplay = computeDisplay(observations, this.isDocDisplay_);
notifyIfChanged(callbacks, newDisplay, oldDisplay);
});
}
Expand All @@ -202,10 +204,10 @@ export class DisplayObserver {
observations = emptyObservations(this.observers_.length);
this.targetObservations_.set(target, observations);
}
const oldDisplay = computeDisplay(observations, this.isDocVisible_);
const oldDisplay = computeDisplay(observations, this.isDocDisplay_);
const index = this.observers_.indexOf(observer);
observations[index] = isIntersecting;
const newDisplay = computeDisplay(observations, this.isDocVisible_);
const newDisplay = computeDisplay(observations, this.isDocDisplay_);
notifyIfChanged(callbacks, newDisplay, oldDisplay);
}
}
Expand Down Expand Up @@ -234,11 +236,11 @@ function emptyObservations(length) {

/**
* @param {?Array<boolean>} observations
* @param {boolean} isDocVisible
* @param {boolean} isDocDisplay
* @return {?boolean}
*/
function computeDisplay(observations, isDocVisible) {
if (!isDocVisible) {
function computeDisplay(observations, isDocDisplay) {
if (!isDocDisplay) {
return false;
}
if (!observations) {
Expand All @@ -248,6 +250,20 @@ function computeDisplay(observations, isDocVisible) {
return observations.reduce(displayReducer);
}

/**
* @param {!VisibilityState} visibilityState
* @return {boolean}
*/
function computeDocIsDisplayed(visibilityState) {
return (
visibilityState == VisibilityState.VISIBLE ||
// The document is still considered "displayed" or at least "displayable"
// when it's hidden (tab is switched). Only prerender/paused/inactive
// states require pause of resources.
visibilityState == VisibilityState.HIDDEN
);
}

/**
* @param {?boolean} acc
* @param {?boolean|undefined} value
Expand Down
45 changes: 44 additions & 1 deletion test/unit/utils/test-display-observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import {
import {removeItem} from '../../../src/utils/array';

describes.realWin('display-observer', {amp: true}, (env) => {
let win, doc;
let win, doc, ampdoc;
let element;
let docObserver, viewportObserver;

beforeEach(() => {
win = env.win;
doc = win.document;
ampdoc = env.ampdoc;
element = doc.createElement('div');
element.id = 'element1';
doc.body.appendChild(element);
Expand Down Expand Up @@ -247,5 +248,47 @@ describes.realWin('display-observer', {amp: true}, (env) => {
expect(docObserver.elements).to.not.include(element);
expect(viewportObserver.elements).to.not.include(element);
});

it('should observe document visibility', async () => {
const callbackCaller = createCallbackCaller();
observeDisplay(element, callbackCaller);

// First response.
docObserver.notify([{target: element, isIntersecting: true}]);
viewportObserver.notify([{target: element, isIntersecting: false}]);
const display1 = await callbackCaller.next();
expect(display1).to.be.true;

// Paused visibility.
ampdoc.overrideVisibilityState('paused');
const display2 = await callbackCaller.next();
expect(display2).to.be.false;

// Visibile visibility.
ampdoc.overrideVisibilityState('visible');
const display3 = await callbackCaller.next();
expect(display3).to.be.true;
});

it('should treat hidden document visibility as displayed', async () => {
const callbackCaller = createCallbackCaller();
observeDisplay(element, callbackCaller);

// First response.
docObserver.notify([{target: element, isIntersecting: true}]);
viewportObserver.notify([{target: element, isIntersecting: false}]);
const display1 = await callbackCaller.next();
expect(display1).to.be.true;

// Paused visibility.
ampdoc.overrideVisibilityState('paused');
const display2 = await callbackCaller.next();
expect(display2).to.be.false;

// Hidden visibility.
ampdoc.overrideVisibilityState('hidden');
const display3 = await callbackCaller.next();
expect(display3).to.be.true;
});
});
});

0 comments on commit 4a43478

Please sign in to comment.