Skip to content

Commit

Permalink
Introduces an optional mechanism for advanced advertising techniques
Browse files Browse the repository at this point in the history
to manipulate incoming ad configuration in a callback function.

This is only available for publishers who host the ad iframe themselves.
With that they can obviously do whatever they want, but with this there is a larger incentive to stay within the AMP system and don't rely on implicit APIs (such as how we pass in configuration).

Fixes ampproject#1210
  • Loading branch information
cramforce authored and msukmanowsky committed Jan 21, 2016
1 parent b680815 commit 719ec11
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 8 deletions.
29 changes: 23 additions & 6 deletions 3p/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,27 @@ register('twitter', twitter);

/**
* Visible for testing.
* Draws an ad to the window. Expects the data to include the ad type.
* Draws a 3p embed to the window. Expects the data to include the 3p type.
* @param {!Window} win
* @param {!Object} data
* @param {function(!Object, function(!Object))|undefined} configCallback
* Optional callback that allows user code to manipulate the incoming
* configuration. See
* https://github.com/ampproject/amphtml/issues/1210 for some context
* on this.
*/
export function draw3p(win, data) {
export function draw3p(win, data, configCallback) {
const type = data.type;
assert(window.context.location.originValidated != null,
assert(win.context.location.originValidated != null,
'Origin should have been validated');
run(type, win, data);
if (configCallback) {
configCallback(data, data => {
assert(data, 'Expected configuration to be passed as first argument');
run(type, win, data);
});
} else {
run(type, win, data);
}
};

/**
Expand Down Expand Up @@ -85,8 +97,13 @@ function masterSelection(type) {

/**
* Draws an embed, optionally synchronously, to the DOM.
* @param {function(!Object, function(!Object))} opt_configCallback If provided
* will be invoked with two arguments:
* 1. The configuration parameters supplied to this embed.
* 2. A callback that MUST be called for rendering to proceed. It takes
* no arguments. Configuration is expected to be modified in-place.
*/
window.draw3p = function() {
window.draw3p = function(opt_configCallback) {
const data = parseFragment(location.hash);
window.context = data._context;
window.context.location = parseUrl(data._context.location.href);
Expand All @@ -104,7 +121,7 @@ window.draw3p = function() {
// This only actually works for ads.
window.context.observeIntersection = observeIntersection;
delete data._context;
draw3p(window, data);
draw3p(window, data, opt_configCallback);
};

function triggerNoContentAvailable() {
Expand Down
18 changes: 18 additions & 0 deletions builtins/amp-ad.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,21 @@ To enable this, copy the file [remote.html](../3p/remote.html) to your web serve
```

The `content` attribute of the meta tag is the absolute URL to your copy of the remote.html file on your web server. This URL must use a "https" schema. It is not allowed to reside on the same origin as your AMP files. E.g. if you host AMP files on "www.example.com", this URL must not be on "www.example.com" but e.g. "something-else.example.com" is OK.

##### Enhance incoming ad configuration

This is completely optional: It is sometimes desired to further process the incoming iframe configuration before drawing the ad using AMP's built-in system.

This is supported by passing a callback to the `draw3p` function call in the [remote.html](../3p/remote.html) file. The callback receives the incoming configuration as first argument and then receives another callback as second argument (Called `done` in the example below). This callback must be called with the updated config in order for ad rendering to proceed.

Example:

```JS
draw3p(function(config, done) {
config.targeting = Math.random() > 0.5 ? 'sport' : 'fashion';
// Don't actually call setTimeout here. This should only serve as an
// example that is OK to call the done callback asynchronously.
setTimeout(function() {
done(config);
}, 100)
});
84 changes: 82 additions & 2 deletions test/functional/test-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
// Tests integration.js
// Most coverage through test-3p-frame

import {validateParentOrigin, parseFragment} from '../../3p/integration';
import {registrations} from '../../src/3p';
import {draw3p, validateParentOrigin, parseFragment}
from '../../3p/integration';
import {registrations, register} from '../../src/3p';

describe('3p integration.js', () => {

afterEach(() => {
delete registrations.testAction;
});

it('should register integrations', () => {
expect(registrations).to.include.key('a9');
expect(registrations).to.include.key('adsense');
Expand Down Expand Up @@ -109,4 +115,78 @@ describe('3p integration.js', () => {
expect(parseFragment('')).to.be.empty;
expect(parseFragment('#')).to.be.empty;
});

it('should call the right action based on type', () => {
const data = {
type: 'testAction',
};
const win = {
context: {
location: {
originValidated: true
},
data: data,
}
};
let called = false;
register('testAction', function(myWin, myData) {
called = true;
expect(myWin).to.equal(win);
expect(myData).to.equal(myData);
});
const domNode = {};
expect(called).to.be.false;
draw3p(win, data);
expect(called).to.be.true;
});

it('should support config processing in draw3p', () => {
const data = {
type: 'testAction2',
};
const win = {
context: {
location: {
originValidated: true
},
data: data,
}
};
let called = false;
register('testAction2', function(myWin, myData) {
expect(called).to.be.false;
called = true;
expect(myWin).to.equal(win);
expect(myData).to.not.equal(data);
expect(myData).to.have.property('custom');
});
const domNode = {};
expect(called).to.be.false;
let finish;
draw3p(win, data, (config, done) => {
finish = () => {
done({
custom: true
});
};
});
expect(called).to.be.false;
finish();
expect(called).to.be.true;
});

it('should throw if origin was never validated', () => {
const data = {
type: 'testAction',
};
const win = {
context: {
location: {},
data: data,
}
};
expect(() => {
draw3p(win, data);
}).to.throw(/Origin should have been validated/);
});
});

0 comments on commit 719ec11

Please sign in to comment.