-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented AmpContext and IframeMessagingClient (#6310)
* Added ampcontext.js, ampcontext-lib and updated gulpfile. * Changed how ampcontext-lib creates window.context (for dev: cherry pick on to frizz-ampcontext) * Split out PostMessenger as a parent class * Changed name of class * Made changes as per Hongfei. Had to expose getRandom(). * super() must be called before we can use this * Fixed broken funtion documentation * Fixed bug in lib and changed error thrown * Made changes to fix presubmit failures * Made fixes as per reviewers * Fixed lint/style and presubmit errors.
- Loading branch information
1 parent
02c4584
commit 555b554
Showing
7 changed files
with
353 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* Copyright 2016 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import {AmpContext} from './ampcontext.js'; | ||
import {initLogConstructor} from '../src/log'; | ||
initLogConstructor(); | ||
|
||
|
||
/** | ||
* If window.context does not exist, we must instantiate a replacement and | ||
* assign it to window.context, to provide the creative with all the required | ||
* functionality. | ||
*/ | ||
try { | ||
const windowContextCreated = new Event('amp-windowContextCreated'); | ||
window.context = new AmpContext(window); | ||
// Allows for pre-existence, consider validating correct window.context lib instance? | ||
window.dispatchEvent(windowContextCreated); | ||
} catch (err) { | ||
// do nothing with error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/** | ||
* Copyright 2016 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import './polyfills'; | ||
import {dev, user} from '../src/log'; | ||
import {IframeMessagingClient} from './iframe-messaging-client'; | ||
|
||
/** | ||
Enum for the different postmessage types for the window.context | ||
postmess api. | ||
*/ | ||
export const MessageType_ = { | ||
SEND_EMBED_STATE: 'send-embed-state', | ||
EMBED_STATE: 'embed-state', | ||
SEND_EMBED_CONTEXT: 'send-embed-context', | ||
EMBED_CONTEXT: 'embed-context', | ||
SEND_INTERSECTIONS: 'send-intersections', | ||
INTERSECTION: 'intersection', | ||
EMBED_SIZE: 'embed-size', | ||
EMBED_SIZE_CHANGED: 'embed-size-changed', | ||
EMBED_SIZE_DENIED: 'embed-size-denied', | ||
}; | ||
|
||
export class AmpContext extends IframeMessagingClient { | ||
|
||
/** | ||
* @param {Window} win The window that the instance is built inside. | ||
*/ | ||
constructor(win) { | ||
super(win); | ||
this.setupMetadata_(); | ||
|
||
/** Calculate the hostWindow / ampWindow_ */ | ||
const sentinelMatch = this.sentinel.match(/((\d+)-\d+)/); | ||
dev().assert(sentinelMatch, 'Incorrect sentinel format'); | ||
this.depth = Number(sentinelMatch[2]); | ||
const ancestors = []; | ||
for (let win = this.win_; win && win != win.parent; win = win.parent) { | ||
// Add window keeping the top-most one at the front. | ||
ancestors.push(win.parent); | ||
} | ||
ancestors.reverse(); | ||
|
||
/** @private */ | ||
this.ampWindow_ = ancestors[this.depth]; | ||
} | ||
|
||
/** @override */ | ||
getHostWindow() { | ||
return this.ampWindow_; | ||
} | ||
|
||
/** @override */ | ||
getSentinel() { | ||
return this.sentinel; | ||
} | ||
|
||
/** @override */ | ||
registerCallback_(messageType, callback) { | ||
user().assertEnumValue(MessageType_, messageType); | ||
this.callbackFor_[messageType] = callback; | ||
return () => { delete this.callbackFor_[messageType]; }; | ||
} | ||
|
||
/** | ||
* Send message to runtime to start sending page visibility messages. | ||
* @param {function(Object)} callback Function to call every time we receive a | ||
* page visibility message. | ||
* @returns {function()} that when called stops triggering the callback | ||
* every time we receive a page visibility message. | ||
*/ | ||
observePageVisibility(callback) { | ||
const stopObserveFunc = this.registerCallback_( | ||
MessageType_.EMBED_STATE, callback); | ||
this.ampWindow_.postMessage/*REVIEW*/({ | ||
sentinel: this.sentinel, | ||
type: MessageType_.SEND_EMBED_STATE, | ||
}, '*'); | ||
|
||
return stopObserveFunc; | ||
}; | ||
|
||
/** | ||
* Send message to runtime to start sending intersection messages. | ||
* @param {function(Object)} callback Function to call every time we receive an | ||
* intersection message. | ||
* @returns {function()} that when called stops triggering the callback | ||
* every time we receive an intersection message. | ||
*/ | ||
observeIntersection(callback) { | ||
const stopObserveFunc = this.registerCallback_( | ||
MessageType_.INTERSECTION, callback); | ||
this.ampWindow_.postMessage/*REVIEW*/({ | ||
sentinel: this.sentinel, | ||
type: MessageType_.SEND_INTERSECTIONS, | ||
}, '*'); | ||
|
||
return stopObserveFunc; | ||
}; | ||
|
||
/** | ||
* Send message to runtime requesting to resize ad to height and width. | ||
* This is not guaranteed to succeed. All this does is make the request. | ||
* @param {int} height The new height for the ad we are requesting. | ||
* @param {int} width The new width for the ad we are requesting. | ||
*/ | ||
requestResize(height, width) { | ||
this.ampWindow_.postMessage/*REVIEW*/({ | ||
sentinel: this.sentinel, | ||
type: MessageType_.EMBED_SIZE, | ||
width, | ||
height, | ||
}, '*'); | ||
}; | ||
|
||
/** | ||
* Allows a creative to set the callback function for when the resize | ||
* request returns a success. The callback should be set before resizeAd | ||
* is ever called. | ||
* @param {function(requestedHeight, requestedWidth)} callback Function | ||
* to call if the resize request succeeds. | ||
*/ | ||
onResizeSuccess(callback) { | ||
this.registerCallback_(MessageType_.EMBED_SIZE_CHANGED, function(obj) { | ||
callback(obj.requestedHeight, obj.requestedWidth); }); | ||
}; | ||
|
||
/** | ||
* Allows a creative to set the callback function for when the resize | ||
* request is denied. The callback should be set before resizeAd | ||
* is ever called. | ||
* @param {function(requestedHeight, requestedWidth)} callback Function | ||
* to call if the resize request is denied. | ||
*/ | ||
onResizeDenied(callback) { | ||
this.registerCallback_(MessageType_.EMBED_SIZE_DENIED, function(obj) { | ||
callback(obj.requestedHeight, obj.requestedWidth); }); | ||
}; | ||
|
||
/** | ||
* Takes the current name on the window, and attaches it to | ||
* the name of the iframe. | ||
* @param {HTMLIframeElement} iframe The iframe we are adding the context to. | ||
*/ | ||
addContextToIframe(iframe) { | ||
iframe.name = this.win_.name; | ||
} | ||
|
||
/** | ||
* Parse the metadata attributes from the name and add them to | ||
* the class instance. | ||
* @private | ||
*/ | ||
setupMetadata_() { | ||
try { | ||
const data = JSON.parse(decodeURI(this.win_.name)); | ||
const context = data._context; | ||
this.location = context.location; | ||
this.canonicalUrl = context.canonicalUrl; | ||
this.pageViewId = context.pageViewId; | ||
this.sentinel = context.sentinel; | ||
this.startTime = context.startTime; | ||
this.referrer = context.referrer; | ||
} catch (err) { | ||
user().error('AMPCONTEXT', '- Could not parse metadata.'); | ||
throw new Error('Could not parse metadata.'); | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** | ||
* Copyright 2016 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import './polyfills'; | ||
import {listen} from '../src/event-helper'; | ||
import {getRandom} from '../src/3p-frame'; | ||
import {map} from '../src/types'; | ||
import {user} from '../src/log'; | ||
import {startsWith} from '../src/string'; | ||
|
||
/** | ||
* @abstract | ||
*/ | ||
export class IframeMessagingClient { | ||
|
||
/** | ||
* @param {Window} win A window object. | ||
*/ | ||
constructor(win) { | ||
/** @private {!Window} */ | ||
this.win_ = win; | ||
/** Map messageType keys to callback functions for when we receive | ||
* that message | ||
* @private {!Object} | ||
*/ | ||
this.callbackFor_ = map(); | ||
this.setupEventListener_(); | ||
} | ||
|
||
/** | ||
* Register callback function for message with type messageType. | ||
* As it stands right now, only one callback can exist at a time. | ||
* 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 | ||
* when a message with type messageType is received. | ||
*/ | ||
registerCallback_(messageType, callback) { | ||
// NOTE : no validation done here. any callback can be register | ||
// for any callback, and then if that message is received, this | ||
// class *will execute* that callback | ||
this.callbackFor_[messageType] = callback; | ||
return () => { delete this.callbackFor_[messageType]; }; | ||
} | ||
|
||
/** | ||
* Sets up event listener for post messages of the desired type. | ||
* The actual implementation only uses a single event listener for all of | ||
* the different messages, and simply diverts the message to be handled | ||
* by different callbacks. | ||
* To add new messages to listen for, call registerCallback with the | ||
* messageType to listen for, and the callback function. | ||
* @private | ||
*/ | ||
setupEventListener_() { | ||
listen(this.win_, 'message', message => { | ||
// Does it look a message from AMP? | ||
if (message.source != this.getHostWindow()) { | ||
return; | ||
} | ||
if (!message.data) { | ||
return; | ||
} | ||
if (!startsWith(String(message.data), 'amp-')) { | ||
return; | ||
} | ||
|
||
// See if we can parse the payload. | ||
try { | ||
const payload = JSON.parse(message.data.substring(4)); | ||
// Check the sentinel as well. | ||
if (payload.sentinel == this.getSentinel() && | ||
this.callbackFor_[payload.type]) { | ||
try { | ||
// We should probably report exceptions within callback | ||
this.callbackFor_[payload.type](payload); | ||
} catch (err) { | ||
user().error( | ||
'IFRAME-MSG', | ||
`- Error in registered callback ${payload.type}`, | ||
err); | ||
} | ||
} | ||
} catch (e) { | ||
// JSON parsing failed. Ignore the message. | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* This must be overwritten by classes that extend this base class | ||
* As implemented, this will only work for messaging the parent iframe. | ||
*/ | ||
getSentinel() { | ||
if (!this.sentinel) { | ||
this.sentinel = '0-' + getRandom(this.win_); | ||
} | ||
return this.sentinel; | ||
} | ||
|
||
/** | ||
* Only valid for the trivial case when we will always be messaging our parent | ||
* Should be overwritten for subclasses | ||
*/ | ||
getHostWindow() { | ||
if (!this.hostWindow) { | ||
this.hostWindow = this.win_.parent; | ||
} | ||
return this.hostWindow; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters