Skip to content

Commit

Permalink
Prebid Core: Check for stale rendering (#6707)
Browse files Browse the repository at this point in the history
* Check for stale rendering

* Check for stale rendering - code review changes
  • Loading branch information
pycnvr authored May 26, 2021
1 parent 8cf4ad9 commit b72e584
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 61 deletions.
6 changes: 6 additions & 0 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* @property {function(): Object} createAuction - creates auction instance and stores it for future reference
* @property {function(): Object} findBidByAdId - find bid received by adId. This function will be called by $$PREBID_GLOBAL$$.renderAd
* @property {function(): Object} getStandardBidderAdServerTargeting - returns standard bidder targeting for all the adapters. Refer http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.bidderSettings for more details
* @property {function(Object): void} addWinningBid - add a winning bid to an auction based on auctionId
* @property {function(): void} clearAllAuctions - clear all auctions for testing
*/

import { uniques, flatten, logWarn } from './utils.js';
Expand Down Expand Up @@ -113,6 +115,10 @@ export function newAuctionManager() {
return _auctions.length && _auctions[_auctions.length - 1].getAuctionId()
};

auctionManager.clearAllAuctions = function() {
_auctions.length = 0;
}

function _addAuction(auction) {
_auctions.push(auction);
}
Expand Down
7 changes: 6 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export function newConfig() {
}

for (let k of Object.keys(val)) {
if (k !== 'secondaryBidders') {
if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') {
utils.logWarn(`Auction Options given an incorrect param: ${k}`)
return false
}
Expand All @@ -277,6 +277,11 @@ export function newConfig() {
utils.logWarn(`Auction Options ${k} must be only string`);
return false
}
} else if (k === 'suppressStaleRender') {
if (!utils.isBoolean(val[k])) {
utils.logWarn(`Auction Options ${k} must be of type boolean`);
return false;
}
}
}
return true;
Expand Down
3 changes: 2 additions & 1 deletion src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"AD_RENDER_FAILED": "adRenderFailed",
"TCF2_ENFORCEMENT": "tcf2Enforcement",
"AUCTION_DEBUG": "auctionDebug",
"BID_VIEWABLE": "bidViewable"
"BID_VIEWABLE": "bidViewable",
"STALE_RENDER": "staleRender"
},
"AD_RENDER_FAILED_REASON" : {
"PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument",
Expand Down
118 changes: 65 additions & 53 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const events = require('./events.js');
const { triggerUserSyncs } = userSync;

/* private variables */
const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED } = CONSTANTS.EVENTS;
const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED, STALE_RENDER } = CONSTANTS.EVENTS;
const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON;

const eventValidators = {
Expand Down Expand Up @@ -390,63 +390,75 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) {
try {
// lookup ad by ad Id
const bid = auctionManager.findBidByAdId(id);

if (bid) {
// replace macros according to openRTB with price paid = bid.cpm
bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm);
bid.adUrl = utils.replaceAuctionPrice(bid.adUrl, bid.cpm);

// replacing clickthrough if submitted
if (options && options.clickThrough) {
const { clickThrough } = options;
bid.ad = utils.replaceClickThrough(bid.ad, clickThrough);
bid.adUrl = utils.replaceClickThrough(bid.adUrl, clickThrough);
let shouldRender = true;
if (bid && bid.status === CONSTANTS.BID_STATUS.RENDERED) {
utils.logWarn(`Ad id ${bid.adId} has been rendered before`);
events.emit(STALE_RENDER, bid);
if (utils.deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
shouldRender = false;
}
}

// save winning bids
auctionManager.addWinningBid(bid);

// emit 'bid won' event here
events.emit(BID_WON, bid);

const { height, width, ad, mediaType, adUrl, renderer } = bid;

const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`);
utils.insertElement(creativeComment, doc, 'body');

if (isRendererRequired(renderer)) {
executeRenderer(renderer, bid);
} else if ((doc === document && !utils.inIframe()) || mediaType === 'video') {
const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`;
emitAdRenderFail({ reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id });
} else if (ad) {
// will check if browser is firefox and below version 67, if so execute special doc.open()
// for details see: https://github.com/prebid/Prebid.js/pull/3524
// TODO remove this browser specific code at later date (when Firefox < 67 usage is mostly gone)
if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox/') > -1) {
const firefoxVerRegx = /firefox\/([\d\.]+)/;
let firefoxVer = navigator.userAgent.toLowerCase().match(firefoxVerRegx)[1]; // grabs the text in the 1st matching group
if (firefoxVer && parseInt(firefoxVer, 10) < 67) {
doc.open('text/html', 'replace');
if (shouldRender) {
// replace macros according to openRTB with price paid = bid.cpm
bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm);
bid.adUrl = utils.replaceAuctionPrice(bid.adUrl, bid.cpm);

// replacing clickthrough if submitted
if (options && options.clickThrough) {
const {clickThrough} = options;
bid.ad = utils.replaceClickThrough(bid.ad, clickThrough);
bid.adUrl = utils.replaceClickThrough(bid.adUrl, clickThrough);
}

// save winning bids
auctionManager.addWinningBid(bid);

// emit 'bid won' event here
events.emit(BID_WON, bid);

const {height, width, ad, mediaType, adUrl, renderer} = bid;

const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`);
utils.insertElement(creativeComment, doc, 'body');

if (isRendererRequired(renderer)) {
executeRenderer(renderer, bid);
} else if ((doc === document && !utils.inIframe()) || mediaType === 'video') {
const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`;
emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id});
} else if (ad) {
// will check if browser is firefox and below version 67, if so execute special doc.open()
// for details see: https://github.com/prebid/Prebid.js/pull/3524
// TODO remove this browser specific code at later date (when Firefox < 67 usage is mostly gone)
if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox/') > -1) {
const firefoxVerRegx = /firefox\/([\d\.]+)/;
let firefoxVer = navigator.userAgent.toLowerCase().match(firefoxVerRegx)[1]; // grabs the text in the 1st matching group
if (firefoxVer && parseInt(firefoxVer, 10) < 67) {
doc.open('text/html', 'replace');
}
}
doc.write(ad);
doc.close();
setRenderSize(doc, width, height);
utils.callBurl(bid);
} else if (adUrl) {
const iframe = utils.createInvisibleIframe();
iframe.height = height;
iframe.width = width;
iframe.style.display = 'inline';
iframe.style.overflow = 'hidden';
iframe.src = adUrl;

utils.insertElement(iframe, doc, 'body');
setRenderSize(doc, width, height);
utils.callBurl(bid);
} else {
const message = `Error trying to write ad. No ad for bid response id: ${id}`;
emitAdRenderFail({reason: NO_AD, message, bid, id});
}
doc.write(ad);
doc.close();
setRenderSize(doc, width, height);
utils.callBurl(bid);
} else if (adUrl) {
const iframe = utils.createInvisibleIframe();
iframe.height = height;
iframe.width = width;
iframe.style.display = 'inline';
iframe.style.overflow = 'hidden';
iframe.src = adUrl;

utils.insertElement(iframe, doc, 'body');
setRenderSize(doc, width, height);
utils.callBurl(bid);
} else {
const message = `Error trying to write ad. No ad for bid response id: ${id}`;
emitAdRenderFail({ reason: NO_AD, message, bid, id });
}
} else {
const message = `Error trying to write ad. Cannot find ad by given id : ${id}`;
Expand Down
14 changes: 12 additions & 2 deletions src/secureCreatives.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
import events from './events.js';
import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js';
import constants from './constants.json';
import { logWarn, replaceAuctionPrice } from './utils.js';
import { logWarn, replaceAuctionPrice, deepAccess } from './utils.js';
import { auctionManager } from './auctionManager.js';
import find from 'core-js-pure/features/array/find.js';
import { isRendererRequired, executeRenderer } from './Renderer.js';
import includes from 'core-js-pure/features/array/includes.js';
import { config } from './config.js';

const BID_WON = constants.EVENTS.BID_WON;
const STALE_RENDER = constants.EVENTS.STALE_RENDER;

export function listenMessagesFromCreative() {
window.addEventListener('message', receiveMessage, false);
}

function receiveMessage(ev) {
export function receiveMessage(ev) {
var key = ev.message ? 'message' : 'data';
var data = {};
try {
Expand All @@ -33,6 +35,14 @@ function receiveMessage(ev) {
});

if (adObject && data.message === 'Prebid Request') {
if (adObject.status === constants.BID_STATUS.RENDERED) {
logWarn(`Ad id ${adObject.adId} has been rendered before`);
events.emit(STALE_RENDER, adObject);
if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
return;
}
}

_sendAdToCreative(adObject, ev);

// save winning bids
Expand Down
19 changes: 18 additions & 1 deletion test/spec/config_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,22 @@ describe('config API', function () {
expect(logWarnSpy.called).to.equal(false);
});

it('sets auctionOptions', function () {
it('sets auctionOptions secondaryBidders', function () {
const auctionOptionsConfig = {
'secondaryBidders': ['rubicon', 'appnexus']
}
setConfig({ auctionOptions: auctionOptionsConfig });
expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig);
});

it('sets auctionOptions suppressStaleRender', function () {
const auctionOptionsConfig = {
'suppressStaleRender': true
}
setConfig({ auctionOptions: auctionOptionsConfig });
expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig);
});

it('should log warning for the wrong value passed to auctionOptions', function () {
setConfig({ auctionOptions: '' });
expect(logWarnSpy.calledOnce).to.equal(true);
Expand All @@ -257,6 +265,15 @@ describe('config API', function () {
assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged');
});

it('should log warning for invalid auctionOptions suppress stale render', function () {
setConfig({ auctionOptions: {
'suppressStaleRender': 'test',
}});
expect(logWarnSpy.calledOnce).to.equal(true);
const warning = 'Auction Options suppressStaleRender must be of type boolean';
assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged');
});

it('should log warning for invalid properties to auctionOptions', function () {
setConfig({ auctionOptions: {
'testing': true
Expand Down
Loading

0 comments on commit b72e584

Please sign in to comment.