/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /** * Bug 1713685 - Shim Google Publisher Tags * * Many sites rely on googletag to place content or drive ad bidding, * and will experience major breakage if it is blocked. This shim provides * enough of the API's frame To mitigate much of that breakage. */ if (window.googletag?.apiReady === undefined) { const version = "2021050601"; const noopthisfn = function () { return this; }; const slots = new Map(); const slotsById = new Map(); const slotsPerPath = new Map(); const slotCreatives = new Map(); const usedCreatives = new Map(); const fetchedSlots = new Set(); const eventCallbacks = new Map(); const fireSlotEvent = (name, slot) => { return new Promise(resolve => { requestAnimationFrame(() => { const size = [0, 0]; for (const cb of eventCallbacks.get(name) || []) { cb({ isEmpty: true, size, slot }); } resolve(); }); }); }; const recreateIframeForSlot = slot => { const eid = `google_ads_iframe_${slot.getId()}`; document.getElementById(eid)?.remove(); const node = document.getElementById(slot.getSlotElementId()); if (node) { const f = document.createElement("iframe"); f.id = eid; f.srcdoc = ""; f.style = "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0"; f.setAttribute("width", 0); f.setAttribute("height", 0); node.appendChild(f); } }; const emptySlotElement = slot => { const node = document.getElementById(slot.getSlotElementId()); while (node?.lastChild) { node.lastChild.remove(); } }; const SizeMapping = class extends Array { getCreatives() { const { clientWidth, clientHeight } = document.documentElement; for (const [size, creatives] of this) { if (clientWidth >= size[0] && clientHeight >= size[1]) { return creatives; } } return []; } }; const fetchSlot = slot => { if (!slot) { return; } const id = slot.getSlotElementId(); const node = document.getElementById(id); if (!node) { return; } let creatives = slotCreatives.get(id); if (creatives instanceof SizeMapping) { creatives = creatives.getCreatives(); } if (!creatives?.length) { return; } for (const creative of creatives) { if (usedCreatives.has(creative)) { return; } } const creative = creatives[0]; usedCreatives.set(creative, slot); fetchedSlots.add(id); }; const displaySlot = async slot => { if (!slot) { return; } const id = slot.getSlotElementId(); if (!document.getElementById(id)) { return; } if (!fetchedSlots.has(id)) { fetchSlot(slot); } const parent = document.getElementById(id); if (parent) { parent.appendChild(document.createElement("div")); } emptySlotElement(slot); recreateIframeForSlot(slot); await fireSlotEvent("slotRenderEnded", slot); await fireSlotEvent("slotRequested", slot); await fireSlotEvent("slotResponseReceived", slot); await fireSlotEvent("slotOnload", slot); await fireSlotEvent("impressionViewable", slot); }; const addEventListener = function (name, listener) { if (!eventCallbacks.has(name)) { eventCallbacks.set(name, new Set()); } eventCallbacks.get(name).add(listener); return this; }; const removeEventListener = function (name, listener) { if (eventCallbacks.has(name)) { return eventCallbacks.get(name).delete(listener); } return false; }; const companionAdsService = { addEventListener, enable() {}, fillSlot() {}, getAttributeKeys: () => [], getDisplayAdsCorrelator: () => "", getName: () => "companion_ads", getSlotIdMap: () => { return {}; }, getSlots: () => [], getVideoStreamCorrelator() {}, isRoadblockingSupported: () => false, isSlotAPersistentRoadblock: () => false, notifyUnfilledSlots() {}, onImplementationLoaded() {}, refreshAllSlots() { for (const slot of slotsById.values()) { fetchSlot(slot); displaySlot(slot); } }, removeEventListener, set() {}, setRefreshUnfilledSlots() {}, setVideoSession() {}, slotRenderEnded() {}, }; const contentService = { addEventListener, setContent() {}, removeEventListener, }; const getTargetingValue = v => { if (typeof v === "string") { return [v]; } try { return [Array.prototype.flat.call(v)[0]]; } catch (_) {} return []; }; const updateTargeting = (targeting, map) => { if (typeof map === "object") { const entries = Object.entries(map || {}); for (const [k, v] of entries) { targeting.set(k, getTargetingValue(v)); } } }; const defineSlot = (adUnitPath, creatives, opt_div) => { if (slotsById.has(opt_div)) { document.getElementById(opt_div)?.remove(); return slotsById.get(opt_div); } const attributes = new Map(); const targeting = new Map(); const exclusions = new Set(); const response = { advertiserId: undefined, campaignId: undefined, creativeId: undefined, creativeTemplateId: undefined, lineItemId: undefined, }; const sizes = [ { getHeight: () => 2, getWidth: () => 2, }, ]; const num = (slotsPerPath.get(adUnitPath) || 0) + 1; slotsPerPath.set(adUnitPath, num); const id = `${adUnitPath}_${num}`; let clickUrl = ""; let collapseEmptyDiv = null; let services = new Set(); const slot = { addService(e) { services.add(e); return slot; }, clearCategoryExclusions: noopthisfn, clearTargeting(k) { if (k === undefined) { targeting.clear(); } else { targeting.delete(k); } }, defineSizeMapping(mapping) { slotCreatives.set(opt_div, mapping); return this; }, get: k => attributes.get(k), getAdUnitPath: () => adUnitPath, getAttributeKeys: () => Array.from(attributes.keys()), getCategoryExclusions: () => Array.from(exclusions), getClickUrl: () => clickUrl, getCollapseEmptyDiv: () => collapseEmptyDiv, getContentUrl: () => "", getDivStartsCollapsed: () => null, getDomId: () => opt_div, getEscapedQemQueryId: () => "", getFirstLook: () => 0, getId: () => id, getHtml: () => "", getName: () => id, getOutOfPage: () => false, getResponseInformation: () => response, getServices: () => Array.from(services), getSizes: () => sizes, getSlotElementId: () => opt_div, getSlotId: () => slot, getTargeting: k => targeting.get(k) || gTargeting.get(k) || [], getTargetingKeys: () => Array.from( new Set(Array.of(...gTargeting.keys(), ...targeting.keys())) ), getTargetingMap: () => Object.assign( Object.fromEntries(gTargeting.entries()), Object.fromEntries(targeting.entries()) ), set(k, v) { attributes.set(k, v); return slot; }, setCategoryExclusion(e) { exclusions.add(e); return slot; }, setClickUrl(u) { clickUrl = u; return slot; }, setCollapseEmptyDiv(v) { collapseEmptyDiv = !!v; return slot; }, setSafeFrameConfig: noopthisfn, setTagForChildDirectedTreatment: noopthisfn, setTargeting(k, v) { targeting.set(k, getTargetingValue(v)); return slot; }, toString: () => id, updateTargetingFromMap(map) { updateTargeting(targeting, map); return slot; }, }; slots.set(adUnitPath, slot); slotsById.set(opt_div, slot); slotCreatives.set(opt_div, creatives); return slot; }; let initialLoadDisabled = false; const gTargeting = new Map(); const gAttributes = new Map(); let imaContent = { vid: "", cmsid: "" }; let videoContent = { vid: "", cmsid: "" }; const pubadsService = { addEventListener, clear() {}, clearCategoryExclusions: noopthisfn, clearTagForChildDirectedTreatment: noopthisfn, clearTargeting(k) { if (k === undefined) { gTargeting.clear(); } else { gTargeting.delete(k); } }, collapseEmptyDivs() {}, defineOutOfPagePassback: (a, o) => defineSlot(a, 0, o), definePassback: (a, s, o) => defineSlot(a, s, o), disableInitialLoad() { initialLoadDisabled = true; return this; }, display(adUnitPath, sizes, opt_div) { const slot = defineSlot(adUnitPath, sizes, opt_div); displaySlot(slot); }, enable() {}, enableAsyncRendering() {}, enableLazyLoad() {}, enableSingleRequest() {}, enableSyncRendering() {}, enableVideoAds() {}, forceExperiment() {}, get: k => gAttributes.get(k), getAttributeKeys: () => Array.from(gAttributes.keys()), getCorrelator() {}, getImaContent: () => imaContent, getName: () => "publisher_ads", getSlots: () => Array.from(slots.values()), getSlotIdMap() { const map = {}; slots.values().forEach(s => { map[s.getId()] = s; }); return map; }, getTagSessionCorrelator() {}, getTargeting: k => gTargeting.get(k) || [], getTargetingKeys: () => Array.from(gTargeting.keys()), getTargetingMap: () => Object.fromEntries(gTargeting.entries()), getVersion: () => version, getVideoContent: () => videoContent, isInitialLoadDisabled: () => initialLoadDisabled, isSRA: () => false, markAsAmp() {}, refresh(slts) { if (!slts) { slts = slots.values(); } else if (!Array.isArray(slts)) { slts = [slts]; } for (const slot of slts) { if (slot) { try { fetchSlot(slot); displaySlot(slot); } catch (e) { console.error(e); } } } }, removeEventListener, set(k, v) { gAttributes[k] = v; return this; }, setCategoryExclusion: noopthisfn, setCentering() {}, setCookieOptions: noopthisfn, setCorrelator: noopthisfn, setForceSafeFrame: noopthisfn, setImaContent(vid, cmsid) { imaContent = { vid, cmsid }; return this; }, setLocation: noopthisfn, setPrivacySettings: noopthisfn, setPublisherProvidedId: noopthisfn, setRequestNonPersonalizedAds: noopthisfn, setSafeFrameConfig: noopthisfn, setTagForChildDirectedTreatment: noopthisfn, setTagForUnderAgeOfConsent: noopthisfn, setTargeting(k, v) { gTargeting.set(k, getTargetingValue(v)); return this; }, setVideoContent(vid, cmsid) { videoContent = { vid, cmsid }; return this; }, updateCorrelator() {}, updateTargetingFromMap(map) { updateTargeting(gTargeting, map); return this; }, }; const SizeMappingBuilder = class { #mapping; constructor() { this.#mapping = new SizeMapping(); } addSize(size, creatives) { if ( size !== "fluid" && (!Array.isArray(size) || isNaN(size[0]) || isNaN(size[1])) ) { this.#mapping = null; } else { this.#mapping?.push([size, creatives]); } return this; } build() { return this.#mapping; } }; let gt = window.googletag; if (!gt) { gt = window.googletag = {}; } Object.assign(gt, { apiReady: true, companionAds: () => companionAdsService, content: () => contentService, defineOutOfPageSlot: (a, o) => defineSlot(a, 0, o), defineSlot: (a, s, o) => defineSlot(a, s, o), destroySlots() { slots.clear(); slotsById.clear(); }, disablePublisherConsole() {}, display(arg) { let id; if (arg?.getSlotElementId) { id = arg.getSlotElementId(); } else if (arg?.nodeType) { id = arg.id; } else { id = String(arg); } displaySlot(slotsById.get(id)); }, enableServices() {}, enums: { OutOfPageFormat: { BOTTOM_ANCHOR: 3, INTERSTITIAL: 5, REWARDED: 4, TOP_ANCHOR: 2, }, }, getVersion: () => version, pubads: () => pubadsService, pubadsReady: true, setAdIframeTitle() {}, sizeMapping: () => new SizeMappingBuilder(), }); const run = function (fn) { if (typeof fn === "function") { try { fn.call(window); } catch (e) { console.error(e); } } }; const cmds = gt.cmd || []; const newCmd = []; newCmd.push = run; gt.cmd = newCmd; for (const cmd of cmds) { run(cmd); } }