-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'prebid:master' into master
- Loading branch information
Showing
43 changed files
with
4,881 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
integrationExamples/postbid/bidViewabilityIO_example.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<html> | ||
<head> | ||
<script> | ||
var pbjs = pbjs || {}; | ||
pbjs.que = pbjs.que || []; | ||
|
||
(function() { | ||
var pbjsEl = document.createElement("script"); | ||
pbjsEl.type = "text/javascript"; | ||
pbjsEl.async = true; | ||
pbjsEl.src = '../../build/dev/prebid.js'; | ||
var pbjsTargetEl = document.getElementsByTagName("head")[0]; | ||
pbjsTargetEl.insertBefore(pbjsEl, pbjsTargetEl.firstChild); | ||
})(); | ||
|
||
pbjs.que.push(function() { | ||
var adUnits = [ | ||
{ | ||
code: 'regular_iframe', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250]] | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: 'appnexus', | ||
params: { | ||
placementId: 13144370 | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
code: 'large_iframe', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[970, 250]] | ||
} | ||
}, | ||
bids: [ | ||
{ | ||
bidder: 'appnexus', | ||
params: { | ||
placementId: 13144370 | ||
} | ||
} | ||
] | ||
}, | ||
]; | ||
|
||
pbjs.setConfig({ | ||
bidderTimeout: 1000, | ||
bidViewabilityIO: { | ||
enabled: true, | ||
} | ||
}); | ||
|
||
pbjs.onEvent('adRenderSucceeded', ({bid}) => { | ||
var p = document.createElement('p'); | ||
p.innerHTML = bid.adUnitCode + ' was rendered'; | ||
document.getElementById('notes').appendChild(p); | ||
}); | ||
|
||
pbjs.onEvent('bidViewable', (bid) => { | ||
var p = document.createElement('p'); | ||
p.innerHTML = bid.adUnitCode + ' was viewed'; | ||
document.getElementById('notes').appendChild(p); | ||
}); | ||
|
||
pbjs.addAdUnits(adUnits); | ||
|
||
pbjs.requestBids({ | ||
bidsBackHandler: function(bidResponses) { | ||
Object.keys(bidResponses).forEach(adUnitCode => { | ||
var highestCpmBids = pbjs.getHighestCpmBids(adUnitCode); | ||
var winner = highestCpmBids.pop(); | ||
var iframe = document.getElementById(adUnitCode); | ||
var iframeDoc = iframe.contentWindow.document; | ||
if (winner && winner.mediaType === 'banner') { | ||
pbjs.renderAd(iframeDoc, winner.adId); | ||
} else if (winner) { | ||
iframe.width = 300; | ||
iframe.height = 300; | ||
iframeDoc.write('<head></head><body>unsupported mediaType</body>'); | ||
iframeDoc.close(); | ||
} else { | ||
iframe.width = 300; | ||
iframe.height = 300; | ||
iframeDoc.write('<head></head><body>no winner</body>'); | ||
iframeDoc.close(); | ||
} | ||
}); | ||
} | ||
}) | ||
}); | ||
|
||
</script> | ||
|
||
</head> | ||
|
||
<body> | ||
<div id="notes" style="position: fixed; right: 0; width: 50%; height: 100%;"></div> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
<iframe id='regular_iframe' | ||
FRAMEBORDER="0" | ||
SCROLLING="no" | ||
MARGINHEIGHT="0" | ||
MARGINWIDTH="0" | ||
TOPMARGIN="0" | ||
LEFTMARGIN="0" | ||
ALLOWTRANSPARENCY="true" | ||
WIDTH="0" | ||
HEIGHT="0"> | ||
</iframe> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
<iframe id='large_iframe' | ||
FRAMEBORDER="0" | ||
SCROLLING="no" | ||
MARGINHEIGHT="0" | ||
MARGINWIDTH="0" | ||
TOPMARGIN="0" | ||
LEFTMARGIN="0" | ||
ALLOWTRANSPARENCY="true" | ||
WIDTH="0" | ||
HEIGHT="0"> | ||
</iframe> | ||
|
||
<div style="height: 100%"></div> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { config } from '../src/config.js'; | ||
import * as events from '../src/events.js'; | ||
import { EVENTS } from '../src/constants.json'; | ||
import * as utils from '../src/utils.js'; | ||
|
||
const MODULE_NAME = 'bidViewabilityIO'; | ||
const CONFIG_ENABLED = 'enabled'; | ||
|
||
// IAB numbers from: https://support.google.com/admanager/answer/4524488?hl=en | ||
const IAB_VIEWABLE_DISPLAY_TIME = 1000; | ||
const IAB_VIEWABLE_DISPLAY_LARGE_PX = 242000; | ||
export const IAB_VIEWABLE_DISPLAY_THRESHOLD = 0.5 | ||
export const IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD = 0.3; | ||
|
||
const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && | ||
'intersectionRatio' in window.IntersectionObserverEntry.prototype; | ||
|
||
const supportedMediaTypes = [ | ||
'banner' | ||
]; | ||
|
||
export let isSupportedMediaType = (bid) => { | ||
return supportedMediaTypes.indexOf(bid.mediaType) > -1; | ||
} | ||
|
||
let logMessage = (message) => { | ||
return utils.logMessage(`${MODULE_NAME}: ${message}`); | ||
} | ||
|
||
// returns options for the iO that detects if the ad is viewable | ||
export let getViewableOptions = (bid) => { | ||
if (bid.mediaType === 'banner') { | ||
return { | ||
root: null, | ||
rootMargin: '0px', | ||
threshold: bid.width * bid.height > IAB_VIEWABLE_DISPLAY_LARGE_PX ? IAB_VIEWABLE_DISPLAY_LARGE_THRESHOLD : IAB_VIEWABLE_DISPLAY_THRESHOLD | ||
} | ||
} | ||
} | ||
|
||
// markViewed returns a function what will be executed when an ad satisifes the viewable iO | ||
export let markViewed = (bid, entry, observer) => { | ||
return () => { | ||
observer.unobserve(entry.target); | ||
events.emit(EVENTS.BID_VIEWABLE, bid); | ||
logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); | ||
} | ||
} | ||
|
||
// viewCallbackFactory creates the callback used by the viewable IntersectionObserver. | ||
// When an ad comes into view, it sets a timeout for a function to be executed | ||
// when that ad would be considered viewed per the IAB specs. The bid that was rendered | ||
// is passed into the factory, so it can pass it into markViewed, so that it can be included | ||
// in the BID_VIEWABLE event data. If the ad leaves view before the timer goes off, the setTimeout | ||
// is cancelled, an the bid will not be marked as viewed. There's probably some kind of race-ish | ||
// thing going on between IO and setTimeout but this isn't going to be perfect, it's just going to | ||
// be pretty good. | ||
export let viewCallbackFactory = (bid) => { | ||
return (entries, observer) => { | ||
entries.forEach(entry => { | ||
if (entry.isIntersecting) { | ||
logMessage(`viewable timer starting for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); | ||
entry.target.view_tracker = setTimeout(markViewed(bid, entry, observer), IAB_VIEWABLE_DISPLAY_TIME); | ||
} else { | ||
logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} is out of view`); | ||
if (entry.target.view_tracker) { | ||
clearTimeout(entry.target.view_tracker); | ||
logMessage(`viewable timer stopped for id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode}`); | ||
} | ||
} | ||
}); | ||
}; | ||
}; | ||
|
||
export let init = () => { | ||
config.getConfig(MODULE_NAME, conf => { | ||
if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) { | ||
// if the module is enabled and the browser supports Intersection Observer, | ||
// then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes | ||
events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { | ||
if (isSupportedMediaType(bid)) { | ||
let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); | ||
let element = document.getElementById(bid.adUnitCode); | ||
viewable.observe(element); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
init() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Overview | ||
|
||
Module Name: bidViewabilityIO | ||
|
||
Purpose: Emit a BID_VIEWABLE event when a bid becomes viewable using the browsers IntersectionObserver API | ||
|
||
Maintainer: [email protected] | ||
|
||
# Description | ||
- This module will trigger a BID_VIEWABLE event which other modules, adapters or publisher code can use to get a sense of viewability | ||
- You can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewabilityIO')``` | ||
- Viewability, as measured by this module is not perfect, nor should it be expected to be. | ||
- The module does not require any specific ad server, or an adserver at all. | ||
|
||
# Limitations | ||
|
||
- Currently only supports the banner mediaType | ||
- Assumes that the adUnitCode of the ad is also the id attribute of the element that the ad is rendered into. | ||
- Does not make any attempt to ensure that the ad inside that element is itself visible. It assumes that the publisher is operating in good faith. | ||
|
||
# Params | ||
- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable | ||
|
||
# Example of consuming BID_VIEWABLE event | ||
``` | ||
pbjs.onEvent('bidViewable', function(bid){ | ||
console.log('got bid details in bidViewable event', bid); | ||
}); | ||
``` | ||
|
||
# Example of using config | ||
``` | ||
pbjs.setConfig({ | ||
bidViewabilityIO: { | ||
enabled: true, | ||
} | ||
}); | ||
``` | ||
|
||
An example implmentation without an ad server can be found in integrationExamples/postbid/bidViewabilityIO_example.html |
Oops, something went wrong.