Skip to content

Commit bb71281

Browse files
committed
Prebid core: accept and propagate AD_RENDER_FAILED / AD_RENDER_SUCCEEDED events from cross-origin ads
This adds a new type of cross-origin message ('Prebid Event') to allow PUC and other cross-origin renderings to correctly report rendering result, and generates the appropriate events. Addresses prebid#7702 Related PUC changes: prebid/prebid-universal-creative#152 Documentation changes TBD
1 parent 30c6f40 commit bb71281

File tree

6 files changed

+249
-87
lines changed

6 files changed

+249
-87
lines changed

integrationExamples/gpt/x-domain/creative.html

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,38 @@
44

55
let windowLocation = window.location;
66
var urlParser = document.createElement('a');
7-
urlParser.href = '%%PATTERN:url%%';
87
var publisherDomain = urlParser.protocol + '//' + urlParser.hostname;
98

9+
urlParser.href = '%%PATTERN:url%%';
10+
const adId = '%%PATTERN:hb_adid%%';
11+
1012
function renderAd(ev) {
11-
var key = ev.message ? 'message' : 'data';
12-
var adObject = {};
13-
try {
14-
adObject = JSON.parse(ev[key]);
15-
} catch (e) {
16-
return;
17-
}
13+
let key = ev.message ? 'message' : 'data';
14+
let adObject = {};
15+
try {
16+
adObject = JSON.parse(ev[key]);
17+
} catch (e) {
18+
return;
19+
}
1820

19-
var origin = ev.origin || ev.originalEvent.origin;
20-
if (adObject.message && adObject.message === 'Prebid Response' &&
21-
publisherDomain === origin &&
22-
adObject.adId === '%%PATTERN:hb_adid%%' &&
23-
(adObject.ad || adObject.adUrl)) {
24-
var body = window.document.body;
25-
var ad = adObject.ad;
26-
var url = adObject.adUrl;
27-
var width = adObject.width;
28-
var height = adObject.height;
21+
let origin = ev.origin || ev.originalEvent.origin;
22+
if (adObject.message && adObject.message === 'Prebid Response' &&
23+
publisherDomain === origin &&
24+
adObject.adId === adId) {
25+
try {
26+
let body = window.document.body;
27+
let ad = adObject.ad;
28+
let url = adObject.adUrl;
29+
let width = adObject.width;
30+
let height = adObject.height;
2931

3032
if (adObject.mediaType === 'video') {
33+
signalRenderResult(false, {
34+
reason: 'preventWritingOnMainDocument',
35+
message: `Cannot render video ad ${adId}`
36+
});
3137
console.log('Error trying to write ad.');
32-
} else
33-
34-
if (ad) {
38+
} else if (ad) {
3539
var frame = document.createElement('iframe');
3640
frame.setAttribute('FRAMEBORDER', 0);
3741
frame.setAttribute('SCROLLING', 'no');
@@ -46,18 +50,42 @@
4650
frame.contentDocument.open();
4751
frame.contentDocument.write(ad);
4852
frame.contentDocument.close();
53+
signalRenderResult(true);
4954
} else if (url) {
5055
body.insertAdjacentHTML('beforeend', '<IFRAME SRC="' + url + '" FRAMEBORDER="0" SCROLLING="no" MARGINHEIGHT="0" MARGINWIDTH="0" TOPMARGIN="0" LEFTMARGIN="0" ALLOWTRANSPARENCY="true" WIDTH="' + width + '" HEIGHT="' + height + '"></IFRAME>');
56+
signalRenderResult(true);
5157
} else {
52-
console.log('Error trying to write ad. No ad for bid response id: ' + id);
58+
signalRenderResult(false, {
59+
reason: 'noAd',
60+
message: `No ad for ${adId}`
61+
});
62+
console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`);
5363
}
64+
} catch (e) {
65+
signalRenderResult(false, {reason: 'exception', message: e.message});
66+
console.log(`Error in rendering ad`, e);
67+
}
68+
}
69+
70+
function signalRenderResult(success, {reason, message} = {}) {
71+
const payload = {
72+
message: 'Prebid Event',
73+
adId,
74+
event: success ? 'adRenderSucceeded' : 'adRenderFailed',
5475
}
76+
if (!success) {
77+
payload.info = {reason, message};
78+
}
79+
ev.source.postMessage(JSON.stringify(payload), publisherDomain);
5580
}
5681

82+
}
83+
84+
5785
function requestAdFromPrebid() {
5886
var message = JSON.stringify({
5987
message: 'Prebid Request',
60-
adId: '%%PATTERN:hb_adid%%'
88+
adId
6189
});
6290
window.parent.postMessage(message, publisherDomain);
6391
}

src/adRendering.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {logError} from './utils.js';
2+
import events from './events.js';
3+
import CONSTANTS from './constants.json';
4+
5+
const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED} = CONSTANTS.EVENTS;
6+
7+
export function emitAdRenderFail({ reason, message, bid, id }) {
8+
const data = { reason, message };
9+
if (bid) data.bid = bid;
10+
if (id) data.adId = id;
11+
12+
logError(message);
13+
events.emit(AD_RENDER_FAILED, data);
14+
}
15+
16+
export function emitAdRenderSucceeded({ doc, bid, id }) {
17+
const data = { doc };
18+
if (bid) data.bid = bid;
19+
if (id) data.adId = id;
20+
21+
events.emit(AD_RENDER_SUCCEEDED, data);
22+
}

src/prebid.js

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { adunitCounter } from './adUnits.js';
1919
import { executeRenderer, isRendererRequired } from './Renderer.js';
2020
import { createBid } from './bidfactory.js';
2121
import { storageCallbacks } from './storageManager.js';
22+
import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js';
2223

2324
const $$PREBID_GLOBAL$$ = getGlobal();
2425
const CONSTANTS = require('./constants.json');
@@ -27,7 +28,7 @@ const events = require('./events.js');
2728
const { triggerUserSyncs } = userSync;
2829

2930
/* private variables */
30-
const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER } = CONSTANTS.EVENTS;
31+
const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, STALE_RENDER } = CONSTANTS.EVENTS;
3132
const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON;
3233

3334
const eventValidators = {
@@ -385,23 +386,6 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) {
385386
events.emit(SET_TARGETING, targeting.getAllTargeting());
386387
};
387388

388-
function emitAdRenderFail({ reason, message, bid, id }) {
389-
const data = { reason, message };
390-
if (bid) data.bid = bid;
391-
if (id) data.adId = id;
392-
393-
logError(message);
394-
events.emit(AD_RENDER_FAILED, data);
395-
}
396-
397-
function emitAdRenderSucceeded({ doc, bid, id }) {
398-
const data = { doc };
399-
if (bid) data.bid = bid;
400-
if (id) data.adId = id;
401-
402-
events.emit(AD_RENDER_SUCCEEDED, data);
403-
}
404-
405389
/**
406390
* This function will check for presence of given node in given parent. If not present - will inject it.
407391
* @param {Node} node node, whose existance is in question

src/secureCreatives.js

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@
44
*/
55

66
import events from './events.js';
7-
import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js';
7+
import {fireNativeTrackers, getAllAssetsMessage, getAssetMessage} from './native.js';
88
import constants from './constants.json';
9-
import { logWarn, replaceAuctionPrice, deepAccess, isGptPubadsDefined, isApnGetTagDefined } from './utils.js';
10-
import { auctionManager } from './auctionManager.js';
9+
import {deepAccess, isApnGetTagDefined, isGptPubadsDefined, logError, logWarn, replaceAuctionPrice} from './utils.js';
10+
import {auctionManager} from './auctionManager.js';
1111
import find from 'core-js-pure/features/array/find.js';
12-
import { isRendererRequired, executeRenderer } from './Renderer.js';
12+
import {executeRenderer, isRendererRequired} from './Renderer.js';
1313
import includes from 'core-js-pure/features/array/includes.js';
14-
import { config } from './config.js';
14+
import {config} from './config.js';
15+
import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js';
1516

1617
const BID_WON = constants.EVENTS.BID_WON;
1718
const STALE_RENDER = constants.EVENTS.STALE_RENDER;
1819

20+
const HANDLER_MAP = {
21+
'Prebid Request': handleRenderRequest,
22+
'Prebid Native': handleNativeRequest,
23+
'Prebid Event': handleEventRequest,
24+
}
25+
1926
export function listenMessagesFromCreative() {
2027
window.addEventListener('message', receiveMessage, false);
2128
}
@@ -29,52 +36,97 @@ export function receiveMessage(ev) {
2936
return;
3037
}
3138

32-
if (data && data.adId) {
39+
if (data && data.adId && data.message) {
3340
const adObject = find(auctionManager.getBidsReceived(), function (bid) {
3441
return bid.adId === data.adId;
3542
});
43+
if (HANDLER_MAP.hasOwnProperty(data.message)) {
44+
HANDLER_MAP[data.message](ev, data, adObject);
45+
}
46+
}
47+
}
3648

37-
if (adObject && data.message === 'Prebid Request') {
38-
if (adObject.status === constants.BID_STATUS.RENDERED) {
39-
logWarn(`Ad id ${adObject.adId} has been rendered before`);
40-
events.emit(STALE_RENDER, adObject);
41-
if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
42-
return;
43-
}
44-
}
49+
function handleRenderRequest(ev, data, adObject) {
50+
if (adObject == null) {
51+
emitAdRenderFail({
52+
reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD,
53+
message: `Cannot find ad '${data.adId}' for cross-origin render request`,
54+
id: data.adId
55+
});
56+
return;
57+
}
58+
if (adObject.status === constants.BID_STATUS.RENDERED) {
59+
logWarn(`Ad id ${adObject.adId} has been rendered before`);
60+
events.emit(STALE_RENDER, adObject);
61+
if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
62+
return;
63+
}
64+
}
4565

46-
_sendAdToCreative(adObject, ev);
66+
_sendAdToCreative(adObject, ev);
4767

48-
// save winning bids
49-
auctionManager.addWinningBid(adObject);
68+
// save winning bids
69+
auctionManager.addWinningBid(adObject);
5070

51-
events.emit(BID_WON, adObject);
52-
}
71+
events.emit(BID_WON, adObject);
72+
}
5373

54-
// handle this script from native template in an ad server
55-
// window.parent.postMessage(JSON.stringify({
56-
// message: 'Prebid Native',
57-
// adId: '%%PATTERN:hb_adid%%'
58-
// }), '*');
59-
if (adObject && data.message === 'Prebid Native') {
60-
if (data.action === 'assetRequest') {
61-
const message = getAssetMessage(data, adObject);
62-
ev.source.postMessage(JSON.stringify(message), ev.origin);
63-
} else if (data.action === 'allAssetRequest') {
64-
const message = getAllAssetsMessage(data, adObject);
65-
ev.source.postMessage(JSON.stringify(message), ev.origin);
66-
} else if (data.action === 'resizeNativeHeight') {
67-
adObject.height = data.height;
68-
adObject.width = data.width;
69-
resizeRemoteCreative(adObject);
70-
} else {
71-
const trackerType = fireNativeTrackers(data, adObject);
72-
if (trackerType === 'click') { return; }
73-
74-
auctionManager.addWinningBid(adObject);
75-
events.emit(BID_WON, adObject);
76-
}
77-
}
74+
function handleNativeRequest(ev, data, adObject) {
75+
// handle this script from native template in an ad server
76+
// window.parent.postMessage(JSON.stringify({
77+
// message: 'Prebid Native',
78+
// adId: '%%PATTERN:hb_adid%%'
79+
// }), '*');
80+
if (adObject == null) {
81+
logError(`Cannot find ad '${data.adId}' for x-origin event request`)
82+
return;
83+
}
84+
if (data.action === 'assetRequest') {
85+
const message = getAssetMessage(data, adObject);
86+
ev.source.postMessage(JSON.stringify(message), ev.origin);
87+
} else if (data.action === 'allAssetRequest') {
88+
const message = getAllAssetsMessage(data, adObject);
89+
ev.source.postMessage(JSON.stringify(message), ev.origin);
90+
} else if (data.action === 'resizeNativeHeight') {
91+
adObject.height = data.height;
92+
adObject.width = data.width;
93+
resizeRemoteCreative(adObject);
94+
} else {
95+
const trackerType = fireNativeTrackers(data, adObject);
96+
if (trackerType === 'click') { return; }
97+
98+
auctionManager.addWinningBid(adObject);
99+
events.emit(BID_WON, adObject);
100+
}
101+
}
102+
103+
function handleEventRequest(ev, data, adObject) {
104+
if (adObject == null) {
105+
logError(`Cannot find ad '${data.adId}' for x-origin event request`);
106+
return;
107+
}
108+
if (adObject.status !== constants.BID_STATUS.RENDERED) {
109+
logWarn(`Received x-origin event request without corresponding render request for ad '${data.adId}'`);
110+
return;
111+
}
112+
switch (data.event) {
113+
case constants.EVENTS.AD_RENDER_FAILED:
114+
emitAdRenderFail({
115+
bid: adObject,
116+
id: data.adId,
117+
reason: data.info.reason,
118+
message: data.info.message
119+
});
120+
break;
121+
case constants.EVENTS.AD_RENDER_SUCCEEDED:
122+
emitAdRenderSucceeded({
123+
doc: null,
124+
bid: adObject,
125+
id: data.adId
126+
});
127+
break;
128+
default:
129+
logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`)
78130
}
79131
}
80132

src/utils.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,17 @@ let consoleLogExists = Boolean(consoleExists && window.console.log);
2222
let consoleInfoExists = Boolean(consoleExists && window.console.info);
2323
let consoleWarnExists = Boolean(consoleExists && window.console.warn);
2424
let consoleErrorExists = Boolean(consoleExists && window.console.error);
25-
var events = require('./events.js');
25+
26+
const emitEvent = (function () {
27+
// lazy load events to avoid circular import
28+
let ev;
29+
return function() {
30+
if (ev == null) {
31+
ev = require('./events.js');
32+
}
33+
return ev.emit.apply(ev, arguments);
34+
}
35+
})();
2636

2737
// this allows stubbing of utility functions that are used internally by other utility functions
2838
export const internal = {
@@ -265,14 +275,14 @@ export function logWarn() {
265275
if (debugTurnedOn() && consoleWarnExists) {
266276
console.warn.apply(console, decorateLog(arguments, 'WARNING:'));
267277
}
268-
events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
278+
emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments});
269279
}
270280

271281
export function logError() {
272282
if (debugTurnedOn() && consoleErrorExists) {
273283
console.error.apply(console, decorateLog(arguments, 'ERROR:'));
274284
}
275-
events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
285+
emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments});
276286
}
277287

278288
function decorateLog(args, prefix) {

0 commit comments

Comments
 (0)