Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨Manual management of amp-next-page document visibility #25388

Merged
merged 17 commits into from
Nov 8, 2019
Merged
107 changes: 82 additions & 25 deletions extensions/amp-next-page/0.1/next-page-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {CSS} from '../../../build/amp-next-page-0.1.css';
import {MultidocManager} from '../../../src/runtime';
import {PositionObserverFidelity} from '../../../src/service/position-observer/position-observer-worker';
import {Services} from '../../../src/services';
import {dev, user, userAssert} from '../../../src/log';
import {VisibilityState} from '../../../src/visibility-state';
import {dev, devAssert, user, userAssert} from '../../../src/log';
import {dict} from '../../../src/utils/object';
import {getAmpdoc} from '../../../src/service';
import {installPositionObserverServiceForDoc} from '../../../src/service/position-observer/position-observer-impl';
Expand All @@ -40,7 +41,7 @@ const TAG = 'amp-next-page';
/**
* @typedef {{
* ampUrl: string,
* amp: ?Object,
* amp: (?../../../src/runtime.ShadowDoc | undefined),
* recUnit: {el: ?Element, isObserving: boolean},
* cancelled: boolean
* }}
Expand Down Expand Up @@ -173,6 +174,17 @@ export class NextPageService {
win.document.title,
canonicalUrl
);

// TODO(wassgha): Untype as ShadowDoc and tighten the ShadowDoc type spec
/** @type {!../../../src/runtime.ShadowDoc} */
const amp = {
ampdoc: ampDoc,
url: win.document.location.href,
title: win.document.title,
canonicalUrl,
};
documentRef.amp = amp;

this.documentRefs_.push(documentRef);
this.activeDocumentRef_ = this.documentRefs_[0];

Expand All @@ -198,7 +210,7 @@ export class NextPageService {
* Attach a ShadowDoc using the given document.
* @param {!Element} shadowRoot Root element to attach the shadow document to.
* @param {!Document} doc Document to attach.
* @return {?Object} Return value of {@link MultidocManager#attachShadowDoc}
* @return {?../../../src/runtime.ShadowDoc} Return value of {@link MultidocManager#attachShadowDoc}
*/
attachShadowDoc_(shadowRoot, doc) {
if (this.hideSelector_) {
Expand All @@ -216,10 +228,14 @@ export class NextPageService {
removeElement(item);
}

const amp = this.multidocManager_.attachShadowDoc(shadowRoot, doc, '', {});
installStylesForDoc(amp.ampdoc, CSS, null, false, TAG);
/** @type {!../../../src/runtime.ShadowDoc} */
const amp = this.multidocManager_.attachShadowDoc(shadowRoot, doc, '', {
visibilityState: VisibilityState.PRERENDER,
});
const ampdoc = devAssert(amp.ampdoc);
installStylesForDoc(ampdoc, CSS, null, false, TAG);

const body = amp.ampdoc.getBody();
const body = ampdoc.getBody();
body.classList.add('i-amphtml-next-page-document');

return amp;
Expand Down Expand Up @@ -313,8 +329,7 @@ export class NextPageService {
}
this.resources_.mutateElement(container, () => {
try {
const amp = this.attachShadowDoc_(shadowRoot, doc);
documentRef.amp = amp;
documentRef.amp = this.attachShadowDoc_(shadowRoot, doc);

toggle(dev().assertElement(documentRef.recUnit.el), false);
this.documentQueued_ = false;
Expand Down Expand Up @@ -457,24 +472,28 @@ export class NextPageService {
return;
}

let documentRef;
let ref = this.documentRefs_[i];
let analyticsEvent = '';

if (position.relativePos === 'top') {
documentRef = this.documentRefs_[i + 1];
analyticsEvent = 'amp-next-page-scroll';
} else if (position.relativePos === 'bottom') {
documentRef = this.documentRefs_[i];
analyticsEvent = 'amp-next-page-scroll-back';
switch (position.relativePos) {
case 'top':
ref = this.documentRefs_[i + 1];
analyticsEvent = 'amp-next-page-scroll';
break;
case 'bottom':
analyticsEvent = 'amp-next-page-scroll-back';
break;
default:
break;
}

if (documentRef && documentRef.amp) {
if (ref && ref.amp) {
this.triggerAnalyticsEvent_(
analyticsEvent,
documentRef.ampUrl,
ref.ampUrl,
this.activeDocumentRef_.ampUrl
);
this.setActiveDocument_(documentRef);
this.setActiveDocument_(ref);
}
}

Expand All @@ -497,19 +516,57 @@ export class NextPageService {

/**
* Sets the specified document as active, updating the document title and URL.
* @param {!DocumentRef} documentRef Reference to the document to set as
* active.
*
* @param {!DocumentRef} ref Reference to the document to be activated
* @private
*/
setActiveDocument_(documentRef) {
const {amp} = documentRef;
this.win_.document.title = amp.title || '';
this.activeDocumentRef_ = documentRef;
this.setActiveDocumentInHistory_(documentRef);
setActiveDocument_(ref) {
this.documentRefs_.forEach(docRef => {
const {amp} = docRef;
// Update the title and history
if (docRef === ref) {
this.win_.document.title = amp.title || '';
this.activeDocumentRef_ = docRef;
this.setActiveDocumentInHistory_(docRef);
// Show the active document
this.setDocumentVisibility_(docRef, VisibilityState.VISIBLE);
} else {
// Hide other documents
this.setDocumentVisibility_(docRef, VisibilityState.HIDDEN);
}
});

// TODO(emarchiori): Consider updating position fixed elements.
}

/**
* Manually overrides the document's visible state to the given state
*
* @param {!DocumentRef} ref Reference to the document to change
* @param {!../../../src/visibility-state.VisibilityState} visibilityState
* @private
*/
setDocumentVisibility_(ref, visibilityState) {
// Prevent updating visibility of the host document
if (ref === this.documentRefs_[0]) {
return;
}

const ampDoc = ref.amp && ref.amp.ampdoc;

// Prevent hiding of documents that are not shadow docs
if (!ampDoc) {
return;
}

// Prevent hiding of documents that are being pre-rendered
if (!ampDoc.hasBeenVisible() && visibilityState == VisibilityState.HIDDEN) {
return;
}

ref.amp.setVisibilityState(visibilityState);
}

/**
* @param {!DocumentRef} documentRef
* @private
Expand Down
105 changes: 72 additions & 33 deletions extensions/amp-next-page/0.1/test/test-amp-next-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import * as DocFetcher from '../../../../src/document-fetcher';
import {AmpNextPage} from '../amp-next-page';
import {Services} from '../../../../src/services';
import {VisibilityState} from '../../../../src/visibility-state';
import {getServicePromiseForDoc} from '../../../../src/service';
import {layoutRectLtwh} from '../../../../src/layout-rect';
import {macroTask} from '../../../../testing/yield';
Expand Down Expand Up @@ -110,12 +111,10 @@ describes.realWin(
it('does not fetch the next document before 3 viewports away', function*() {
const xhrMock = sandbox.mock(Services.xhrFor(win));
xhrMock.expects('fetch').never();
sandbox.stub(viewport, 'getClientRectAsync').callsFake(() => {
sandbox
.stub(viewport, 'getClientRectAsync')
// 4x viewports away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 5)
);
});
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 5));

win.dispatchEvent(new Event('scroll'));
yield macroTask();
Expand All @@ -125,12 +124,10 @@ describes.realWin(

it('fetches the next document within 3 viewports away', function*() {
env.fetchMock.get('*', EXAMPLE_PAGE);
sandbox.stub(viewport, 'getClientRectAsync').callsFake(() => {
sandbox
.stub(viewport, 'getClientRectAsync')
// 1x viewport away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 2)
);
});
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));

win.dispatchEvent(new Event('scroll'));
yield macroTask();
Expand All @@ -146,12 +143,10 @@ describes.realWin(
.returns(new Promise(() => {}))
.once();

sandbox.stub(viewport, 'getClientRectAsync').callsFake(() => {
sandbox
.stub(viewport, 'getClientRectAsync')
// 1x viewport away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 2)
);
});
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));

win.dispatchEvent(new Event('scroll'));
yield macroTask();
Expand All @@ -175,12 +170,8 @@ describes.realWin(
sandbox
.stub(viewport, 'getClientRectAsync')
.onFirstCall()
.callsFake(() => {
// 1x viewport away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 2)
);
});
// 1x viewport away
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));

win.dispatchEvent(new Event('scroll'));
yield macroTask();
Expand Down Expand Up @@ -216,12 +207,8 @@ describes.realWin(
sandbox
.stub(viewport, 'getClientRectAsync')
.onFirstCall()
.callsFake(() => {
// 1x viewport away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 2)
);
});
// 1x viewport away
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));
win.dispatchEvent(new Event('scroll'));
yield macroTask();
const shadowDoc = attachShadowDocSpy.firstCall.returnValue.ampdoc;
Expand Down Expand Up @@ -249,12 +236,8 @@ describes.realWin(
sandbox
.stub(viewport, 'getClientRectAsync')
.onFirstCall()
.callsFake(() => {
// 1x viewport away
return Promise.resolve(
layoutRectLtwh(0, 0, sizes.width, sizes.height * 2)
);
});
// 1x viewport away
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));
win.dispatchEvent(new Event('scroll'));
yield macroTask();

Expand Down Expand Up @@ -528,6 +511,62 @@ describes.realWin(
expect(registerSpy.calledWith(element, config)).to.be.true;
});
});

describe('manual visibility management', () => {
beforeEach(done => {
element.innerHTML = `
<script type="application/json">
{
"pages": [
{
"image": "/examples/img/hero@1x.jpg",
"title": "Title 1",
"ampUrl": "/document1"
},
{
"image": "/examples/img/hero@1x.jpg",
"title": "Title 2",
"ampUrl": "/document2"
}
],
"hideSelectors": [
"header",
"footer"
]
}
</script>`;
nextPage.buildCallback().then(done);
});

it('defaults to the prerender visibility state for the next document', function*() {
env.fetchMock.get('*', EXAMPLE_PAGE);

const nextPageService = yield getServicePromiseForDoc(
ampdoc,
'next-page'
);
const attachShadowDocSpy = sandbox.spy(
nextPageService.multidocManager_,
'attachShadowDoc'
);

sandbox
.stub(viewport, 'getClientRectAsync')
.onFirstCall()
// 1x viewport away
.resolves(layoutRectLtwh(0, 0, sizes.width, sizes.height * 2));

win.dispatchEvent(new Event('scroll'));
yield macroTask();

const shadowDoc = attachShadowDocSpy.firstCall.returnValue.ampdoc;
yield shadowDoc.whenReady();

expect(shadowDoc.getVisibilityState()).to.equal(
VisibilityState.PRERENDER
);
});
});
}
);

Expand Down
23 changes: 21 additions & 2 deletions src/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ setReportError(reportErrorForWin.bind(null, self));
/** @const @private {string} */
const TAG = 'runtime';

/**
* @typedef {{
wassgha marked this conversation as resolved.
Show resolved Hide resolved
* url: (string|undefined),
* title: (string|undefined),
* canonicalUrl: (string|undefined),
* head: (Element|undefined),
* ampdoc: (!./service/ampdoc-impl.AmpDoc | undefined),
* setVisibilityState: (function(!VisibilityState)|undefined),
* postMessage: (function()|undefined),
* onMessage: (function()|undefined),
* close: (function()|undefined),
* getState: (function()|undefined),
* setState: (function()|undefined),
* toggleRuntime: (function()|undefined),
* resources: (!./service/resources-interface.ResourcesInterface | undefined)
* }}
*/
export let ShadowDoc;

/**
* Applies the runtime to a given global scope for a single-doc mode. Multi
* frame support is currently incomplete.
Expand Down Expand Up @@ -420,7 +439,7 @@ export class MultidocManager {
* @param {!Object<string, string>|undefined} params
* @param {function(!Object, !ShadowRoot,
* !./service/ampdoc-impl.AmpDocShadow):!Promise} builder
* @return {!Object}
* @return {!ShadowDoc}
* @private
*/
attachShadowDoc_(hostElement, url, params, builder) {
Expand Down Expand Up @@ -579,7 +598,7 @@ export class MultidocManager {
* @param {!Document} doc
* @param {string} url
* @param {!Object<string, string>=} opt_initParams
* @return {!Object}
* @return {!ShadowDoc}
*/
attachShadowDoc(hostElement, doc, url, opt_initParams) {
dev().fine(TAG, 'Attach shadow doc:', doc);
Expand Down