Skip to content

Commit

Permalink
Merge tag '1455166600475' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
erwinmombay committed Feb 11, 2016
2 parents 1f6aa80 + a9e09d4 commit bfb86e5
Show file tree
Hide file tree
Showing 93 changed files with 1,811 additions and 622 deletions.
15 changes: 12 additions & 3 deletions 3p/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export function setInViewportForTesting(inV) {

let rafId = 0;
let rafQueue = {};
// Active intervals. Must be global, because people clear intervals
// with clearInterval from a different window.
const intervals = {};
let intervalId = 0;

/**
* Add instrumentation to a window and all child iframes.
Expand Down Expand Up @@ -180,20 +184,25 @@ function instrumentEntryPoints(win) {
};
// Implement setInterval in terms of setTimeout to make
// it respect the same rules
const intervals = {};
let intervalId = 0;
win.setInterval = function(fn, time) {
const id = intervalId++;
function next() {
intervals[id] = win.setTimeout(function() {
next();
return fn.apply(this, arguments);
if (typeof fn == 'string') {
// Handle rare and dangerous string arg case.
return (0, win.eval/*NOT OK but whatcha gonna do.*/).call(win, fn);
} else {
return fn.apply(this, arguments);
}
}, time);
}
next();
return id;
};
const clearInterval = win.clearInterval;
win.clearInterval = function(id) {
clearInterval(id);
win.clearTimeout(intervals[id]);
delete intervals[id];
};
Expand Down
20 changes: 20 additions & 0 deletions 3p/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {facebook} from './facebook';
import {manageWin} from './environment';
import {nonSensitiveDataPostMessage, listenParent} from './messaging';
import {twitter} from './twitter';
import {yieldmo} from '../ads/yieldmo';
import {register, run} from '../src/3p';
import {parseUrl} from '../src/url';
import {assert} from '../src/asserts';
Expand All @@ -58,6 +59,7 @@ register('plista', plista);
register('doubleclick', doubleclick);
register('taboola', taboola);
register('dotandads', dotandads);
register('yieldmo', yieldmo);
register('_ping_', function(win, data) {
win.document.getElementById('c').textContent = data.ping;
});
Expand Down Expand Up @@ -154,6 +156,7 @@ window.draw3p = function(opt_configCallback) {
delete data._context;
manageWin(window);
draw3p(window, data, opt_configCallback);
updateVisibilityState(window);
nonSensitiveDataPostMessage('render-start');
};

Expand Down Expand Up @@ -193,6 +196,23 @@ function observeIntersection(observerCallback) {
});
}

/**
* Listens for events via postMessage and updates `context.hidden` based on
* it and forwards the event to a custom event called `amp:visibilitychange`.
* @param {!Window} global
*/
function updateVisibilityState(global) {
listenParent('embed-state', function(data) {
global.context.hidden = data.pageHidden;
const event = global.document.createEvent('Event');
event.data = {
hidden: data.pageHidden,
};
event.initEvent('amp:visibilitychange', true, true);
global.dispatchEvent(event);
});
}

/**
* Registers a callback for communicating when a resize request succeeds.
* @param {function(number)} observerCallback
Expand Down
18 changes: 4 additions & 14 deletions 3p/twitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// TODO(malteubl) Move somewhere else since this is not an ad.

import {loadScript} from '../src/3p';
import {computeInMasterFrame, loadScript} from '../src/3p';

/**
* Produces the Twitter API object for the passed in callback. If the current
Expand All @@ -27,21 +27,11 @@ import {loadScript} from '../src/3p';
* @param {function(!Object)} cb
*/
function getTwttr(global, cb) {
if (context.isMaster) {
global.twttrCbs = [cb];
computeInMasterFrame(global, 'twttrCbs', done => {
loadScript(global, 'https://platform.twitter.com/widgets.js', () => {
for (let i = 0; i < global.twttrCbs.length; i++) {
global.twttrCbs[i](global.twttr);
}
global.twttrCbs.push = function(cb) {
cb(global.twttr);
};
done(global.twttr);
});
} else {
// Because we rely on this global existing it is important that
// this array is created synchronously after master selection.
context.master.twttrCbs.push(cb);
}
}, cb);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ limitations under the License.

### Slack and mailing list

Please join our [announcements mailing list](https://groups.google.com/forum/#!forum/amphtml-announce). This is a curated, low volume list for announcements about breaking changes and similar issues in AMP.

We discuss implementation issues on [amphtml-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/amphtml-discuss).

For more immediate feedback, [sign up for our Slack](https://docs.google.com/forms/d/1wAE8w3K5preZnBkRk-MD1QkX8FmlRDxd_vs4bFSeJlQ/viewform?fbzx=4406980310789882877).
Expand Down
26 changes: 19 additions & 7 deletions ads/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Integrating ad networks into AMP

See also our [ad integration guidelines](../3p/README.md#ads).
See also our [ad integration guidelines](../3p/README.md#ads) and [3rd party ads integration guidelines](./integration-guide.md)

## Overview
Ads are just another external resource and must play within the same constraints placed on all resources in AMP. We aim to support a large subset of existing ads with little or no changes to how the integrations work. Our long term goal is to further improve the impact of ads on the user experience through changes across the entire vertical client side stack.


## Constraints
A summary of constraints placed on external resources such as ads in AMP HTML:

Expand All @@ -20,7 +19,6 @@ Reasons include:
- The AMP runtime may at any moment decide that there are too many iframes on a page and that memory is low. In that case it would unload ads that were previously loaded and are no longer visible. It may later load new ads in the same slot if the user scrolls them back into view.
- The AMP runtime may decide to set an ad that is currently not visible to `display: none` to reduce browser layout and compositing cost.


## The iframe sandbox

The ad itself is hosted within a document that has an origin different from the primary page.
Expand Down Expand Up @@ -50,6 +48,8 @@ More information can be provided in a similar fashion if needed (Please file an

### Ad viewability

#### Position in viewport

Ads can call the special API `window.context.observeIntersection(changesCallback)` to receive IntersectionObserver style [change records](http://rawgit.com/slightlyoff/IntersectionObserver/master/index.html#intersectionobserverentry) of the ad's intersection with the parent viewport.

The API allows specifying a callback that fires with change records when AMP observes that an ad becomes visible and then while it is visible, changes are reported as they happen.
Expand Down Expand Up @@ -79,6 +79,16 @@ Example usage:
unlisten();
```

#### Page visibility

AMP documents may be practically invisible without the visibility being reflected by the [page visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API). This is primarily the case when a document is swiped away or being prerendered.

Whether a document is actually being visible can be queried using:

`window.context.hidden` which is true if the page is not visible as per page visibility API or because the AMP viewer currently does not show it.

Additionally one can observe the `amp:visibilitychange` on the `window` object to be notified about changes in visibility.

### Ad resizing

Ads can call the special API
Expand All @@ -103,25 +113,24 @@ var unlisten = window.context.onResizeDenied(function(requestedHeight) {
});
```


Here are some factors that affect how fast the resize will be executed:

- Whether the resize is triggered by the user action;
- Whether the resize is requested for a currently active ad;
- Whether the resize is requested for an ad below the viewport or above the viewport.


### Optimizing ad performance

#### JS reuse across iframes
To allow ads to bundle HTTP requests across multiple ad units on the same page the object `window.context.master` will contain the window object of the iframe being elected master iframe for the current page. The `window.context.isMaster` property is `true` when the current frame is the master frame.
To allow ads to bundle HTTP requests across multiple ad units on the same page the object `window.context.master` will contain the window object of the iframe being elected master iframe for the current page. The `window.context.isMaster` property is `true` when the current frame is the master frame.

The `computeInMasterFrame` function is designed to make it easy to perform a task only in the master frame and provide the result to all frames.

#### Preconnect and prefetch
Add the JS URLs that an ad **always** fetches or always connects to (if you know the origin but not the path) to [_config.js](_config.js).

This triggers prefetch/preconnect when the ad is first seen, so that loads are faster when they come into view.


### Ad markup
Ads are loaded using a the <amp-ad> tag given the type of the ad network and name value pairs of configuration. This is an example for the A9 network:

Expand Down Expand Up @@ -159,4 +168,7 @@ Technically the `<amp-ad>` tag loads an iframe to a generic bootstrap URL that k
### 1st party cookies

Access to a publishers 1st party cookies may be achieved through a custom ad bootstrap

file. See ["Running ads from a custom domain"](../builtins/amp-ad.md) in the ad documentation for details.

If the publisher would like to add custom JavaScript in the `remote.html` file that wants to read or write to the publisher owned cookies, then the publisher needs to ensure that the `remote.html` file is hosted on a sub-domain of the publisher URL. e.g. if the publisher hosts a webpage on https://nytimes.com, then the remote file should be hosted on something similar to https://sub-domain.nytimes.com for the custom JavaScript to have the abiity to read or write cookies for nytimes.com.
6 changes: 6 additions & 0 deletions ads/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const adPrefetch = {
adsense: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js',
dotandads: 'https://amp.ad.dotandad.com/dotandadsAmp.js',
smartadserver: 'https://ec-ns.sascdn.com/diff/js/smart.js',
yieldmo: 'https://static.yieldmo.com/ym.amp1.js',
};

/**
Expand All @@ -47,6 +48,11 @@ export const adPreconnect = {
'https://tpc.googlesyndication.com',
],
dotandads: 'https://bal.ad.dotandad.com',
yieldmo: [
'https://static.yieldmo.com',
'https://s.yieldmo.com',
'https://ads.yieldmo.com',
],
};

/**
Expand Down
3 changes: 1 addition & 2 deletions ads/adsense.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export function adsense(global, data) {
hid: global.context.pageViewId,
};
}
/*eslint "google-camelcase/google-camelcase": 0*/
global.google_page_url = global.context.canonicalUrl;
const s = document.createElement('script');
s.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
global.document.body.appendChild(s);
Expand All @@ -40,6 +38,7 @@ export function adsense(global, data) {
if (data['adSlot']) {
i.setAttribute('data-ad-slot', data['adSlot']);
}
i.setAttribute('data-page-url', global.context.canonicalUrl);
i.setAttribute('class', 'adsbygoogle');
i.style.cssText = 'display:inline-block;width:100%;height:100%;';
global.document.getElementById('c').appendChild(i);
Expand Down
9 changes: 9 additions & 0 deletions ads/doubleclick.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ export function doubleclick(global, data) {
parseInt(data.overrideWidth || data.width, 10),
parseInt(data.overrideHeight || data.height, 10)
]];
const clientId = window.context.clientId;
const pageViewId = window.context.pageViewId;
let correlator = null;
if (clientId != null) {
correlator = pageViewId + (clientId.replace(/\D/g, '') % 1e6) * 1e6;
} else {
correlator = pageViewId;
}
const pubads = googletag.pubads();
const slot = googletag.defineSlot(data.slot, dimensions, 'c')
.addService(pubads);
pubads.enableSingleRequest();
pubads.markAsAmp();
pubads.set('page_url', context.canonicalUrl);
pubads.setCorrelator(Number(correlator));
googletag.enableServices();

if (data.targeting) {
Expand Down
90 changes: 90 additions & 0 deletions ads/integration-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
If you are an ad technology provider looking to integrate with AMP HTML, please see guidelines below.
In order to ensure minimum latency and quality, please follow the instructions listed [here](../3p/README.md#ads) before submitting a pull request to the AMP open source project. For general guidance on how to get started with contributing to the AMP project, please see [here](../CONTRIBUTING.md).

#### Ad Server

*Examples : DFP, A9*

As an adserver, publishers you support include a javascript library provided by you and place various ‘ad snippets’ that rely on the javascript library to fetch ads and render them on the publisher’s website.

Since AMP doesn’t allow publishers to execute arbitrary JavaScript (like the library above), you will need to contribute to the AMP open source code to allow the amp-ad built-in tag to request ads from your ad server.

For example : Amazon A9 server can be invoked using following syntax :

```html
<amp-ad width=300 height=250
type="a9"
data-aax_size="300x250"
data-aax_pubname="test123"
data-aax_src="302">
</amp-ad>
```

Also note that each of the attributes that follow ‘type’ are dependent on the parameters that Amazon’s A9 server expects in order to deliver an ad. The file [a9.js](/a9.js) shows you how the parameters are mapped to making a JavaScript call which will invoke the server by invoking the URL : https://c.amazon-adsystem.com/aax2/assoc.js and appending the corresponding parameters being passed in by the AMP ad tag and return the ad.

##### Server Side Platform (SSP) or an Ad Exchange

*Examples : Rubicon, Criteo OR Appnexus, Ad-Exchange*

If you are a sell side platform that wants to get called directly from a publisher’s webpage, you will need to follow the same directions as listed integrating with an Ad Server. Adding your own ‘type’ value to the amp-ad tag will allow you to distribute your tag directly to the publisher, so they can insert your tags directly into their AMP pages.

More commonly, SSPs work with the publisher to traffick the SSP’s ad tags in their ad server. In this case, ensure that all assets being loaded by your script in the ad server’s creative are being made over HTTPS. There are some restrictions around some ad formats like expandables, so we recommend that you test out the most commonly delivered creative formats with your publishers.

#### Ad Agency
*Examples : Essence, Omnicom*

Work with your publisher to ensure that the creatives you develop are AMP compliant. Since all creatives are served into iframes whose size is determined when the ad is called, ensure that your creative doesn't try to modify the size of the iframe.

Ensure that all assets that are part of the creative are requested using HTTPS.
Some ad formats are not fully supported at the moment and we recommend testing the creatives in an AMP environment. Some examples are : Rich Media Expandables, Interstitials, Page Level Ads

#### Video Player

*Examples : Brightcove, Ooyala*

A video player that works in regular HTML pages will not work in AMP and therefore a specific tag needs to be created that will allow the AMP runtime to load your player.
Brightcove has created a custom [amp-brightcove](https://github.com/ampproject/amphtml/blob/master/extensions/amp-brightcove/amp-brightcove.md) tag that allows media and ads to be played in AMP pages.

A brightcove player can be invoked by the following:

```html
<amp-brightcove
data-account="906043040001"
data-video-id="1401169490001"
data-player-id="180a5658-8be8-4f33-8eba-d562ab41b40c"
layout="responsive" width="480" height="270">
</amp-brightcove>
```
For instructions on how to develop an amp tag like brightcove, please see pull request [here](https://github.com/ampproject/amphtml/pull/1052).

#### Video Ad Network

*Examples : Tremor, Brightroll*

If you are a video ad network, please work with your publisher to ensure that :

- All video assets are being served over HTTPS
- The publisher’s video player has AMP support

#### Data Management Platform (DMP)
*Examples : KRUX, Bluekai*

[See how to enhance custom ad configuration](https://github.com/ampproject/amphtml/blob/master/builtins/amp-ad.md#enhance-incoming-ad-configuration)

You can use a similar approach to enrich the ad call by passing in audience segments that you get from the user cookie into the ad call.

#### Viewability Provider

*Examples : MOAT, Integral Ad Science*

Viewability providers typically integrate with publishers via the ad server’s creative wrappers. If that is the case, ensure that the creative wrapper loads all assets over HTTPS.

For e.g. for MOAT, make sure http://js.moatads.com is switched to https://z.moatads.com

Also see approach to using the [intersection observer pattern](https://github.com/ampproject/amphtml/blob/master/ads/README.md#ad-viewability).

#### Content-Recommendation Platform

*Examples : Taboola, Outbrain*

If you have some piece of JavaScript embeded on the publisher website today but the approach will not work in AMP pages. If you would like to recommend content on an AMP page, we suggest that you use the amp-embed extension to request the content details. Please see the [Taboola](https://github.com/ampproject/amphtml/blob/master/ads/taboola.md) example.
Loading

0 comments on commit bfb86e5

Please sign in to comment.