Skip to content
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

native Rendering : fix bug where click trackers are not fired #12655

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions creative/renderers/native/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ function loadScript(url, doc) {
});
}

function getRenderFrames(node) {
return Array.from(node.querySelectorAll('iframe[srcdoc*="render"]'))
}

function getInnerHTML(node) {
const clone = node.cloneNode(true);
getRenderFrames(clone).forEach(node => node.parentNode.removeChild(node));
return clone.innerHTML;
}

export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) {
const {rendererUrl, assets, ortb, adTemplate} = nativeData;
const doc = win.document;
Expand All @@ -58,21 +68,32 @@ export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript)
return win.renderAd(payload);
});
} else {
return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML));
return Promise.resolve(replacer(adTemplate ?? getInnerHTML(doc.body)));
}
}

export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) {
const {head, body} = win.document;
const resize = () => sendMessage(MESSAGE_NATIVE, {
action: ACTION_RESIZE,
height: body.offsetHeight,
width: body.offsetWidth
});
const resize = () => {
// force redraw - for some reason this is needed to get the right dimensions
body.style.display = 'none';
body.style.display = 'block';
sendMessage(MESSAGE_NATIVE, {
action: ACTION_RESIZE,
height: body.offsetHeight,
width: body.offsetWidth
});
}
function replaceMarkup(target, markup) {
// do not remove the rendering logic if it's embedded in this window; things will break otherwise
const renderFrames = getRenderFrames(target);
Array.from(target.childNodes).filter(node => !renderFrames.includes(node)).forEach(node => target.removeChild(node));
target.insertAdjacentHTML('afterbegin', markup);
}
const replacer = getReplacer(adId, native);
head && (head.innerHTML = replacer(head.innerHTML));
replaceMarkup(head, replacer(getInnerHTML(head)));
return getMarkup(adId, native, replacer, win).then(markup => {
body.innerHTML = markup;
replaceMarkup(body, markup);
if (typeof win.postRenderAd === 'function') {
win.postRenderAd({adId, ...native});
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/creative-renderer-native/renderer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion modules/nativeRendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {getCreativeRendererSource} from '../src/creativeRenderers.js';
function getRenderingDataHook(next, bidResponse, options) {
if (isNativeResponse(bidResponse)) {
next.bail({
native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse))
native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)),
rendererVersion: 2 // 9.28 fixed a rendering bug; this signals to PUC that the native renderer is safe to use
})
} else {
next(bidResponse, options)
Expand Down
9 changes: 6 additions & 3 deletions src/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,16 @@ function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}
message: 'assetResponse',
adId: data.adId,
};
let renderData = getRenderingData(adObject).native;
let {native: renderData, rendererVersion} = getRenderingData(adObject);
if (renderData) {
// if we have native rendering data (set up by the nativeRendering module)
// include it in full ("all assets") together with the renderer.
// this is to allow PUC to use dynamic renderers without requiring changes in creative setup
msg.native = Object.assign({}, renderData);
msg.renderer = getCreativeRendererSource(adObject);
Object.assign(msg, {
native: Object.assign({}, renderData),
renderer: getCreativeRendererSource(adObject),
rendererVersion,
})
if (keys != null) {
renderData.assets = renderData.assets.filter(({key}) => keys.includes(key))
}
Expand Down
55 changes: 45 additions & 10 deletions test/spec/creative/nativeRenderer_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,24 @@ describe('Native creative renderer', () => {
});
});
describe('otherwise, calls replacer', () => {
let replacer;
let replacer, frame;
beforeEach(() => {
replacer = sinon.stub().returns('markup');
frame = document.createElement('iframe');
document.body.appendChild(frame);
win.document = frame.contentDocument;
});
afterEach(() => {
document.body.removeChild(frame);
})
it('with adTemplate, if present', () => {
return getAdMarkup('123', {adTemplate: 'tpl'}, replacer, win).then((result) => {
expect(result).to.eql('markup');
sinon.assert.calledWith(replacer, 'tpl');
});
});
it('with document body otherwise', () => {
win.document = {body: {innerHTML: 'body'}};
win.document.body.innerHTML = 'body'
return getAdMarkup('123', {}, replacer, win).then((result) => {
expect(result).to.eql('markup');
sinon.assert.calledWith(replacer, 'body');
Expand Down Expand Up @@ -186,26 +192,29 @@ describe('Native creative renderer', () => {
});

describe('render', () => {
let getMarkup, sendMessage, adId, nativeData, exc;
let getMarkup, sendMessage, adId, nativeData, exc, frame;
beforeEach(() => {
adId = '123';
nativeData = {}
getMarkup = sinon.stub();
sendMessage = sinon.stub()
exc = sinon.stub();
win.document = {
querySelectorAll() { return [] },
body: {}
}
frame = document.createElement('iframe');
document.body.appendChild(frame);
win.document = frame.contentDocument;
});

afterEach(() => {
document.body.removeChild(frame);
})

function runRender() {
return render({adId, native: nativeData}, {sendMessage, exc}, win, getMarkup)
}

it('replaces placeholders in head, if present', () => {
getMarkup.returns(Promise.resolve(''))
win.document.head = {innerHTML: '##hb_native_asset_id_1##'};
win.document.head.innerHTML = '##hb_native_asset_id_1##';
nativeData.ortb = {
assets: [
{id: 1, data: {value: 'repl'}}
Expand All @@ -216,6 +225,14 @@ describe('Native creative renderer', () => {
})
});

it('does not replace iframes with srcdoc that contain "renderer"', () => {
win.document.head.innerHTML = win.document.body.innerHTML = '<iframe srcdoc="renderer"></iframe>';
getMarkup.returns(Promise.resolve(''))
return runRender().then(() => {
expect(Array.from(win.document.querySelectorAll('iframe[srcdoc="renderer"]')).length).to.eql(2);
})
})

it('drops markup on body, and fires imp trackers', () => {
getMarkup.returns(Promise.resolve('markup'));
return runRender().then(() => {
Expand Down Expand Up @@ -246,9 +263,27 @@ describe('Native creative renderer', () => {

describe('requests resize', () => {
beforeEach(() => {
const mkNode = () => {
const node = {
innerHTML: '',
childNodes: [],
insertAdjacentHTML: () => {},
style: {},
querySelectorAll: () => [],
cloneNode: () => node
};
return node;
}
win.document = {
head: mkNode(),
body: Object.assign(mkNode(), {
offsetHeight: 123,
offsetWidth: 321
}),
querySelectorAll: () => [],
style: {}
};
getMarkup.returns(Promise.resolve('markup'));
win.document.body.offsetHeight = 123;
win.document.body.offsetWidth = 321;
});

it('immediately, if document is loaded', () => {
Expand Down
6 changes: 4 additions & 2 deletions test/spec/native_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ describe('native.js', function () {
'returns native data': {
renderDataHook(next, bidResponse) {
next.bail({
native: getNativeRenderingData(bidResponse, adUnit)
native: getNativeRenderingData(bidResponse, adUnit),
rendererVersion: 'native-render-version'
});
},
renderSourceHook(next) {
Expand Down Expand Up @@ -433,8 +434,9 @@ describe('native.js', function () {
function checkRenderer(message) {
if (withRenderer) {
expect(message.renderer).to.eql('mock-native-renderer')
expect(message.rendererVersion).to.eql('native-render-version');
Object.entries(message).forEach(([key, val]) => {
if (!['native', 'adId', 'message', 'assets', 'renderer'].includes(key)) {
if (!['native', 'adId', 'message', 'assets', 'renderer', 'rendererVersion'].includes(key)) {
expect(message.native[key]).to.eql(val);
}
})
Expand Down
Loading