diff --git a/src/nativeAssetManager.js b/src/nativeAssetManager.js index 7be8667f..0de13aac 100644 --- a/src/nativeAssetManager.js +++ b/src/nativeAssetManager.js @@ -27,9 +27,15 @@ const NATIVE_KEYS = { salePrice: 'hb_native_saleprice', }; +const NATIVE_CLASS_IDS = { + icon: 'pb-icon', + image: 'pb-image' +}; + export function newNativeAssetManager(win) { let callback; let errorCountEscapeHatch = 0; + let tokenType = 'default'; /* * Entry point to search for placeholderes and set up postmessage roundtrip @@ -51,10 +57,30 @@ export function newNativeAssetManager(win) { function scanForPlaceholders(adId) { let placeholders = []; - Object.keys(NATIVE_KEYS).forEach(key => { + Object.keys(NATIVE_KEYS).forEach(function(key) { const placeholderKey = NATIVE_KEYS[key]; - const placeholder = `${placeholderKey}:${adId}`; - const placeholderIndex = win.document.body.innerHTML.indexOf(placeholder); + const sendIdPlaceholder = `${placeholderKey}:${adId}`; + const hardKeyPlaceholder = `%%${placeholderKey}%%`; + + let placeholderIndex = -1; + let tokensToCheck = [sendIdPlaceholder]; + + if (placeholderKey === NATIVE_KEYS.image) { + tokensToCheck.push('pb-image'); + } else if (placeholderKey === NATIVE_KEYS.icon) { + tokensToCheck.push('pb-icon'); + } else { + tokensToCheck.push(hardKeyPlaceholder); + } + + tokensToCheck.forEach(function(token) { + if (win.document.body.innerHTML.indexOf(token) !== -1) { + placeholderIndex = win.document.body.innerHTML.indexOf(token); + if (tokenType === 'default') { + tokenType = (token === sendIdPlaceholder) ? 'sendId' : 'hardKey'; + } + } + }); if (~placeholderIndex) { placeholders.push(placeholderKey); @@ -85,7 +111,7 @@ export function newNativeAssetManager(win) { * Postmessage listener for when Prebid responds with requested native assets. */ function replaceAssets(event) { - var data = {}; + let data = {}; try { data = JSON.parse(event.data); @@ -105,6 +131,7 @@ export function newNativeAssetManager(win) { const newHtml = replace(body, data); win.document.body.innerHTML = newHtml; + if (tokenType === 'hardKey') insertImages(win.document.body, data); callback && callback(); win.removeEventListener('message', replaceAssets); } @@ -116,14 +143,27 @@ export function newNativeAssetManager(win) { */ function replace(document, { assets, adId }) { let html = document; - - (assets || []).forEach(asset => { - html = html.replace(`${NATIVE_KEYS[asset.key]}:${adId}`, asset.value); + (assets || []).forEach(function(asset) { + let tokenSyntax = (tokenType === 'sendId') ? `${NATIVE_KEYS[asset.key]}:${adId}` : `%%${NATIVE_KEYS[asset.key]}%%`; + html = html.replace(tokenSyntax, asset.value); }); return html; } + /** + * Adds the src attribute to specific img tags identified by the class name. + * The value for the added src is dervied from either the native.icon or native.image bid assets. + */ + function insertImages(document, { assets }) { + (assets || []).forEach(function(asset) { + if (asset.key === 'icon' || asset.key === 'image') { + let imageElement = document.getElementsByClassName(NATIVE_CLASS_IDS[asset.key]); + imageElement[0].setAttribute('src', asset.value); + } + }); + } + return { loadAssets }; diff --git a/test/spec/nativeAssetManager_spec.js b/test/spec/nativeAssetManager_spec.js index 3207ae92..67d7ab80 100644 --- a/test/spec/nativeAssetManager_spec.js +++ b/test/spec/nativeAssetManager_spec.js @@ -25,14 +25,14 @@ function createResponder(assets) { }; } -describe('nativeTrackerManager', () => { +describe('nativeAssetManager', () => { let win; beforeEach(() => { win = merge(mocks.createFakeWindow(), mockDocument.getWindowObject()); }); - it('replaces native placeholders with their asset values', () => { + it('replaces native placeholders with their asset values while using sendId notation', () => { win.document.body.innerHTML = `

hb_native_title

hb_native_body:${AD_ID}

@@ -54,6 +54,39 @@ describe('nativeTrackerManager', () => { expect(win.document.body.innerHTML).to.include('

hb_native_title

'); }); + it('replaces native placeholders with their asset values while using hardKey notation', () => { + win.document.body.innerHTML = ` +

hb_native_title

+

%%hb_native_body%%

+ Click Here + + `; + win.document.body.getElementsByClassName = sinon.stub().returns([{ + setAttribute: function(property, val) { + let attribute = `class="pb-icon" ${property}="${val}"` + win.document.body.innerHTML = win.document.body.innerHTML.replace('class="pb-icon"', attribute); + } + }]); + win.addEventListener = createResponder([ + { key: 'body', value: 'new value' }, + { key: 'clickUrl', value: 'http://www.example.com' }, + { key: 'icon', value: 'http://my.fake.images/somewhere/lost/1x1.gif' } + ]); + + const nativeAssetManager = newNativeAssetManager(win); + nativeAssetManager.loadAssets(AD_ID); + + expect(win.document.body.innerHTML).to.include('

new value

'); + expect(win.document.body.innerHTML).to.include(` + Click Here + `); + expect(win.document.body.innerHTML).to.include(` + + `) + // title was not a requested asset so this should stay as is + expect(win.document.body.innerHTML).to.include('

hb_native_title

'); + }); + it('attaches and removes message listeners', () => { win.document.body.innerHTML = `

hb_native_title:${AD_ID}

`; win.addEventListener = createResponder();