-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bidViewablityIO Module: add new submodule for detecting viewability without ad server dependancies #7151
Merged
Merged
bidViewablityIO Module: add new submodule for detecting viewability without ad server dependancies #7151
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b8ff1de
Add bidViewablityIO module
jsut 2b72b39
fix issues in integration example and tests
jsut df08062
only register the event handler if the module is configured
jsut 6c4e342
fix config example in markdown
jsut 60d0093
use getConfig's subscribe functionality
jsut 7c3a5eb
use indexOf instead of includes
jsut e416efd
wrap logMessage to prefix MODULE_NAME on messages
jsut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { config } from '../src/config.js'; | ||
import * as events from '../src/events.js'; | ||
import { EVENTS } from '../src/constants.json'; | ||
import { logMessage } 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; | ||
} | ||
|
||
// 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: adam.prime@alum.utoronto.ca | ||
|
||
# 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is me being picky, but when I am testing / debugging pub pages I like when modules emit their name inside their console logs, makes it easy to filter.
You could wrap logMessage in your own function which just adds in MODULE_NAME on each message you send.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't sound picky, it sounds like an excellent practice.