diff --git a/3p/ampcontext.js b/3p/ampcontext.js index 2143221d17364..0b3a5ecbc6e50 100644 --- a/3p/ampcontext.js +++ b/3p/ampcontext.js @@ -142,7 +142,7 @@ export class AbstractAmpContext { */ onPageVisibilityChange(callback) { return this.client_.registerCallback(MessageType.EMBED_STATE, data => { - callback({hidden: data.pageHidden}); + callback({hidden: data['pageHidden']}); }); } @@ -194,7 +194,7 @@ export class AbstractAmpContext { */ onResizeSuccess(callback) { this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, obj => { - callback(obj.requestedHeight, obj.requestedWidth); }); + callback(obj['requestedHeight'], obj['requestedWidth']); }); }; /** @@ -206,7 +206,7 @@ export class AbstractAmpContext { */ onResizeDenied(callback) { this.client_.registerCallback(MessageType.EMBED_SIZE_DENIED, obj => { - callback(obj.requestedHeight, obj.requestedWidth); + callback(obj['requestedHeight'], obj['requestedWidth']); }); }; diff --git a/3p/iframe-messaging-client.js b/3p/iframe-messaging-client.js index 69e405f53e4d0..250159774fab5 100644 --- a/3p/iframe-messaging-client.js +++ b/3p/iframe-messaging-client.js @@ -20,6 +20,7 @@ import { serializeMessage, deserializeMessage, } from '../src/3p-frame-messaging'; +import {getData} from '../src/event-helper'; import {getMode} from '../src/mode'; import {dev} from '../src/log'; @@ -66,7 +67,7 @@ export class IframeMessagingClient { * All future calls will overwrite any previously registered * callbacks. * @param {string} messageType The type of the message. - * @param {function(Object)} callback The callback function to call + * @param {function(?JsonObject)} callback The callback function to call * when a message with type messageType is received. */ registerCallback(messageType, callback) { @@ -105,7 +106,7 @@ export class IframeMessagingClient { return; } - const message = deserializeMessage(event.data); + const message = deserializeMessage(getData(event)); if (!message || message['sentinel'] != this.sentinel_) { return; } @@ -130,7 +131,7 @@ export class IframeMessagingClient { /** * @param {string} messageType - * @return {!Observable} + * @return {!Observable} */ getOrCreateObservableFor_(messageType) { if (!(messageType in this.observableFor_)) { diff --git a/3p/integration.js b/3p/integration.js index abb627151ea11..0525d8164fa36 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -589,8 +589,8 @@ function getHtml(selector, attributes, callback) { })); const unlisten = listenParent(window, 'get-html-result', data => { - if (data.messageId === messageId) { - callback(data.content); + if (data['messageId'] === messageId) { + callback(data['content']); unlisten(); } }); @@ -610,7 +610,7 @@ function observeIntersection(observerCallback) { // Send request to received records. nonSensitiveDataPostMessage('send-intersections'); return listenParent(window, 'intersection', data => { - observerCallback(data.changes); + observerCallback(data['changes']); }); } @@ -621,8 +621,8 @@ function observeIntersection(observerCallback) { */ function updateVisibilityState(global) { listenParent(window, 'embed-state', function(data) { - global.context.hidden = data.pageHidden; - dispatchVisibilityChangeEvent(global, data.pageHidden); + global.context.hidden = data['pageHidden']; + dispatchVisibilityChangeEvent(global, data['pageHidden']); }); } @@ -642,7 +642,7 @@ function dispatchVisibilityChangeEvent(win, isHidden) { */ function onResizeSuccess(observerCallback) { return listenParent(window, 'embed-size-changed', data => { - observerCallback(data.requestedHeight, data.requestedWidth); + observerCallback(data['requestedHeight'], data['requestedWidth']); }); } @@ -654,7 +654,7 @@ function onResizeSuccess(observerCallback) { */ function onResizeDenied(observerCallback) { return listenParent(window, 'embed-size-denied', data => { - observerCallback(data.requestedHeight, data.requestedWidth); + observerCallback(data['requestedHeight'], data['requestedWidth']); }); } diff --git a/3p/messaging.js b/3p/messaging.js index c1d60039db206..40d50aaa3bf35 100644 --- a/3p/messaging.js +++ b/3p/messaging.js @@ -15,6 +15,7 @@ */ import {parseJson} from '../src/json'; +import {getData} from '../src/event-helper'; /** * Send messages to parent frame. These should not contain user data. @@ -34,7 +35,7 @@ export function nonSensitiveDataPostMessage(type, opt_object) { /** * Message event listeners. - * @const {!Array<{type: string, cb: function(!Object)}>} + * @const {!Array<{type: string, cb: function(!JsonObject)}>} */ const listeners = []; @@ -42,7 +43,7 @@ const listeners = []; * Listen to message events from document frame. * @param {!Window} win * @param {string} type Type of messages - * @param {function(*)} callback Called with data payload of message. + * @param {function(!JsonObject)} callback Called with data payload of message. * @return {function()} function to unlisten for messages. */ export function listenParent(win, type, callback) { @@ -72,14 +73,16 @@ function startListening(win) { win.AMP_LISTENING = true; win.addEventListener('message', function(event) { // Cheap operations first, so we don't parse JSON unless we have to. + const eventData = getData(event); if (event.source != win.parent || event.origin != win.context.location.origin || - typeof event.data != 'string' || - event.data.indexOf('amp-') != 0) { + typeof eventData != 'string' || + eventData.indexOf('amp-') != 0) { return; } // Parse JSON only once per message. - const data = parseJson(event.data.substr(4)); + const data = /** @type {!JsonObject} */ ( + parseJson(/**@type {string} */ (getData(event)).substr(4))); if (win.context.sentinel && data['sentinel'] != win.context.sentinel) { return; } diff --git a/ads/inabox/inabox-messaging-host.js b/ads/inabox/inabox-messaging-host.js index ba88e8a097579..3029eb918c5be 100644 --- a/ads/inabox/inabox-messaging-host.js +++ b/ads/inabox/inabox-messaging-host.js @@ -21,6 +21,7 @@ import { MessageType, } from '../../src/3p-frame-messaging'; import {dev} from '../../src/log'; +import {getData} from '../../src/event-helper'; import {dict} from '../../src/utils/object'; import {expandFrame, collapseFrame} from './frame-overlay-helper'; @@ -90,11 +91,11 @@ export class InaboxMessagingHost { * {type: string, sentinel: string}. The allowed types are listed in the * REQUEST_TYPE enum. * - * @param message {!{data: *, source: !Window, origin: string}} + * @param {!MessageEvent} message * @return {boolean} true if message get successfully processed */ processMessage(message) { - const request = deserializeMessage(message.data); + const request = deserializeMessage(getData(message)); if (!request || !request['sentinel']) { dev().fine(TAG, 'Ignored non-AMP message:', message); return false; diff --git a/build-system/conformance-config.textproto b/build-system/conformance-config.textproto index de316b5b6db85..41b2b67115baf 100644 --- a/build-system/conformance-config.textproto +++ b/build-system/conformance-config.textproto @@ -167,6 +167,22 @@ requirement: { whitelist: 'src/json.js' # Where jsonParse itself is implemented. } +requirement: { + type: BANNED_PROPERTY_READ + error_message: 'Use eventHelper#getData to read the data property. OK to whitelist 3p ads for now.' + value: 'Event.prototype.data' + value: 'MessageEvent.prototype.data' + whitelist: 'src/event-helper.js' # getData is implemented here + whitelist: 'src/web-worker/amp-worker.js' # OK to use in version bound worker + whitelist: 'src/web-worker/web-worker.js' # OK to use in version bound worker + + # 3p ads are OK + whitelist: 'ads/adfox.js' + whitelist: 'ads/google/imaVideo.js' + whitelist: 'ads/netletix.js' + whitelist: 'ads/yandex.js' +} + requirement: { type: RESTRICTED_METHOD_CALL error_message: 'postMessage must be called with a string or a JsonObject' diff --git a/extensions/amp-ad/0.1/a2a-listener.js b/extensions/amp-ad/0.1/a2a-listener.js index 8da9ad6ac8573..8fb1ecdabc58f 100644 --- a/extensions/amp-ad/0.1/a2a-listener.js +++ b/extensions/amp-ad/0.1/a2a-listener.js @@ -15,6 +15,7 @@ import {closestByTag} from '../../../src/dom'; import {isExperimentOn} from '../../../src/experiments'; +import {getData} from '../../../src/event-helper'; import {user} from '../../../src/log'; import {viewerForDoc} from '../../../src/services'; import {isProxyOrigin} from '../../../src/url'; @@ -45,7 +46,7 @@ export function setupA2AListener(win) { * @visibleForTesting */ export function handleMessageEvent(win, event) { - const data = event.data; + const data = getData(event); // Only handle messages starting with the magic string. if (typeof data != 'string' || data.indexOf('a2a;') != 0) { return; diff --git a/extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js b/extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js index 7fc0d215306ac..16a2adde12784 100644 --- a/extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js +++ b/extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js @@ -29,7 +29,7 @@ import {dev} from '../../../src/log'; import {dict} from '../../../src/utils/object'; import {timerFor} from '../../../src/services'; import {setStyle} from '../../../src/style'; -import {loadPromise} from '../../../src/event-helper'; +import {getData, loadPromise} from '../../../src/event-helper'; import {getHtml} from '../../../src/get-html'; import {removeElement} from '../../../src/dom'; import {getServiceForDoc} from '../../../src/service'; @@ -136,7 +136,7 @@ export class AmpAdXOriginIframeHandler { // iframe. listenForOncePromise(this.iframe, 'entity-id', true) .then(info => { - this.element_.creativeId = info.data.id; + this.element_.creativeId = info.data['id']; }); this.unlisteners_.push(listenFor(this.iframe, 'get-html', @@ -145,7 +145,9 @@ export class AmpAdXOriginIframeHandler { return; } - const {selector, attributes, messageId} = info; + const selector = info['selector']; + const attributes = info['attributes']; + const messageId = info['messageId']; let content = ''; if (this.element_.hasAttribute('data-html-access-allowed')) { @@ -164,7 +166,7 @@ export class AmpAdXOriginIframeHandler { // Install iframe resize API. this.unlisteners_.push(listenFor(this.iframe, 'embed-size', (data, source, origin) => { - this.handleResize_(data.height, data.width, source, origin); + this.handleResize_(data['height'], data['width'], source, origin); }, true, true)); this.unlisteners_.push(this.viewer_.onVisibilityChanged(() => { @@ -204,7 +206,7 @@ export class AmpAdXOriginIframeHandler { listenForOncePromise(this.iframe, ['render-start', 'no-content'], true).then(info => { const data = info.data; - if (data.type == 'render-start') { + if (data['type'] == 'render-start') { this.renderStart_(info); renderStartResolve(); } else { @@ -273,7 +275,7 @@ export class AmpAdXOriginIframeHandler { /** * callback functon on receiving render-start - * @param {!Object=} opt_info + * @param {{data: !JsonObject}=} opt_info * @private */ renderStart_(opt_info) { @@ -281,9 +283,9 @@ export class AmpAdXOriginIframeHandler { if (!opt_info) { return; } - const data = opt_info.data; + const data = getData(opt_info); this.handleResize_( - data.height, data.width, opt_info.source, opt_info.origin); + data['height'], data['width'], opt_info['source'], opt_info['origin']); if (this.baseInstance_.emitLifecycleEvent) { this.baseInstance_.emitLifecycleEvent('renderCrossDomainStart'); } diff --git a/extensions/amp-brid-player/0.1/amp-brid-player.js b/extensions/amp-brid-player/0.1/amp-brid-player.js index 095e30f57ae6d..198dc112bc396 100644 --- a/extensions/amp-brid-player/0.1/amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/amp-brid-player.js @@ -23,7 +23,7 @@ import {VideoEvents} from '../../../src/video-interface'; import {videoManagerForDoc} from '../../../src/services'; import {assertAbsoluteHttpOrHttpsUrl} from '../../../src/url'; import {removeElement} from '../../../src/dom'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; /** * @implements {../../../src/video-interface.VideoInterface} @@ -216,13 +216,14 @@ class AmpBridPlayer extends AMP.BaseElement { /** @private */ handleBridMessages_(event) { + const eventData = /** @type {?string|undefined} */ (getData(event)); if (event.origin !== 'https://services.brid.tv' || event.source != this.iframe_.contentWindow || - typeof event.data !== 'string' || event.data.indexOf('Brid') !== 0) { + typeof eventData !== 'string' || eventData.indexOf('Brid') !== 0) { return; } - const params = event.data.split('|'); + const params = eventData.split('|'); if (params[2] == 'trigger') { if (params[3] == 'ready') { diff --git a/extensions/amp-dailymotion/0.1/amp-dailymotion.js b/extensions/amp-dailymotion/0.1/amp-dailymotion.js index c4faa1ea071b3..ea5f41e8b39cb 100644 --- a/extensions/amp-dailymotion/0.1/amp-dailymotion.js +++ b/extensions/amp-dailymotion/0.1/amp-dailymotion.js @@ -21,7 +21,7 @@ import {VideoEvents} from '../../../src/video-interface'; import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {videoManagerForDoc} from '../../../src/services'; import {parseQueryString} from '../../../src/url'; @@ -185,10 +185,10 @@ class AmpDailymotion extends AMP.BaseElement { event.source != this.iframe_.contentWindow) { return; } - if (!event.data || !event.type || event.type != 'message') { + if (!getData(event) || !event.type || event.type != 'message') { return; // Event empty } - const data = parseQueryString(event.data); + const data = parseQueryString(/** @type {string} */ (getData(event))); if (data === undefined) { return; // The message isn't valid } diff --git a/extensions/amp-facebook-comments/0.1/amp-facebook-comments.js b/extensions/amp-facebook-comments/0.1/amp-facebook-comments.js index f9094b866472b..65843926e51f0 100644 --- a/extensions/amp-facebook-comments/0.1/amp-facebook-comments.js +++ b/extensions/amp-facebook-comments/0.1/amp-facebook-comments.js @@ -62,7 +62,7 @@ class AmpFacebookComments extends AMP.BaseElement { this.applyFillContent(iframe); // Triggered by context.updateDimensions() inside the iframe. listenFor(iframe, 'embed-size', data => { - this./*OK*/changeHeight(data.height); + this./*OK*/changeHeight(data['height']); }, /* opt_is3P */true); this.element.appendChild(iframe); this.iframe_ = iframe; diff --git a/extensions/amp-facebook-like/0.1/amp-facebook-like.js b/extensions/amp-facebook-like/0.1/amp-facebook-like.js index 788ff3eca7e88..0cd4fa9666e72 100644 --- a/extensions/amp-facebook-like/0.1/amp-facebook-like.js +++ b/extensions/amp-facebook-like/0.1/amp-facebook-like.js @@ -62,7 +62,7 @@ class AmpFacebookLike extends AMP.BaseElement { this.applyFillContent(iframe); // Triggered by context.updateDimensions() inside the iframe. listenFor(iframe, 'embed-size', data => { - this.attemptChangeHeight(data.height).catch(() => { + this.attemptChangeHeight(data['height']).catch(() => { /* ignore failures */ }); }, /* opt_is3P */true); diff --git a/extensions/amp-facebook/0.1/amp-facebook.js b/extensions/amp-facebook/0.1/amp-facebook.js index e578b33c01b22..ee81dbf1dc09f 100644 --- a/extensions/amp-facebook/0.1/amp-facebook.js +++ b/extensions/amp-facebook/0.1/amp-facebook.js @@ -62,7 +62,7 @@ class AmpFacebook extends AMP.BaseElement { this.applyFillContent(iframe); // Triggered by context.updateDimensions() inside the iframe. listenFor(iframe, 'embed-size', data => { - this./*OK*/changeHeight(data.height); + this./*OK*/changeHeight(data['height']); }, /* opt_is3P */true); this.element.appendChild(iframe); this.iframe_ = iframe; diff --git a/extensions/amp-gist/0.1/amp-gist.js b/extensions/amp-gist/0.1/amp-gist.js index 970a1f7b236a0..499e915ad7591 100644 --- a/extensions/amp-gist/0.1/amp-gist.js +++ b/extensions/amp-gist/0.1/amp-gist.js @@ -62,7 +62,7 @@ export class AmpGist extends AMP.BaseElement { this.applyFillContent(iframe); // Triggered by window.context.requestResize() inside the iframe. listenFor(iframe, 'embed-size', data => { - this./*OK*/changeHeight(data.height); + this./*OK*/changeHeight(data['height']); }, /* opt_is3P */true); this.element.appendChild(iframe); diff --git a/extensions/amp-ima-video/0.1/amp-ima-video.js b/extensions/amp-ima-video/0.1/amp-ima-video.js index 76ab51b9371ef..baf8c4f9419b2 100644 --- a/extensions/amp-ima-video/0.1/amp-ima-video.js +++ b/extensions/amp-ima-video/0.1/amp-ima-video.js @@ -26,11 +26,11 @@ import { toArray, } from '../../../src/types'; import { + getData, listen, } from '../../../src/event-helper'; import {dict} from '../../../src/utils/object'; import {removeElement} from '../../../src/dom'; -import {startsWith} from '../../../src/string'; import {user} from '../../../src/log'; import {VideoEvents} from '../../../src/video-interface'; import {videoManagerForDoc} from '../../../src/services'; @@ -200,21 +200,19 @@ class AmpImaVideo extends AMP.BaseElement { if (event.source != this.iframe_.contentWindow) { return; } - if (!event.data || - !(isObject(event.data) || startsWith(event.data, '{'))) { - return; // Doesn't look like JSON. - } - - if (isObject(event.data)) { - if (event.data.event == VideoEvents.LOAD || - event.data.event == VideoEvents.PLAY || - event.data.event == VideoEvents.PAUSE || - event.data.event == VideoEvents.MUTED || - event.data.event == VideoEvents.UNMUTED) { - if (event.data.event == VideoEvents.LOAD) { + const eventData = getData(event); + + if (isObject(eventData)) { + const videoEvent = eventData['event']; + if (videoEvent == VideoEvents.LOAD || + videoEvent == VideoEvents.PLAY || + videoEvent == VideoEvents.PAUSE || + videoEvent == VideoEvents.MUTED || + videoEvent == VideoEvents.UNMUTED) { + if (videoEvent == VideoEvents.LOAD) { this.playerReadyResolver_(this.iframe_); } - this.element.dispatchCustomEvent(event.data.event); + this.element.dispatchCustomEvent(videoEvent); } } } diff --git a/extensions/amp-imgur/0.1/amp-imgur.js b/extensions/amp-imgur/0.1/amp-imgur.js index e5cb772da6a0b..bd5f1a2248155 100644 --- a/extensions/amp-imgur/0.1/amp-imgur.js +++ b/extensions/amp-imgur/0.1/amp-imgur.js @@ -32,7 +32,7 @@ import {isLayoutSizeDefined} from '../../../src/layout'; import {removeElement} from '../../../src/dom'; import {tryParseJson} from '../../../src/json'; import {isObject} from '../../../src/types'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {startsWith} from '../../../src/string'; export class AmpImgur extends AMP.BaseElement { @@ -92,12 +92,14 @@ export class AmpImgur extends AMP.BaseElement { event.source != this.iframe_.contentWindow) { return; } - if (!event.data || !(isObject(event.data)) || startsWith(event.data, '{')) { + const eventData = getData(event); + if (!eventData || !(isObject(eventData)) + || startsWith(/** @type {string} */ (eventData), '{')) { return; } - const data = isObject(event.data) ? event.data : tryParseJson(event.data); - if (data.message == 'resize_imgur') { - const height = data.height; + const data = isObject(eventData) ? eventData : tryParseJson(eventData); + if (data['message'] == 'resize_imgur') { + const height = data['height']; this.attemptChangeHeight(height).catch(() => {}); } } diff --git a/extensions/amp-instagram/0.1/amp-instagram.js b/extensions/amp-instagram/0.1/amp-instagram.js index 6331a2aa3e738..0dd661c97aa14 100644 --- a/extensions/amp-instagram/0.1/amp-instagram.js +++ b/extensions/amp-instagram/0.1/amp-instagram.js @@ -53,7 +53,7 @@ import {removeElement} from '../../../src/dom'; import {user} from '../../../src/log'; import {tryParseJson} from '../../../src/json'; import {isObject} from '../../../src/types'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {startsWith} from '../../../src/string'; /* @@ -195,15 +195,17 @@ class AmpInstagram extends AMP.BaseElement { event.source != this.iframe_.contentWindow) { return; } - if (!event.data || !(isObject(event.data) || startsWith(event.data, '{'))) { + const eventData = getData(event); + if (!eventData || !(isObject(eventData) + || startsWith(/** @type {string} */ (eventData), '{'))) { return; // Doesn't look like JSON. } - const data = isObject(event.data) ? event.data : tryParseJson(event.data); + const data = isObject(eventData) ? eventData : tryParseJson(eventData); if (data === undefined) { return; // We only process valid JSON. } - if (data.type == 'MEASURE' && data.details) { - const height = data.details.height; + if (data['type'] == 'MEASURE' && data['details']) { + const height = data['details']['height']; this.getVsync().measure(() => { if (this.iframe_ && this.iframe_./*OK*/offsetHeight !== height) { // Height returned by Instagram includes header, so diff --git a/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js index 31396e74f7a26..7364da6a9b5a5 100644 --- a/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js +++ b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js @@ -23,7 +23,7 @@ import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; import {removeElement} from '../../../src/dom'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {isObject} from '../../../src/types'; import {VideoEvents} from '../../../src/video-interface'; import {videoManagerForDoc} from '../../../src/services'; @@ -182,22 +182,25 @@ class AmpNexxtvPlayer extends AMP.BaseElement { // emitter handleNexxMessages_(event) { - if (!event.data || event.source !== this.iframe_.contentWindow) { + if (!getData(event) || event.source !== this.iframe_.contentWindow) { return; } - const data = isObject(event.data) ? event.data : tryParseJson(event.data); - if (data === undefined) { + /** @const {?JsonObject} */ + const data = /** @type {?JsonObject} */ (isObject(getData(event)) + ? getData(event) + : tryParseJson(getData(event))); + if (!data) { return; } - if (data.event == 'play') { + if (data['event'] == 'play') { this.element.dispatchCustomEvent(VideoEvents.PLAY); - } else if (data.event == 'pause') { + } else if (data['event'] == 'pause') { this.element.dispatchCustomEvent(VideoEvents.PAUSE); - } else if (data.event == 'mute') { + } else if (data['event'] == 'mute') { this.element.dispatchCustomEvent(VideoEvents.MUTED); - } else if (data.event == 'unmute') { + } else if (data['event'] == 'unmute') { this.element.dispatchCustomEvent(VideoEvents.UNMUTED); } } diff --git a/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js b/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js index ec7d2e86a9c91..f68482083ffac 100644 --- a/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js +++ b/extensions/amp-ooyala-player/0.1/amp-ooyala-player.js @@ -22,7 +22,7 @@ import { installVideoManagerForDoc, } from '../../../src/service/video-manager-impl'; import {isObject} from '../../../src/types'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {VideoEvents} from '../../../src/video-interface'; import {videoManagerForDoc} from '../../../src/services'; @@ -155,17 +155,20 @@ class AmpOoyalaPlayer extends AMP.BaseElement { /** @private */ handleOoyalaMessages_(event) { - const data = isObject(event.data) ? event.data : tryParseJson(event.data); + /** @const {?JsonObject|undefined} */ + const data = /** @type {?JsonObject} */ (isObject(getData(event)) + ? getData(event) + : tryParseJson(getData(event))); if (data === undefined) { return; // We only process valid JSON. } - if (data.data == 'playing') { + if (data['data'] == 'playing') { this.element.dispatchCustomEvent(VideoEvents.PLAY); - } else if (data.data == 'paused') { + } else if (data['data'] == 'paused') { this.element.dispatchCustomEvent(VideoEvents.PAUSE); - } else if (data.data == 'muted') { + } else if (data['data'] == 'muted') { this.element.dispatchCustomEvent('mute'); - } else if (data.data == 'unmuted') { + } else if (data['data'] == 'unmuted') { this.element.dispatchCustomEvent('unmute'); } } diff --git a/extensions/amp-twitter/0.1/amp-twitter.js b/extensions/amp-twitter/0.1/amp-twitter.js index 979fd4842c433..ef248b70d2309 100644 --- a/extensions/amp-twitter/0.1/amp-twitter.js +++ b/extensions/amp-twitter/0.1/amp-twitter.js @@ -65,7 +65,7 @@ class AmpTwitter extends AMP.BaseElement { // We only get the message if and when there is a tweet to display, // so hide the placeholder. this.togglePlaceholder(false); - this./*OK*/changeHeight(data.height); + this./*OK*/changeHeight(data['height']); }, /* opt_is3P */true); this.element.appendChild(iframe); this.iframe_ = iframe; diff --git a/extensions/amp-viewer-integration/0.1/amp-viewer-integration.js b/extensions/amp-viewer-integration/0.1/amp-viewer-integration.js index 92bd024b42fd8..0b34e3627c983 100644 --- a/extensions/amp-viewer-integration/0.1/amp-viewer-integration.js +++ b/extensions/amp-viewer-integration/0.1/amp-viewer-integration.js @@ -25,6 +25,7 @@ import {isIframed} from '../../../src/dom'; import {listen, listenOnce} from '../../../src/event-helper'; import {dev} from '../../../src/log'; import {dict} from '../../../src/utils/object'; +import {getData} from '../../../src/event-helper'; import {getSourceUrl} from '../../../src/url'; import {viewerForDoc} from '../../../src/services'; @@ -102,8 +103,9 @@ export class AmpViewerIntegration { webviewPreHandshakePromise_(source, origin) { return new Promise(resolve => { const unlisten = listen(this.win, 'message', e => { - dev().fine(TAG, 'AMPDOC got a pre-handshake message:', e.type, e.data); - const data = parseMessage(e.data); + dev().fine(TAG, 'AMPDOC got a pre-handshake message:', e.type, + getData(e)); + const data = parseMessage(getData(e)); if (!data) { return; } diff --git a/extensions/amp-viewer-integration/0.1/messaging/messaging.js b/extensions/amp-viewer-integration/0.1/messaging/messaging.js index 94e1a17f1274b..82ccb0ef4080a 100644 --- a/extensions/amp-viewer-integration/0.1/messaging/messaging.js +++ b/extensions/amp-viewer-integration/0.1/messaging/messaging.js @@ -15,6 +15,7 @@ */ import {parseJson} from '../../../../src/json'; +import {getData} from '../../../../src/event-helper'; const TAG = 'amp-viewer-messaging'; export const APP = '__AMPHTML__'; @@ -85,7 +86,7 @@ export class WindowPortEmulator { addEventListener(eventType, handler) { this.win.addEventListener('message', e => { if (e.origin == this.origin_ && - e.source == this.target_ && e.data.app == APP) { + e.source == this.target_ && getData(e)['app'] == APP) { handler(e); } }); @@ -172,7 +173,7 @@ export class Messaging { * @private */ handleMessage_(event) { - const message = parseMessage(event.data); + const message = parseMessage(getData(event)); if (!message) { return; } diff --git a/extensions/amp-youtube/0.1/amp-youtube.js b/extensions/amp-youtube/0.1/amp-youtube.js index 28d7cdb51b475..70e33a7d7cf41 100644 --- a/extensions/amp-youtube/0.1/amp-youtube.js +++ b/extensions/amp-youtube/0.1/amp-youtube.js @@ -17,7 +17,7 @@ import {getDataParamsFromAttributes} from '../../../src/dom'; import {tryParseJson} from '../../../src/json'; import {removeElement} from '../../../src/dom'; -import {listen} from '../../../src/event-helper'; +import {getData, listen} from '../../../src/event-helper'; import {isLayoutSizeDefined} from '../../../src/layout'; import {dev, user} from '../../../src/log'; import { @@ -287,26 +287,30 @@ class AmpYoutube extends AMP.BaseElement { event.source != this.iframe_.contentWindow) { return; } - if (!event.data || !(isObject(event.data) || startsWith(event.data, '{'))) { + if (!getData(event) || !(isObject(getData(event)) + || startsWith(/** @type {string} */ (getData(event)), '{'))) { return; // Doesn't look like JSON. } - const data = isObject(event.data) ? event.data : tryParseJson(event.data); + /** @const {?JsonObject} */ + const data = /** @type {?JsonObject} */ (isObject(getData(event)) + ? getData(event) + : tryParseJson(getData(event))); if (data === undefined) { return; // We only process valid JSON. } - if (data.event == 'infoDelivery' && - data.info && data.info.playerState !== undefined) { - this.playerState_ = data.info.playerState; + if (data['event'] == 'infoDelivery' && + data['info'] && data['info']['playerState'] !== undefined) { + this.playerState_ = data['info']['playerState']; if (this.playerState_ == PlayerStates.PAUSED || this.playerState_ == PlayerStates.ENDED) { this.element.dispatchCustomEvent(VideoEvents.PAUSE); } else if (this.playerState_ == PlayerStates.PLAYING) { this.element.dispatchCustomEvent(VideoEvents.PLAY); } - } else if (data.event == 'infoDelivery' && - data.info && data.info.muted !== undefined) { - if (this.muted_ != data.info.muted) { - this.muted_ = data.info.muted; + } else if (data['event'] == 'infoDelivery' && + data['info'] && data['info']['muted'] !== undefined) { + if (this.muted_ != data['info']['muted']) { + this.muted_ = data['info']['muted']; const evt = this.muted_ ? VideoEvents.MUTED : VideoEvents.UNMUTED; this.element.dispatchCustomEvent(evt); } diff --git a/src/base-element.js b/src/base-element.js index c8b7a4db29576..a26d656796ebb 100644 --- a/src/base-element.js +++ b/src/base-element.js @@ -16,6 +16,7 @@ import {ActionTrust} from './action-trust'; import {Layout} from './layout'; +import {getData} from './event-helper'; import {loadPromise} from './event-helper'; import {preconnectForElement} from './preconnect'; import {isArray} from './types'; @@ -617,7 +618,7 @@ export class BaseElement { events = isArray(events) ? events : [events]; for (let i = 0; i < events.length; i++) { element.addEventListener(events[i], event => { - this.element.dispatchCustomEvent(events[i], event.data || {}); + this.element.dispatchCustomEvent(events[i], getData(event) || {}); }); } } diff --git a/src/chunk.js b/src/chunk.js index bdb174b4913ef..9229ff2b40e0a 100644 --- a/src/chunk.js +++ b/src/chunk.js @@ -16,6 +16,7 @@ import PriorityQueue from './utils/priority-queue'; import {dev} from './log'; +import {getData} from './event-helper'; import {registerServiceBuilderForDoc, getServiceForDoc} from './service'; import {makeBodyVisible} from './style-installer'; import {viewerPromiseForDoc} from './services'; @@ -317,7 +318,7 @@ class Chunks { this.viewerPromise_ = viewerPromiseForDoc(ampDoc); this.win_.addEventListener('message', e => { - if (e.data == 'amp-macro-task') { + if (getData(e) == 'amp-macro-task') { this.execute_(/* idleDeadline */ null); } }); diff --git a/src/event-helper.js b/src/event-helper.js index d73f6d2cd5c1f..950a890281af4 100644 --- a/src/event-helper.js +++ b/src/event-helper.js @@ -57,6 +57,14 @@ export function listen(element, eventType, listener, opt_capture) { element, eventType, listener, opt_capture); } +/** + * Returns the data property of an event with the correct type. + * @param {!Event|{data: !JsonObject}} event + * @return {?JsonObject|string|undefined} + */ +export function getData(event) { + return /** @type {?JsonObject|string|undefined} */ (event.data); +} /** * Listens for the specified event on the element and removes the listener diff --git a/src/iframe-helper.js b/src/iframe-helper.js index 78cd0588b8a22..2c4f8cbcf51cd 100644 --- a/src/iframe-helper.js +++ b/src/iframe-helper.js @@ -16,6 +16,8 @@ import {deserializeMessage, isAmpMessage} from './3p-frame-messaging'; import {dev} from './log'; +import {dict} from './utils/object'; +import {getData} from './event-helper'; import {filterSplice} from './utils/array'; import {parseUrl} from './url'; import {tryParseJson} from './json'; @@ -29,7 +31,7 @@ const UNLISTEN_SENTINEL = 'unlisten'; /** * @typedef {{ * frame: !Element, - * events: !Object> + * events: !Object> * }} */ let WindowEventsDef; @@ -75,7 +77,7 @@ function getListenForSentinel(parentWin, sentinel, opt_create) { * @param {!Element} iframe the iframe element who's context will trigger the * event * @param {boolean=} opt_is3P set to true if the iframe is 3p. - * @return {?Object>} + * @return {?Object>} */ function getOrCreateListenForEvents(parentWin, iframe, opt_is3P) { const origin = parseUrl(iframe.src).origin; @@ -109,7 +111,7 @@ function getOrCreateListenForEvents(parentWin, iframe, opt_is3P) { * @param {string} sentinel the sentinel of the message * @param {string} origin the source window's origin * @param {!Window} triggerWin the window that triggered the event - * @return {?Object>} + * @return {?Object>} */ function getListenForEvents(parentWin, sentinel, origin, triggerWin) { const listenSentinel = getListenForSentinel(parentWin, sentinel); @@ -166,7 +168,7 @@ function isDescendantWindow(ancestor, descendant) { * @param {!Array} listenSentinel */ function dropListenSentinel(listenSentinel) { - const noopData = {sentinel: UNLISTEN_SENTINEL}; + const noopData = dict({'sentinel': UNLISTEN_SENTINEL}); for (let i = listenSentinel.length - 1; i >= 0; i--) { const windowEvents = listenSentinel[i]; @@ -194,17 +196,17 @@ function registerGlobalListenerIfNeeded(parentWin) { return; } const listenForListener = function(event) { - if (!event.data) { + if (!getData(event)) { return; } - const data = parseIfNeeded(event.data); - if (!data || !data.sentinel) { + const data = parseIfNeeded(getData(event)); + if (!data || !data['sentinel']) { return; } const listenForEvents = getListenForEvents( parentWin, - data.sentinel, + data['sentinel'], event.origin, event.source ); @@ -212,7 +214,7 @@ function registerGlobalListenerIfNeeded(parentWin) { return; } - let listeners = listenForEvents[data.type]; + let listeners = listenForEvents[data['type']]; if (!listeners) { return; } @@ -236,8 +238,8 @@ function registerGlobalListenerIfNeeded(parentWin) { * * @param {!Element} iframe. * @param {string} typeOfMessage. - * @param {?function(!Object, !Window, string)} callback Called when a message of - * this type arrives for this iframe. + * @param {?function(!JsonObject, !Window, string)} callback Called when a + * message of this type arrives for this iframe. * @param {boolean=} opt_is3P set to true if the iframe is 3p. * @param {boolean=} opt_includingNestedWindows set to true if a messages from * nested frames should also be accepted. @@ -302,7 +304,7 @@ export function listenFor( * @param {!Element} iframe * @param {string|!Array} typeOfMessages * @param {boolean=} opt_is3P - * @return {!Promise} + * @return {!Promise} */ export function listenForOncePromise(iframe, typeOfMessages, opt_is3P) { const unlistenList = []; @@ -379,7 +381,7 @@ function getSentinel_(iframe, opt_is3P) { /** * JSON parses event.data if it needs to be * @param {*} data - * @returns {?Object} object message + * @returns {?JsonObject} object message * @private * @visibleForTesting */ @@ -397,7 +399,7 @@ export function parseIfNeeded(data) { data = null; } } - return /** @type {?Object} */ (data); + return /** @type {?JsonObject} */ (data); } @@ -412,7 +414,7 @@ export class SubscriptionApi { * @param {!Element} iframe The iframe. * @param {string} type Type of the subscription message. * @param {boolean} is3p set to true if the iframe is 3p. - * @param {function(!Object, !Window, string)} requestCallback Callback + * @param {function(!JsonObject, !Window, string)} requestCallback Callback * invoked whenever a new window subscribes. */ constructor(iframe, type, is3p, requestCallback) { diff --git a/src/service/position-observer-impl.js b/src/service/position-observer-impl.js index 790778eb4c916..9c2425e5fa472 100644 --- a/src/service/position-observer-impl.js +++ b/src/service/position-observer-impl.js @@ -28,6 +28,7 @@ import { import {isExperimentOn} from '../../src/experiments'; import {serializeMessage} from '../../src/3p-frame-messaging'; import {parseJson, tryParseJson} from '../../src/json.js'; +import {getData} from '../../src/event-helper'; /** @const @private */ const TAG = 'POSITION_OBSERVER'; @@ -362,12 +363,13 @@ export class InaboxAmpDocPositionObserver extends AbstractPositionObserver { this.ampdoc_.win.addEventListener('message', event => { // Cheap operations first, so we don't parse JSON unless we have to. - if (event.source != win.parent || typeof event.data != 'string' || - event.data.indexOf('amp-') != 0) { + if (event.source != win.parent || typeof getData(event) != 'string' || + dev().assertString(getData(event)).indexOf('amp-') != 0) { return; } // Parse JSON only once per message. - const data = parseJson(event.data.substr(4)); + const data = parseJson( + dev().assertString(getData(event)).substr(4)); if (data['sentinel'] != sentinel) { return; }