Source code

Revision control

Copy as Markdown

Other Tools

/* 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 = "<body></body>";
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);
}
}