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

Implemented AmpContext and IframeMessagingClient #6310

Merged
merged 11 commits into from Nov 30, 2016
Merged

Implemented AmpContext and IframeMessagingClient #6310

merged 11 commits into from Nov 30, 2016

Conversation

bradfrizzell
Copy link
Contributor

No description provided.

/**
* @abstract
*/
export class XDomainChildMessageHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about IframeMessagingClient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me

try {
// We should probably report exceptions within callback
this.callbackFor_[payload.type](payload);
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we just let it fail? since we've no idea if the failure is recoverable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would letting the error propagate be a good thing? So if the callback fails for whatever reason, that the ad can see that it failed?

* Only valid for the trivial case when we will always be messaging our parent
* Should be overwritten for subclasses
*/
getAmpWindow(){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHostWindow(), since in amp-inabox use case, it's beyond AMP

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

*/
try {
console.log("Attempting to make AmpContext");
const windowContextCreated = new Event('windowContextCreated');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe prefix the event name with amp to avoid conflict?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg

@@ -173,6 +173,28 @@ function compile(watch, shouldMinify, opt_preventRemoveAndMakeDir,
includePolyfills: true,
});

compileJs('./3p/', 'ampcontext.js',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need 2 binaries ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the reason that it is split into 2 files, ampcontext and ampcontext-lib, is because firing the 'windowContextCreated' event from within the AmpContext constructor sends it before the instance is actually ready. Whether we actually need 2 binaries is a good question. Are the binaries only made for files that ad networks should get access to off our cdn? In that case I guess we don't need one for ampcontext.js
@keithwrightbos

* @returns {function} that when called stops triggering the callback
* every time we receive a page visibility message.
*/
AmpContext.prototype.observePageVisibility = function(callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not moving those methods into the class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Enum for the different postmessage types for the window.context
postmess api.
*/
export const MessageType_ = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we plan to use it outside this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea I think I was, I can double check where. I know there was a reason I put the export on

* the class instance.
* @private
*/
setupMetadata_() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls move private method to the bottom of the class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

this.startTime = context.startTime;
this.referrer = context.referrer;
} catch (err) {
user().error('Could not parse metadata.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be a dev() error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


/** @override */
getSentinel(){
this.setupMetadata_();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we move this to constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, actually don't need to call it all in the constructor, setupmetadata takes care of it. just need to overwrite the parent class getSentinel. fixed it.

this.ancestors = [];
for (let win = this.win_; win && win != win.parent; win = win.parent) {
// Add window keeping the top-most one at the front.
this.ancestors.unshift(win.parent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: pushing then reversing the final array will be faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return this.ancestors[this.depth];
} else {
dev().error('Incorrect sentinel format.');
throw new Error('Incorrect sentinel format.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done as a dev().assert:

const sentinelMatch = this.sentinel.match(/((\d+)-\d+)/);
dev().assert(sentinelMatch, 'Incorrect sentinel format.');
// use sentinelMatch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Add window keeping the top-most one at the front.
this.ancestors.unshift(win.parent);
}
return this.ancestors[this.depth];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So depth is top-down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

const sentinelMatch = this.sentinel.match(/((\d+)-\d+)/);
if (sentinelMatch) {
this.depth = Number(sentinelMatch[2]);
this.ancestors = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need ancestors as a property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, done

* that message
* @private {object}
*/
this.callbackFor_ = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a map helper in types.js.

listen(this.win_, 'message', message => {
// Does it look a message from AMP?
if (message.source == this.ampWindow && message.data &&
message.data.indexOf('amp-') == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

this.callbackFor_[payload.type]) {
try {
// We should probably report exceptions within callback
this.callbackFor_[payload.type](payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this will leak the callbackFor_ object as the this context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is setting callbackFor_ as private enough? I'm not 100% sure what you are saying by this, sorry. Do you just mean that the way it currently is has callbackFor_ as public?

setupEventListener_() {
listen(this.win_, 'message', message => {
// Does it look a message from AMP?
if (message.source == this.ampWindow && message.data &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be better flattened out:

if (message.source !== this.ampWindow) {
  return;
}

if (!startsWith(String(message.data), 'amp-') {
  return;
}

if (payload.sentinel !== this.sentinel) {
  return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

constructor(win) {
super(win);
this.setupMetadata_();
this.ampWindow = this.getHostWindow();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a private property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that's fine

/**
* Takes the current name on the window, and attaches it to
* the name of the iframe.
* @param {Iframe} iframe The iframe we are adding the context to.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTMLIframeElement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* functionality.
*/
try {
console/*OK*/.log('Attempting to make AmpContext');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use dev()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
import './polyfills';
import {
dev,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, can you merge them to one line to save some space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

/** @override */
getHostWindow() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHostWindow as a public method, can potentially be called multiple times, should return a cache when available.

we can move the computation logic into constructor.
or, if you want to do lazy initialization, which I don't see much value here, you can initialize this.ampWindow here. Then all the other code should not reference this.ampWindow directly, they should reference this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. moved the logic into the constructor. getHostWindow just returns this.ampWindow now

this.ancestors.unshift(win.parent);
}
return this.ancestors[this.depth];
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: can we reverse the if-else? i.e., fail early in the code and save some indentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n/a, got rid of it

*/
observePageVisibility(callback) {
const stopObserveFunc = this.registerCallback_(MessageType_.EMBED_STATE,
callback);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use 4 space indentation for line breaks.

in this case, you can break after '('

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import {listen} from '../src/event-helper';
import {getRandom} from '../src/3p-frame';
import {user} from '../src/log';
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @param {function(object)} callback The callback function to call
* when a message with type messageType is received.
*/
registerCallback_(messageType, callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe call it listenFor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is already a listenFor function in use, would rather be able to disambiguate

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

@@ -173,6 +173,17 @@ function compile(watch, shouldMinify, opt_preventRemoveAndMakeDir,
includePolyfills: true,
});

compileJs('./3p/', 'ampcontext-lib.js',
'./dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), {
minifiedName: 'ampcontext-lib.js',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about ampcontext-v0.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine

@@ -0,0 +1,32 @@
/**
* Copyright 2015 The AMP HTML Authors. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2016

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -0,0 +1,203 @@
/**
* Copyright 2015 The AMP HTML Authors. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2016

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@bradfrizzell
Copy link
Contributor Author

PTAL - I've addressed all the comments

Copy link
Contributor

@lannka lannka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some cosmetic nitpicks.

this.callbackFor_[payload.type](payload);
} catch (err) {
user().error('IFRAME-MSG',
`- Error in registered callback ${payload.type}`, err);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably want to config your IDE so that it indents 4 spaces on line break, instead of aligning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gah whoops, fixed

if (!message.data) {
return;
}
if (!startsWith(String(message.data), 'amp-')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about merging the 3 ifs into one block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how I originally had it, I split it apart as per a comment from @jridgewell

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine, people have different taste :-)

* @param {function(object)} callback The callback function to call
* when a message with type messageType is received.
*/
registerCallback_(messageType, callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

@lannka lannka self-assigned this Nov 30, 2016
@lannka lannka changed the title Implemented AmpContext and XDomainChildMessageHandler Implemented AmpContext and IframeMessagingClient Nov 30, 2016
@lannka
Copy link
Contributor

lannka commented Nov 30, 2016

Can you fix the Travis, so I can go ahead and merge?

@bradfrizzell
Copy link
Contributor Author

bradfrizzell commented Nov 30, 2016 via email

@bradfrizzell
Copy link
Contributor Author

bradfrizzell commented Nov 30, 2016 via email

@lannka lannka merged commit 555b554 into ampproject:master Nov 30, 2016
// Add window keeping the top-most one at the front.
ancestors.push(win.parent);
}
ancestors.reverse();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid the reverse by counting backwards:

this.ampWindow = ancestors[ancestors.length - 1 - this.depth];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/** Calculate the hostWindow / ampWindow_ */
const sentinelMatch = this.sentinel.match(/((\d+)-\d+)/);
dev().assert(sentinelMatch, 'Incorrect sentinel format');
this.depth = Number(sentinelMatch[2]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this property necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, not needed as a property anymore. fixed


/** @override */
registerCallback_(messageType, callback) {
user().assertEnumValue(MessageType_, messageType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the send-* types, which weren't allowed before. Is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I had previously made a mistake.

observePageVisibility(callback) {
const stopObserveFunc = this.registerCallback_(
MessageType_.EMBED_STATE, callback);
this.ampWindow_.postMessage/*REVIEW*/({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*OK*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

observeIntersection(callback) {
const stopObserveFunc = this.registerCallback_(
MessageType_.INTERSECTION, callback);
this.ampWindow_.postMessage/*REVIEW*/({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*OK*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @param {int} width The new width for the ad we are requesting.
*/
requestResize(height, width) {
this.ampWindow_.postMessage/*REVIEW*/({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*OK*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

this.callbackFor_[payload.type]) {
try {
// We should probably report exceptions within callback
this.callbackFor_[payload.type](payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this leaks the callbackFor_ private property. We can assign the function to a variable, then call the variable to avoid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

/** @override */
getHostWindow() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid these awkward overrides by using a generator method in the base class:

class Base {
  getHostWindow() {
    if (!this.window_) {
      this.window_ = this.generateWindow();
    }
    return this.window_;
  }

  generateWindow() {
    return this.win.parent;
  }
}

class Special extends Base {
  generateWindow() {
    // depth stuff
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds better. @bradfrizzell what you say?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementing.

Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* 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.
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants