diff --git a/extensions/amp-iframe/0.1/amp-iframe.js b/extensions/amp-iframe/0.1/amp-iframe.js index ec55e13749b5..74d0e20c6915 100644 --- a/extensions/amp-iframe/0.1/amp-iframe.js +++ b/extensions/amp-iframe/0.1/amp-iframe.js @@ -19,10 +19,10 @@ import {IntersectionObserverApi} from '../../../src/intersection-observer-polyfi import {LayoutPriority, isLayoutSizeDefined} from '../../../src/layout'; import {Services} from '../../../src/services'; import {base64EncodeFromBytes} from '../../../src/utils/base64.js'; -import {createCustomEvent, getData} from '../../../src/event-helper'; +import {createCustomEvent, getData, listen} from '../../../src/event-helper'; import {devAssert, user, userAssert} from '../../../src/log'; import {dict} from '../../../src/utils/object'; -import {endsWith} from '../../../src/string'; +import {endsWith, startsWith} from '../../../src/string'; import { isAdLike, isPausable, @@ -103,6 +103,9 @@ export class AmpIframe extends AMP.BaseElement { /** @private {string} */ this.sandbox_ = ''; + /** @private {Function} */ + this.unlistenPym_ = null; + /** * The source of the iframe. May change to null for tracking iframes * to prevent them from being recreated. @@ -470,6 +473,11 @@ export class AmpIframe extends AMP.BaseElement { /*opt_allowOpaqueOrigin*/ true ); + // Listen for resize messages sent by Pym.js. + this.unlistenPym_ = listen(this.win, 'message', event => { + return this.listenForPymMessage_(/** @type {!MessageEvent} */ (event)); + }); + if (this.isClickToPlay_) { listenFor(iframe, 'embed-ready', this.activateIframe_.bind(this)); } @@ -490,6 +498,37 @@ export class AmpIframe extends AMP.BaseElement { }); } + /** + * Listen for Pym.js messages for 'height' and 'width'. + * + * @see http://blog.apps.npr.org/pym.js/ + * @param {!MessageEvent} event + * @private + */ + listenForPymMessage_(event) { + if (!this.iframe_ || event.source !== this.iframe_.contentWindow) { + return; + } + const data = getData(event); + if (typeof data !== 'string' || !startsWith(data, 'pym')) { + return; + } + + // The format of the message takes the form of `pymxPYMx${id}xPYMx${type}xPYMx${message}`. + // The id is unnecessary for integration with amp-iframe; the possible types include + // 'height', 'width', 'parentPositionInfo', 'navigateTo', and 'scrollToChildPos'. + // Only the 'height' and 'width' messages are currently supported. + // See + const args = data.split(/xPYMx/); + if ('height' === args[2]) { + this.updateSize_(parseInt(args[3], 10), undefined); + } else if ('width' === args[2]) { + this.updateSize_(undefined, parseInt(args[3], 10)); + } else { + user().warn(TAG_, `Unsupported Pym.js message: ${data}`); + } + } + /** @override */ unlayoutOnPause() { return !this.isPausable_(); @@ -528,6 +567,10 @@ export class AmpIframe extends AMP.BaseElement { * @override **/ unlayoutCallback() { + if (this.unlistenPym_) { + this.unlistenPym_(); + this.unlistenPym_ = null; + } if (this.iframe_) { removeElement(this.iframe_); if (this.placeholder_) { diff --git a/extensions/amp-iframe/0.1/test/test-amp-iframe.js b/extensions/amp-iframe/0.1/test/test-amp-iframe.js index 498571c1e209..74512a8ed239 100644 --- a/extensions/amp-iframe/0.1/test/test-amp-iframe.js +++ b/extensions/amp-iframe/0.1/test/test-amp-iframe.js @@ -1059,6 +1059,64 @@ describes.realWin( ActionTrust.HIGH ); }); + + it('should listen for Pym.js height event', function*() { + const ampIframe = createAmpIframe(env, { + src: iframeSrc, + sandbox: 'allow-scripts allow-same-origin', + width: 200, + height: 200, + resizable: '', + }); + yield waitForAmpIframeLayoutPromise(doc, ampIframe); + const impl = ampIframe.implementation_; + return new Promise((resolve, unusedReject) => { + impl.updateSize_ = (height, width) => { + resolve({height, width}); + }; + const iframe = ampIframe.querySelector('iframe'); + iframe.contentWindow.postMessage( + { + sentinel: 'amp-test', + type: 'requestPymjsHeight', + height: 234, + }, + '*' + ); + }).then(res => { + expect(res.height).to.equal(234); + expect(res.width).to.be.an('undefined'); + }); + }); + + it('should listen for Pym.js width event', function*() { + const ampIframe = createAmpIframe(env, { + src: iframeSrc, + sandbox: 'allow-scripts allow-same-origin', + width: 200, + height: 200, + resizable: '', + }); + yield waitForAmpIframeLayoutPromise(doc, ampIframe); + const impl = ampIframe.implementation_; + return new Promise((resolve, unusedReject) => { + impl.updateSize_ = (height, width) => { + resolve({height, width}); + }; + const iframe = ampIframe.querySelector('iframe'); + iframe.contentWindow.postMessage( + { + sentinel: 'amp-test', + type: 'requestPymjsWidth', + width: 345, + }, + '*' + ); + }).then(res => { + expect(res.width).to.equal(345); + expect(res.height).to.be.an('undefined'); + }); + }); }); describe('pause/resume', () => { diff --git a/test/fixtures/served/iframe.html b/test/fixtures/served/iframe.html index def1fc00b976..787035b51f90 100644 --- a/test/fixtures/served/iframe.html +++ b/test/fixtures/served/iframe.html @@ -25,9 +25,9 @@ } window.addEventListener('message', function(event) { + var sentinel, msg; if (event.data) { - if (event.data.type == 'requestHeight') { - var sentinel; + if (event.data.type === 'requestHeight') { if (event.data.is3p) { sentinel = event.data.sentinel; } else { @@ -40,8 +40,7 @@ height: event.data.height, width: event.data.width, }, '*'); - } else if (event.data.type == 'subscribeToEmbedState') { - var sentinel; + } else if (event.data.type === 'subscribeToEmbedState') { if (event.data.is3p) { sentinel = event.data.sentinel; } else { @@ -52,6 +51,22 @@ sentinel: sentinel, type: 'send-embed-state', }, '*'); + } else if (event.data.type === 'requestPymjsHeight') { + msg = [ 'pym', 'example', 'height', event.data.height ].join( 'xPYMx' ); + if (event.data.is3p) { + sentinel = event.data.sentinel; + } else { + sentinel = 'amp'; + } + getAmpWindow(sentinel)./*OK*/postMessage( msg, '*'); + } else if (event.data.type === 'requestPymjsWidth' || 1) { + msg = [ 'pym', 'example', 'width', event.data.width ].join( 'xPYMx' ); + if (event.data.is3p) { + sentinel = event.data.sentinel; + } else { + sentinel = 'amp'; + } + getAmpWindow(sentinel)./*OK*/postMessage( msg, '*'); } } });