prebid-universal-creative 1.15.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.
Files changed (56) hide show
  1. package/.circleci/config.yml +44 -30
  2. package/.github/workflows/codeql.yml +98 -0
  3. package/.github/workflows/issue_tracker.yml +32 -16
  4. package/README.md +4 -2
  5. package/dist/amp.js +3 -3
  6. package/dist/banner.js +3 -3
  7. package/dist/caf7688498213fb0c19f.max.js +1046 -0
  8. package/dist/creative.js +3 -3
  9. package/dist/load-cookie-with-consent.html +1 -1
  10. package/dist/load-cookie.html +1 -1
  11. package/dist/mobile.js +3 -3
  12. package/dist/native-render.js +3 -3
  13. package/dist/native-trk.js +3 -3
  14. package/dist/native.js +3 -3
  15. package/dist/uid.js +2 -2
  16. package/dist/video.js +3 -3
  17. package/gulpfile.js +15 -31
  18. package/integ-test/fixtures/test.js +79 -0
  19. package/integ-test/pages/amp.html +80 -0
  20. package/integ-test/pages/banner.html +96 -0
  21. package/integ-test/pages/native_legacy.html +107 -0
  22. package/integ-test/spec/amp_spec.js +111 -0
  23. package/integ-test/spec/banner_spec.js +85 -0
  24. package/integ-test/spec/native_legacy_spec.js +213 -0
  25. package/karma.conf.maker.js +4 -6
  26. package/package.json +10 -16
  27. package/playwright.config.js +108 -0
  28. package/src/adHtmlRender.js +11 -0
  29. package/src/cookieSync.js +3 -0
  30. package/src/cookieSyncWithConsent.js +3 -0
  31. package/src/domHelper.js +25 -15
  32. package/src/dynamicRenderer.js +56 -0
  33. package/src/messaging.js +23 -2
  34. package/src/mobileAndAmpRender.js +17 -20
  35. package/src/nativeAssetManager.js +134 -80
  36. package/src/nativeORTBTrackerManager.js +3 -3
  37. package/src/nativeRenderManager.js +44 -72
  38. package/src/nativeTrackerManager.js +2 -2
  39. package/src/renderingManager.js +17 -18
  40. package/src/utils.js +0 -9
  41. package/test/helpers/mocks.js +1 -0
  42. package/test/spec/dynamicRenderer_spec.js +167 -0
  43. package/test/spec/messaging_spec.js +98 -3
  44. package/test/spec/mobileAndAmpRender_spec.js +53 -63
  45. package/test/spec/nativeAssetManager_spec.js +290 -93
  46. package/test/spec/nativeORTBTrackerManager_spec.js +3 -19
  47. package/test/spec/nativeRenderManager_spec.js +77 -56
  48. package/test/spec/renderingManager_spec.js +20 -6
  49. package/webpack.conf.js +0 -1
  50. package/.nvmrc +0 -1
  51. package/dist/creative.max.js +0 -3101
  52. package/src/postscribeRender.js +0 -8
  53. package/test/e2e/specs/hello_world_banner_non_sf.spec.js +0 -14
  54. package/test/e2e/specs/outstream_non_sf.spec.js +0 -14
  55. package/test/e2e/specs/outstream_sf.spec.js +0 -14
  56. package/wdio.conf.js +0 -50
@@ -1,7 +1,7 @@
1
1
  import { getCreativeCommentMarkup, triggerPixel, createTrackPixelHtml, loadScript, getCreativeComment, writeAdUrl, transformAuctionTargetingData, sendRequest, getUUID } from './utils';
2
2
  import { isSafeFrame, isMobileApp } from './environment';
3
3
  import { insertElement } from './domHelper';
4
- import { writeAdHtml } from './postscribeRender';
4
+ import { writeAdHtml } from './adHtmlRender';
5
5
 
6
6
  const DEFAULT_CACHE_HOST = 'prebid.adnxs.com';
7
7
  const DEFAULT_CACHE_PATH = '/pbc/v1/cache';
@@ -62,26 +62,24 @@ function updateIframe(size) {
62
62
  * @param {Number} height height of creative
63
63
  */
64
64
  function resizeIframe(width, height) {
65
- if (isSafeFrame(window)) {
66
- const iframeWidth = window.innerWidth;
67
- const iframeHeight = window.innerHeight;
68
-
69
- function resize(status) {
70
- let newWidth = width - iframeWidth;
71
- let newHeight = height - iframeHeight;
72
- window.$sf.ext.expand({ r: newWidth, b: newHeight, push: true });
73
- }
74
-
75
- if (iframeWidth !== width || iframeHeight !== height) {
65
+ const iframeWidth = window.innerWidth;
66
+ const iframeHeight = window.innerHeight;
67
+ if (iframeWidth !== width || iframeHeight !== height) {
68
+ if (isSafeFrame(window)) {
69
+ function resize(status) {
70
+ let newWidth = width - iframeWidth;
71
+ let newHeight = height - iframeHeight;
72
+ window.$sf.ext.expand({r: newWidth, b: newHeight, push: true});
73
+ }
76
74
  window.$sf.ext.register(width, height, resize);
77
- // we need to resize the DFP container as well
78
- window.parent.postMessage({
79
- sentinel: 'amp',
80
- type: 'embed-size',
81
- width: width,
82
- height: height
83
- }, '*');
84
75
  }
76
+ // AMP resize request in case the parent is AMP
77
+ window.parent.postMessage({
78
+ sentinel: 'amp',
79
+ type: 'embed-size',
80
+ width: width,
81
+ height: height
82
+ }, '*');
85
83
  }
86
84
  }
87
85
 
@@ -151,7 +149,6 @@ function responseCallback(isMobileApp, hbPb) {
151
149
  if (isMobileApp) {
152
150
  let adhtml = loadScript(window, bidObject.nurl);
153
151
  ad += constructMarkup(adhtml.outerHTML, width, height);
154
-
155
152
  writeAdHtml(ad);
156
153
  } else {
157
154
  let nurl = bidObject.nurl;
@@ -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
@@ -59,9 +60,22 @@ const assetTypeMapping = {
59
60
  const DEFAULT_CACHE_HOST = 'prebid.adnxs.com';
60
61
  const DEFAULT_CACHE_PATH = '/pbc/v1/cache';
61
62
 
62
- export function newNativeAssetManager(win, pubUrl) {
63
- const sendMessage = prebidMessenger(pubUrl, win);
64
- let callback;
63
+ const CLICK_URL_UNESC = `%%CLICK_URL_UNESC%%`;
64
+
65
+ let clickUrlUnesc = '';
66
+
67
+ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessenger) {
68
+
69
+ // clickUrlUnesc contains the url to track clicks in GAM. we check if it
70
+ // has been transformed, by GAM, in an URL.
71
+ // if CLICK_URL_UNESC is the string "%%CLICK_URL_UNESC%%", we're not in GAM.
72
+ if (nativeTag.clickUrlUnesc && nativeTag.clickUrlUnesc !== CLICK_URL_UNESC) {
73
+ clickUrlUnesc = nativeTag.clickUrlUnesc;
74
+ }
75
+ const {pubUrl} = nativeTag;
76
+
77
+ const sendMessage = mkMessenger(pubUrl, win);
78
+ let callback, errCallback;
65
79
  let errorCountEscapeHatch = 0;
66
80
  let cancelMessageListener;
67
81
 
@@ -183,7 +197,8 @@ export function newNativeAssetManager(win, pubUrl) {
183
197
  * and requestAllAssets flag is set in the tag, postmessage roundtrip
184
198
  * to retrieve native assets that have a value on the corresponding bid
185
199
  */
186
- function loadAssets(adId, cb) {
200
+ function loadAssets(adId, cb, onError) {
201
+ errCallback = onError;
187
202
  const placeholders = scanDOMForPlaceHolders(adId);
188
203
 
189
204
  if (hasPbNativeData() && win.pbNativeData.hasOwnProperty('assetsToReplace')) {
@@ -199,6 +214,8 @@ export function newNativeAssetManager(win, pubUrl) {
199
214
  } else if (placeholders.length > 0) {
200
215
  callback = cb;
201
216
  cancelMessageListener = requestAssets(adId, placeholders);
217
+ } else {
218
+ onError && onError(new Error('No assets to load: no placeholders found in template'));
202
219
  }
203
220
  }
204
221
 
@@ -239,7 +256,7 @@ export function newNativeAssetManager(win, pubUrl) {
239
256
  assets,
240
257
  };
241
258
 
242
- return sendMessage(message, replaceAssets);
259
+ return sendMessage(message, replaceAssets(adId));
243
260
  }
244
261
 
245
262
  /*
@@ -252,7 +269,7 @@ export function newNativeAssetManager(win, pubUrl) {
252
269
  action: 'allAssetRequest',
253
270
  adId,
254
271
  };
255
- return sendMessage(message, replaceAssets);
272
+ return sendMessage(message, replaceAssets(adId));
256
273
  }
257
274
 
258
275
  /*
@@ -269,117 +286,154 @@ export function newNativeAssetManager(win, pubUrl) {
269
286
  sendMessage(message);
270
287
  }
271
288
 
272
- /*
273
- * Postmessage listener for when Prebid responds with requested native assets.
274
- */
275
- function replaceAssets(event) {
276
- var data = {};
277
289
 
278
- try {
279
- data = JSON.parse(event.data);
280
- } catch (e) {
281
- if (errorCountEscapeHatch++ > 10) {
282
- /*
283
- * if for some reason Prebid never responds with the native assets,
284
- * get rid of this listener because other messages won't stop coming
285
- */
286
- stopListening();
287
- }
288
- return;
289
- }
290
+ function replaceAssets(adId) {
291
+ return function(event) {
292
+ try {
290
293
 
291
- if (data.message === 'assetResponse') {
292
- const body = win.document.body.innerHTML;
293
- const head = win.document.head.innerHTML;
294
+ var data = {};
294
295
 
295
- if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
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
+ }
296
310
 
297
- if (head) win.document.head.innerHTML = replace(head, data);
311
+ if (data.message === 'assetResponse' && data.adId === adId) {
312
+ if(hasDynamicRenderer(data)) {
313
+ runDynamicRenderer(adId, data, sendMessage, win);
314
+ return;
315
+ }
298
316
 
299
- data.assets = data.assets || [];
300
- let renderPayload = data.assets;
301
- if (data.ortb) {
302
- renderPayload.ortb = data.ortb;
303
- callback = () => {
304
- fireNativeImpressionTrackers(data.adId, sendMessage);
305
- addNativeClickTrackers(data.adId, data.ortb, sendMessage);
306
- }
307
- }
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;
308
321
 
309
- if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
310
- if (win.renderAd) {
311
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
322
+ callback = ((cb) => {
323
+ return () => {
324
+ fireNativeImpressionTrackers(data.adId, sendMessage);
325
+ addNativeClickTrackers(data.adId, sendMessage);
326
+ cb && cb();
327
+ }
328
+ })(callback);
312
329
 
313
- renderAd(newHtml, data);
314
- } else if (document.getElementById('pb-native-renderer')) {
315
- document.getElementById('pb-native-renderer').addEventListener('load', function() {
316
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
330
+ if (head) win.document.head.innerHTML = replace(head, data);
317
331
 
318
- renderAd(newHtml, data);
319
- });
320
- } else {
321
- loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function() {
322
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
332
+
333
+ data.assets = data.assets || [];
334
+ let renderPayload = data.assets;
335
+ if (data.ortb) {
336
+ renderPayload.ortb = data.ortb;
337
+ }
338
+
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) {
347
+ const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
348
+
349
+ renderAd(newHtml, data);
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);
323
366
 
324
367
  renderAd(newHtml, data);
325
- })
368
+ } else {
369
+ const newHtml = replace(body, data);
370
+
371
+ win.document.body.innerHTML = newHtml;
372
+ callback && callback(); // all the other scenarios hit the callback via renderAd()
373
+ stopListening();
374
+ }
326
375
  }
327
- } else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
328
- const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
329
- const newHtml = replace(template, data);
330
-
331
- renderAd(newHtml, data);
332
- } else {
333
- const newHtml = replace(body, data);
334
-
335
- win.document.body.innerHTML = newHtml;
336
- callback && callback();
337
- stopListening();
376
+ } catch (e) {
377
+ errCallback && errCallback(e);
338
378
  }
339
379
  }
340
380
  }
341
381
 
342
382
  /** This function returns the element that contains the current iframe. */
343
383
  function getCurrentFrameContainer(win) {
344
- let currentWindow = win;
345
- let currentParentWindow;
384
+ try {
385
+ let currentWindow = win;
386
+ let currentParentWindow;
346
387
 
347
- while (currentWindow !== win.top) {
388
+ while (currentWindow !== win.top) {
348
389
  currentParentWindow = currentWindow.parent;
349
390
  if (!currentParentWindow.frames || !currentParentWindow.frames.length) return null;
350
391
  for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
351
- if (currentParentWindow.frames[idx] === currentWindow) {
352
- if (!currentParentWindow.document) return null;
353
- for (let frameElement of currentParentWindow.document.getElementsByTagName('iframe')) {
354
- if (!frameElement.contentWindow) return null;
355
- if (frameElement.contentWindow === currentWindow) {
356
- return frameElement.parentElement;
357
- }
358
- }
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
+ }
359
399
  }
400
+ }
401
+ }
402
+ } catch (e) {
403
+ // parent is cross-frame
360
404
  }
361
- }
405
+ }
362
406
 
363
407
  function renderAd(html, bid) {
364
408
  // if the current iframe is not a safeframe, try to set the
365
409
  // current iframe width to the width of the container. This
366
410
  // is to handle the case where the native ad is rendered inside
367
411
  // a GAM display ad.
412
+
413
+ // NOTE: this may be unnecessary, see https://github.com/prebid/prebid-universal-creative/issues/253.
368
414
  if (!isSafeFrame(window)) {
369
415
  let iframeContainer = getCurrentFrameContainer(win);
370
416
  if (iframeContainer && iframeContainer.children && iframeContainer.children[0]) {
371
- const iframe = iframeContainer.children[0];
417
+ const iframe = iframeContainer.children[0];
372
418
  if (iframe.width === '1' && iframe.height === '1') {
373
419
  let width = iframeContainer.getBoundingClientRect().width;
374
420
  win.document.body.style.width = `${width}px`;
375
421
  }
376
422
  }
377
423
  }
424
+
425
+ //substitute CLICK_URL_UNESC with actual value
426
+ html = html.replaceAll(CLICK_URL_UNESC, bid.clickUrlUnesc || "");
427
+
378
428
  win.document.body.innerHTML += html;
379
429
  callback && callback();
380
- win.removeEventListener('message', replaceAssets);
381
430
  stopListening();
382
- requestHeightResize(bid.adId, (document.body.clientHeight || document.body.offsetHeight), document.body.clientWidth);
431
+ const resize = () => requestHeightResize(
432
+ bid.adId,
433
+ (document.body.clientHeight || document.body.offsetHeight),
434
+ document.body.clientWidth > 1 ? document.body.clientWidth : undefined
435
+ );
436
+ document.readyState === 'complete' ? resize() : window.onload = resize;
383
437
 
384
438
  if (typeof window.postRenderAd === 'function') {
385
439
  window.postRenderAd(bid);
@@ -405,22 +459,22 @@ export function newNativeAssetManager(win, pubUrl) {
405
459
  }
406
460
 
407
461
  ortb.assets.forEach(asset => {
408
- html = html.replace(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
462
+ html = html.replaceAll(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
409
463
  if (asset.link && asset.link.url) {
410
- html = html.replace(`##hb_native_asset_link_id_${asset.id}##`, asset.link.url);
464
+ html = html.replaceAll(`##hb_native_asset_link_id_${asset.id}##`, asset.link.url);
411
465
  }
412
466
  });
413
467
 
414
468
  html = html.replaceAll(/##hb_native_asset_id_\d+##/gm, '');
415
469
 
416
470
  if (ortb.privacy) {
417
- html = html.replace("##hb_native_privacy##", ortb.privacy);
471
+ html = html.replaceAll("##hb_native_privacy##", ortb.privacy);
418
472
  }
419
473
 
420
474
  if (ortb.link) {
421
475
  html = html.replaceAll("##hb_native_linkurl##", ortb.link.url);
422
476
  }
423
-
477
+
424
478
  return html;
425
479
  }
426
480
 
@@ -12,16 +12,16 @@ export function fireNativeImpressionTrackers(adId, sendMessage) {
12
12
  sendMessage(message);
13
13
  }
14
14
 
15
- export function addNativeClickTrackers(adId, ortb, sendMessage) {
15
+ export function addNativeClickTrackers(adId, sendMessage) {
16
16
  const message = {
17
17
  message: 'Prebid Native',
18
18
  action: 'click',
19
- adId
19
+ adId
20
20
  };
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('click', (event) => {
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);
@@ -1,76 +1,48 @@
1
1
  /*
2
2
  * Script to handle firing impression and click trackers from native teamplates
3
3
  */
4
- import { newNativeAssetManager } from './nativeAssetManager';
5
- import {prebidMessenger} from './messaging.js';
6
-
7
- const AD_ANCHOR_CLASS_NAME = 'pb-click';
8
- const AD_DATA_ADID_ATTRIBUTE = 'pbAdId';
9
-
10
- export function newNativeRenderManager(win) {
11
- let sendMessage;
12
-
13
-
14
- function findAdElements(className) {
15
- let adElements = win.document.getElementsByClassName(className);
16
- return adElements || [];
17
- }
18
-
19
- function loadClickTrackers(event, adId) {
20
- fireTracker(adId, 'click');
21
- }
22
-
23
- function fireTracker(adId, action) {
24
- if (adId === '') {
25
- console.warn('Prebid tracking event was missing \'adId\'. Was adId macro set in the HTML attribute ' + AD_DATA_ADID_ATTRIBUTE + 'on the ad\'s anchor element');
26
- } else {
27
- let message = { message: 'Prebid Native', adId: adId };
28
-
29
- // fires click trackers when called via link
30
- if (action === 'click') {
31
- message.action = 'click';
32
- }
33
- sendMessage(message);
34
- }
35
- }
36
-
37
- function fireNativeImpTracker(adId) {
38
- fireTracker(adId, 'impression');
39
- }
40
-
41
- function fireNativeCallback() {
42
- const adElements = findAdElements(AD_ANCHOR_CLASS_NAME);
43
- for (let i = 0; i < adElements.length; i++) {
44
- adElements[i].addEventListener('click', function(event) {
45
- loadClickTrackers(event, window.pbNativeData.adId);
46
- }, true);
47
- }
48
- }
49
-
50
- // START OF MAIN CODE
51
- let renderNativeAd = function(doc, nativeTag) {
52
- window.pbNativeData = nativeTag;
53
- sendMessage = prebidMessenger(nativeTag.pubUrl, win);
54
- const nativeAssetManager = newNativeAssetManager(window, nativeTag.pubUrl);
55
-
56
- if (nativeTag.hasOwnProperty('adId')) {
57
-
58
- if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
59
- const scr = doc.createElement('SCRIPT');
60
- scr.src = nativeTag.rendererUrl,
61
- scr.id = 'pb-native-renderer';
62
- doc.body.appendChild(scr);
63
- }
64
- nativeAssetManager.loadAssets(nativeTag.adId, () => {
65
- fireNativeImpTracker(nativeTag.adId);
66
- fireNativeCallback();
67
- });
68
- } else {
69
- console.warn("Prebid Native Tag object was missing 'adId'.");
70
- }
71
- }
72
-
73
- return {
74
- renderNativeAd
75
- }
4
+ import {newNativeAssetManager} from './nativeAssetManager';
5
+ import {prebidMessenger, renderEventMessage} from './messaging.js';
6
+
7
+ export function newNativeRenderManager(win, mkMessenger = prebidMessenger, assetMgr = newNativeAssetManager) {
8
+ let sendMessage;
9
+
10
+
11
+ let renderNativeAd = function (doc, nativeTag) {
12
+ window.pbNativeData = nativeTag;
13
+ sendMessage = mkMessenger(nativeTag.pubUrl, win);
14
+
15
+ function signalResult(adId, errorInfo) {
16
+ sendMessage(renderEventMessage(adId, errorInfo));
17
+ }
18
+
19
+ try {
20
+ const nativeAssetManager = assetMgr(window, nativeTag);
21
+
22
+ if (nativeTag.adId != null) {
23
+
24
+ if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
25
+ const scr = doc.createElement('SCRIPT');
26
+ scr.src = nativeTag.rendererUrl,
27
+ scr.id = 'pb-native-renderer';
28
+ doc.body.appendChild(scr);
29
+ }
30
+ nativeAssetManager.loadAssets(nativeTag.adId, () => {
31
+ signalResult(nativeTag.adId);
32
+ }, (e) => {
33
+ signalResult(nativeTag.adId, {reason: 'exception', message: e.message});
34
+ });
35
+ } else {
36
+ signalResult(null, {reason: 'missingDocOrAdid'});
37
+ console.warn('Prebid Native Tag object was missing \'adId\'.');
38
+ }
39
+ } catch (e) {
40
+ signalResult(nativeTag && nativeTag.adId, {reason: 'exception', message: e.message});
41
+ console.error('Error rendering ad', e);
42
+ }
43
+ };
44
+
45
+ return {
46
+ renderNativeAd
47
+ };
76
48
  }
@@ -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('click', function(event) {
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)
@@ -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);
@@ -19,18 +20,23 @@ export function renderBannerOrDisplayAd(doc, dataObject) {
19
20
  * @param {string} adId Id of creative to render
20
21
  */
21
22
  export function renderLegacy(doc, adId) {
23
+ let found = false;
22
24
  let w = window;
23
25
  for (let i = 0; i < 10; i++) {
24
26
  w = w.parent;
25
27
  if (w.$$PREBID_GLOBAL$$) {
26
28
  try {
27
29
  w.$$PREBID_GLOBAL$$.renderAd(doc, adId);
30
+ found = true;
28
31
  break;
29
32
  } catch (e) {
30
33
  continue;
31
34
  }
32
35
  }
33
36
  }
37
+ if (!found) {
38
+ console.error("Unable to locate $$PREBID_GLOBAL$$.renderAd function!");
39
+ }
34
40
  }
35
41
 
36
42
  /**
@@ -44,6 +50,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
44
50
  let adServerDomain = pubAdServerDomain || win.location.hostname;
45
51
  let fullAdServerDomain = windowLocation.protocol + '//' + adServerDomain;
46
52
  const sendMessage = prebidMessenger(pubUrl, win);
53
+ const signalRenderResult = (errorInfo) => sendMessage(renderEventMessage(adId, errorInfo));
47
54
 
48
55
  function renderAd(ev) {
49
56
  let key = ev.message ? "message" : "data";
@@ -59,6 +66,10 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
59
66
  adObject.message === "Prebid Response" &&
60
67
  adObject.adId === adId
61
68
  ) {
69
+ if (hasDynamicRenderer(adObject)) {
70
+ runDynamicRenderer(adId, adObject, sendMessage, win);
71
+ return;
72
+ }
62
73
  try {
63
74
  let body = win.document.body;
64
75
  let ad = adObject.ad;
@@ -67,7 +78,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
67
78
  let height = adObject.height;
68
79
 
69
80
  if (adObject.mediaType === "video") {
70
- signalRenderResult(false, {
81
+ signalRenderResult({
71
82
  reason: "preventWritingOnMainDocument",
72
83
  message: `Cannot render video ad ${adId}`,
73
84
  });
@@ -78,7 +89,7 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
78
89
  iframe.contentDocument.open();
79
90
  iframe.contentDocument.write(ad);
80
91
  iframe.contentDocument.close();
81
- signalRenderResult(true);
92
+ signalRenderResult();
82
93
  } else if (url) {
83
94
  const iframe = getEmptyIframe(height, width);
84
95
  iframe.style.display = "inline";
@@ -86,9 +97,9 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
86
97
  iframe.src = url;
87
98
 
88
99
  insertElement(iframe, document, "body");
89
- signalRenderResult(true);
100
+ signalRenderResult();
90
101
  } else {
91
- signalRenderResult(false, {
102
+ signalRenderResult({
92
103
  reason: "noAd",
93
104
  message: `No ad for ${adId}`,
94
105
  });
@@ -97,22 +108,10 @@ export function renderCrossDomain(win, adId, pubAdServerDomain = '', pubUrl) {
97
108
  );
98
109
  }
99
110
  } catch (e) {
100
- signalRenderResult(false, { reason: "exception", message: e.message });
111
+ signalRenderResult({ reason: "exception", message: e.message });
101
112
  console.log(`Error in rendering ad`, e);
102
113
  }
103
114
  }
104
-
105
- function signalRenderResult(success, { reason, message } = {}) {
106
- const payload = {
107
- message: "Prebid Event",
108
- adId,
109
- event: success ? "adRenderSucceeded" : "adRenderFailed",
110
- };
111
- if (!success) {
112
- payload.info = { reason, message };
113
- }
114
- sendMessage(payload);
115
- }
116
115
  }
117
116
 
118
117
  function requestAdFromPrebid() {
package/src/utils.js CHANGED
@@ -30,15 +30,6 @@ export function writeAdUrl(adUrl, width, height) {
30
30
  document.body.appendChild(iframe);
31
31
  }
32
32
 
33
- export function writeAdHtml(markup) {
34
- // remove <?xml> and <!doctype> tags
35
- // https://github.com/prebid/prebid-universal-creative/issues/134
36
- markup = markup.replace(/\<(\?xml|(\!DOCTYPE[^\>\[]+(\[[^\]]+)?))+[^>]+\>/g, '');
37
- postscribe(document.body, markup, {
38
- error: console.error
39
- });
40
- }
41
-
42
33
  export function sendRequest(url, callback) {
43
34
  function reqListener() {
44
35
  callback(oReq.responseText);