prebid-universal-creative 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +44 -30
- package/.github/workflows/codeql.yml +98 -0
- package/README.md +2 -2
- package/dist/amp.js +3 -3
- package/dist/banner.js +3 -3
- package/dist/caf7688498213fb0c19f.max.js +1046 -0
- package/dist/creative.js +3 -3
- package/dist/load-cookie-with-consent.html +1 -1
- package/dist/load-cookie.html +1 -1
- package/dist/mobile.js +3 -3
- package/dist/native-render.js +3 -3
- package/dist/native-trk.js +3 -3
- package/dist/native.js +3 -3
- package/dist/uid.js +2 -2
- package/dist/video.js +3 -3
- package/gulpfile.js +12 -24
- package/integ-test/fixtures/test.js +79 -0
- package/integ-test/pages/amp.html +80 -0
- package/integ-test/pages/banner.html +96 -0
- package/integ-test/pages/native_legacy.html +107 -0
- package/integ-test/spec/amp_spec.js +111 -0
- package/integ-test/spec/banner_spec.js +85 -0
- package/integ-test/spec/native_legacy_spec.js +213 -0
- package/package.json +7 -13
- package/playwright.config.js +108 -0
- package/src/adHtmlRender.js +11 -0
- package/src/cookieSync.js +3 -0
- package/src/cookieSyncWithConsent.js +3 -0
- package/src/domHelper.js +25 -15
- package/src/dynamicRenderer.js +56 -0
- package/src/messaging.js +23 -2
- package/src/mobileAndAmpRender.js +17 -20
- package/src/nativeAssetManager.js +98 -79
- package/src/nativeORTBTrackerManager.js +1 -1
- package/src/nativeRenderManager.js +7 -12
- package/src/nativeTrackerManager.js +2 -2
- package/src/renderingManager.js +13 -19
- package/test/helpers/mocks.js +1 -0
- package/test/spec/dynamicRenderer_spec.js +167 -0
- package/test/spec/messaging_spec.js +98 -3
- package/test/spec/mobileAndAmpRender_spec.js +47 -65
- package/test/spec/nativeAssetManager_spec.js +73 -23
- package/test/spec/renderingManager_spec.js +20 -6
- package/webpack.conf.js +0 -1
- package/.nvmrc +0 -1
- package/dist/creative.max.js +0 -3102
- package/src/postscribeRender.js +0 -10
- package/test/e2e/specs/hello_world_banner_non_sf.spec.js +0 -14
- package/test/e2e/specs/outstream_non_sf.spec.js +0 -14
- package/test/e2e/specs/outstream_sf.spec.js +0 -14
- package/wdio.conf.js +0 -50
@@ -7,6 +7,7 @@ import { addNativeClickTrackers, fireNativeImpressionTrackers } from './nativeOR
|
|
7
7
|
import { sendRequest, loadScript } from './utils';
|
8
8
|
import {prebidMessenger} from './messaging.js';
|
9
9
|
import { isSafeFrame } from './environment.js';
|
10
|
+
import {hasDynamicRenderer, runDynamicRenderer} from './dynamicRenderer.js';
|
10
11
|
/*
|
11
12
|
* Native asset->key mapping from Prebid.js/src/constants.json
|
12
13
|
* https://github.com/prebid/Prebid.js/blob/8635c91942de9df4ec236672c39b19448545a812/src/constants.json#L67
|
@@ -255,7 +256,7 @@ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessen
|
|
255
256
|
assets,
|
256
257
|
};
|
257
258
|
|
258
|
-
return sendMessage(message, replaceAssets);
|
259
|
+
return sendMessage(message, replaceAssets(adId));
|
259
260
|
}
|
260
261
|
|
261
262
|
/*
|
@@ -268,7 +269,7 @@ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessen
|
|
268
269
|
action: 'allAssetRequest',
|
269
270
|
adId,
|
270
271
|
};
|
271
|
-
return sendMessage(message, replaceAssets);
|
272
|
+
return sendMessage(message, replaceAssets(adId));
|
272
273
|
}
|
273
274
|
|
274
275
|
/*
|
@@ -285,116 +286,131 @@ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessen
|
|
285
286
|
sendMessage(message);
|
286
287
|
}
|
287
288
|
|
288
|
-
/*
|
289
|
-
* Postmessage listener for when Prebid responds with requested native assets.
|
290
|
-
*/
|
291
|
-
function replaceAssets(event) {
|
292
|
-
try {
|
293
|
-
|
294
|
-
var data = {};
|
295
289
|
|
290
|
+
function replaceAssets(adId) {
|
291
|
+
return function(event) {
|
296
292
|
try {
|
297
|
-
data = JSON.parse(event.data);
|
298
|
-
} catch (e) {
|
299
|
-
if (errorCountEscapeHatch++ > 10) {
|
300
|
-
// TODO: this should be a timeout, not an arbitrary cap on the number of messages received
|
301
|
-
/*
|
302
|
-
* if for some reason Prebid never responds with the native assets,
|
303
|
-
* get rid of this listener because other messages won't stop coming
|
304
|
-
*/
|
305
|
-
stopListening();
|
306
|
-
throw e;
|
307
|
-
}
|
308
|
-
return;
|
309
|
-
}
|
310
293
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
294
|
+
var data = {};
|
295
|
+
|
296
|
+
try {
|
297
|
+
data = JSON.parse(event.data);
|
298
|
+
} catch (e) {
|
299
|
+
if (errorCountEscapeHatch++ > 10) {
|
300
|
+
// TODO: this should be a timeout, not an arbitrary cap on the number of messages received
|
301
|
+
/*
|
302
|
+
* if for some reason Prebid never responds with the native assets,
|
303
|
+
* get rid of this listener because other messages won't stop coming
|
304
|
+
*/
|
305
|
+
stopListening();
|
306
|
+
throw e;
|
307
|
+
}
|
308
|
+
return;
|
309
|
+
}
|
318
310
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
cb && cb();
|
311
|
+
if (data.message === 'assetResponse' && data.adId === adId) {
|
312
|
+
if(hasDynamicRenderer(data)) {
|
313
|
+
runDynamicRenderer(adId, data, sendMessage, win);
|
314
|
+
return;
|
324
315
|
}
|
325
|
-
})(callback);
|
326
316
|
|
327
|
-
|
317
|
+
// add GAM %%CLICK_URL_UNESC%% to the data object to be eventually used in renderers
|
318
|
+
data.clickUrlUnesc = clickUrlUnesc;
|
319
|
+
const body = win.document.body.innerHTML;
|
320
|
+
const head = win.document.head.innerHTML;
|
328
321
|
|
322
|
+
callback = ((cb) => {
|
323
|
+
return () => {
|
324
|
+
fireNativeImpressionTrackers(data.adId, sendMessage);
|
325
|
+
addNativeClickTrackers(data.adId, sendMessage);
|
326
|
+
cb && cb();
|
327
|
+
}
|
328
|
+
})(callback);
|
329
329
|
|
330
|
-
|
331
|
-
let renderPayload = data.assets;
|
332
|
-
if (data.ortb) {
|
333
|
-
renderPayload.ortb = data.ortb;
|
334
|
-
}
|
330
|
+
if (head) win.document.head.innerHTML = replace(head, data);
|
335
331
|
|
336
|
-
if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
|
337
|
-
if (win.renderAd) {
|
338
|
-
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
|
339
332
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
333
|
+
data.assets = data.assets || [];
|
334
|
+
let renderPayload = data.assets;
|
335
|
+
if (data.ortb) {
|
336
|
+
renderPayload.ortb = data.ortb;
|
337
|
+
}
|
344
338
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
339
|
+
// if there's a rendererUrl, we need to check whether it's already been loaded.
|
340
|
+
// There are 3 scenarios:
|
341
|
+
// 1) it's already been loaded (window.renderAd is present)
|
342
|
+
// 2) it is currently being loaded (through a script tag with id "pb-native-renderer")
|
343
|
+
// 3) it hasn't been loaded yet
|
344
|
+
// 1 and 2 seem intended to work with logic in nativeRenderManager.js, which (sometimes) loads rendererUrl through a <script id="pb-native-renderer">, but they could conceivably be used in an undocumented way to embed renderer logic directly in the creative.
|
345
|
+
if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
|
346
|
+
if (win.renderAd) {
|
349
347
|
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
|
350
348
|
|
351
349
|
renderAd(newHtml, data);
|
352
|
-
})
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
350
|
+
} else if (document.getElementById('pb-native-renderer')) {
|
351
|
+
document.getElementById('pb-native-renderer').addEventListener('load', function () {
|
352
|
+
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
|
353
|
+
|
354
|
+
renderAd(newHtml, data);
|
355
|
+
});
|
356
|
+
} else {
|
357
|
+
loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function () {
|
358
|
+
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
|
359
|
+
|
360
|
+
renderAd(newHtml, data);
|
361
|
+
});
|
362
|
+
}
|
363
|
+
} else if ((data.hasOwnProperty('adTemplate') && data.adTemplate) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
|
364
|
+
const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
|
365
|
+
const newHtml = replace(template, data);
|
357
366
|
|
358
|
-
|
359
|
-
|
360
|
-
|
367
|
+
renderAd(newHtml, data);
|
368
|
+
} else {
|
369
|
+
const newHtml = replace(body, data);
|
361
370
|
|
362
|
-
|
363
|
-
|
364
|
-
|
371
|
+
win.document.body.innerHTML = newHtml;
|
372
|
+
callback && callback(); // all the other scenarios hit the callback via renderAd()
|
373
|
+
stopListening();
|
374
|
+
}
|
365
375
|
}
|
376
|
+
} catch (e) {
|
377
|
+
errCallback && errCallback(e);
|
366
378
|
}
|
367
|
-
} catch (e) {
|
368
|
-
errCallback && errCallback(e);
|
369
379
|
}
|
370
380
|
}
|
371
381
|
|
372
382
|
/** This function returns the element that contains the current iframe. */
|
373
383
|
function getCurrentFrameContainer(win) {
|
374
|
-
|
375
|
-
|
384
|
+
try {
|
385
|
+
let currentWindow = win;
|
386
|
+
let currentParentWindow;
|
376
387
|
|
377
|
-
|
388
|
+
while (currentWindow !== win.top) {
|
378
389
|
currentParentWindow = currentWindow.parent;
|
379
390
|
if (!currentParentWindow.frames || !currentParentWindow.frames.length) return null;
|
380
391
|
for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
}
|
392
|
+
if (currentParentWindow.frames[idx] === currentWindow) {
|
393
|
+
if (!currentParentWindow.document) return null;
|
394
|
+
for (let frameElement of currentParentWindow.document.getElementsByTagName('iframe')) {
|
395
|
+
if (!frameElement.contentWindow) return null;
|
396
|
+
if (frameElement.contentWindow === currentWindow) {
|
397
|
+
return frameElement.parentElement;
|
398
|
+
}
|
389
399
|
}
|
400
|
+
}
|
401
|
+
}
|
402
|
+
} catch (e) {
|
403
|
+
// parent is cross-frame
|
390
404
|
}
|
391
|
-
}
|
405
|
+
}
|
392
406
|
|
393
407
|
function renderAd(html, bid) {
|
394
408
|
// if the current iframe is not a safeframe, try to set the
|
395
409
|
// current iframe width to the width of the container. This
|
396
410
|
// is to handle the case where the native ad is rendered inside
|
397
411
|
// a GAM display ad.
|
412
|
+
|
413
|
+
// NOTE: this may be unnecessary, see https://github.com/prebid/prebid-universal-creative/issues/253.
|
398
414
|
if (!isSafeFrame(window)) {
|
399
415
|
let iframeContainer = getCurrentFrameContainer(win);
|
400
416
|
if (iframeContainer && iframeContainer.children && iframeContainer.children[0]) {
|
@@ -411,9 +427,12 @@ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessen
|
|
411
427
|
|
412
428
|
win.document.body.innerHTML += html;
|
413
429
|
callback && callback();
|
414
|
-
win.removeEventListener('message', replaceAssets);
|
415
430
|
stopListening();
|
416
|
-
const resize = () => requestHeightResize(
|
431
|
+
const resize = () => requestHeightResize(
|
432
|
+
bid.adId,
|
433
|
+
(document.body.clientHeight || document.body.offsetHeight),
|
434
|
+
document.body.clientWidth > 1 ? document.body.clientWidth : undefined
|
435
|
+
);
|
417
436
|
document.readyState === 'complete' ? resize() : window.onload = resize;
|
418
437
|
|
419
438
|
if (typeof window.postRenderAd === 'function') {
|
@@ -21,7 +21,7 @@ export function addNativeClickTrackers(adId, sendMessage) {
|
|
21
21
|
const adElements = document.getElementsByClassName(AD_ANCHOR_CLASS_NAME) || [];
|
22
22
|
// get all assets that have 'link' property, map asset.id -> asset.link
|
23
23
|
for (let i = 0; i < adElements.length; i++) {
|
24
|
-
adElements[i].addEventListener('
|
24
|
+
adElements[i].addEventListener('pointerdown', (event) => {
|
25
25
|
let targetElement = event.target;
|
26
26
|
// check if clicked element is associated with any native asset (look for 'hb_native_asset_id' attribute)
|
27
27
|
let assetId = targetElement && targetElement.getAttribute(ASSET_ID_ELEMENT_ATTRIBUTE);
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* Script to handle firing impression and click trackers from native teamplates
|
3
3
|
*/
|
4
4
|
import {newNativeAssetManager} from './nativeAssetManager';
|
5
|
-
import {prebidMessenger} from './messaging.js';
|
5
|
+
import {prebidMessenger, renderEventMessage} from './messaging.js';
|
6
6
|
|
7
7
|
export function newNativeRenderManager(win, mkMessenger = prebidMessenger, assetMgr = newNativeAssetManager) {
|
8
8
|
let sendMessage;
|
@@ -12,13 +12,8 @@ export function newNativeRenderManager(win, mkMessenger = prebidMessenger, asset
|
|
12
12
|
window.pbNativeData = nativeTag;
|
13
13
|
sendMessage = mkMessenger(nativeTag.pubUrl, win);
|
14
14
|
|
15
|
-
function signalResult(adId,
|
16
|
-
sendMessage(
|
17
|
-
message: 'Prebid Event',
|
18
|
-
adId,
|
19
|
-
event: success ? 'adRenderSucceeded' : 'adRenderFailed',
|
20
|
-
info
|
21
|
-
});
|
15
|
+
function signalResult(adId, errorInfo) {
|
16
|
+
sendMessage(renderEventMessage(adId, errorInfo));
|
22
17
|
}
|
23
18
|
|
24
19
|
try {
|
@@ -33,16 +28,16 @@ export function newNativeRenderManager(win, mkMessenger = prebidMessenger, asset
|
|
33
28
|
doc.body.appendChild(scr);
|
34
29
|
}
|
35
30
|
nativeAssetManager.loadAssets(nativeTag.adId, () => {
|
36
|
-
signalResult(nativeTag.adId
|
31
|
+
signalResult(nativeTag.adId);
|
37
32
|
}, (e) => {
|
38
|
-
signalResult(nativeTag.adId,
|
33
|
+
signalResult(nativeTag.adId, {reason: 'exception', message: e.message});
|
39
34
|
});
|
40
35
|
} else {
|
41
|
-
signalResult(null,
|
36
|
+
signalResult(null, {reason: 'missingDocOrAdid'});
|
42
37
|
console.warn('Prebid Native Tag object was missing \'adId\'.');
|
43
38
|
}
|
44
39
|
} catch (e) {
|
45
|
-
signalResult(nativeTag && nativeTag.adId,
|
40
|
+
signalResult(nativeTag && nativeTag.adId, {reason: 'exception', message: e.message});
|
46
41
|
console.error('Error rendering ad', e);
|
47
42
|
}
|
48
43
|
};
|
@@ -47,7 +47,7 @@ export function newNativeTrackerManager(win) {
|
|
47
47
|
|
48
48
|
for (let i = 0; i < adElements.length; i++) {
|
49
49
|
let adId = readAdIdFromSingleElement(adElements[i]);
|
50
|
-
adElements[i].addEventListener('
|
50
|
+
adElements[i].addEventListener('pointerdown', function(event) {
|
51
51
|
listener(event, adId);
|
52
52
|
}, true);
|
53
53
|
}
|
@@ -83,7 +83,7 @@ export function newNativeTrackerManager(win) {
|
|
83
83
|
attachClickListeners(false, boundedLoadMobileClickTrackers);
|
84
84
|
|
85
85
|
(impTrackers || []).forEach(triggerPixel);
|
86
|
-
|
86
|
+
|
87
87
|
// fire impression IMG trackers
|
88
88
|
eventtrackers
|
89
89
|
.filter(tracker => tracker.event === 1 && tracker.method === 1)
|
package/src/renderingManager.js
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import { parseUrl, transformAuctionTargetingData } from './utils';
|
2
2
|
import { canLocatePrebid } from './environment';
|
3
3
|
import { insertElement, getEmptyIframe } from './domHelper';
|
4
|
-
import {prebidMessenger} from './messaging.js';
|
4
|
+
import {prebidMessenger, renderEventMessage} from './messaging.js';
|
5
|
+
import {hasDynamicRenderer, runDynamicRenderer} from './dynamicRenderer.js';
|
5
6
|
|
6
7
|
export function renderBannerOrDisplayAd(doc, dataObject) {
|
7
8
|
const targetingData = transformAuctionTargetingData(dataObject);
|
@@ -25,8 +26,8 @@ export function renderLegacy(doc, adId) {
|
|
25
26
|
w = w.parent;
|
26
27
|
if (w.$$PREBID_GLOBAL$$) {
|
27
28
|
try {
|
28
|
-
found = true;
|
29
29
|
w.$$PREBID_GLOBAL$$.renderAd(doc, adId);
|
30
|
+
found = true;
|
30
31
|
break;
|
31
32
|
} catch (e) {
|
32
33
|
continue;
|
@@ -49,6 +50,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
49
50
|
let adServerDomain = pubAdServerDomain || win.location.hostname;
|
50
51
|
let fullAdServerDomain = windowLocation.protocol + '//' + adServerDomain;
|
51
52
|
const sendMessage = prebidMessenger(pubUrl, win);
|
53
|
+
const signalRenderResult = (errorInfo) => sendMessage(renderEventMessage(adId, errorInfo));
|
52
54
|
|
53
55
|
function renderAd(ev) {
|
54
56
|
let key = ev.message ? "message" : "data";
|
@@ -64,6 +66,10 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
64
66
|
adObject.message === "Prebid Response" &&
|
65
67
|
adObject.adId === adId
|
66
68
|
) {
|
69
|
+
if (hasDynamicRenderer(adObject)) {
|
70
|
+
runDynamicRenderer(adId, adObject, sendMessage, win);
|
71
|
+
return;
|
72
|
+
}
|
67
73
|
try {
|
68
74
|
let body = win.document.body;
|
69
75
|
let ad = adObject.ad;
|
@@ -72,7 +78,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
72
78
|
let height = adObject.height;
|
73
79
|
|
74
80
|
if (adObject.mediaType === "video") {
|
75
|
-
signalRenderResult(
|
81
|
+
signalRenderResult({
|
76
82
|
reason: "preventWritingOnMainDocument",
|
77
83
|
message: `Cannot render video ad ${adId}`,
|
78
84
|
});
|
@@ -83,7 +89,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
83
89
|
iframe.contentDocument.open();
|
84
90
|
iframe.contentDocument.write(ad);
|
85
91
|
iframe.contentDocument.close();
|
86
|
-
signalRenderResult(
|
92
|
+
signalRenderResult();
|
87
93
|
} else if (url) {
|
88
94
|
const iframe = getEmptyIframe(height, width);
|
89
95
|
iframe.style.display = "inline";
|
@@ -91,9 +97,9 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
91
97
|
iframe.src = url;
|
92
98
|
|
93
99
|
insertElement(iframe, document, "body");
|
94
|
-
signalRenderResult(
|
100
|
+
signalRenderResult();
|
95
101
|
} else {
|
96
|
-
signalRenderResult(
|
102
|
+
signalRenderResult({
|
97
103
|
reason: "noAd",
|
98
104
|
message: `No ad for ${adId}`,
|
99
105
|
});
|
@@ -102,22 +108,10 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
|
|
102
108
|
);
|
103
109
|
}
|
104
110
|
} catch (e) {
|
105
|
-
signalRenderResult(
|
111
|
+
signalRenderResult({ reason: "exception", message: e.message });
|
106
112
|
console.log(`Error in rendering ad`, e);
|
107
113
|
}
|
108
114
|
}
|
109
|
-
|
110
|
-
function signalRenderResult(success, { reason, message } = {}) {
|
111
|
-
const payload = {
|
112
|
-
message: "Prebid Event",
|
113
|
-
adId,
|
114
|
-
event: success ? "adRenderSucceeded" : "adRenderFailed",
|
115
|
-
};
|
116
|
-
if (!success) {
|
117
|
-
payload.info = { reason, message };
|
118
|
-
}
|
119
|
-
sendMessage(payload);
|
120
|
-
}
|
121
115
|
}
|
122
116
|
|
123
117
|
function requestAdFromPrebid() {
|
package/test/helpers/mocks.js
CHANGED
@@ -0,0 +1,167 @@
|
|
1
|
+
import {makeIframe} from '../../src/domHelper.js';
|
2
|
+
import {hasDynamicRenderer, MIN_RENDERER_VERSION, runDynamicRenderer} from '../../src/dynamicRenderer.js';
|
3
|
+
import {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, PREBID_EVENT} from '../../src/messaging.js';
|
4
|
+
|
5
|
+
describe('hasDynamicRenderer', () => {
|
6
|
+
Object.entries({
|
7
|
+
'neither': {},
|
8
|
+
'renderer, but no version': {
|
9
|
+
renderer: 'mock-renderer'
|
10
|
+
},
|
11
|
+
'renderer, but version is too low': {
|
12
|
+
renderer: 'mock-renderer',
|
13
|
+
rendererVersion: 1
|
14
|
+
},
|
15
|
+
}).forEach(([t, data]) => {
|
16
|
+
it(`returns false with ${t}`, () => {
|
17
|
+
expect(hasDynamicRenderer(data)).to.be.false;
|
18
|
+
})
|
19
|
+
});
|
20
|
+
|
21
|
+
it('returns true when both renderer and version are present', () => {
|
22
|
+
expect(hasDynamicRenderer({
|
23
|
+
renderer: 'mock-renderer',
|
24
|
+
rendererVersion: MIN_RENDERER_VERSION
|
25
|
+
})).to.be.true;
|
26
|
+
})
|
27
|
+
})
|
28
|
+
|
29
|
+
describe('runDynamicRenderer', () => {
|
30
|
+
let sendMessage, frame, render;
|
31
|
+
const adId = '123';
|
32
|
+
beforeEach(() => {
|
33
|
+
render = sinon.stub();
|
34
|
+
sendMessage = sinon.stub();
|
35
|
+
frame = makeIframe(document);
|
36
|
+
return new Promise((resolve) => {
|
37
|
+
frame.onload = resolve;
|
38
|
+
document.body.appendChild(frame);
|
39
|
+
}).then(() => {
|
40
|
+
frame.contentWindow._render = render;
|
41
|
+
});
|
42
|
+
});
|
43
|
+
|
44
|
+
afterEach(() => {
|
45
|
+
document.body.removeChild(frame);
|
46
|
+
});
|
47
|
+
|
48
|
+
function runRenderer(data) {
|
49
|
+
return runDynamicRenderer(adId, Object.assign({
|
50
|
+
renderer: `window.render = window.parent._render`
|
51
|
+
}, data), sendMessage, frame.contentWindow).catch(() => null);
|
52
|
+
}
|
53
|
+
|
54
|
+
it('runs renderer', () => {
|
55
|
+
const data = {ad: 'markup'};
|
56
|
+
return runRenderer(data).then(() => {
|
57
|
+
sinon.assert.calledWith(render, sinon.match(data), sinon.match({mkFrame: makeIframe}), frame.contentWindow);
|
58
|
+
});
|
59
|
+
});
|
60
|
+
|
61
|
+
Object.entries({
|
62
|
+
'returns': null,
|
63
|
+
'returns a promise that resolves': Promise.resolve()
|
64
|
+
}).forEach(([t, ret]) => {
|
65
|
+
it(`emits AD_RENDER_SUCCEDED when renderer ${t}`, () => {
|
66
|
+
render.callsFake(() => ret);
|
67
|
+
return runRenderer().then(() => {
|
68
|
+
sinon.assert.calledWith(sendMessage, {
|
69
|
+
adId,
|
70
|
+
message: PREBID_EVENT,
|
71
|
+
event: AD_RENDER_SUCCEEDED
|
72
|
+
});
|
73
|
+
});
|
74
|
+
});
|
75
|
+
});
|
76
|
+
|
77
|
+
describe('emits AD_RENDER_FAILED', () => {
|
78
|
+
Object.entries({
|
79
|
+
throws: (ret) => {
|
80
|
+
throw ret;
|
81
|
+
},
|
82
|
+
'returns a promise that rejects': (ret) => Promise.reject(ret)
|
83
|
+
}).forEach(([t, transform]) => {
|
84
|
+
describe(`when renderer ${t}`, () => {
|
85
|
+
Object.entries({
|
86
|
+
'error': {
|
87
|
+
ret: new Error('error message'),
|
88
|
+
info: {
|
89
|
+
reason: 'exception',
|
90
|
+
message: 'error message'
|
91
|
+
}
|
92
|
+
},
|
93
|
+
'error with reason': {
|
94
|
+
ret: {
|
95
|
+
reason: 'failure',
|
96
|
+
message: 'error message'
|
97
|
+
},
|
98
|
+
info: {
|
99
|
+
reason: 'failure',
|
100
|
+
message: 'error message'
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}).forEach(([t, {ret, info}]) => {
|
104
|
+
it(`an ${t}`, () => {
|
105
|
+
render.callsFake(() => transform(ret));
|
106
|
+
return runRenderer().then(() => {
|
107
|
+
sinon.assert.calledWith(sendMessage, {
|
108
|
+
adId,
|
109
|
+
message: PREBID_EVENT,
|
110
|
+
event: AD_RENDER_FAILED,
|
111
|
+
info
|
112
|
+
});
|
113
|
+
});
|
114
|
+
});
|
115
|
+
});
|
116
|
+
});
|
117
|
+
});
|
118
|
+
});
|
119
|
+
|
120
|
+
describe('renderer sendMessage', () => {
|
121
|
+
let rndSendMessage;
|
122
|
+
beforeEach(() => {
|
123
|
+
render.callsFake(() => new Promise())
|
124
|
+
return new Promise((resolve) => {
|
125
|
+
render.callsFake((_, {sendMessage}) => {
|
126
|
+
rndSendMessage = sendMessage;
|
127
|
+
resolve();
|
128
|
+
});
|
129
|
+
runRenderer();
|
130
|
+
})
|
131
|
+
});
|
132
|
+
it('adds message type and adId', () => {
|
133
|
+
rndSendMessage('type', {msg: 'data'});
|
134
|
+
sinon.assert.calledWith(sendMessage, {
|
135
|
+
message: 'type',
|
136
|
+
adId,
|
137
|
+
msg: 'data'
|
138
|
+
});
|
139
|
+
});
|
140
|
+
it('accepts response listeners', () => {
|
141
|
+
const listener = sinon.stub();
|
142
|
+
sendMessage.callsFake((_, responseListener) => {
|
143
|
+
responseListener('response');
|
144
|
+
})
|
145
|
+
rndSendMessage('msg', {}, listener);
|
146
|
+
sinon.assert.calledWith(listener, 'response');
|
147
|
+
});
|
148
|
+
|
149
|
+
it('emits AD_RENDER_FAILED if listener throws', () => {
|
150
|
+
const listener = sinon.stub().callsFake(() => { throw new Error('err') });
|
151
|
+
sendMessage.callsFake((_, responseListener) => {
|
152
|
+
responseListener && responseListener('response');
|
153
|
+
});
|
154
|
+
rndSendMessage('msg', {}, listener);
|
155
|
+
sinon.assert.calledWith(sendMessage, {
|
156
|
+
message: PREBID_EVENT,
|
157
|
+
event: AD_RENDER_FAILED,
|
158
|
+
adId,
|
159
|
+
info: {
|
160
|
+
reason: 'exception',
|
161
|
+
message: 'err'
|
162
|
+
}
|
163
|
+
})
|
164
|
+
})
|
165
|
+
});
|
166
|
+
|
167
|
+
});
|