Skip to content

Commit 23d1f09

Browse files
authored
Signal AD_RENDER_FAILED / AD_RENDER_SUCCEEDED events to Prebid (#152)
Add a new type of cross-origin message ('Prebid Message') to signal when render-related events should be generated by Prebid.
1 parent 320a6d1 commit 23d1f09

File tree

2 files changed

+182
-61
lines changed

2 files changed

+182
-61
lines changed

src/renderingManager.js

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -87,35 +87,62 @@ export function newRenderingManager(win, environment) {
8787
let origin = ev.origin || ev.originalEvent.origin;
8888
if (adObject.message && adObject.message === 'Prebid Response' &&
8989
publisherDomain === origin &&
90-
adObject.adId === adId &&
91-
(adObject.ad || adObject.adUrl)) {
92-
let body = win.document.body;
93-
let ad = adObject.ad;
94-
let url = adObject.adUrl;
95-
let width = adObject.width;
96-
let height = adObject.height;
97-
98-
if (adObject.mediaType === 'video') {
99-
console.log('Error trying to write ad.');
100-
} else if (ad) {
101-
const iframe = domHelper.getEmptyIframe(adObject.height, adObject.width);
102-
body.appendChild(iframe);
103-
iframe.contentDocument.open();
104-
iframe.contentDocument.write(ad);
105-
iframe.contentDocument.close();
106-
} else if (url) {
107-
const iframe = domHelper.getEmptyIframe(height, width);
108-
iframe.style.display = 'inline';
109-
iframe.style.overflow = 'hidden';
110-
iframe.src = url;
111-
112-
domHelper.insertElement(iframe, document, 'body');
113-
} else {
114-
console.log(`Error trying to write ad. No ad for bid response id: ${id}`);
90+
adObject.adId === adId) {
91+
try {
92+
let body = win.document.body;
93+
let ad = adObject.ad;
94+
let url = adObject.adUrl;
95+
let width = adObject.width;
96+
let height = adObject.height;
97+
98+
if (adObject.mediaType === 'video') {
99+
signalRenderResult(false, {
100+
reason: 'preventWritingOnMainDocument',
101+
message: `Cannot render video ad ${adId}`
102+
});
103+
console.log('Error trying to write ad.');
104+
} else if (ad) {
105+
const iframe = domHelper.getEmptyIframe(adObject.height, adObject.width);
106+
body.appendChild(iframe);
107+
iframe.contentDocument.open();
108+
iframe.contentDocument.write(ad);
109+
iframe.contentDocument.close();
110+
signalRenderResult(true);
111+
} else if (url) {
112+
const iframe = domHelper.getEmptyIframe(height, width);
113+
iframe.style.display = 'inline';
114+
iframe.style.overflow = 'hidden';
115+
iframe.src = url;
116+
117+
domHelper.insertElement(iframe, document, 'body');
118+
signalRenderResult(true);
119+
} else {
120+
signalRenderResult(false, {
121+
reason: 'noAd',
122+
message: `No ad for ${adId}`
123+
});
124+
console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`);
125+
}
126+
} catch (e) {
127+
signalRenderResult(false, {reason: "exception", message: e.message});
128+
console.log(`Error in rendering ad`, e);
115129
}
116130
}
131+
132+
function signalRenderResult(success, {reason, message} = {}) {
133+
const payload = {
134+
message: 'Prebid Event',
135+
adId,
136+
event: success ? 'adRenderSucceeded' : 'adRenderFailed',
137+
}
138+
if (!success) {
139+
payload.info = {reason, message};
140+
}
141+
ev.source.postMessage(JSON.stringify(payload), publisherDomain);
142+
}
117143
}
118144

145+
119146
function requestAdFromPrebid() {
120147
let message = JSON.stringify({
121148
message: 'Prebid Request',
@@ -144,7 +171,7 @@ export function newRenderingManager(win, environment) {
144171

145172
return `https://${host}${path}`;
146173
}
147-
174+
148175
/**
149176
* update iframe by using size string to resize
150177
* @param {string} size

test/spec/renderingManager_spec.js

Lines changed: 129 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as domHelper from 'src/domHelper';
44
import { expect } from 'chai';
55
import { mocks } from 'test/helpers/mocks';
66
import { merge } from 'lodash';
7-
import * as postscribe from "postscribe";
87

98
const renderingMocks = {
109
messages: [],
@@ -41,11 +40,14 @@ const renderingMocks = {
4140
}
4241
}
4342

44-
let mockIframe = {
45-
contentDocument: {
46-
open: sinon.spy(),
47-
write: sinon.spy(),
48-
close: sinon.spy()
43+
function createMockIframe() {
44+
return {
45+
contentDocument: {
46+
open: sinon.spy(),
47+
write: sinon.spy(),
48+
close: sinon.spy()
49+
},
50+
style: {},
4951
}
5052
}
5153

@@ -304,59 +306,151 @@ describe('renderingManager', function() {
304306
});
305307

306308
describe('cross domain creative', function() {
309+
const ORIGIN = 'http://example.com';
307310
let parseStub;
308311
let iframeStub;
309312
let triggerPixelSpy;
313+
let mockWin;
314+
let env;
315+
let renderObject;
316+
let ucTagData;
317+
let mockIframe;
318+
let eventSource;
319+
310320
beforeEach(function(){
321+
mockIframe = createMockIframe();
311322
parseStub = sinon.stub(utils, 'parseUrl');
312-
iframeStub = sinon.stub(domHelper, 'getEmptyIframe');
323+
iframeStub = sinon.stub(domHelper, 'getEmptyIframe').returns(mockIframe);
313324
triggerPixelSpy = sinon.stub(utils, 'triggerPixel');
314-
});
315-
316-
after(function() {
317-
parseStub.restore();
318-
iframeStub.restore();
319-
triggerPixelSpy.restore();
320-
});
321-
322-
it('should render cross domain creative', function() {
323325
parseStub.returns({
324326
protocol: 'http',
325327
host: 'example.com'
326328
});
327-
iframeStub.returns(mockIframe);
328-
329-
const mockWin = merge(mocks.createFakeWindow('http://example.com'), renderingMocks.getWindowObject());
330-
const env = {
329+
mockWin = merge(mocks.createFakeWindow(ORIGIN), renderingMocks.getWindowObject());
330+
env = {
331331
isMobileApp: () => false,
332332
isAmp: () => false,
333333
canLocatePrebid: () => false
334334
};
335-
const renderObject = newRenderingManager(mockWin, env);
336-
let ucTagData = {
335+
renderObject = newRenderingManager(mockWin, env);
336+
ucTagData = {
337337
adId: '123',
338338
adServerDomain: 'mypub.com',
339-
pubUrl: 'http://example.com'
339+
pubUrl: ORIGIN,
340340
};
341+
eventSource = null;
341342

342343
renderObject.renderAd(mockWin.document, ucTagData);
343344

344-
// dummy implementation of postmessage from prebid.js
345-
let ev = {
346-
origin: 'http://example.com',
347-
message: JSON.stringify({
348-
message: 'Prebid Response',
349-
ad: 'ad',
350-
adUrl: 'http://example.com',
351-
adId: '123',
352-
width: 300,
353-
height: 250
354-
})
345+
});
346+
347+
afterEach(function() {
348+
parseStub.restore();
349+
iframeStub.restore();
350+
triggerPixelSpy.restore();
351+
});
352+
353+
function mockPrebidResponse(msg) {
354+
eventSource = {
355+
postMessage: sinon.spy()
355356
};
357+
mockWin.postMessage({
358+
source: eventSource,
359+
origin: ORIGIN,
360+
message: JSON.stringify(Object.assign({message: 'Prebid Response'}, msg))
361+
});
362+
}
356363

357-
mockWin.postMessage(ev);
364+
it('should render cross domain creative', function() {
365+
mockPrebidResponse({
366+
ad: 'ad',
367+
adUrl: ORIGIN,
368+
adId: '123',
369+
width: 300,
370+
height: 250
371+
});
358372
expect(mockIframe.contentDocument.write.args[0][0]).to.equal("ad");
359373
});
374+
375+
describe('should signal event', () => {
376+
const RENDER_FAILED = 'adRenderFailed',
377+
RENDER_SUCCESS = 'adRenderSucceeded';
378+
379+
function expectEventMessage(expected) {
380+
const actual = JSON.parse(eventSource.postMessage.args[0][0]);
381+
sinon.assert.match(actual, Object.assign({message: 'Prebid Event'}, expected));
382+
}
383+
384+
describe('AD_RENDER_FAILED', () => {
385+
it('on video ads', () => {
386+
mockPrebidResponse({
387+
ad: 'ad',
388+
adId: '123',
389+
mediaType: 'video'
390+
});
391+
expectEventMessage({
392+
adId: '123',
393+
event: RENDER_FAILED,
394+
info: {
395+
reason: 'preventWritingOnMainDocument'
396+
}
397+
})
398+
});
399+
400+
it('on ads that have no markup or adUrl', () => {
401+
mockPrebidResponse({
402+
adId: '123',
403+
})
404+
expectEventMessage({
405+
adId: '123',
406+
event: RENDER_FAILED,
407+
info: {
408+
reason: 'noAd'
409+
}
410+
});
411+
});
412+
413+
it('on exceptions', () => {
414+
iframeStub.callsFake(() => {
415+
throw new Error()
416+
});
417+
mockPrebidResponse({
418+
adId: '123',
419+
ad: 'ad',
420+
adUrl: ORIGIN,
421+
});
422+
expectEventMessage({
423+
adId: '123',
424+
event: RENDER_FAILED,
425+
info: {
426+
reason: 'exception'
427+
}
428+
});
429+
})
430+
});
431+
describe('should post AD_RENDER_SUCCEEDED', () => {
432+
it('on ad with markup', () => {
433+
mockPrebidResponse({
434+
adId: '123',
435+
ad: 'markup'
436+
});
437+
expectEventMessage({
438+
adId: '123',
439+
event: RENDER_SUCCESS
440+
});
441+
});
442+
it('on ad with adUrl', () => {
443+
mockPrebidResponse({
444+
adId: '123',
445+
adUrl: 'url'
446+
});
447+
expectEventMessage({
448+
adId: '123',
449+
event: RENDER_SUCCESS
450+
});
451+
})
452+
})
453+
});
360454
});
361455

362456
describe('legacy creative', function() {

0 commit comments

Comments
 (0)