Prebid Core: add documentResolver callback and allow the user to supply a different document object to render#8262
Conversation
* cache url's per resolvable context * pass options as config to Renderer * pass renderDocument to render * utility function to resolve the containing window object of a document
* support user-provided renderer context * handle when there is no div[id^='google_ads'] element
src/utils.js
Outdated
| if (!doc) { | ||
| return null; | ||
| } | ||
| return doc.defaultView || doc.parentWindow; |
There was a problem hiding this comment.
where does parentWindow come from? I don't see it specified on MDN: https://developer.mozilla.org/en-US/docs/Web/API/Document
There was a problem hiding this comment.
parentWindow is used on older IE browsers
There was a problem hiding this comment.
We do not support older IE browsers - in fact we don't support IE at all - many other things would not work. IMO it can be removed.
There was a problem hiding this comment.
I agree. We shouldnt be adding support back in for old IE versions. Can the contents of this function also just be one ternary line saying return (doc) ? doc.defaultView : null
src/Renderer.js
Outdated
| context = renderer.config.contextResolver(bid, document, doc);// a user provided callback, which should return a Document, and expect the parameters; bid, sourceDocument, renderDocument | ||
| } | ||
| if (!context) { | ||
| context = document; |
There was a problem hiding this comment.
IMO doc would be a better default; what is the reasoning for using document instead?
There was a problem hiding this comment.
document is the current default, changing it to doc would introduce a possible breaking change in the current behaviour. But generally i agree, in fact one of the main reasons this PR exists.
There was a problem hiding this comment.
You're right - I misread and thought it would default to the document passed to renderAd. Nevermind.
modules/appnexusBidAdapter.js
Outdated
| } | ||
|
|
||
| function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { | ||
| debugger; |
src/adloader.js
Outdated
| * @param {function} [callback] callback function to be called after the script is loaded | ||
| */ | ||
| export function loadExternalScript(url, moduleCode, callback) { | ||
| export function loadExternalScript(url, moduleCode, callback, context) { |
There was a problem hiding this comment.
Why does this need to be aware of the context? (also, is context a good name for what is always a document?)
Would it work if instead of keeping track of context, we add an option to always run the callback, and let the caller decide if they need to run their logic again?
There was a problem hiding this comment.
the renderer can load scripts, when the config contains a url property, like;
renderer: {
url: 'https://cdn.adnxs.com/renderer/video/ANOutstreamVideo.js'
}in which case the script should be loaded in the correct/desired document context.
I'm not sure if calling the callback always would be the best solution, since other modules also make use of this method.
Also this would push the (repeating) logic of caching or not, always to the callback.
However there is 1 advantage to that, since once a script is loaded, it could reference/re initiate the already loaded code in the new context. But this could also be implemented else if desired, like in a pre-render hook. Which would save a url load, if the script needs to be loaded in different document contexts.
Also one could say, you could simply rely on the caching behaviour of the browser.
There was a problem hiding this comment.
I missed that you are using context.createScript - you are right. I have two suggestions:
- change the name from
contextto something that's clearly a document (likedoc) - use
WeakMapto separate caches by document, e.g.const caches = new WeakMap(); const _requestCache = caches.get(doc) || caches.set(doc, {}).get(doc). It's a smaller change, easier to follow, and marginally more efficient.
modules/appnexusBidAdapter.js
Outdated
| } | ||
|
|
||
| function outstreamRender(bid) { | ||
| function outstreamRender(bid, context) { |
There was a problem hiding this comment.
Since this new context option needs support from individual adapters anyway,, it looks to me like you could reduce the scope of this change if you look for the document / context in here, without requiring it to be passed around as an argument. By that I mean look at this.options.context in here to see what document / window to use, leaving almost everything in core unchanged.
There was a problem hiding this comment.
I considered that, but only the fields url, renderder and config/options are passed allong, so adding another field would mean to make sure the new context field is always passed in all circumstances. having it a prop of config, ensures it's always passed.
While it's true Adapter should implement this to support it fully, and the ones i've checked it seems implementations vary a lot surrounding this subject.
The core part, for loading the url, when there is not an adapter specific renderer defined, requires this change to live in the core part.
There was a problem hiding this comment.
I think I agree after giving it some more thought, if the idea is that the same renderer instance could be used on different documents. The only pushback I'll give on is the naming again - context sound a lot like object, while the arument is very well defined as a document. Same for contextResolver.
* use WeakMap for caching of url's and documents * flipped key order of url cache * added unit test
|
i've renamed all context's to doc's, implemented the WeakMap for the caching store, and added a unit test. |
| function requestResource(tagSrc, callback) { | ||
| var jptScript = document.createElement('script'); | ||
| function requestResource(tagSrc, callback, doc) { | ||
| if (!doc) { |
There was a problem hiding this comment.
here you check that doc and cacheObject are defined before using them. It doesn't hurt, except maybe for adding a couple bytes, but they should always be defined here - conceptually, if you're going to check for that, it would make more sense to throw an error when they are undefined (but I wouldn't do that either).
It's not that consequential so I don't mind if it stays this way, just noting it as an odd choice IMO.
There was a problem hiding this comment.
It depends, doc being not set defaults to the loaded document, which in theory does also happen down the line in insertElement and some other methods. But now it's specific from here on. But i do agree it could also be an Argument Exception if there is a need to be specific about this.
The cacheObject could theoeratically be gone for what ever reason it's resolved there. This could actually be an error, since the tag property won't be set, but the callback would flag the object as loaded, which is sort of inconsistent. But not failing there and just loading the script, without failing there is i think not that troublesome.
| expect(loadExternalScript.called).to.be.true; | ||
| }); | ||
|
|
||
| it('call\'s documentResolver when configured', function () { |
There was a problem hiding this comment.
Can you add a case to adloater_spec to check that when the same script, but different document is passed to loadExternalScript, it does not hit the cache?
modules/appnexusBidAdapter.js
Outdated
| if (el[0]) { | ||
| el[0].style.setProperty('display', 'none'); | ||
| try { | ||
| var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); |
There was a problem hiding this comment.
Since you are making changes here anyway, can this be updated to use a const instead of a var?
src/utils.js
Outdated
| if (!doc) { | ||
| return null; | ||
| } | ||
| return doc.defaultView || doc.parentWindow; |
There was a problem hiding this comment.
I agree. We shouldnt be adding support back in for old IE versions. Can the contents of this function also just be one ternary line saying return (doc) ? doc.defaultView : null
* drop IE support
|
i've made the requested amendments. |
Type of change
Description of change
This PR, allows the user to supply a different document object to render the ad/outstream video in. This can be usefull if the prebid instance is loaded in a different document/iframe, while the ad/video should be rendered in another iframe/document.
This implements this optional change also in the Appnexus bid adapter, and allows the Appnexus bid adapter to fail silently when certain elements don't exist.
The feature add's an optional
contextResolvercallback method to the config of theRenderer, which receives the current Bid, the document where prebid is loaded, and the document object which is passed to the renderAd method. And should return the document object in which it should render the adConfiguration can be on the adunit or mediaType level.
For example;