(ZPK !<4 /1about-compat/aboutPage.jsPK ! injections/css/bug0000000-testbed-css-injection.cssPK !Ginjections/css/bug1829949-tomshardware.com-scrollbar-width.cssPK !< C9Jinjections/css/bug1829952-eventer.co.il-button-height.cssPK !injections/js/bug1731825-office365-email-handling-prompt-autohide.jsPK ! { if (AppConstants.NIGHTLY_BUILD) { return "nightly"; } else if (AppConstants.MOZ_DEV_EDITION) { return "dev_edition"; } else if (AppConstants.EARLY_BETA_OR_EARLIER) { return "early_beta_or_earlier"; } else if (AppConstants.RELEASE_OR_BETA) { return "release_or_beta"; } return "unknown"; }, }, }; } }; PK !< -77#experiment-apis/aboutConfigPrefs.js/* 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"; /* global ExtensionAPI, ExtensionCommon, Services, XPCOMUtils */ this.aboutConfigPrefs = class extends ExtensionAPI { getAPI(context) { const EventManager = ExtensionCommon.EventManager; const extensionIDBase = context.extension.id.split("@")[0]; const extensionPrefNameBase = `extensions.${extensionIDBase}.`; return { aboutConfigPrefs: { onPrefChange: new EventManager({ context, name: "aboutConfigPrefs.onUAOverridesPrefChange", register: (fire, name) => { const prefName = `${extensionPrefNameBase}${name}`; const callback = () => { fire.async(name).catch(() => {}); // ignore Message Manager disconnects }; Services.prefs.addObserver(prefName, callback); return () => { Services.prefs.removeObserver(prefName, callback); }; }, }).api(), async getBranch(branchName) { const branch = `${extensionPrefNameBase}${branchName}.`; return Services.prefs.getChildList(branch).map(pref => { const name = pref.replace(branch, ""); return { name, value: Services.prefs.getBoolPref(pref) }; }); }, async getPref(name) { try { return Services.prefs.getBoolPref( `${extensionPrefNameBase}${name}` ); } catch (_) { return undefined; } }, async setPref(name, value) { Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value); }, }, }; } }; PK !<9!!%experiment-apis/trackingProtection.js/* 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"; /* global ExtensionAPI, ExtensionCommon, ExtensionParent, Services, XPCOMUtils */ // eslint-disable-next-line mozilla/reject-importGlobalProperties XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]); class AllowList { constructor(id) { this._id = id; } setShims(patterns, notHosts) { this._shimPatterns = patterns; this._shimMatcher = new MatchPatternSet(patterns || []); this._shimNotHosts = notHosts || []; return this; } setAllows(patterns, hosts) { this._allowPatterns = patterns; this._allowMatcher = new MatchPatternSet(patterns || []); this._allowHosts = hosts || []; return this; } shims(url, topHost) { return ( this._shimMatcher?.matches(url) && !this._shimNotHosts?.includes(topHost) ); } allows(url, topHost) { return ( this._allowMatcher?.matches(url) && this._allowHosts?.includes(topHost) ); } } class Manager { constructor() { this._allowLists = new Map(); } _getAllowList(id) { if (!this._allowLists.has(id)) { this._allowLists.set(id, new AllowList(id)); } return this._allowLists.get(id); } _ensureStarted() { if (this._classifierObserver) { return; } this._unblockedChannelIds = new Set(); this._channelClassifier = Cc[ "@mozilla.org/url-classifier/channel-classifier-service;1" ].getService(Ci.nsIChannelClassifierService); this._classifierObserver = {}; this._classifierObserver.observe = (subject, topic, data) => { switch (topic) { case "http-on-stop-request": { const { channelId } = subject.QueryInterface(Ci.nsIIdentChannel); this._unblockedChannelIds.delete(channelId); break; } case "urlclassifier-before-block-channel": { const channel = subject.QueryInterface( Ci.nsIUrlClassifierBlockedChannel ); const { channelId, url } = channel; let topHost; try { topHost = new URL(channel.topLevelUrl).hostname; } catch (_) { return; } // If anti-tracking webcompat is disabled, we only permit replacing // channels, not fully unblocking them. if (Manager.ENABLE_WEBCOMPAT) { // if any allowlist unblocks the request entirely, we allow it for (const allowList of this._allowLists.values()) { if (allowList.allows(url, topHost)) { this._unblockedChannelIds.add(channelId); channel.allow(); return; } } } // otherwise, if any allowlist shims the request we say it's replaced for (const allowList of this._allowLists.values()) { if (allowList.shims(url, topHost)) { this._unblockedChannelIds.add(channelId); channel.replace(); return; } } break; } } }; Services.obs.addObserver(this._classifierObserver, "http-on-stop-request"); this._channelClassifier.addListener(this._classifierObserver); } stop() { if (!this._classifierObserver) { return; } Services.obs.removeObserver( this._classifierObserver, "http-on-stop-request" ); this._channelClassifier.removeListener(this._classifierObserver); delete this._channelClassifier; delete this._classifierObserver; } wasChannelIdUnblocked(channelId) { return this._unblockedChannelIds?.has(channelId); } allow(allowListId, patterns, hosts) { this._ensureStarted(); this._getAllowList(allowListId).setAllows(patterns, hosts); } shim(allowListId, patterns, notHosts) { this._ensureStarted(); this._getAllowList(allowListId).setShims(patterns, notHosts); } revoke(allowListId) { this._allowLists.delete(allowListId); } } var manager = new Manager(); function getChannelId(context, requestId) { const wrapper = ChannelWrapper.getRegisteredChannel( requestId, context.extension.policy, context.xulBrowser.frameLoader.remoteTab ); return wrapper?.channel?.QueryInterface(Ci.nsIIdentChannel)?.channelId; } var dFPIPrefName = "network.cookie.cookieBehavior"; var dFPIPbPrefName = "network.cookie.cookieBehavior.pbmode"; var dFPIStatus; function updateDFPIStatus() { dFPIStatus = { nonPbMode: 5 == Services.prefs.getIntPref(dFPIPrefName), pbMode: 5 == Services.prefs.getIntPref(dFPIPbPrefName), }; } this.trackingProtection = class extends ExtensionAPI { onShutdown(isAppShutdown) { if (manager) { manager.stop(); } Services.prefs.removeObserver(dFPIPrefName, updateDFPIStatus); Services.prefs.removeObserver(dFPIPbPrefName, updateDFPIStatus); } getAPI(context) { Services.prefs.addObserver(dFPIPrefName, updateDFPIStatus); Services.prefs.addObserver(dFPIPbPrefName, updateDFPIStatus); updateDFPIStatus(); return { trackingProtection: { async shim(allowListId, patterns, notHosts) { manager.shim(allowListId, patterns, notHosts); }, async allow(allowListId, patterns, hosts) { manager.allow(allowListId, patterns, hosts); }, async revoke(allowListId) { manager.revoke(allowListId); }, async wasRequestUnblocked(requestId) { if (!manager) { return false; } const channelId = getChannelId(context, requestId); if (!channelId) { return false; } return manager.wasChannelIdUnblocked(channelId); }, async isDFPIActive(isPrivate) { if (isPrivate) { return dFPIStatus.pbMode; } return dFPIStatus.nonPbMode; }, }, }; } }; XPCOMUtils.defineLazyPreferenceGetter( Manager, "ENABLE_WEBCOMPAT", "privacy.antitracking.enableWebcompat", false ); PK ! button { float: inline-end; } } /* Mobile UI where common.css is not loaded */ @media (any-pointer: coarse), (any-pointer: none) { * { margin: 0; padding: 0; } :root { --background-color: #fff; --text-color: #0c0c0d; --border-color: #e1e1e2; --button-background-color: #f5f5f5; --selected-tab-text-color: #0061e0; } @media (prefers-color-scheme: dark) { :root { --background-color: #292833; --text-color: #f9f9fa; --border-color: rgba(255, 255, 255, 0.15); --button-background-color: rgba(0, 0, 0, 0.15); --selected-tab-text-color: #00ddff; } } body { background-color: var(--background-color); color: var(--text-color); font: message-box; font-size: 14px; -moz-text-size-adjust: none; display: grid; grid-template-areas: "a b c" "d d d"; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: fit-content(100%) 1fr; } .tab[data-l10n-id="label-overrides"] { grid-area: a; } .tab[data-l10n-id="label-interventions"] { grid-area: b; } .tab[data-l10n-id="label-smartblock"] { grid-area: c; } table { grid-area: d; } table, tr, p { display: block; } table { border-top: 2px solid var(--border-color); margin-top: -2px; width: 100%; z-index: 1; display: none; } tr { border-bottom: 1px solid var(--border-color); padding: 0; } a { color: inherit; font-size: 94%; } .tab { cursor: pointer; z-index: 2; display: inline-block; text-align: left; border-block: 2px solid transparent; font-size: 1em; font-weight: bold; padding: 1em; } .tab.active { color: var(--selected-tab-text-color); border-bottom-color: currentColor; margin-bottom: 0; padding-bottom: calc(1em + 2px); } .tab.active + table { display: block; } td { grid-area: b; padding-left: 1em; } td:first-child { grid-area: a; padding-top: 1em; } td:last-child { grid-area: c; padding-bottom: 1em; } tr { display: grid; grid-template-areas: "a c" "b c"; grid-template-columns: 1fr 6.5em; } td[colspan="4"] { padding: 1em; font-style: italic; text-align: center; } td:not([colspan]):nth-child(1) { font-weight: bold; padding-bottom: 0.25em; } td:nth-child(2) { padding-bottom: 1em; } td:nth-child(3) { display: flex; padding: 0; } button { cursor: pointer; width: 100%; height: 100%; background: var(--button-background-color); color: inherit; inset-inline-end: 0; margin: 0; padding: 0; border: 0; border-inline-start: 1px solid var(--border-color); font-weight: 600; appearance: none; } button::-moz-focus-inner { border: 0; } } PK !

PK !<2about-compat/aboutCompat.js/* 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"; /* globals browser */ let availablePatches; const portToAddon = (function () { let port; function connect() { port = browser.runtime.connect({ name: "AboutCompatTab" }); port.onMessage.addListener(onMessageFromAddon); port.onDisconnect.addListener(e => { port = undefined; }); } connect(); async function send(message) { if (port) { return port.postMessage(message); } return Promise.reject("background script port disconnected"); } return { send }; })(); const $ = function (sel) { return document.querySelector(sel); }; const DOMContentLoadedPromise = new Promise(resolve => { document.addEventListener( "DOMContentLoaded", () => { resolve(); }, { once: true } ); }); Promise.all([ browser.runtime.sendMessage("getAllInterventions"), DOMContentLoadedPromise, ]).then(([info]) => { document.body.addEventListener("click", async evt => { const ele = evt.target; if (ele.nodeName === "BUTTON") { const row = ele.closest("[data-id]"); if (row) { evt.preventDefault(); ele.disabled = true; const id = row.getAttribute("data-id"); try { await browser.runtime.sendMessage({ command: "toggle", id }); } catch (_) { ele.disabled = false; } } } else if (ele.classList.contains("tab")) { document.querySelectorAll(".tab").forEach(tab => { tab.classList.remove("active"); }); ele.classList.add("active"); } }); availablePatches = info; redraw(); }); function onMessageFromAddon(msg) { const alsoShowHidden = location.hash === "#all"; if ("interventionsChanged" in msg) { redrawTable($("#interventions"), msg.interventionsChanged, alsoShowHidden); } if ("overridesChanged" in msg) { redrawTable($("#overrides"), msg.overridesChanged, alsoShowHidden); } if ("shimsChanged" in msg) { updateShimTables(msg.shimsChanged, alsoShowHidden); } const id = msg.toggling || msg.toggled; const button = $(`[data-id="${id}"] button`); if (!button) { return; } const active = msg.active; document.l10n.setAttributes( button, active ? "label-disable" : "label-enable" ); button.disabled = !!msg.toggling; } function redraw() { if (!availablePatches) { return; } const { overrides, interventions, shims } = availablePatches; const alsoShowHidden = location.hash === "#all"; redrawTable($("#overrides"), overrides, alsoShowHidden); redrawTable($("#interventions"), interventions, alsoShowHidden); updateShimTables(shims, alsoShowHidden); } function clearTableAndAddMessage(table, msgId) { table.querySelectorAll("tr").forEach(tr => { tr.remove(); }); const tr = document.createElement("tr"); tr.className = "message"; tr.id = msgId; const td = document.createElement("td"); td.setAttribute("colspan", "3"); document.l10n.setAttributes(td, msgId); tr.appendChild(td); table.appendChild(tr); } function hideMessagesOnTable(table) { table.querySelectorAll("tr.message").forEach(tr => { tr.remove(); }); } function updateShimTables(shimsChanged, alsoShowHidden) { const tables = document.querySelectorAll("table.shims"); if (!tables.length) { return; } for (const { bug, disabledReason, hidden, id, name, type } of shimsChanged) { // if any shim is disabled by global pref, all of them are. just show the // "disabled in about:config" message on each shim table in that case. if (disabledReason === "globalPref") { for (const table of tables) { clearTableAndAddMessage(table, "text-disabled-in-about-config"); } return; } // otherwise, find which table the shim belongs in. if there is none, // ignore the shim (we're not showing it on the UI for whatever reason). const table = document.querySelector(`table.shims#${type}`); if (!table) { continue; } // similarly, skip shims hidden from the UI (only for testing, etc). if (!alsoShowHidden && hidden) { continue; } // also, hide the shim if it is disabled because it is not meant for this // platform, release (etc) rather than being disabled by pref/about:compat const notApplicable = disabledReason && disabledReason !== "pref" && disabledReason !== "session"; if (!alsoShowHidden && notApplicable) { continue; } // create an updated table-row for the shim const tr = document.createElement("tr"); tr.setAttribute("data-id", id); let td = document.createElement("td"); td.innerText = name; tr.appendChild(td); td = document.createElement("td"); const a = document.createElement("a"); a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`; document.l10n.setAttributes(a, "label-more-information", { bug }); a.target = "_blank"; td.appendChild(a); tr.appendChild(td); td = document.createElement("td"); tr.appendChild(td); const button = document.createElement("button"); document.l10n.setAttributes( button, disabledReason ? "label-enable" : "label-disable" ); td.appendChild(button); // is it already in the table? const row = table.querySelector(`tr[data-id="${id}"]`); if (row) { row.replaceWith(tr); } else { table.appendChild(tr); } } for (const table of tables) { if (!table.querySelector("tr:not(.message)")) { // no shims? then add a message that none are available for this platform/config clearTableAndAddMessage(table, `text-no-${table.id}`); } else { // otherwise hide any such message, since we have shims on the list hideMessagesOnTable(table); } } } function redrawTable(table, data, alsoShowHidden) { const df = document.createDocumentFragment(); table.querySelectorAll("tr").forEach(tr => { tr.remove(); }); let noEntriesMessage; if (data === false) { noEntriesMessage = "text-disabled-in-about-config"; } else if (data.length === 0) { noEntriesMessage = `text-no-${table.id}`; } if (noEntriesMessage) { const tr = document.createElement("tr"); df.appendChild(tr); const td = document.createElement("td"); td.setAttribute("colspan", "3"); document.l10n.setAttributes(td, noEntriesMessage); tr.appendChild(td); table.appendChild(df); return; } for (const row of data) { if (row.hidden && !alsoShowHidden) { continue; } const tr = document.createElement("tr"); tr.setAttribute("data-id", row.id); df.appendChild(tr); let td = document.createElement("td"); td.innerText = row.domain; tr.appendChild(td); td = document.createElement("td"); const a = document.createElement("a"); const bug = row.bug; a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`; document.l10n.setAttributes(a, "label-more-information", { bug }); a.target = "_blank"; td.appendChild(a); tr.appendChild(td); td = document.createElement("td"); tr.appendChild(td); const button = document.createElement("button"); document.l10n.setAttributes( button, row.active ? "label-disable" : "label-enable" ); td.appendChild(button); } table.appendChild(df); } window.onhashchange = redraw; PK ! { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 for WebCompat" ); }, }, }, { /* * Bug 1577519 - directv.com - Create a UA override for directv.com for playback on desktop * WebCompat issue #3846 - https://webcompat.com/issues/3846 * * directv.com (attwatchtv.com) is blocking Firefox via UA sniffing. Spoofing as Chrome allows * to access the site and playback works fine. This is former directvnow.com */ id: "bug1577519", platform: "desktop", domain: "directv.com", bug: "1577519", config: { matches: [ "*://*.attwatchtv.com/*", "*://*.directv.com.ec/*", // bug 1827706 "*://*.directv.com/*", ], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" ); }, }, }, { /* * Bug 1570108 - steamcommunity.com - UA override for steamcommunity.com * WebCompat issue #34171 - https://webcompat.com/issues/34171 * * steamcommunity.com blocks chat feature for Firefox users showing unsupported browser message. * When spoofing as Chrome the chat works fine */ id: "bug1570108", platform: "desktop", domain: "steamcommunity.com", bug: "1570108", config: { matches: ["*://steamcommunity.com/chat*"], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" ); }, }, }, { /* * Bug 1582582 - sling.com - UA override for sling.com * WebCompat issue #17804 - https://webcompat.com/issues/17804 * * sling.com blocks Firefox users showing unsupported browser message. * When spoofing as Chrome playing content works fine */ id: "bug1582582", platform: "desktop", domain: "sling.com", bug: "1582582", config: { matches: ["https://watch.sling.com/*", "https://www.sling.com/*"], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" ); }, }, }, { /* * Bug 1610026 - www.mobilesuica.com - UA override for www.mobilesuica.com * WebCompat issue #4608 - https://webcompat.com/issues/4608 * * mobilesuica.com showing unsupported message for Firefox users * Spoofing as Chrome allows to access the page */ id: "bug1610026", platform: "all", domain: "www.mobilesuica.com", bug: "1610026", config: { matches: ["https://www.mobilesuica.com/*"], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" ); }, }, }, { /* * Bug 1385206 - Create UA override for rakuten.co.jp on Firefox Android * (Imported from ua-update.json.in) * * rakuten.co.jp serves a Desktop version if Firefox is included in the UA. */ id: "bug1385206", platform: "android", domain: "rakuten.co.jp", bug: "1385206", config: { matches: ["*://*.rakuten.co.jp/*"], uaTransformer: originalUA => { return originalUA.replace(/Firefox.+$/, ""); }, }, }, { /* * Bug 969844 - mobile.de sends desktop site to Firefox on Android * * mobile.de sends the desktop site to Firefox Mobile. * Spoofing as Chrome works fine. */ id: "bug969844", platform: "android", domain: "mobile.de", bug: "969844", config: { matches: ["*://*.mobile.de/*"], uaTransformer: _ => { return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"; }, }, }, { /* * Bug 1509873 - zmags.com - Add UA override for secure.viewer.zmags.com * WebCompat issue #21576 - https://webcompat.com/issues/21576 * * The zmags viewer locks out Firefox Mobile with a "Browser unsupported" * message, but tests showed that it works just fine with a Chrome UA. * Outreach attempts were unsuccessful, and as the site has a relatively * high rank, we alter the UA. */ id: "bug1509873", platform: "android", domain: "zmags.com", bug: "1509873", config: { matches: ["*://*.viewer.zmags.com/*"], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36" ); }, }, }, { /* * Bug 1574522 - UA override for enuri.com on Firefox for Android * WebCompat issue #37139 - https://webcompat.com/issues/37139 * * enuri.com returns a different template for Firefox on Android * based on server side UA detection. This results in page content cut offs. * Spoofing as Chrome fixes the issue */ id: "bug1574522", platform: "android", domain: "enuri.com", bug: "1574522", config: { matches: ["*://enuri.com/*"], uaTransformer: _ => { return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"; }, }, }, { /* * Bug 1574564 - UA override for ceskatelevize.cz on Firefox for Android * WebCompat issue #15467 - https://webcompat.com/issues/15467 * * ceskatelevize sets streamingProtocol depending on the User-Agent it sees * in the request headers, returning DASH for Chrome, HLS for iOS, * and Flash for Firefox Mobile. Since Mobile has no Flash, the video * doesn't work. Spoofing as Chrome makes the video play */ id: "bug1574564", platform: "android", domain: "ceskatelevize.cz", bug: "1574564", config: { matches: ["*://*.ceskatelevize.cz/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1577267 - UA override for metfone.com.kh on Firefox for Android * WebCompat issue #16363 - https://webcompat.com/issues/16363 * * metfone.com.kh has a server side UA detection which returns desktop site * for Firefox for Android. Spoofing as Chrome allows to receive mobile version */ id: "bug1577267", platform: "android", domain: "metfone.com.kh", bug: "1577267", config: { matches: ["*://*.metfone.com.kh/*"], uaTransformer: originalUA => { return ( UAHelpers.getPrefix(originalUA) + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36" ); }, }, }, { /* * Bug 1598198 - User Agent extension for Samsung's galaxy.store URLs * * Samsung's galaxy.store shortlinks are supposed to redirect to a Samsung * intent:// URL on Samsung devices, but to an error page on other brands. * As we do not provide device info in our user agent string, this check * fails, and even Samsung users land on an error page if they use Firefox * for Android. * This intervention adds a simple "Samsung" identifier to the User Agent * on only the Galaxy Store URLs if the device happens to be a Samsung. */ id: "bug1598198", platform: "android", domain: "galaxy.store", bug: "1598198", config: { matches: [ "*://galaxy.store/*", "*://dev.galaxy.store/*", "*://stg.galaxy.store/*", ], uaTransformer: originalUA => { if (!browser.systemManufacturer) { return originalUA; } const manufacturer = browser.systemManufacturer.getManufacturer(); if (manufacturer && manufacturer.toLowerCase() === "samsung") { return originalUA.replace("Mobile;", "Mobile; Samsung;"); } return originalUA; }, }, }, { /* * Bug 1595215 - UA overrides for Uniqlo sites * Webcompat issue #38825 - https://webcompat.com/issues/38825 * * To receive the proper mobile version instead of the desktop version or * avoid redirect loop, the UA is spoofed. */ id: "bug1595215", platform: "android", domain: "uniqlo.com", bug: "1595215", config: { matches: ["*://*.uniqlo.com/*"], uaTransformer: originalUA => { return originalUA + " Mobile Safari"; }, }, }, { /* * Bug 1622063 - UA override for wp1-ext.usps.gov * Webcompat issue #29867 - https://webcompat.com/issues/29867 * * The Job Search site for USPS does not work for Firefox Mobile * browsers (a 500 is returned). */ id: "bug1622063", platform: "android", domain: "wp1-ext.usps.gov", bug: "1622063", config: { matches: ["*://wp1-ext.usps.gov/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1697324 - Update the override for mobile2.bmo.com * Previously Bug 1622081 - UA override for mobile2.bmo.com * Webcompat issue #45019 - https://webcompat.com/issues/45019 * * Unless the UA string contains "Chrome", mobile2.bmo.com will * display a modal saying the browser is out-of-date. */ id: "bug1697324", platform: "android", domain: "mobile2.bmo.com", bug: "1697324", config: { matches: ["*://mobile2.bmo.com/*"], uaTransformer: originalUA => { return originalUA + " Chrome"; }, }, }, { /* * Bug 1628455 - UA override for autotrader.ca * Webcompat issue #50961 - https://webcompat.com/issues/50961 * * autotrader.ca is showing desktop site for Firefox on Android * based on server side UA detection. Spoofing as Chrome allows to * get mobile experience */ id: "bug1628455", platform: "android", domain: "autotrader.ca", bug: "1628455", config: { matches: ["https://*.autotrader.ca/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1646791 - bancosantander.es - Re-add UA override. * Bug 1665129 - *.gruposantander.es - Add wildcard domains. * WebCompat issue #33462 - https://webcompat.com/issues/33462 * SuMo request - https://support.mozilla.org/es/questions/1291085 * * santanderbank expects UA to have 'like Gecko', otherwise it runs * xmlDoc.onload whose support has been dropped. It results in missing labels in forms * and some other issues. Adding 'like Gecko' fixes those issues. */ id: "bug1646791", platform: "all", domain: "santanderbank.com", bug: "1646791", config: { matches: [ "*://*.bancosantander.es/*", "*://*.gruposantander.es/*", "*://*.santander.co.uk/*", ], uaTransformer: originalUA => { // The first line related to Firefox 100 is for Bug 1743445. // [TODO]: Remove when bug 1743429 gets backed out. return UAHelpers.capVersionTo99(originalUA).replace( "Gecko", "like Gecko" ); }, }, }, { /* * Bug 1651292 - UA override for www.jp.square-enix.com * Webcompat issue #53018 - https://webcompat.com/issues/53018 * * Unless the UA string contains "Chrome 66+", a section of * www.jp.square-enix.com will show a never ending LOADING * page. */ id: "bug1651292", platform: "android", domain: "www.jp.square-enix.com", bug: "1651292", config: { matches: ["*://www.jp.square-enix.com/music/sem/page/FF7R/ost/*"], uaTransformer: originalUA => { return originalUA + " Chrome/83"; }, }, }, { /* * Bug 1666754 - Mobile UA override for lffl.org * Bug 1665720 - lffl.org article page takes 2x as much time to load on Moto G * * This site returns desktop site based on server side UA detection. * Spoofing as Chrome allows to get mobile experience */ id: "bug1666754", platform: "android", domain: "lffl.org", bug: "1666754", config: { matches: ["*://*.lffl.org/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1704673 - Add UA override for app.xiaomi.com * Webcompat issue #66163 - https://webcompat.com/issues/66163 * * The page isn’t redirecting properly error message received. * Spoofing as Chrome makes the page load */ id: "bug1704673", platform: "android", domain: "app.xiaomi.com", bug: "1704673", config: { matches: ["*://app.xiaomi.com/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1712807 - Add UA override for www.dealnews.com * Webcompat issue #39341 - https://webcompat.com/issues/39341 * * The sites shows Firefox a different layout compared to Chrome. * Spoofing as Chrome fixes this. */ id: "bug1712807", platform: "android", domain: "www.dealnews.com", bug: "1712807", config: { matches: ["*://www.dealnews.com/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1719859 - Add UA override for saxoinvestor.fr * Webcompat issue #74678 - https://webcompat.com/issues/74678 * * The site blocks Firefox with a server-side UA sniffer. Appending a * Chrome version segment to the UA makes it work. */ id: "bug1719859", platform: "all", domain: "saxoinvestor.fr", bug: "1719859", config: { matches: ["*://*.saxoinvestor.fr/*"], uaTransformer: originalUA => { return originalUA + " Chrome/91.0.4472.114"; }, }, }, { /* * Bug 1722954 - Add UA override for game.granbluefantasy.jp * Webcompat issue #34310 - https://github.com/webcompat/web-bugs/issues/34310 * * The website is sending a version of the site which is too small. Adding a partial * safari iOS version of the UA sends us the right layout. */ id: "bug1722954", platform: "android", domain: "granbluefantasy.jp", bug: "1722954", config: { matches: ["*://*.granbluefantasy.jp/*"], uaTransformer: originalUA => { return originalUA + " iPhone OS 12_0 like Mac OS X"; }, }, }, { /* * Bug 1738317 - Add UA override for vmos.cn * Webcompat issue #90432 - https://github.com/webcompat/web-bugs/issues/90432 * * Firefox for Android receives a desktop-only layout based on server-side * UA sniffing. Spoofing as Chrome works fine. */ id: "bug1738317", platform: "android", domain: "vmos.cn", bug: "1738317", config: { matches: ["*://*.vmos.cn/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1743627 - Add UA override for renaud-bray.com * Webcompat issue #55276 - https://github.com/webcompat/web-bugs/issues/55276 * * Firefox for Android depends on "Version/" being there in the UA string, * or it'll throw a runtime error. */ id: "bug1743627", platform: "android", domain: "renaud-bray.com", bug: "1743627", config: { matches: ["*://*.renaud-bray.com/*"], uaTransformer: originalUA => { return originalUA + " Version/0"; }, }, }, { /* * Bug 1743751 - Add UA override for slrclub.com * Webcompat issue #91373 - https://github.com/webcompat/web-bugs/issues/91373 * * On Firefox Android, the browser is receiving the desktop layout. * Spoofing as Chrome works fine. */ id: "bug1743751", platform: "android", domain: "slrclub.com", bug: "1743751", config: { matches: ["*://*.slrclub.com/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1743754 - Add UA override for slrclub.com * Webcompat issue #86839 - https://github.com/webcompat/web-bugs/issues/86839 * * On Firefox Android, the browser is failing a UA parsing on Firefox UA. */ id: "bug1743754", platform: "android", domain: "workflow.base.vn", bug: "1743754", config: { matches: ["*://workflow.base.vn/*"], uaTransformer: () => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1743429 - Add UA override for sites broken with the Version 100 User Agent * * Some sites have issues with a UA string with Firefox version 100 or higher, * so present as version 99 for now. */ id: "bug1743429", platform: "all", domain: "Sites with known Version 100 User Agent breakage", bug: "1743429", config: { matches: [ "*://411.ca/", // #121332 "*://*.commerzbank.de/*", // Bug 1767630 "*://*.mms.telekom.de/*", // #1800241 "*://ubank.com.au/*", // #104099 "*://wifi.sncf/*", // #100194 ], uaTransformer: originalUA => { return UAHelpers.capVersionTo99(originalUA); }, }, }, { /* * Bug 1753461 - UA override for serieson.naver.com * Webcompat issue #99993 - https://webcompat.com/issues/97298 * * The site locks out Firefox users unless a Chrome UA is given, * and locks out Linux users as well (so we use Windows+Chrome). */ id: "bug1753461", platform: "desktop", domain: "serieson.naver.com", bug: "1753461", config: { matches: ["*://serieson.naver.com/*"], uaTransformer: originalUA => { return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"; }, }, }, { /* * Bug 1756872 - UA override for www.dolcegabbana.com * Webcompat issue #99993 - https://webcompat.com/issues/99993 * * The site's layout is broken on Firefox for Android * without a full Chrome user-agent string. */ id: "bug1756872", platform: "android", domain: "www.dolcegabbana.com", bug: "1756872", config: { matches: ["*://www.dolcegabbana.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1771200 - UA override for animalplanet.com * Webcompat issue #99993 - https://webcompat.com/issues/103727 * * The videos are not playing and an error message is displayed * in Firefox for Android, but work with Chrome UA */ id: "bug1771200", platform: "android", domain: "animalplanet.com", bug: "1771200", config: { matches: ["*://*.animalplanet.com/video/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1771200 - UA override for lazada.co.id * Webcompat issue #106229 - https://webcompat.com/issues/106229 * * The map is not playing and an error message is displayed * in Firefox for Android, but work with Chrome UA */ id: "bug1779059", platform: "android", domain: "lazada.co.id", bug: "1779059", config: { matches: ["*://member-m.lazada.co.id/address/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1778168 - UA override for watch.antennaplus.gr * Webcompat issue #106529 - https://webcompat.com/issues/106529 * * The site's content is not loaded unless a Chrome UA is used, * and breaks on Linux (so we claim Windows instead in that case). */ id: "bug1778168", platform: "desktop", domain: "watch.antennaplus.gr", bug: "1778168", config: { matches: ["*://watch.antennaplus.gr/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA({ desktopOS: "nonLinux", }); }, }, }, { /* * Bug 1776897 - UA override for www.edencast.fr * Webcompat issue #106545 - https://webcompat.com/issues/106545 * * The site's podcast audio player does not load unless a Chrome UA is used. */ id: "bug1776897", platform: "all", domain: "www.edencast.fr", bug: "1776897", config: { matches: ["*://www.edencast.fr/zoomcast*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1784361 - UA override for coldwellbankerhomes.com * Webcompat issue #108535 - https://webcompat.com/issues/108535 * * An error is thrown due to missing element, unless Chrome UA is used */ id: "bug1784361", platform: "android", domain: "coldwellbankerhomes.com", bug: "1784361", config: { matches: ["*://*.coldwellbankerhomes.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1786404 - UA override for business.help.royalmail.com * Webcompat issue #109070 - https://webcompat.com/issues/109070 * * Replacing `Firefox` with `FireFox` to evade one of their UA tests... */ id: "bug1786404", platform: "all", domain: "business.help.royalmail.com", bug: "1786404", config: { matches: ["*://business.help.royalmail.com/app/webforms/*"], uaTransformer: originalUA => { return originalUA.replace("Firefox", "FireFox"); }, }, }, { /* * Bug 1790698 - UA override for wolf777.com * Webcompat issue #103981 - https://webcompat.com/issues/103981 * * Add 'Linux; ' next to the Android version or the site breaks */ id: "bug1790698", platform: "android", domain: "wolf777.com", bug: "1790698", config: { matches: ["*://wolf777.com/*"], uaTransformer: originalUA => { return originalUA.replace("Android", "Linux; Android"); }, }, }, { /* * Bug 1800936 - UA override for cov19ent.kdca.go.kr * Webcompat issue #110655 - https://webcompat.com/issues/110655 * * Add 'Chrome;' to the UA for the site to load styles */ id: "bug1800936", platform: "all", domain: "cov19ent.kdca.go.kr", bug: "1800936", config: { matches: ["*://cov19ent.kdca.go.kr/*"], uaTransformer: originalUA => { return originalUA + " Chrome"; }, }, }, { /* * Bug 1803131 - UA override for argaam.com * Webcompat issue #113638 - https://webcompat.com/issues/113638 * * To receive the proper mobile version instead of the desktop version * the UA is spoofed. */ id: "bug1803131", platform: "android", domain: "argaam.com", bug: "1803131", config: { matches: ["*://*.argaam.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1819702 - UA override for feelgoodcontacts.com * Webcompat issue #118030 - https://webcompat.com/issues/118030 * * Spoof the UA to receive the mobile version instead * of the broken desktop version for Android. */ id: "bug1819702", platform: "android", domain: "feelgoodcontacts.com", bug: "1819702", config: { matches: ["*://*.feelgoodcontacts.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1823966 - UA override for elearning.dmv.ca.gov * Original report: https://bugzilla.mozilla.org/show_bug.cgi?id=1823785 */ id: "bug1823966", platform: "all", domain: "elearning.dmv.ca.gov", bug: "1823966", config: { matches: ["*://*.elearning.dmv.ca.gov/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for admissions.nid.edu * Webcompat issue #65753 - https://webcompat.com/issues/65753 */ id: "bug1827678-webc65753", platform: "all", domain: "admissions.nid.edu", bug: "1827678", config: { matches: ["*://*.admissions.nid.edu/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for www.hepsiburada.com * Webcompat issue #66888 - https://webcompat.com/issues/66888 */ id: "bug1827678-webc66888", platform: "android", domain: "www.hepsiburada.com", bug: "1827678", config: { matches: ["*://www.hepsiburada.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for bankmandiri.co.id * Webcompat issue #67924 - https://webcompat.com/issues/67924 */ id: "bug1827678-webc67924", platform: "android", domain: "bankmandiri.co.id", bug: "1827678", config: { matches: ["*://*.bankmandiri.co.id/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for frankfred.com * Webcompat issue #68007 - https://webcompat.com/issues/68007 */ id: "bug1827678-webc68007", platform: "android", domain: "frankfred.com", bug: "1827678", config: { matches: ["*://*.frankfred.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for static.slots.lv * Webcompat issue #68379 - https://webcompat.com/issues/68379 */ id: "bug1827678-webc68379", platform: "android", domain: "static.slots.lv", bug: "1827678", config: { matches: ["*://static.slots.lv/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for mobile.onvue.com * Webcompat issue #68520 - https://webcompat.com/issues/68520 */ id: "bug1827678-webc68520", platform: "android", domain: "mobile.onvue.com", bug: "1827678", config: { matches: ["*://mobile.onvue.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for avizia.com * Webcompat issue #68635 - https://webcompat.com/issues/68635 */ id: "bug1827678-webc68635", platform: "all", domain: "avizia.com", bug: "1827678", config: { matches: ["*://*.avizia.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for www.yourtexasbenefits.com * Webcompat issue #76785 - https://webcompat.com/issues/76785 */ id: "bug1827678-webc76785", platform: "android", domain: "www.yourtexasbenefits.com", bug: "1827678", config: { matches: ["*://www.yourtexasbenefits.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for www.free4talk.com * Webcompat issue #77727 - https://webcompat.com/issues/77727 */ id: "bug1827678-webc77727", platform: "android", domain: "www.free4talk.com", bug: "1827678", config: { matches: ["*://www.free4talk.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for watch.indee.tv * Webcompat issue #77912 - https://webcompat.com/issues/77912 */ id: "bug1827678-webc77912", platform: "all", domain: "watch.indee.tv", bug: "1827678", config: { matches: ["*://watch.indee.tv/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for viewer-ebook.books.com.tw * Webcompat issue #80180 - https://webcompat.com/issues/80180 */ id: "bug1827678-webc80180", platform: "all", domain: "viewer-ebook.books.com.tw", bug: "1827678", config: { matches: ["*://viewer-ebook.books.com.tw/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for jelly.jd.com * Webcompat issue #83269 - https://webcompat.com/issues/83269 */ id: "bug1827678-webc83269", platform: "android", domain: "jelly.jd.com", bug: "1827678", config: { matches: ["*://jelly.jd.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for f2bbs.com * Webcompat issue #84932 - https://webcompat.com/issues/84932 */ id: "bug1827678-webc84932", platform: "android", domain: "f2bbs.com", bug: "1827678", config: { matches: ["*://f2bbs.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for kt.com * Webcompat issue #119012 - https://webcompat.com/issues/119012 */ id: "bug1827678-webc119012", platform: "all", domain: "kt.com", bug: "1827678", config: { matches: ["*://*.kt.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for oirsa.org * Webcompat issue #119402 - https://webcompat.com/issues/119402 */ id: "bug1827678-webc119402", platform: "all", domain: "oirsa.org", bug: "1827678", config: { matches: ["*://*.oirsa.org/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for sistema.ibglbrasil.com.br * Webcompat issue #119785 - https://webcompat.com/issues/119785 */ id: "bug1827678-webc119785", platform: "all", domain: "sistema.ibglbrasil.com.br", bug: "1827678", config: { matches: ["*://sistema.ibglbrasil.com.br/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1827678 - UA override for onp.cloud.waterloo.ca * Webcompat issue #120450 - https://webcompat.com/issues/120450 */ id: "bug1827678-webc120450", platform: "all", domain: "onp.cloud.waterloo.ca", bug: "1827678", config: { matches: ["*://onp.cloud.waterloo.ca/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1830739 - UA override for casino sites * * The sites are showing unsupported message with the same UI */ id: "bug1830739", platform: "android", domain: "casino sites", bug: "1830739", config: { matches: [ "*://*.captainjackcasino.com/*", // 79490 "*://*.casinoextreme.eu/*", // 118175 "*://*.cryptoloko.com/*", // 117911 "*://*.heapsowins.com/*", // 120027 "*://*.planet7casino.com/*", // 120609 "*://*.yebocasino.co.za/*", // 88409 ], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1830821 - UA override for m.tworld.co.kr * Webcompat issue #118998 - https://webcompat.com/issues/118998 */ id: "bug1830821-webc118998", platform: "android", domain: "m.tworld.co.kr", bug: "1830821", config: { matches: ["*://m.tworld.co.kr/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1830821 - UA override for webcartop.jp * Webcompat issue #113663 - https://webcompat.com/issues/113663 */ id: "bug1830821-webc113663", platform: "android", domain: "webcartop.jp", bug: "1830821", config: { matches: ["*://*.webcartop.jp/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1830821 - UA override for enjoy.point.auone.jp * Webcompat issue #90981 - https://webcompat.com/issues/90981 */ id: "bug1830821-webc90981", platform: "android", domain: "enjoy.point.auone.jp", bug: "1830821", config: { matches: ["*://enjoy.point.auone.jp/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1751604 - UA override for /www.otsuka.co.jp/fib/ * * The site's content is not loaded on mobile unless a Chrome UA is used. */ id: "bug1829126", platform: "android", domain: "www.otsuka.co.jp", bug: "1829126", config: { matches: ["*://www.otsuka.co.jp/fib/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1831441 - UA override for luna.amazon.com * * Games are unplayable unless a Chrome UA is used. */ id: "bug1831441", platform: "all", domain: "luna.amazon.com", bug: "1831441", config: { matches: ["*://luna.amazon.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1836109 - UA override for watch.tonton.com.my * * The site's content is not loaded unless a Chrome UA is used. */ id: "bug1836109", platform: "all", domain: "watch.tonton.com.my", bug: "1836109", config: { matches: ["*://watch.tonton.com.my/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1836112 - UA override for www.capcut.cn * * The site's content is not loaded unless a Chrome UA is used. */ id: "bug1836112", platform: "all", domain: "www.capcut.cn", bug: "1836112", config: { matches: ["*://www.capcut.cn/editor*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1836116 - UA override for www.slushy.com * * The site's content is not loaded without a Chrome UA spoof. */ id: "bug1836116", platform: "all", domain: "www.slushy.com", bug: "1836116", config: { matches: ["*://www.slushy.com/*"], uaTransformer: originalUA => { return originalUA + " Chrome/113.0.0.0"; }, }, }, { /* * Bug 1836135 - UA override for gts-pro.sdimedia.com * * The site's content is not loaded without a Chrome UA spoof. */ id: "bug1836135", platform: "all", domain: "gts-pro.sdimedia.com", bug: "1836135", config: { matches: ["*://gts-pro.sdimedia.com/*"], uaTransformer: originalUA => { return originalUA.replace("Firefox/", "Fx/") + " Chrome/113.0.0.0"; }, }, }, { /* * Bug 1836140 - UA override for indices.iriworldwide.com * * The site's content is not loaded without a UA spoof. */ id: "bug1836140", platform: "all", domain: "indices.iriworldwide.com", bug: "1836140", config: { matches: ["*://indices.iriworldwide.com/covid19/*"], uaTransformer: originalUA => { return originalUA.replace("Firefox/", "Fx/"); }, }, }, { /* * Bug 1836178 - UA override for atracker.pro * * The site's content is not loaded without a Chrome UA spoof. */ id: "bug1836178", platform: "all", domain: "atracker.pro", bug: "1836178", config: { matches: ["*://atracker.pro/*"], uaTransformer: originalUA => { return originalUA + " Chrome/113.0.0.0"; }, }, }, { /* * Bug 1836181 - UA override for conference.amwell.com * * The site's content is not loaded unless a Chrome UA is used. */ id: "bug1836181", platform: "all", domain: "conference.amwell.com", bug: "1836181", config: { matches: ["*://conference.amwell.com/*"], uaTransformer: originalUA => { return UAHelpers.getDeviceAppropriateChromeUA(); }, }, }, { /* * Bug 1836182 - UA override for www.flatsatshadowglen.com * * The site's content is not loaded without a Chrome UA spoof. */ id: "bug1836182", platform: "all", domain: "www.flatsatshadowglen.com", bug: "1836182", config: { matches: ["*://www.flatsatshadowglen.com/*"], uaTransformer: originalUA => { return originalUA + " Chrome/113.0.0.0"; }, }, }, ]; module.exports = AVAILABLE_UA_OVERRIDES; PK ! { return set.matches(url); }, }, context.cloneScope, { cloneFunctions: true, } ); }, }, }; } }; PK !injections/css/bug1829949-tomshardware.com-scrollbar-width.css/* 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/. */ /** * tomshardware.com - a scrollbar covering navigation menu * Bug #1829949 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829949 * WebCompat issue #121170 - https://github.com/webcompat/web-bugs/issues/121170 * * The scrollbar is covering navigation items and that makes them half hidden. * There are some ::-webkit-scrollbar css rules applied to the scrollbar, * making it thinner. Adding similar rules for Firefox fixes the issue. */ .trending__list { scrollbar-width: thin; scrollbar-color: #000 #f5f5f5; } PK !< C9injections/css/bug1829952-eventer.co.il-button-height.css/* 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/. */ /** * eventer.co.il - a button is covering entire page * Bug #1829952 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829952 * WebCompat issue #121296 - https://github.com/webcompat/web-bugs/issues/121296 * * The button is covering the page only in Firefox on mobile * because of additional styles applied via @-moz-document url-prefix. * Resetting the height makes the button normal size */ #purchasePageRedesignContainer .mobileStripButton { height: auto; min-height: auto; } PK ! * due to https://bugzilla.mozilla.org/show_bug.cgi?id=1461852. Setting the height to * fit-content makes it work as expected. */ #fixed-table tr td .cmp-summary-box, .cmpr-table .textpanel { height: fit-content; } PK !< _<injections/css/bug1830796-copyleaks.com-hide-unsupported.css/* 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/. */ /** * copyleaks.com - Unsupported message * Bug #1830796 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830796 * WebCompat issue #121395 - https://github.com/webcompat/web-bugs/issues/121395 */ #outdated { display: none !important; } PK !<3 |?injections/css/bug1830810-interceramic.com-hide-unsupported.css/* 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/. */ /** * interceramic.com - Unsupported message * Bug #1830810 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830810 * WebCompat issue #117807 - https://github.com/webcompat/web-bugs/issues/117807 */ #ff-modal { display: none !important; } PK !< ґ//?injections/css/bug1830813-page.onstove.com-hide-unsupported.css/* 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/. */ /** * onstove.com - Unsupported message * Bug #1830813 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830813 * WebCompat issue #116760 - https://github.com/webcompat/web-bugs/issues/116760 */ .gnb-alerts.gnb-old-browser { height: 0; } .isCampaign .gnb-stove.gnb-default-fixed, .isCampaign .layout.layout-base .layout-header { height: 68px; } PK ! { // In case the element appeared before the MutationObserver was activated. for (const elem of document.querySelectorAll(selector)) { elem.disabled = false; } // Start watching for the insertion of the "Leave now" button. const moOptions = { attributeFilter: ["disabled"], attributes: true, subtree: true, }; const mo = new MutationObserver(function (records) { for (const { target } of records) { if (target.matches(selector)) { target.disabled = false; } } }); mo.observe(document.body, moOptions); }); PK !<Y  ;injections/js/bug1631811-datastudio.google.com-indexedDB.js/* 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 1631811 - disable indexedDB for datastudio.google.com iframes * * Indexed DB is disabled already for these iframes due to cookie blocking. * This intervention changes the functionality from throwing a SecurityError * when indexedDB is accessed to removing it from the window object */ console.info( "window.indexedDB has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1631811 for details." ); Object.defineProperty(window.wrappedJSObject, "indexedDB", { get: undefined, set: undefined, }); PK !<p5injections/js/bug1722955-frontgate.com-ua-override.js/* 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 1722955 - Add UA override for frontgate.com * Webcompat issue #36277 - https://github.com/webcompat/web-bugs/issues/36277 * * The website is sending the desktop version to Firefox on mobile devices * based on UA sniffing. Spoofing as Chrome fixes this. */ /* globals exportFunction, UAHelpers */ console.info( "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/36277 for details." ); UAHelpers.overrideWithDeviceAppropriateChromeUA(); PK !<-<<(injections/js/bug1724764-window-print.js/* 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"; /** * Generic window.print shim * * Issues related to an error caused by missing window.print() method on Android. * Adding print to the window object allows to unbreak the sites. */ /* globals exportFunction */ if (typeof window.print === "undefined") { console.info( "window.print has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1659818 for details." ); Object.defineProperty(window.wrappedJSObject, "print", { get: exportFunction(function () { return true; }, window), set: exportFunction(function () {}, window), }); } PK !<AA8injections/js/bug1724868-news.yahoo.co.jp-ua-override.js/* 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 1724868 - news.yahoo.co.jp - Override UA * WebCompat issue #82605 - https://webcompat.com/issues/82605 * * Yahoo Japan news doesn't allow playing video in Firefox on Android * as they don't have it in their support matrix. They check UA override twice * and display different ui with the same error. Changing UA to Chrome via * content script allows playing the videos. */ /* globals exportFunction */ console.info( "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/82605 for details." ); Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { get: exportFunction(function () { return "Mozilla/5.0 (Linux; Android 11; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36"; }, window), set: exportFunction(function () {}, window), }); PK !<:Dinjections/js/bug1731825-office365-email-handling-prompt-autohide.js/* 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 1731825 - Office 365 email handling prompt autohide * * This site patch prevents the notification bar on Office 365 * apps from popping up on each page-load, offering to handle * email with Outlook. */ /* globals exportFunction */ const warning = "Office 365 Outlook email handling prompt has been hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=1731825 for details."; const localStorageKey = "mailProtocolHandlerAlreadyOffered"; const nav = navigator.wrappedJSObject; const { registerProtocolHandler } = nav; const { localStorage } = window.wrappedJSObject; Object.defineProperty(navigator.wrappedJSObject, "registerProtocolHandler", { value: exportFunction(function (scheme, url, title) { if (localStorage.getItem(localStorageKey)) { console.info(warning); return undefined; } registerProtocolHandler.call(nav, scheme, url, title); localStorage.setItem(localStorageKey, true); return undefined; }, window), }); PK ! { observer.disconnect(); const finalTextNode = obsEditor.querySelector( `[data-offset-key="${obsKey}"] [data-text='true']` ).firstChild; const end = obsStart + obsText.length; window .getSelection() .setBaseAndExtent(finalTextNode, end, finalTextNode, end); }; observer = new MutationObserver(obsHandler); document.documentElement.addEventListener( "beforeinput", e => { if (e.inputType != "insertFromPaste") { return; } const { target } = e; obsEditor = target.closest(EditorCSS); if (!obsEditor) { return; } const items = e?.dataTransfer.items; for (let item of items) { if (item.type === "text/plain") { e.preventDefault(); item.getAsString(text => { obsText = text; // find the editor-managed which contains the text node the // cursor starts on, and the cursor's location (or the selection start) const sel = window.getSelection(); obsStart = sel.anchorOffset; let anchor = sel.anchorNode; if (!anchor.closest) { anchor = anchor.parentElement; } anchor = anchor.closest("[data-offset-key]"); obsKey = anchor.getAttribute("data-offset-key"); // set us up to wait for the editor to either update or replace the // with that key (the one containing the text to be changed). // we will then make sure the cursor is after the pasted text, as if // the editor recreates the node, the cursor position is lost observer.observe(obsEditor, obsConfig); // force the editor to "paste". sending paste or other events will not // work, nor using execCommand (adding HTML will screw up the DOM that // the editor expects, and adding plain text will make it ignore newlines). target.dispatchEvent( new InputEvent("beforeinput", { inputType: "insertText", data: text, bubbles: true, cancelable: true, }) ); // blur the editor to force it to update/flush its state, because otherwise // the paste works, but the editor doesn't show it (until it is re-focused). obsEditor.blur(); }); break; } } }, true ); })(); } PK !<.'3injections/js/bug1769762-tiktok.com-plugins-shim.js/* 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 1769762 - Empty out navigator.plugins * WebCompat issue #103612 - https://webcompat.com/issues/103612 * * Certain features of the site are breaking if navigator.plugins array is not empty: * * 1. "Likes" on the comments are not saved * 2. Can't reply to other people's comments * 3. "Likes" on the videos are not saved * 4. Can't follow an account (after refreshing "Follow" button is visible again) * * (note that the first 2 are still broken if you open devtools even with this intervention) */ /* globals exportFunction */ console.info( "The PluginArray has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1753874 for details." ); const pluginsArray = new window.wrappedJSObject.Array(); Object.setPrototypeOf(pluginsArray, PluginArray.prototype); Object.defineProperty(navigator.wrappedJSObject, "plugins", { get: exportFunction(function () { return pluginsArray; }, window), set: exportFunction(function (val) {}, window), }); PK ! {}, removeEventListener: () => {}, effectiveType: "4g", }; window.navigator.wrappedJSObject.connection = cloneInto(connection, window, { cloneFunctions: true, }); PK ! { for (const { addedNodes, target, attributeName } of mutations) { if (attributeName === "disabled") { check(target); } else { addedNodes?.forEach(node => { if (!check(node)) { node .querySelectorAll?.(SELECTOR) ?.forEach(n => n.removeAttribute("disabled")); } }); } } }).observe(document, { attributes: true, childList: true, subtree: true }); PK !<"-u@injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js/* 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 1799968 - Build site patch for www.samsung.com * WebCompat issue #108993 - https://webcompat.com/issues/108993 * * Samsung's Watch pages try to detect the OS via navigator.appVersion, * but fail with Linux because they expect it to contain the literal * string "linux", and their JS breaks. * * As such this site patch sets appVersion to "5.0 (Linux)", and is * only meant to be applied on Linux. */ /* globals exportFunction */ console.info( "navigator.appVersion has been shimmed for compatibility reasons. See https://webcompat.com/issues/108993 for details." ); Object.defineProperty(navigator.wrappedJSObject, "appVersion", { get: exportFunction(function () { return "5.0 (Linux)"; }, window), set: exportFunction(function () {}, window), }); PK !<(8injections/js/bug1799980-healow.com-infinite-loop-fix.js/* 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 1799980 - Healow gets stuck in an infinite loop while pages load * * This patch keeps Healow's localization scripts from getting stuck in * an infinite loop while their pages are loading. * * This happens because they use synchronous XMLHttpRequests to fetch a * JSON file with their localized text on the first call to their i18n * function, and then force subsequent calls to wait for it by waiting * in an infinite loop. * * But since they're in an infinite loop, the code after the syncXHR will * never be able to run, so this ultimately triggers a slow script warning. * * We can improve this by just preventing the infinite loop from happening, * though since they disable caching on their JSON files it means that more * XHRs may happen. But since those files are small, this seems like a * reasonable compromise until they migrate to a better i18n solution. * * See https://bugzilla.mozilla.org/show_bug.cgi?id=1799980 for details. */ /* globals exportFunction */ Object.defineProperty(window.wrappedJSObject, "ajaxRequestProcessing", { get: exportFunction(function () { return false; }, window), set: exportFunction(function () {}, window), }); PK !< 1injections/js/bug1818818-fastclick-legacy-shim.js/* 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 1818818 - Neutralize FastClick * * The patch is applied on sites using older version of FastClick library. * This allows to disable FastClick and fix various breakage caused * by the library. */ /* globals exportFunction */ const proto = CSS2Properties.prototype.wrappedJSObject; Object.defineProperty(proto, "msTouchAction", { get: exportFunction(function () { return "none"; }, window), set: exportFunction(function () {}, window), }); PK !<0pp2injections/js/bug1819450-cmbchina.com-ua-change.js/* 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 1819450 - cmbchina.com - Override UA * * The site is using UA detection to redirect to * m.cmbchina.com (mobile version of the site). Adding `SAMSUNG` allows * to bypass the detection of mobile browser. */ /* globals exportFunction */ console.info( "The user agent has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1081239 for details." ); const MODIFIED_UA = navigator.userAgent + " SAMSUNG"; Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { get: exportFunction(function () { return MODIFIED_UA; }, window), set: exportFunction(function () {}, window), }); PK ! { for (const { addedNodes, target, attributeName } of mutations) { if (attributeName === "disabled") { check(target); } else { addedNodes?.forEach(node => { if (!check(node)) { node .querySelectorAll?.(SELECTOR) ?.forEach(n => n.removeAttribute("disabled")); } }); } } }).observe(document, { attributes: true, childList: true, subtree: true }); PK !<7<injections/js/bug1819678-free4talk.com-window-chrome-shim.js/* 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 1827678 - UA spoof for www.free4talk.com * * This site is checking for window.chrome, so let's spoof that. */ /* globals exportFunction */ console.info( "window.chrome has been shimmed for compatibility reasons. See https://github.com/webcompat/web-bugs/issues/77727 for details." ); Object.defineProperty(window.wrappedJSObject, "chrome", { get: exportFunction(function () { return true; }, window), set: exportFunction(function () {}, window), }); PK ! { ports.add(port); port.onDisconnect.addListener(function () { ports.delete(port); }); }); async function broadcast(message) { for (const port of ports) { port.postMessage(message); } } return { broadcast }; } filterOverrides(overrides) { return overrides .filter(override => override.availableOnPlatform) .map(override => { const { id, active, bug, domain, hidden } = override; return { id, active, bug, domain, hidden }; }); } getInterventionById(id) { for (const [type, things] of Object.entries({ overrides: this._uaOverrides?.getAvailableOverrides() || [], interventions: this._injections?.getAvailableInjections() || [], shims: this._shims?.getAvailableShims() || [], })) { for (const what of things) { if (what.id === id) { return { type, what }; } } } return {}; } bootup() { onMessageFromTab(msg => { switch (msg.command || msg) { case "toggle": { const id = msg.id; const { type, what } = this.getInterventionById(id); if (!what) { return Promise.reject( `No such override or intervention to toggle: ${id}` ); } const active = type === "shims" ? !what.disabledReason : what.active; this.portsToAboutCompatTabs .broadcast({ toggling: id, active }) .then(async () => { switch (type) { case "interventions": { if (active) { await this._injections?.disableInjection(what); } else { await this._injections?.enableInjection(what); } break; } case "overrides": { if (active) { await this._uaOverrides?.disableOverride(what); } else { await this._uaOverrides?.enableOverride(what); } break; } case "shims": { if (active) { await this._shims?.disableShimForSession(id); } else { await this._shims?.enableShimForSession(id); } // no need to broadcast the "toggled" signal for shims, as // they send a shimsUpdated message themselves instead return; } } this.portsToAboutCompatTabs.broadcast({ toggled: id, active: !active, }); }); break; } case "getAllInterventions": { return Promise.resolve({ overrides: (this._uaOverrides?.isEnabled() && this.filterOverrides( this._uaOverrides?.getAvailableOverrides() )) || false, interventions: (this._injections?.isEnabled() && this.filterOverrides( this._injections?.getAvailableInjections() )) || false, shims: this._shims?.getAvailableShims() || false, }); } } return undefined; }); } } module.exports = AboutCompatBroker; PK !<ک lib/custom_functions.js/* 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"; /* globals browser, module */ const replaceStringInRequest = ( requestId, inString, outString, inEncoding = "utf-8" ) => { const filter = browser.webRequest.filterResponseData(requestId); const decoder = new TextDecoder(inEncoding); const encoder = new TextEncoder(); const RE = new RegExp(inString, "g"); const carryoverLength = inString.length; let carryover = ""; filter.ondata = event => { const replaced = ( carryover + decoder.decode(event.data, { stream: true }) ).replace(RE, outString); filter.write(encoder.encode(replaced.slice(0, -carryoverLength))); carryover = replaced.slice(-carryoverLength); }; filter.onstop = event => { if (carryover.length) { filter.write(encoder.encode(carryover)); } filter.close(); }; }; const CUSTOM_FUNCTIONS = { detectSwipeFix: injection => { const { urls, types } = injection.data; const listener = (injection.data.listener = ({ requestId }) => { replaceStringInRequest( requestId, "preventDefault:true", "preventDefault:false" ); return {}; }); browser.webRequest.onBeforeRequest.addListener(listener, { urls, types }, [ "blocking", ]); }, detectSwipeFixDisable: injection => { const { listener } = injection.data; browser.webRequest.onBeforeRequest.removeListener(listener); delete injection.data.listener; }, noSniffFix: injection => { const { urls, contentType } = injection.data; const listener = (injection.data.listener = e => { e.responseHeaders.push(contentType); return { responseHeaders: e.responseHeaders }; }); browser.webRequest.onHeadersReceived.addListener(listener, { urls }, [ "blocking", "responseHeaders", ]); }, noSniffFixDisable: injection => { const { listener } = injection.data; browser.webRequest.onHeadersReceived.removeListener(listener); delete injection.data.listener; }, runScriptBeforeRequest: injection => { const { bug, message, request, script, types } = injection; const warning = `${message} See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; const listener = (injection.listener = e => { const { tabId, frameId } = e; return browser.tabs .executeScript(tabId, { file: script, frameId, runAt: "document_start", }) .then(() => { browser.tabs.executeScript(tabId, { code: `console.warn(${JSON.stringify(warning)})`, runAt: "document_start", }); }) .catch(_ => {}); }); browser.webRequest.onBeforeRequest.addListener( listener, { urls: request, types: types || ["script"] }, ["blocking"] ); }, runScriptBeforeRequestDisable: injection => { const { listener } = injection; browser.webRequest.onBeforeRequest.removeListener(listener); delete injection.data.listener; }, }; module.exports = CUSTOM_FUNCTIONS; PK ! { this.checkInjectionPref(); }, this.INJECTION_PREF); this.checkInjectionPref(); } checkInjectionPref() { browser.aboutConfigPrefs.getPref(this.INJECTION_PREF).then(value => { if (value === undefined) { browser.aboutConfigPrefs.setPref(this.INJECTION_PREF, true); } else if (value === false) { this.unregisterContentScripts(); } else { this.registerContentScripts(); } }); } getAvailableInjections() { return this._availableInjections; } isEnabled() { return this._injectionsEnabled; } async registerContentScripts() { const platformInfo = await browser.runtime.getPlatformInfo(); const platformMatches = [ "all", platformInfo.os, platformInfo.os == "android" ? "android" : "desktop", ]; for (const injection of this._availableInjections) { if (platformMatches.includes(injection.platform)) { injection.availableOnPlatform = true; await this.enableInjection(injection); } } this._injectionsEnabled = true; this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ interventionsChanged: this._aboutCompatBroker.filterOverrides( this._availableInjections ), }); } assignContentScriptDefaults(contentScripts) { let finalConfig = Object.assign({}, contentScripts); if (!finalConfig.runAt) { finalConfig.runAt = "document_start"; } return finalConfig; } async enableInjection(injection) { if (injection.active) { return undefined; } if (injection.customFunc) { return this.enableCustomInjection(injection); } return this.enableContentScripts(injection); } enableCustomInjection(injection) { if (injection.customFunc in this._customFunctions) { this._customFunctions[injection.customFunc](injection); injection.active = true; } else { console.error( `Provided function ${injection.customFunc} wasn't found in functions list` ); } } async enableContentScripts(injection) { try { const handle = await browser.contentScripts.register( this.assignContentScriptDefaults(injection.contentScripts) ); this._activeInjections.set(injection, handle); injection.active = true; } catch (ex) { console.error( "Registering WebCompat GoFaster content scripts failed: ", ex ); } } unregisterContentScripts() { for (const injection of this._availableInjections) { this.disableInjection(injection); } this._injectionsEnabled = false; this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ interventionsChanged: false, }); } async disableInjection(injection) { if (!injection.active) { return undefined; } if (injection.customFunc) { return this.disableCustomInjections(injection); } return this.disableContentScripts(injection); } disableCustomInjections(injection) { const disableFunc = injection.customFunc + "Disable"; if (disableFunc in this._customFunctions) { this._customFunctions[disableFunc](injection); injection.active = false; } else { console.error( `Provided function ${disableFunc} for disabling injection wasn't found in functions list` ); } } async disableContentScripts(injection) { const contentScript = this._activeInjections.get(injection); await contentScript.unregister(); this._activeInjections.delete(injection); injection.active = false; } } module.exports = Injections; PK !<*܍U U lib/intervention_helpers.js/* 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"; /* globals module */ const GOOGLE_TLDS = [ "com", "ac", "ad", "ae", "com.af", "com.ag", "com.ai", "al", "am", "co.ao", "com.ar", "as", "at", "com.au", "az", "ba", "com.bd", "be", "bf", "bg", "com.bh", "bi", "bj", "com.bn", "com.bo", "com.br", "bs", "bt", "co.bw", "by", "com.bz", "ca", "com.kh", "cc", "cd", "cf", "cat", "cg", "ch", "ci", "co.ck", "cl", "cm", "cn", "com.co", "co.cr", "com.cu", "cv", "com.cy", "cz", "de", "dj", "dk", "dm", "com.do", "dz", "com.ec", "ee", "com.eg", "es", "com.et", "fi", "com.fj", "fm", "fr", "ga", "ge", "gf", "gg", "com.gh", "com.gi", "gl", "gm", "gp", "gr", "com.gt", "gy", "com.hk", "hn", "hr", "ht", "hu", "co.id", "iq", "ie", "co.il", "im", "co.in", "io", "is", "it", "je", "com.jm", "jo", "co.jp", "co.ke", "ki", "kg", "co.kr", "com.kw", "kz", "la", "com.lb", "com.lc", "li", "lk", "co.ls", "lt", "lu", "lv", "com.ly", "co.ma", "md", "me", "mg", "mk", "ml", "com.mm", "mn", "ms", "com.mt", "mu", "mv", "mw", "com.mx", "com.my", "co.mz", "com.na", "ne", "com.nf", "com.ng", "com.ni", "nl", "no", "com.np", "nr", "nu", "co.nz", "com.om", "com.pk", "com.pa", "com.pe", "com.ph", "pl", "com.pg", "pn", "com.pr", "ps", "pt", "com.py", "com.qa", "ro", "rs", "ru", "rw", "com.sa", "com.sb", "sc", "se", "com.sg", "sh", "si", "sk", "com.sl", "sn", "sm", "so", "st", "sr", "com.sv", "td", "tg", "co.th", "com.tj", "tk", "tl", "tm", "to", "tn", "com.tr", "tt", "com.tw", "co.tz", "com.ua", "co.ug", "co.uk", "com", "com.uy", "co.uz", "com.vc", "co.ve", "vg", "co.vi", "com.vn", "vu", "ws", "co.za", "co.zm", "co.zw", ]; var InterventionHelpers = { /** * Useful helper to generate a list of domains with a fixed base domain and * multiple country-TLDs or other cases with various TLDs. * * Example: * matchPatternsForTLDs("*://mozilla.", "/*", ["com", "org"]) * => ["*://mozilla.com/*", "*://mozilla.org/*"] */ matchPatternsForTLDs(base, suffix, tlds) { return tlds.map(tld => base + tld + suffix); }, /** * A modified version of matchPatternsForTLDs that always returns the match * list for all known Google country TLDs. */ matchPatternsForGoogle(base, suffix = "/*") { return InterventionHelpers.matchPatternsForTLDs(base, suffix, GOOGLE_TLDS); }, }; module.exports = InterventionHelpers; PK ! { const promises = [...handlers.values()].map(fn => fn(msg, sender)); return Promise.allSettled(promises).then(results => { for (const { reason, value } of results) { if (reason) { console.error(reason); } else if (value !== undefined) { return value; } } return undefined; }); }); return function (handler) { handlers.add(handler); }; })(); PK !<rlib/module_shim.js/* 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"; /** * We cannot yet use proper JS modules within webextensions, as support for them * is highly experimental and highly instable. So we end up just including all * the JS files we need as separate background scripts, and since they all are * executed within the same context, this works for our in-browser deployment. * * However, this code is tracked outside of mozilla-central, and we work on * shipping this code in other products, like android-components as well. * Because of that, we have automated tests running within that repository. To * make our lives easier, we add `module.exports` statements to the JS source * files, so we can easily import their contents into our NodeJS-based test * suite. * * This works fine, but obviously, `module` is not defined when running * in-browser. So let's use this empty object as a shim, so we don't run into * runtime exceptions because of that. */ var module = {}; PK !<6AA"lib/requestStorageAccess_helper.js/* 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/. */ /* globals browser */ // Helper for calling the internal requestStorageAccessForOrigin method. The // method is called on the first-party document for the third-party which needs // first-party storage access. browser.runtime.onMessage.addListener(request => { let { requestStorageAccessOrigin, warning } = request; if (!requestStorageAccessOrigin) { return false; } // Log a warning to the web console, informing about the shim. console.warn(warning); // Call the internal storage access API. Passing false means we don't require // user activation, but will always show the storage access prompt. The user // has to explicitly allow storage access. return document .requestStorageAccessForOrigin(requestStorageAccessOrigin, false) .then(() => { return { success: true }; }) .catch(() => { return { success: false }; }); }); PK !<'Ŕlib/shim_messaging_helper.js/* 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"; /* globals browser */ if (!window.Shims) { window.Shims = new Map(); } if (!window.ShimsHelperReady) { window.ShimsHelperReady = true; browser.runtime.onMessage.addListener(details => { const { shimId, warning } = details; if (!shimId) { return; } window.Shims.set(shimId, details); if (warning) { console.warn(warning); } }); async function handleMessage(port, shimId, messageId, message) { let response; const shim = window.Shims.get(shimId); if (shim) { const { needsShimHelpers, origin } = shim; if (origin === location.origin) { if (needsShimHelpers?.includes(message)) { const msg = { shimId, message }; try { response = await browser.runtime.sendMessage(msg); } catch (_) {} } } } port.postMessage({ messageId, response }); } window.addEventListener( "ShimConnects", e => { e.stopPropagation(); e.preventDefault(); const { port, pendingMessages, shimId } = e.detail; const shim = window.Shims.get(shimId); if (!shim) { return; } port.onmessage = ({ data }) => { handleMessage(port, shimId, data.messageId, data.message); }; for (const [messageId, message] of pendingMessages) { handleMessage(port, shimId, messageId, message); } }, true ); window.dispatchEvent(new CustomEvent("ShimHelperReady")); } PK !<弇}prpr lib/shims.js/* 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"; /* globals browser, module, onMessageFromTab */ // To grant shims access to bundled logo images without risking // exposing our moz-extension URL, we have the shim request them via // nonsense URLs which we then redirect to the actual files (but only // on tabs where a shim using a given logo happens to be active). const LogosBaseURL = "https://smartblock.firefox.etp/"; const releaseBranchPromise = browser.appConstants.getReleaseBranch(); const platformPromise = browser.runtime.getPlatformInfo().then(info => { return info.os === "android" ? "android" : "desktop"; }); let debug = async function () { if ((await releaseBranchPromise) !== "release_or_beta") { console.debug.apply(this, arguments); } }; let error = async function () { if ((await releaseBranchPromise) !== "release_or_beta") { console.error.apply(this, arguments); } }; let warn = async function () { if ((await releaseBranchPromise) !== "release_or_beta") { console.warn.apply(this, arguments); } }; class Shim { constructor(opts, manager) { this.manager = manager; const { contentScripts, matches, unblocksOnOptIn } = opts; this.branches = opts.branches; this.bug = opts.bug; this.isGoogleTrendsDFPIFix = opts.custom == "google-trends-dfpi-fix"; this.file = opts.file; this.hiddenInAboutCompat = opts.hiddenInAboutCompat; this.hosts = opts.hosts; this.id = opts.id; this.logos = opts.logos || []; this.matches = []; this.name = opts.name; this.notHosts = opts.notHosts; this.onlyIfBlockedByETP = opts.onlyIfBlockedByETP; this.onlyIfDFPIActive = opts.onlyIfDFPIActive; this.onlyIfPrivateBrowsing = opts.onlyIfPrivateBrowsing; this._options = opts.options || {}; this.needsShimHelpers = opts.needsShimHelpers; this.platform = opts.platform || "all"; this.runFirst = opts.runFirst; this.unblocksOnOptIn = unblocksOnOptIn; this.requestStorageAccessForRedirect = opts.requestStorageAccessForRedirect; this._hostOptIns = new Set(); this._disabledByConfig = opts.disabled; this._disabledGlobally = false; this._disabledForSession = false; this._disabledByPlatform = false; this._disabledByReleaseBranch = false; this._activeOnTabs = new Set(); this._showedOptInOnTabs = new Set(); const pref = `disabled_shims.${this.id}`; this.redirectsRequests = !!this.file && matches?.length; this._contentScriptRegistrations = []; this.contentScripts = contentScripts || []; for (const script of this.contentScripts) { if (typeof script.css === "string") { script.css = [{ file: `/shims/${script.css}` }]; } if (typeof script.js === "string") { script.js = [{ file: `/shims/${script.js}` }]; } } for (const match of matches || []) { if (!match.types) { this.matches.push({ patterns: [match], types: ["script"] }); } else { this.matches.push(match); } if (match.target) { this.redirectsRequests = true; } } browser.aboutConfigPrefs.onPrefChange.addListener(async () => { const value = await browser.aboutConfigPrefs.getPref(pref); this._disabledPrefValue = value; this._onEnabledStateChanged(); }, pref); this.ready = Promise.all([ browser.aboutConfigPrefs.getPref(pref), platformPromise, releaseBranchPromise, ]).then(([disabledPrefValue, platform, branch]) => { this._disabledPrefValue = disabledPrefValue; this._disabledByPlatform = this.platform !== "all" && this.platform !== platform; this._disabledByReleaseBranch = false; for (const supportedBranchAndPlatform of this.branches || []) { const [supportedBranch, supportedPlatform] = supportedBranchAndPlatform.split(":"); if ( (!supportedPlatform || supportedPlatform == platform) && supportedBranch != branch ) { this._disabledByReleaseBranch = true; } } this._preprocessOptions(platform, branch); this._onEnabledStateChanged(); }); } _preprocessOptions(platform, branch) { // options may be any value, but can optionally be gated for specified // platform/branches, if in the format `{value, branches, platform}` this.options = {}; for (const [k, v] of Object.entries(this._options)) { if (v?.value) { if ( (!v.platform || v.platform === platform) && (!v.branches || v.branches.includes(branch)) ) { this.options[k] = v.value; } } else { this.options[k] = v; } } } get enabled() { if (this._disabledGlobally || this._disabledForSession) { return false; } if (this._disabledPrefValue !== undefined) { return !this._disabledPrefValue; } return ( !this._disabledByConfig && !this._disabledByPlatform && !this._disabledByReleaseBranch ); } get disabledReason() { if (this._disabledGlobally) { return "globalPref"; } if (this._disabledForSession) { return "session"; } if (this._disabledPrefValue !== undefined) { if (this._disabledPrefValue === true) { return "pref"; } return false; } if (this._disabledByConfig) { return "config"; } if (this._disabledByPlatform) { return "platform"; } if (this._disabledByReleaseBranch) { return "releaseBranch"; } return false; } onAllShimsEnabled() { const wasEnabled = this.enabled; this._disabledGlobally = false; if (!wasEnabled) { this._onEnabledStateChanged(); } } onAllShimsDisabled() { const wasEnabled = this.enabled; this._disabledGlobally = true; if (wasEnabled) { this._onEnabledStateChanged(); } } enableForSession() { const wasEnabled = this.enabled; this._disabledForSession = false; if (!wasEnabled) { this._onEnabledStateChanged(); } } disableForSession() { const wasEnabled = this.enabled; this._disabledForSession = true; if (wasEnabled) { this._onEnabledStateChanged(); } } async _onEnabledStateChanged() { this.manager?.onShimStateChanged(this.id); if (!this.enabled) { await this._unregisterContentScripts(); return this._revokeRequestsInETP(); } await this._registerContentScripts(); return this._allowRequestsInETP(); } async _registerContentScripts() { if ( this.contentScripts.length && !this._contentScriptRegistrations.length ) { const matches = []; for (const options of this.contentScripts) { matches.push(options.matches); const reg = await browser.contentScripts.register(options); this._contentScriptRegistrations.push(reg); } const urls = Array.from(new Set(matches.flat())); debug("Enabling content scripts for these URLs:", urls); } } async _unregisterContentScripts() { for (const registration of this._contentScriptRegistrations) { registration.unregister(); } this._contentScriptRegistrations = []; } async _allowRequestsInETP() { const matches = this.matches.map(m => m.patterns).flat(); if (matches.length) { await browser.trackingProtection.shim(this.id, matches); } if (this._hostOptIns.size) { const optIns = this.getApplicableOptIns(); if (optIns.length) { await browser.trackingProtection.allow( this.id, this._optInPatterns, Array.from(this._hostOptIns) ); } } } _revokeRequestsInETP() { return browser.trackingProtection.revoke(this.id); } setActiveOnTab(tabId, active = true) { if (active) { this._activeOnTabs.add(tabId); } else { this._activeOnTabs.delete(tabId); this._showedOptInOnTabs.delete(tabId); } } isActiveOnTab(tabId) { return this._activeOnTabs.has(tabId); } meantForHost(host) { const { hosts, notHosts } = this; if (hosts || notHosts) { if ( (notHosts && notHosts.includes(host)) || (hosts && !hosts.includes(host)) ) { return false; } } return true; } async unblocksURLOnOptIn(url) { if (!this._optInPatterns) { this._optInPatterns = await this.getApplicableOptIns(); } if (!this._optInMatcher) { this._optInMatcher = browser.matchPatterns.getMatcher( Array.from(this._optInPatterns) ); } return this._optInMatcher.matches(url); } isTriggeredByURLAndType(url, type) { for (const entry of this.matches || []) { if (!entry.types.includes(type)) { continue; } if (!entry.matcher) { entry.matcher = browser.matchPatterns.getMatcher( Array.from(entry.patterns) ); } if (entry.matcher.matches(url)) { return entry; } } return undefined; } async getApplicableOptIns() { if (this._applicableOptIns) { return this._applicableOptIns; } const optins = []; for (const unblock of this.unblocksOnOptIn || []) { if (typeof unblock === "string") { optins.push(unblock); continue; } const { branches, patterns, platforms } = unblock; if (platforms?.length) { const platform = await platformPromise; if (platform !== "all" && !platforms.includes(platform)) { continue; } } if (branches?.length) { const branch = await releaseBranchPromise; if (!branches.includes(branch)) { continue; } } optins.push.apply(optins, patterns); } this._applicableOptIns = optins; return optins; } async onUserOptIn(host) { const optins = await this.getApplicableOptIns(); if (optins.length) { this.userHasOptedIn = true; this._hostOptIns.add(host); await browser.trackingProtection.allow( this.id, optins, Array.from(this._hostOptIns) ); } } hasUserOptedInAlready(host) { return this._hostOptIns.has(host); } showOptInWarningOnce(tabId, origin) { if (this._showedOptInOnTabs.has(tabId)) { return Promise.resolve(); } this._showedOptInOnTabs.add(tabId); const { bug, name } = this; const warning = `${name} is allowed on ${origin} for this browsing session due to user opt-in. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; return browser.tabs .executeScript(tabId, { code: `console.warn(${JSON.stringify(warning)})`, runAt: "document_start", }) .catch(() => {}); } } class Shims { constructor(availableShims) { if (!browser.trackingProtection) { console.error("Required experimental add-on APIs for shims unavailable"); return; } this._registerShims(availableShims); onMessageFromTab(this._onMessageFromShim.bind(this)); this.ENABLED_PREF = "enable_shims"; browser.aboutConfigPrefs.onPrefChange.addListener(() => { this._checkEnabledPref(); }, this.ENABLED_PREF); this._haveCheckedEnabledPref = this._checkEnabledPref(); } bindAboutCompatBroker(broker) { this._aboutCompatBroker = broker; } getShimInfoForAboutCompat(shim) { const { bug, disabledReason, hiddenInAboutCompat, id, name } = shim; const type = "smartblock"; return { bug, disabledReason, hidden: hiddenInAboutCompat, id, name, type }; } disableShimForSession(id) { const shim = this.shims.get(id); shim?.disableForSession(); } enableShimForSession(id) { const shim = this.shims.get(id); shim?.enableForSession(); } onShimStateChanged(id) { if (!this._aboutCompatBroker) { return; } const shim = this.shims.get(id); if (!shim) { return; } const shimsChanged = [this.getShimInfoForAboutCompat(shim)]; this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ shimsChanged }); } getAvailableShims() { const shims = Array.from(this.shims.values()).map( this.getShimInfoForAboutCompat ); shims.sort((a, b) => a.name.localeCompare(b.name)); return shims; } _registerShims(shims) { if (this.shims) { throw new Error("_registerShims has already been called"); } this.shims = new Map(); for (const shimOpts of shims) { const { id } = shimOpts; if (!this.shims.has(id)) { this.shims.set(shimOpts.id, new Shim(shimOpts, this)); } } // Register onBeforeRequest listener which handles storage access requests // on matching redirects. let redirectTargetUrls = Array.from(shims.values()) .filter(shim => shim.requestStorageAccessForRedirect) .flatMap(shim => shim.requestStorageAccessForRedirect) .map(([, dstUrl]) => dstUrl); // Unique target urls. redirectTargetUrls = Array.from(new Set(redirectTargetUrls)); if (redirectTargetUrls.length) { debug("Registering redirect listener for requestStorageAccess helper", { redirectTargetUrls, }); browser.webRequest.onBeforeRequest.addListener( this._onRequestStorageAccessRedirect.bind(this), { urls: redirectTargetUrls, types: ["main_frame"] }, ["blocking"] ); } function addTypePatterns(type, patterns, set) { if (!set.has(type)) { set.set(type, { patterns: new Set() }); } const allSet = set.get(type).patterns; for (const pattern of patterns) { allSet.add(pattern); } } const allMatchTypePatterns = new Map(); const allHeaderChangingMatchTypePatterns = new Map(); const allLogos = []; for (const shim of this.shims.values()) { const { logos, matches } = shim; allLogos.push(...logos); for (const { patterns, target, types } of matches || []) { for (const type of types) { if (shim.isGoogleTrendsDFPIFix) { addTypePatterns(type, patterns, allHeaderChangingMatchTypePatterns); } if (target || shim.file || shim.runFirst) { addTypePatterns(type, patterns, allMatchTypePatterns); } } } } if (allLogos.length) { const urls = Array.from(new Set(allLogos)).map(l => { return `${LogosBaseURL}${l}`; }); debug("Allowing access to these logos:", urls); const unmarkShimsActive = tabId => { for (const shim of this.shims.values()) { shim.setActiveOnTab(tabId, false); } }; browser.tabs.onRemoved.addListener(unmarkShimsActive); browser.tabs.onUpdated.addListener((tabId, changeInfo) => { if (changeInfo.discarded || changeInfo.url) { unmarkShimsActive(tabId); } }); browser.webRequest.onBeforeRequest.addListener( this._redirectLogos.bind(this), { urls, types: ["image"] }, ["blocking"] ); } if (allHeaderChangingMatchTypePatterns) { for (const [ type, { patterns }, ] of allHeaderChangingMatchTypePatterns.entries()) { const urls = Array.from(patterns); debug("Shimming these", type, "URLs:", urls); browser.webRequest.onBeforeSendHeaders.addListener( this._onBeforeSendHeaders.bind(this), { urls, types: [type] }, ["blocking", "requestHeaders"] ); browser.webRequest.onHeadersReceived.addListener( this._onHeadersReceived.bind(this), { urls, types: [type] }, ["blocking", "responseHeaders"] ); } } if (!allMatchTypePatterns.size) { debug("Skipping shims; none enabled"); return; } for (const [type, { patterns }] of allMatchTypePatterns.entries()) { const urls = Array.from(patterns); debug("Shimming these", type, "URLs:", urls); browser.webRequest.onBeforeRequest.addListener( this._ensureShimForRequestOnTab.bind(this), { urls, types: [type] }, ["blocking"] ); } } async _checkEnabledPref() { await browser.aboutConfigPrefs.getPref(this.ENABLED_PREF).then(value => { if (value === undefined) { browser.aboutConfigPrefs.setPref(this.ENABLED_PREF, true); } else if (value === false) { this.enabled = false; } else { this.enabled = true; } }); } get enabled() { return this._enabled; } set enabled(enabled) { if (enabled === this._enabled) { return; } this._enabled = enabled; for (const shim of this.shims.values()) { if (enabled) { shim.onAllShimsEnabled(); } else { shim.onAllShimsDisabled(); } } } async _onRequestStorageAccessRedirect({ originUrl: srcUrl, url: dstUrl, tabId, }) { debug("Detected redirect", { srcUrl, dstUrl, tabId }); // Check if a shim needs to request storage access for this redirect. This // handler is called when the *source url* matches a shims redirect pattern, // but we still need to check if the *destination url* matches. const matchingShims = Array.from(this.shims.values()).filter(shim => { const { enabled, requestStorageAccessForRedirect } = shim; if (!enabled || !requestStorageAccessForRedirect) { return false; } return requestStorageAccessForRedirect.some( ([srcPattern, dstPattern]) => browser.matchPatterns.getMatcher([srcPattern]).matches(srcUrl) && browser.matchPatterns.getMatcher([dstPattern]).matches(dstUrl) ); }); // For each matching shim, find out if its enabled in regard to dFPI state. const bugNumbers = new Set(); let isDFPIActive = null; await Promise.all( matchingShims.map(async shim => { if (shim.onlyIfDFPIActive) { // Only get the dFPI state for the first shim which requires it. if (isDFPIActive === null) { const tabIsPB = (await browser.tabs.get(tabId)).incognito; isDFPIActive = await browser.trackingProtection.isDFPIActive( tabIsPB ); } if (!isDFPIActive) { return; } } bugNumbers.add(shim.bug); }) ); // If there is no shim which needs storage access for this redirect src/dst // pair, resume it. if (!bugNumbers.size) { return; } // Inject the helper to call requestStorageAccessForOrigin on the document. await browser.tabs.executeScript(tabId, { file: "/lib/requestStorageAccess_helper.js", runAt: "document_start", }); const bugUrls = Array.from(bugNumbers) .map(bugNo => `https://bugzilla.mozilla.org/show_bug.cgi?id=${bugNo}`) .join(", "); const warning = `Firefox calls the Storage Access API for ${dstUrl} on behalf of ${srcUrl}. See the following bugs for details: ${bugUrls}`; // Request storage access for the origin of the destination url of the // redirect. const { origin: requestStorageAccessOrigin } = new URL(dstUrl); // Wait for the requestStorageAccess request to finish before resuming the // redirect. const { success } = await browser.tabs.sendMessage(tabId, { requestStorageAccessOrigin, warning, }); debug("requestStorageAccess callback", { success, requestStorageAccessOrigin, srcUrl, dstUrl, bugNumbers, }); } async _onMessageFromShim(payload, sender, sendResponse) { const { tab, frameId } = sender; const { id, url } = tab; const { shimId, message } = payload; // Ignore unknown messages (for instance, from about:compat). if (message !== "getOptions" && message !== "optIn") { return undefined; } if (sender.id !== browser.runtime.id || id === -1) { throw new Error("not allowed"); } // Important! It is entirely possible for sites to spoof // these messages, due to shims allowing web pages to // communicate with the extension. const shim = this.shims.get(shimId); if (!shim?.needsShimHelpers?.includes(message)) { throw new Error("not allowed"); } if (message === "getOptions") { return Object.assign( { platform: await platformPromise, releaseBranch: await releaseBranchPromise, }, shim.options ); } else if (message === "optIn") { try { await shim.onUserOptIn(new URL(url).hostname); const origin = new URL(tab.url).origin; warn( "** User opted in for", shim.name, "shim on", origin, "on tab", id, "frame", frameId ); await shim.showOptInWarningOnce(id, origin); } catch (err) { console.error(err); throw new Error("error"); } } return undefined; } async _redirectLogos(details) { await this._haveCheckedEnabledPref; if (!this.enabled) { return { cancel: true }; } const { tabId, url } = details; const logo = new URL(url).pathname.slice(1); for (const shim of this.shims.values()) { await shim.ready; if (!shim.enabled) { continue; } if (shim.onlyIfDFPIActive) { const isPB = (await browser.tabs.get(details.tabId)).incognito; if (!(await browser.trackingProtection.isDFPIActive(isPB))) { continue; } } if (!shim.logos.includes(logo)) { continue; } if (shim.isActiveOnTab(tabId)) { return { redirectUrl: browser.runtime.getURL(`shims/${logo}`) }; } } return { cancel: true }; } async _onHeadersReceived(details) { await this._haveCheckedEnabledPref; for (const shim of this.shims.values()) { await shim.ready; if (!shim.enabled) { continue; } if (shim.onlyIfDFPIActive) { const isPB = (await browser.tabs.get(details.tabId)).incognito; if (!(await browser.trackingProtection.isDFPIActive(isPB))) { continue; } } if (shim.isGoogleTrendsDFPIFix) { if (shim.GoogleNidCookieToUse) { continue; } for (const header of details.responseHeaders) { if (header.name == "set-cookie") { shim.GoogleNidCookieToUse = header.value; return { redirectUrl: details.url }; } } } } return undefined; } async _onBeforeSendHeaders(details) { await this._haveCheckedEnabledPref; const { frameId, requestHeaders, tabId } = details; if (!this.enabled) { return { requestHeaders }; } for (const shim of this.shims.values()) { await shim.ready; if (!shim.enabled) { continue; } if (shim.isGoogleTrendsDFPIFix) { const value = shim.GoogleNidCookieToUse; if (!value) { continue; } let found; for (let header of requestHeaders) { if (header.name.toLowerCase() === "cookie") { header.value = value; found = true; } } if (!found) { requestHeaders.push({ name: "Cookie", value }); } browser.tabs .get(tabId) .then(({ url }) => { debug( `Google Trends dFPI fix used on tab ${tabId} frame ${frameId} (${url})` ); }) .catch(() => {}); const warning = `Working around Google Trends tracking protection breakage. See https://bugzilla.mozilla.org/show_bug.cgi?id=${shim.bug} for details.`; browser.tabs .executeScript(tabId, { code: `console.warn(${JSON.stringify(warning)})`, runAt: "document_start", }) .catch(() => {}); } } return { requestHeaders }; } async _ensureShimForRequestOnTab(details) { await this._haveCheckedEnabledPref; if (!this.enabled) { return undefined; } // We only ever reach this point if a request is for a URL which ought to // be shimmed. We never get here if a request is blocked, and we only // unblock requests if at least one shim matches it. const { frameId, originUrl, requestId, tabId, type, url } = details; // Ignore requests unrelated to tabs if (tabId < 0) { return undefined; } // We need to base our checks not on the frame's host, but the tab's. const topHost = new URL((await browser.tabs.get(tabId)).url).hostname; const unblocked = await browser.trackingProtection.wasRequestUnblocked( requestId ); let match; let shimToApply; for (const shim of this.shims.values()) { await shim.ready; if (!shim.enabled || (!shim.redirectsRequests && !shim.runFirst)) { continue; } if (shim.onlyIfDFPIActive || shim.onlyIfPrivateBrowsing) { const isPB = (await browser.tabs.get(details.tabId)).incognito; if (!isPB && shim.onlyIfPrivateBrowsing) { continue; } if ( shim.onlyIfDFPIActive && !(await browser.trackingProtection.isDFPIActive(isPB)) ) { continue; } } // Do not apply the shim if it is only meant to apply when strict mode ETP // (content blocking) was going to block the request. if (!unblocked && shim.onlyIfBlockedByETP) { continue; } if (!shim.meantForHost(topHost)) { continue; } // If this URL and content type isn't meant for this shim, don't apply it. match = shim.isTriggeredByURLAndType(url, type); if (match) { if (!unblocked && match.onlyIfBlockedByETP) { continue; } // If the user has already opted in for this shim, all requests it covers // should be allowed; no need for a shim anymore. if (shim.hasUserOptedInAlready(topHost)) { warn( `Allowing tracking ${type} ${url} on tab ${tabId} frame ${frameId} due to opt-in` ); shim.showOptInWarningOnce(tabId, new URL(originUrl).origin); return undefined; } shimToApply = shim; break; } } let runFirst = false; if (shimToApply) { // Note that sites may request the same shim twice, but because the requests // may differ enough for some to fail (CSP/CORS/etc), we always let the request // complete via local redirect. Shims should gracefully handle this as well. const { target } = match; const { bug, file, id, name, needsShimHelpers } = shimToApply; runFirst = shimToApply.runFirst; const redirect = target || file; warn( `Shimming tracking ${type} ${url} on tab ${tabId} frame ${frameId} with ${ redirect || runFirst }` ); const warning = `${name} is being shimmed by Firefox. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; let needConsoleMessage = true; if (runFirst) { try { await browser.tabs.executeScript(tabId, { file: `/shims/${runFirst}`, frameId, runAt: "document_start", }); } catch (_) {} } // For scripts, we also set up any needed shim helpers. if (type === "script" && needsShimHelpers?.length) { try { await browser.tabs.executeScript(tabId, { file: "/lib/shim_messaging_helper.js", frameId, runAt: "document_start", }); const origin = new URL(originUrl).origin; await browser.tabs.sendMessage( tabId, { origin, shimId: id, needsShimHelpers, warning }, { frameId } ); needConsoleMessage = false; shimToApply.setActiveOnTab(tabId); } catch (_) {} } if (needConsoleMessage) { try { await browser.tabs.executeScript(tabId, { code: `console.warn(${JSON.stringify(warning)})`, runAt: "document_start", }); } catch (_) {} } if (!redirect.indexOf("http://") || !redirect.indexOf("https://")) { return { redirectUrl: redirect }; } // If any shims matched the request to replace it, then redirect to the local // file bundled with SmartBlock, so the request never hits the network. return { redirectUrl: browser.runtime.getURL(`shims/${redirect}`) }; } // Sanity check: if no shims end up handling this request, // yet it was meant to be blocked by ETP, then block it now. if (unblocked) { error(`unexpected: ${url} not shimmed on tab ${tabId} frame ${frameId}`); return { cancel: true }; } if (!runFirst) { debug(`ignoring ${url} on tab ${tabId} frame ${frameId}`); } return undefined; } } module.exports = Shims; PK !<Ȃ lib/ua_helpers.js/* 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"; /* globals exportFunction, module */ var UAHelpers = { _deviceAppropriateChromeUAs: {}, getDeviceAppropriateChromeUA(config = {}) { const { version = "103.0.5060.71", androidDevice, desktopOS } = config; const key = `${version}:${androidDevice}:${desktopOS}`; if (!UAHelpers._deviceAppropriateChromeUAs[key]) { const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : ""; const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [ "", "58.0", ])[1]; if (userAgent.includes("Android")) { const RunningAndroidVersion = userAgent.match(/Android [0-9.]+/) || "Android 6.0"; if (androidDevice) { UAHelpers._deviceAppropriateChromeUAs[ key ] = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; ${androidDevice}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`; } else { const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`; const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; const IsPhone = userAgent.includes("Mobile"); UAHelpers._deviceAppropriateChromeUAs[key] = IsPhone ? ChromePhoneUA : ChromeTabletUA; } } else { let osSegment = "Windows NT 10.0; Win64; x64"; if (desktopOS === "macOS" || userAgent.includes("Macintosh")) { osSegment = "Macintosh; Intel Mac OS X 10_15_7"; } if ( desktopOS !== "nonLinux" && (desktopOS === "linux" || userAgent.includes("Linux")) ) { osSegment = "X11; Ubuntu; Linux x86_64"; } UAHelpers._deviceAppropriateChromeUAs[ key ] = `Mozilla/5.0 (${osSegment}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; } } return UAHelpers._deviceAppropriateChromeUAs[key]; }, getPrefix(originalUA) { return originalUA.substr(0, originalUA.indexOf(")") + 1); }, overrideWithDeviceAppropriateChromeUA(config) { const chromeUA = UAHelpers.getDeviceAppropriateChromeUA(config); Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { get: exportFunction(() => chromeUA, window), set: exportFunction(function () {}, window), }); }, capVersionTo99(originalUA) { const ver = originalUA.match(/Firefox\/(\d+\.\d+)/); if (!ver || parseFloat(ver[1]) < 100) { return originalUA; } return originalUA .replace(`Firefox/${ver[1]}`, "Firefox/99.0") .replace(`rv:${ver[1]}`, "rv:99.0"); }, }; if (typeof module !== "undefined") { module.exports = UAHelpers; } PK !<_lib/ua_overrides.js/* 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"; /* globals browser, module */ class UAOverrides { constructor(availableOverrides) { this.OVERRIDE_PREF = "perform_ua_overrides"; this._overridesEnabled = true; this._availableOverrides = availableOverrides; this._activeListeners = new Map(); } bindAboutCompatBroker(broker) { this._aboutCompatBroker = broker; } bootup() { browser.aboutConfigPrefs.onPrefChange.addListener(() => { this.checkOverridePref(); }, this.OVERRIDE_PREF); this.checkOverridePref(); } checkOverridePref() { browser.aboutConfigPrefs.getPref(this.OVERRIDE_PREF).then(value => { if (value === undefined) { browser.aboutConfigPrefs.setPref(this.OVERRIDE_PREF, true); } else if (value === false) { this.unregisterUAOverrides(); } else { this.registerUAOverrides(); } }); } getAvailableOverrides() { return this._availableOverrides; } isEnabled() { return this._overridesEnabled; } enableOverride(override) { if (override.active) { return; } const { blocks, matches, uaTransformer } = override.config; const listener = details => { // Don't actually override the UA for an experiment if the user is not // part of the experiment (unless they force-enabed the override). if ( !override.config.experiment || override.permanentPrefEnabled === true ) { for (const header of details.requestHeaders) { if (header.name.toLowerCase() === "user-agent") { // Don't override the UA if we're on a mobile device that has the // "Request Desktop Site" mode enabled. The UA for the desktop mode // is set inside Gecko with a simple string replace, so we can use // that as a check, see https://searchfox.org/mozilla-central/rev/89d33e1c3b0a57a9377b4815c2f4b58d933b7c32/mobile/android/chrome/geckoview/GeckoViewSettingsChild.js#23-28 let isMobileWithDesktopMode = override.currentPlatform == "android" && header.value.includes("X11; Linux x86_64"); if (!isMobileWithDesktopMode) { header.value = uaTransformer(header.value); } } } } return { requestHeaders: details.requestHeaders }; }; browser.webRequest.onBeforeSendHeaders.addListener( listener, { urls: matches }, ["blocking", "requestHeaders"] ); const listeners = { onBeforeSendHeaders: listener }; if (blocks) { const blistener = details => { return { cancel: true }; }; browser.webRequest.onBeforeRequest.addListener( blistener, { urls: blocks }, ["blocking"] ); listeners.onBeforeRequest = blistener; } this._activeListeners.set(override, listeners); override.active = true; } onOverrideConfigChanged(override) { // Check whether the override should be hidden from about:compat. override.hidden = override.config.hidden; // Setting the override's permanent pref overrules whether it is hidden. if (override.permanentPrefEnabled !== undefined) { override.hidden = !override.permanentPrefEnabled; } // Also check whether the override should be active. let shouldBeActive = true; // Overrides can be force-deactivated by their permanent preference. if (override.permanentPrefEnabled === false) { shouldBeActive = false; } // Overrides gated behind an experiment the user is not part of do not // have to be activated, unless they are gathering telemetry, or the // user has force-enabled them with their permanent pref. if (override.config.experiment && override.permanentPrefEnabled !== true) { shouldBeActive = false; } if (shouldBeActive) { this.enableOverride(override); } else { this.disableOverride(override); } if (this._overridesEnabled) { this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ overridesChanged: this._aboutCompatBroker.filterOverrides( this._availableOverrides ), }); } } async registerUAOverrides() { const platformMatches = ["all"]; let platformInfo = await browser.runtime.getPlatformInfo(); platformMatches.push(platformInfo.os == "android" ? "android" : "desktop"); for (const override of this._availableOverrides) { if (platformMatches.includes(override.platform)) { override.availableOnPlatform = true; override.currentPlatform = platformInfo.os; // If there is a specific about:config preference governing // this override, monitor its state. const pref = override.config.permanentPref; override.permanentPrefEnabled = pref && (await browser.aboutConfigPrefs.getPref(pref)); if (pref) { const checkOverridePref = () => { browser.aboutConfigPrefs.getPref(pref).then(value => { override.permanentPrefEnabled = value; this.onOverrideConfigChanged(override); }); }; browser.aboutConfigPrefs.onPrefChange.addListener( checkOverridePref, pref ); } this.onOverrideConfigChanged(override); } } this._overridesEnabled = true; this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ overridesChanged: this._aboutCompatBroker.filterOverrides( this._availableOverrides ), }); } unregisterUAOverrides() { for (const override of this._availableOverrides) { this.disableOverride(override); } this._overridesEnabled = false; this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ overridesChanged: false, }); } disableOverride(override) { if (!override.active) { return; } const listeners = this._activeListeners.get(override); for (const [name, listener] of Object.entries(listeners)) { browser.webRequest[name].removeListener(listener); } override.active = false; this._activeListeners.delete(override); } } module.exports = UAOverrides; PK !" ], "background": { "scripts": [ "lib/module_shim.js", "lib/messaging_helper.js", "lib/intervention_helpers.js", "lib/requestStorageAccess_helper.js", "lib/ua_helpers.js", "data/injections.js", "data/shims.js", "data/ua_overrides.js", "lib/about_compat_broker.js", "lib/custom_functions.js", "lib/injections.js", "lib/shims.js", "lib/ua_overrides.js", "run.js" ] }, "web_accessible_resources": [ "shims/addthis-angular.js", "shims/adform.js", "shims/adnexus-ast.js", "shims/adnexus-prebid.js", "shims/adsafeprotected-ima.js", "shims/apstag.js", "shims/blogger.js", "shims/bloggerAccount.js", "shims/bmauth.js", "shims/branch.js", "shims/chartbeat.js", "shims/crave-ca.js", "shims/criteo.js", "shims/cxense.js", "shims/doubleverify.js", "shims/eluminate.js", "shims/empty-script.js", "shims/empty-shim.txt", "shims/everest.js", "shims/facebook-sdk.js", "shims/facebook.svg", "shims/fastclick.js", "shims/firebase.js", "shims/google-ads.js", "shims/google-analytics-and-tag-manager.js", "shims/google-analytics-ecommerce-plugin.js", "shims/google-analytics-legacy.js", "shims/google-ima.js", "shims/google-page-ad.js", "shims/google-publisher-tags.js", "shims/google-safeframe.html", "shims/history.js", "shims/iam.js", "shims/iaspet.js", "shims/instagram.js", "shims/kinja.js", "shims/live-test-shim.js", "shims/maxmind-geoip.js", "shims/microsoftLogin.js", "shims/microsoftVirtualAssistant.js", "shims/moat.js", "shims/mochitest-shim-1.js", "shims/mochitest-shim-2.js", "shims/mochitest-shim-3.js", "shims/nielsen.js", "shims/optimizely.js", "shims/play.svg", "shims/private-browsing-web-api-fixes.js", "shims/rambler-authenticator.js", "shims/rich-relevance.js", "shims/spotify-embed.js", "shims/tracking-pixel.png", "shims/vast2.xml", "shims/vast3.xml", "shims/vidible.js", "shims/vmad.xml", "shims/webtrends.js" ] } PK !<run.js/* 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"; /* globals AboutCompatBroker, AVAILABLE_INJECTIONS, AVAILABLE_SHIMS, AVAILABLE_PIP_OVERRIDES, AVAILABLE_UA_OVERRIDES, CUSTOM_FUNCTIONS, Injections, Shims, UAOverrides */ let injections, shims, uaOverrides; try { injections = new Injections(AVAILABLE_INJECTIONS, CUSTOM_FUNCTIONS); injections.bootup(); } catch (e) { console.error("Injections failed to start", e); injections = undefined; } try { uaOverrides = new UAOverrides(AVAILABLE_UA_OVERRIDES); uaOverrides.bootup(); } catch (e) { console.error("UA overrides failed to start", e); uaOverrides = undefined; } try { shims = new Shims(AVAILABLE_SHIMS); } catch (e) { console.error("Shims failed to start", e); shims = undefined; } try { const aboutCompatBroker = new AboutCompatBroker({ injections, shims, uaOverrides, }); aboutCompatBroker.bootup(); } catch (e) { console.error("about:compat broker failed to start", e); } PK !<  shims/addthis-angular.js/* 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 1713694 - Shim AddThis Angular module * * Sites using Angular with AddThis can break entirely if the module is * blocked. This shim mitigates that breakage by loading an empty module. */ if (!window.addthisModule) { window.addthisModule = window?.angular?.module("addthis", ["ng"]); } PK ! { const { targetId } = adObj; const handlers = gEventHandlers[type]?.[targetId]; if (!handlers) { return Promise.resolve(); } const evt = { adObj, type }; return new Promise(done => { setTimeout(() => { for (const cb of handlers) { try { cb(evt); } catch (e) { console.error(e); } } done(); }, 1); }); }; const refreshTag = targetId => { const tag = gTags.get(targetId); if (!tag) { return; } if (!gAds.has(targetId)) { gAds.set(targetId, new Ad(tag.tagId, targetId)); } const adObj = gAds.get(targetId); fireAdEvent("adRequested", adObj).then(() => { // TODO: do some sites expect adAvailable+adLoaded instead of adNoBid? fireAdEvent("adNoBid", adObj); }); }; const off = (type, targetId, cb) => { gEventHandlers[type]?.[targetId]?.delete(cb); }; const on = (type, targetId, cb) => { gEventHandlers[type] = gEventHandlers[type] || {}; gEventHandlers[type][targetId] = gEventHandlers[type][targetId] || new Set(); gEventHandlers[type][targetId].add(cb); }; const Tag = class { static #nextId = 0; debug = undefined; displayed = false; initialHeight = 1; initialWidth = 1; keywords = {}; member = 0; showTagCalled = false; sizes = []; targetId = ""; utCalled = true; utDivId = ""; utiframeId = ""; uuid = ""; constructor(raw) { const { keywords, sizes, targetId } = raw; this.tagId = Tag.#nextId++; this.keywords = keywords || {}; this.sizes = sizes || []; this.targetId = targetId || ""; } modifyTag() {} off(type, cb) { off(type, this.targetId, cb); } on(type, cb) { on(type, this.targetId, cb); } setKeywords(kw) { this.keywords = kw; } }; window.apntag = { anq, attachClickTrackers() {}, checkAdAvailable() {}, clearPageTargeting() {}, clearRequest() {}, collapseAd() {}, debug: false, defineTag(dfn) { const { targetId } = dfn; if (!targetId) { return; } gTags.set(targetId, new Tag(dfn)); }, disableDebug() {}, dongle: undefined, emitEvent(adObj, type) { fireAdEvent(type, adObj); }, enableCookieSet() {}, enableDebug() {}, fireImpressionTrackers() {}, getAdMarkup: () => "", getAdWrap() {}, getAstVersion: () => "0.49.0", getPageTargeting() {}, getTag(targetId) { return gTags.get(targetId); }, handleCb() {}, handleMediationBid() {}, highlightAd() {}, loaded: true, loadTags() { for (const tagName of gTags.keys()) { refreshTag(tagName); } }, modifyTag() {}, notify() {}, offEvent(type, target, cb) { off(type, target, cb); }, onEvent(type, target, cb) { on(type, target, cb); }, recordErrorEvent() {}, refresh() {}, registerRenderer() {}, requests: {}, resizeAd() {}, setEndpoint() {}, setKeywords() {}, setPageOpts() {}, setPageTargeting() {}, setSafeFrameConfig() {}, setSizes() {}, showTag() {}, }; const push = function (fn) { if (typeof fn === "function") { try { fn(); } catch (e) { console.trace(e); } } }; anq.push = push; anq.forEach(push); } PK !<99shims/adnexus-prebid.js/* 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 1694401 - Shim Prebid.js * * Some sites rely on prebid.js to place content, perhaps in conjunction with * other services like Google Publisher Tags and Amazon TAM. This shim prevents * site breakage like image galleries breaking as the user browsers them, by * allowing the content placement to succeed. */ if (!window.pbjs?.requestBids) { const que = window.pbjs?.que || []; const cmd = window.pbjs?.cmd || []; const adUnits = window.pbjs?.adUnits || []; window.pbjs = { adUnits, addAdUnits(arr) { if (!Array.isArray(arr)) { arr = [arr]; } adUnits.push(arr); }, cmd, offEvent() {}, que, refreshAds() {}, removeAdUnit(codes) { if (!Array.isArray(codes)) { codes = [codes]; } for (const code of codes) { for (let i = adUnits.length - 1; i >= 0; i--) { if (adUnits[i].code === code) { adUnits.splice(i, 1); } } } }, renderAd() {}, requestBids(params) { params?.bidsBackHandler?.(); }, setConfig() {}, setTargetingForGPTAsync() {}, }; const push = function (fn) { if (typeof fn === "function") { try { fn(); } catch (e) { console.trace(e); } } }; que.push = push; cmd.push = push; que.forEach(push); cmd.forEach(push); } PK ! { return { amznbid: "", amzniid: "", amznp: "", amznsz: "0x0", size: "0x0", slotID: config.slotID, }; }; window.apstag = { _Q, _getSlotIdToNameMapping() {}, bids() {}, debug() {}, deleteId() {}, fetchBids(cfg, cb) { if (!Array.isArray(cfg?.slots)) { return; } setTimeout(() => { cb(cfg.slots.map(s => newBid(s))); }, 1); }, init() {}, punt() {}, renderImp() {}, renewId() {}, setDisplayBids() {}, targetingKeys: () => [], thirdPartyData: {}, updateId() {}, }; window.apstagLOADED = true; _Q.push = function (prefix, args) { try { switch (prefix) { case "f": window.apstag.fetchBids(...args); break; case "i": window.apstag.init(...args); break; } } catch (e) { console.trace(e); } }; for (const cmd of _Q) { _Q.push(cmd); } } PK !
{ // Filter oauth popups. if (!url.startsWith(GOOGLE_OAUTH_PATH_PREFIX)) { return origOpen(url, ...args); } // Request storage access for the Blogger iframe. document.requestStorageAccess().then(() => { origOpen(url, ...args); }); // We don't have the window object yet which window.open returns, since the // sign-in flow is dependent on the async storage access request. This isn't // a problem as long as the website does not consume it. return null; }, window), }); PK !<shims/bloggerAccount.js/* 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/. */ /* globals exportFunction */ "use strict"; /** * Blogger uses Google as the auth provider. The account panel uses a * third-party iframe of https://ogs.google.com, which requires first-party * storage access to authenticate. This shim calls requestStorageAccess on * behalf of the site when the user opens the account panel. */ console.warn( `When logging in with Google, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1777690 for details.` ); const STORAGE_ACCESS_ORIGIN = "https://ogs.google.com"; document.documentElement.addEventListener( "click", e => { const { target, isTrusted } = e; if (!isTrusted) { return; } const anchorEl = target.closest("a"); if (!anchorEl) { return; } if ( !anchorEl.href.startsWith("https://accounts.google.com/SignOutOptions") ) { return; } // The storage access request below runs async so the panel won't open // immediately. Mitigate this UX issue by updating the clicked element's // style so the user gets some immediate feedback. anchorEl.style.opacity = 0.5; e.stopPropagation(); e.preventDefault(); document .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) .then(() => { // Reload all iframes of ogs.google.com so the first-party cookies are // sent to the server. // The reload mechanism here is a bit of a hack, since we don't have // access to the content window of a cross-origin iframe. document .querySelectorAll("iframe[src^='https://ogs.google.com/']") .forEach(frame => (frame.src += "")); }) // Show the panel in both success and error state. When the user denies // the storage access prompt they will see an error message in the account // panel. .finally(() => { anchorEl.style.opacity = 1.0; target.click(); }); }, true ); PK !< shims/bmauth.js/* 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"; if (!window.BmAuth) { window.BmAuth = { init: () => new Promise(() => {}), handleSignIn: () => { // TODO: handle this properly! }, isAuthenticated: () => Promise.resolve(false), addListener: () => {}, api: { event: { addListener: () => {}, }, }, }; } PK !< shims/branch.js/* 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 1716220 - Shim Branch Web SDK * * Sites such as TataPlay may not load properly if Branch Web SDK is * blocked. This shim stubs out its script so the page still loads. */ if (!window?.branch?.b) { const queue = window?.branch?._q || []; window.branch = new (class { V = {}; g = 0; X = "web2.62.0"; b = { A: {}, clear() {}, get() {}, getAll() {}, isEnabled: () => true, remove() {}, set() {}, ca() {}, g: [], l: 0, o: 0, s: null, }; addListener() {} applyCode() {} autoAppIndex() {} banner() {} c() {} closeBanner() {} closeJourney() {} constructor() {} creditHistory() {} credits() {} crossPlatformIds() {} data() {} deepview() {} deepviewCta() {} disableTracking() {} first() {} getBrowserFingerprintId() {} getCode() {} init(key, ...args) { const cb = args.pop(); if (typeof cb === "function") { cb(undefined, {}); } } lastAttributedTouchData() {} link() {} logEvent() {} logout() {} qrCode() {} redeem() {} referrals() {} removeListener() {} renderFinalize() {} renderQueue() {} sendSMS() {} setAPIResponseCallback() {} setBranchViewData() {} setIdentity() {} track() {} trackCommerceEvent() {} validateCode() {} })(); const push = ([fn, ...args]) => { try { window.branch[fn].apply(window.branch, args); } catch (e) { console.error(e); } }; queue.forEach(push); } PK !< shims/chartbeat.js/* 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 1713699 - Shim ChartBeat tracking * * Sites may rely on chartbeat's tracking as they might with Google Analytics, * expecting it to be present for interactive site content to function. This * shim mitigates related breakage. */ window.pSUPERFLY = { activity() {}, virtualPage() {}, }; PK ! { const { target, isTrusted } = e; if (!isTrusted) { return; } const button = target.closest("button"); if (!button) { return; } const form = target.closest(".login-form"); if (!form) { return; } console.warn( "Calling the Storage Access API on behalf of " + STORAGE_ACCESS_ORIGIN ); button.disabled = true; e.stopPropagation(); e.preventDefault(); document .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) .then(() => { button.disabled = false; target.click(); }) .catch(() => { button.disabled = false; }); }, true ); PK !< shims/criteo.js/* 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 1713720 - Shim Criteo * * Sites relying on window.Criteo to be loaded can experience * breakage if it is blocked. Stubbing out the API in a shim can * mitigate this breakage. */ if (window.Criteo?.CallRTA === undefined) { window.Criteo = { CallRTA() {}, ComputeStandaloneDFPTargeting() {}, DisplayAcceptableAdIfAdblocked() {}, DisplayAd() {}, GetBids() {}, GetBidsForAdUnit() {}, Passback: { RequestBids() {}, RenderAd() {}, }, PubTag: { Adapters: { AMP() {}, Prebid() {}, }, Context: { GetIdfs() {}, SetIdfs() {}, }, DirectBidding: { DirectBiddingEvent() {}, DirectBiddingSlot() {}, DirectBiddingUrlBuilder() {}, Size() {}, }, RTA: { DefaultCrtgContentName: "crtg_content", DefaultCrtgRtaCookieName: "crtg_rta", }, }, RenderAd() {}, RequestBids() {}, RequestBidsOnGoogleTagSlots() {}, SetCCPAExplicitOptOut() {}, SetCeh() {}, SetDFPKeyValueTargeting() {}, SetLineItemRanges() {}, SetPublisherExt() {}, SetSlotsExt() {}, SetTargeting() {}, SetUserExt() {}, events: { push() {}, }, passbackEvents: [], usePrebidEvents: true, }; } PK !<QAQAshims/cxense.js/* 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 1713721 - Shim Cxense * * Sites relying on window.cX can experience breakage if it is blocked. * Stubbing out the API in a shim can mitigate this breakage. There are * two versions of the API, one including window.cX.CCE, but both appear * to be very similar so we use one shim for both. */ if (window.cX?.getUserSegmentIds === undefined) { const callQueue = window.cX?.callQueue || []; const callQueueCCE = window.cX?.CCE?.callQueue || []; function getRandomString(l = 16) { const v = crypto.getRandomValues(new Uint8Array(l)); const s = Array.from(v, c => c.toString(16)).join(""); return s.slice(0, l); } const call = (cb, ...args) => { if (typeof cb !== "function") { return; } try { cb(...args); } catch (e) { console.error(e); } }; const invokeOn = lib => { return (fn, ...args) => { try { lib[fn](...args); } catch (e) { console.error(e); } }; }; const userId = getRandomString(); const cxUserId = `cx:${getRandomString(25)}:${getRandomString(12)}`; const topLeft = { left: 0, top: 0 }; const margins = { left: 0, top: 0, right: 0, bottom: 0 }; const ccePushUrl = "https://comcluster.cxense.com/cce/push?callback={{callback}}"; const displayWidget = (divId, a, ctx, callback) => call(callback, ctx, divId); const getUserSegmentIds = a => call(a?.callback, a?.defaultValue || []); const init = (a, b, c, d, callback) => call(callback); const render = (a, data, ctx, callback) => call(callback, data, ctx); const run = (params, ctx, callback) => call(callback, params, ctx); const runCtrlVersion = (a, b, callback) => call(callback); const runCxVersion = (a, data, b, ctx, callback) => call(callback, data, ctx); const runTest = (a, divId, b, c, ctx, callback) => call(callback, divId, ctx); const sendConversionEvent = (a, options) => call(options?.callback, {}); const sendEvent = (a, b, args) => call(args?.callback, {}); const getDivId = className => { const e = document.querySelector(`.${className}`); if (e) { return `${className}-01`; } return null; }; const getDocumentSize = () => { const width = document.body.clientWidth; const height = document.body.clientHeight; return { width, height }; }; const getNowSeconds = () => { return Math.round(new Date().getTime() / 1000); }; const getPageContext = () => { return { location: location.href, pageViewRandom: "", userId, }; }; const getWindowSize = () => { const width = window.innerWidth; const height = window.innerHeight; return { width, height }; }; const isObject = i => { return typeof i === "object" && i !== null && !Array.isArray(i); }; const runMulti = widgets => { widgets?.forEach(({ widgetParams, widgetContext, widgetCallback }) => { call(widgetCallback, widgetParams, widgetContext); }); }; let testGroup = -1; let snapPoints = []; const startTime = new Date(); const library = { addCustomerScript() {}, addEventListener() {}, addExternalId() {}, afterInitializePage() {}, allUserConsents() {}, backends: { production: { baseAdDeliveryUrl: "http://adserver.cxad.cxense.com/adserver/search", secureBaseAdDeliveryUrl: "https://s-adserver.cxad.cxense.com/adserver/search", }, sandbox: { baseAdDeliveryUrl: "http://adserver.sandbox.cxad.cxense.com/adserver/search", secureBaseAdDeliveryUrl: "https://s-adserver.sandbox.cxad.cxense.com/adserver/search", }, }, calculateAdSpaceSize(adCount, adUnitSize, marginA, marginB) { return adCount * (adUnitSize + marginA + marginB); }, cdn: { template: { direct: { http: "http://cdn.cxpublic.com/", https: "https://cdn.cxpublic.com/", }, mapped: { http: "http://cdn-templates.cxpublic.com/", https: "https://cdn-templates.cxpublic.com/", }, }, }, cint() {}, cleanUpGlobalIds: [], clearBaseUrl: "https://scdn.cxense.com/sclear.html", clearCustomParameters() {}, clearIdUrl: "https://scomcluster.cxense.com/public/clearid", clearIds() {}, clickTracker: (a, b, callback) => call(callback), clientStorageUrl: "https://clientstorage.cxense.com", combineArgs: () => Object.create(), combineKeywordsIntoArray: () => [], consentClasses: ["pv", "segment", "ad", "recs"], consentClassesV2: ["geo", "device"], cookieSyncRUrl: "csyn-r.cxense.com", createDelegate() {}, csdUrls: { domainScriptUrl: "//csd.cxpublic.com/d/", customerScriptUrl: "//csd.cxpublic.com/t/", }, cxenseGlobalIdIframeUrl: "https://scdn.cxense.com/sglobal.html", cxenseUserIdUrl: "https://id.cxense.com/public/user/id", decodeUrlEncodedNameValuePairs: () => Object.create(), defaultAdRenderer: () => "", deleteCookie() {}, denyWithoutConsent: { addExternalId: "pv", getUserSegmentIds: "segment", insertAdSpace: "ad", insertMultipleAdSpaces: "ad", sendEvent: "pv", sendPageViewEvent: "pv", sync: "ad", }, dmpPushUrl: "https://comcluster.cxense.com/dmp/push?callback={{callback}}", emptyWidgetUrl: "https://scdn.cxense.com/empty.html", eventReceiverBaseUrl: "https://scomcluster.cxense.com/Repo/rep.html", eventReceiverBaseUrlGif: "https://scomcluster.cxense.com/Repo/rep.gif", getAllText: () => "", getClientStorageVariable() {}, getCookie: () => null, getCxenseUserId: () => cxUserId, getDocumentSize, getElementPosition: () => topLeft, getHashFragment: () => location.hash.substr(1), getLocalStats: () => Object.create(), getNodeValue: n => n.nodeValue, getNowSeconds, getPageContext, getRandomString, getScrollPos: () => topLeft, getSessionId: () => "", getSiteId: () => "", getTimezoneOffset: () => new Date().getTimezoneOffset(), getTopLevelDomain: () => location.hostname, getUserId: () => userId, getUserSegmentIds, getWindowSize, hasConsent: () => true, hasHistory: () => true, hasLocalStorage: () => true, hasPassiveEventListeners: () => true, hasPostMessage: () => true, hasSessionStorage() {}, initializePage() {}, insertAdSpace() {}, insertMultipleAdSpaces() {}, insertWidget() {}, invoke: invokeOn(library), isAmpIFrame() {}, isArray() {}, isCompatModeActive() {}, isConsentRequired() {}, isEdge: () => false, isFirefox: () => true, isIE6Or7: () => false, isObject, isRecsDestination: () => false, isSafari: () => false, isTextNode: n => n?.nodeType === 3, isTopWindow: () => window === top, jsonpRequest: () => false, loadScript() {}, m_accountId: "0", m_activityEvents: false, m_activityState: { activeTime: startTime, currScrollLeft: 0, currScrollTop: 0, exitLink: "", hadHIDActivity: false, maxViewLeft: 1, maxViewTop: 1, parentMetrics: undefined, prevActivityTime: startTime + 2, prevScreenX: 0, prevScreenY: 0, prevScrollLeft: 0, prevScrollTop: 0, prevTime: startTime + 1, prevWindowHeight: 1, prevWindowWidth: 1, scrollDepthPercentage: 0, scrollDepthPixels: 0, }, m_atfr: null, m_c1xTpWait: 0, m_clientStorage: { iframeEl: null, iframeIsLoaded: false, iframeOrigin: "https://clientstorage.cxense.com", iframePath: "/clientstorage_v2.html", messageContexts: {}, messageQueue: [], }, m_compatMode: {}, m_compatModeActive: false, m_compatPvSent: false, m_consentVersion: 1, m_customParameters: [], m_documentSizeRequestedFromChild: false, m_externalUserIds: [], m_globalIdLoading: { globalIdIFrameEl: null, globalIdIFrameElLoaded: false, }, m_isSpaRecsDestination: false, m_knownMessageSources: [], m_p1Complete: false, m_prevLocationHash: "", m_previousPageViewReport: null, m_rawCustomParameters: {}, m_rnd: getRandomString(), m_scriptStartTime: startTime, m_siteId: "0", m_spaRecsClickUrl: null, m_thirdPartyIds: true, m_usesConsent: false, m_usesIabConsent: false, m_usesSecureCookies: true, m_usesTcf20Consent: false, m_widgetSpecs: {}, Object, onClearIds() {}, onFFP1() {}, onP1() {}, p1BaseUrl: "https://scdn.cxense.com/sp1.html", p1JsUrl: "https://p1cluster.cxense.com/p1.js", parseHashArgs: () => Object.create(), parseMargins: () => margins, parseUrlArgs: () => Object.create(), postMessageToParent() {}, publicWidgetDataUrl: "https://api.cxense.com/public/widget/data", removeClientStorageVariable() {}, removeEventListener() {}, renderContainedImage: () => "
", renderTemplate: () => "
", reportActivity() {}, requireActivityEvents() {}, requireConsent() {}, requireOnlyFirstPartyIds() {}, requireSecureCookies() {}, requireTcf20() {}, sendEvent, sendSpaRecsClick: (a, callback) => call(callback), setAccountId() {}, setAllConsentsTo() {}, setClientStorageVariable() {}, setCompatMode() {}, setConsent() {}, setCookie() {}, setCustomParameters() {}, setEventAttributes() {}, setGeoPosition() {}, setNodeValue() {}, setRandomId() {}, setRestrictionsToConsentClasses() {}, setRetargetingParameters() {}, setSiteId() {}, setUserProfileParameters() {}, setupIabCmp() {}, setupTcfApi() {}, shouldPollActivity() {}, startLocalStats() {}, startSessionAnnotation() {}, stopAllSessionAnnotations() {}, stopSessionAnnotation() {}, sync() {}, trackAmpIFrame() {}, trackElement() {}, trim: s => s.trim(), tsridUrl: "https://tsrid.cxense.com/lookup?callback={{callback}}", userSegmentUrl: "https://api.cxense.com/profile/user/segment?callback={{callback}}", }; const libraryCCE = { "__cx-toolkit__": { isShown: true, data: [], }, activeSnapPoint: null, activeWidgets: [], ccePushUrl, clickTracker: () => "", displayResult() {}, displayWidget, getDivId, getTestGroup: () => testGroup, init, insertMaster() {}, instrumentClickLinks() {}, invoke: invokeOn(libraryCCE), noCache: false, offerProductId: null, persistedQueryId: null, prefix: null, previewCampaign: null, previewDiv: null, previewId: null, previewTestId: null, processCxResult() {}, render, reportTestImpression() {}, run, runCtrlVersion, runCxVersion, runMulti, runTest, sendConversionEvent, sendPageViewEvent: (a, b, c, callback) => call(callback), setSnapPoints(x) { snapPoints = x; }, setTestGroup(x) { testGroup = x; }, setVisibilityField() {}, get snapPoints() { return snapPoints; }, startTime, get testGroup() { return testGroup; }, testVariant: null, trackTime: 0.5, trackVisibility() {}, updateRecsClickUrls() {}, utmParams: [], version: "2.42", visibilityField: "timeHalf", }; const CCE = { activeSnapPoint: null, activeWidgets: [], callQueue: callQueueCCE, ccePushUrl, clickTracker: () => "", displayResult() {}, displayWidget, getDivId, getTestGroup: () => testGroup, init, insertMaster() {}, instrumentClickLinks() {}, invoke: invokeOn(libraryCCE), library: libraryCCE, noCache: false, offerProductId: null, persistedQueryId: null, prefix: null, previewCampaign: null, previewDiv: null, previewId: null, previewTestId: null, processCxResult() {}, render, reportTestImpression() {}, run, runCtrlVersion, runCxVersion, runMulti, runTest, sendConversionEvent, sendPageViewEvent: (a, b, c, callback) => call(callback), setSnapPoints(x) { snapPoints = x; }, setTestGroup(x) { testGroup = x; }, setVisibilityField() {}, get snapPoints() { return snapPoints; }, startTime, get testGroup() { return testGroup; }, testVariant: null, trackTime: 0.5, trackVisibility() {}, updateRecsClickUrls() {}, utmParams: [], version: "2.42", visibilityField: "timeHalf", }; window.cX = { addCustomerScript() {}, addEventListener() {}, addExternalId() {}, afterInitializePage() {}, allUserConsents: () => undefined, Array, calculateAdSpaceSize: () => 0, callQueue, CCE, cint: () => undefined, clearCustomParameters() {}, clearIds() {}, clickTracker: () => "", combineArgs: () => Object.create(), combineKeywordsIntoArray: () => [], createDelegate() {}, decodeUrlEncodedNameValuePairs: () => Object.create(), defaultAdRenderer: () => "", deleteCookie() {}, getAllText: () => "", getClientStorageVariable() {}, getCookie: () => null, getCxenseUserId: () => cxUserId, getDocumentSize, getElementPosition: () => topLeft, getHashFragment: () => location.hash.substr(1), getLocalStats: () => Object.create(), getNodeValue: n => n.nodeValue, getNowSeconds, getPageContext, getRandomString, getScrollPos: () => topLeft, getSessionId: () => "", getSiteId: () => "", getTimezoneOffset: () => new Date().getTimezoneOffset(), getTopLevelDomain: () => location.hostname, getUserId: () => userId, getUserSegmentIds, getWindowSize, hasConsent: () => true, hasHistory: () => true, hasLocalStorage: () => true, hasPassiveEventListeners: () => true, hasPostMessage: () => true, hasSessionStorage() {}, initializePage() {}, insertAdSpace() {}, insertMultipleAdSpaces() {}, insertWidget() {}, invoke: invokeOn(library), isAmpIFrame() {}, isArray() {}, isCompatModeActive() {}, isConsentRequired() {}, isEdge: () => false, isFirefox: () => true, isIE6Or7: () => false, isObject, isRecsDestination: () => false, isSafari: () => false, isTextNode: n => n?.nodeType === 3, isTopWindow: () => window === top, JSON, jsonpRequest: () => false, library, loadScript() {}, Object, onClearIds() {}, onFFP1() {}, onP1() {}, parseHashArgs: () => Object.create(), parseMargins: () => margins, parseUrlArgs: () => Object.create(), postMessageToParent() {}, removeClientStorageVariable() {}, removeEventListener() {}, renderContainedImage: () => "
", renderTemplate: () => "
", reportActivity() {}, requireActivityEvents() {}, requireConsent() {}, requireOnlyFirstPartyIds() {}, requireSecureCookies() {}, requireTcf20() {}, sendEvent, sendPageViewEvent: (a, callback) => call(callback, {}), sendSpaRecsClick() {}, setAccountId() {}, setAllConsentsTo() {}, setClientStorageVariable() {}, setCompatMode() {}, setConsent() {}, setCookie() {}, setCustomParameters() {}, setEventAttributes() {}, setGeoPosition() {}, setNodeValue() {}, setRandomId() {}, setRestrictionsToConsentClasses() {}, setRetargetingParameters() {}, setSiteId() {}, setUserProfileParameters() {}, setupIabCmp() {}, setupTcfApi() {}, shouldPollActivity() {}, startLocalStats() {}, startSessionAnnotation() {}, stopAllSessionAnnotations() {}, stopSessionAnnotation() {}, sync() {}, trackAmpIFrame() {}, trackElement() {}, trim: s => s.trim(), }; window.cxTest = window.cX; window.cx_pollActiveTime = () => undefined; window.cx_pollActivity = () => undefined; window.cx_pollFragmentMessage = () => undefined; const execQueue = (lib, queue) => { return () => { const invoke = invokeOn(lib); setTimeout(() => { queue.push = cmd => { setTimeout(() => invoke(...cmd), 1); }; for (const cmd of queue) { invoke(...cmd); } }, 25); }; }; window.cx_callQueueExecute = execQueue(library, callQueue); window.cxCCE_callQueueExecute = execQueue(libraryCCE, callQueueCCE); window.cx_callQueueExecute(); window.cxCCE_callQueueExecute(); } PK ! cb?.([]), init: () => {}, loadSignals: (_, cb) => cb?.(), loadSignalsForSlots: (_, cb) => cb?.(), PTS: {}, }; } PK ! {}, }, partner: {}, invokeFunctionWhenAvailable: a => { a(); }, gup: d => "", privacy: { isDoNotTrackEnabled: () => false, setDoNotTrack: () => {}, getDoNotTrack: () => false, }, setSubCookie: () => {}, }; const noopfn = () => {}; const w = window; w.cmAddShared = noopfn; w.cmCalcSKUString = noopfn; w.cmCreateManualImpressionTag = noopfn; w.cmCreateManualLinkClickTag = noopfn; w.cmCreateManualPageviewTag = noopfn; w.cmCreateOrderTag = noopfn; w.cmCreatePageviewTag = noopfn; w.cmExecuteTagQueue = noopfn; w.cmRetrieveUserID = noopfn; w.cmSetClientID = noopfn; w.cmSetCurrencyCode = noopfn; w.cmSetFirstPartyIDs = noopfn; w.cmSetSubCookie = noopfn; w.cmSetupCookieMigration = noopfn; w.cmSetupNormalization = noopfn; w.cmSetupOther = noopfn; w.cmStartTagSet = noopfn; w.cmCreateConversionEventTag = noopfn; w.cmCreateDefaultPageviewTag = noopfn; w.cmCreateElementTag = noopfn; w.cmCreateManualImpressionTag = noopfn; w.cmCreateManualLinkClickTag = noopfn; w.cmCreateManualPageviewTag = noopfn; w.cmCreatePageElementTag = noopfn; w.cmCreatePageviewTag = noopfn; w.cmCreateProductElementTag = noopfn; w.cmCreateProductviewTag = noopfn; w.cmCreateTechPropsTag = noopfn; w.cmLoadIOConfig = noopfn; w.cmSetClientID = noopfn; w.cmSetCurrencyCode = noopfn; w.cmSetFirstPartyIDs = noopfn; w.cmSetupCookieMigration = noopfn; w.cmSetupNormalization = noopfn; w.cmSetupOther = b => { for (const a in b) { window[a] = b[a]; } }; const techProps = {}; w.coremetrics = { cmLastReferencedPageID: "", cmLoad: noopfn, cmUpdateConfig: noopfn, getTechProps: () => techProps, isDef: c => typeof c !== "undefined" && c, }; } PK !<4shims/empty-script.js/* 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/. */ /* This script is intentionally empty */ PK !<shims/empty-shim.txtPK !< shims/everest.js/* 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 1728114 - Shim Adobe EverestJS * * Sites assuming EverestJS will load can break if it is blocked. * This shim mitigates that breakage. */ if (!window.__ql) { window.__ql = {}; } if (!window.EF) { const AdCloudLocalStorage = { get: (_, cb) => cb(), isInitDone: true, isInitSuccess: true, }; const emptyObj = {}; const nullSrc = { getHosts: () => [undefined], getProtocols: () => [undefined], hash: {}, hashParamsOrder: [], host: undefined, path: [], port: undefined, query: {}, queryDelimiter: "&", queryParamsOrder: [], queryPrefix: "?", queryWithoutEncode: {}, respectEmptyQueryParamValue: undefined, scheme: undefined, text: "//", userInfo: undefined, }; const pixelDetailsEvent = { addToDom() {}, canAddToDom: () => false, fire() {}, getDomElement() {}, initializeUri() {}, pixelDetailsReceiver() {}, scheme: "https:", uri: nullSrc, userid: 0, }; window.EF = { AdCloudLocalStorage, accessTopUrl: 0, acquireCookieMatchingSlot() {}, addListener() {}, addPixelDetailsReadyListener() {}, addToDom() {}, allow3rdPartyPixels: 1, appData: "", appendDictionary() {}, checkGlobalSid() {}, checkUrlParams() {}, cmHost: "cm.everesttech.net", context: { isFbApp: () => 0, isPageview: () => false, isSegmentation: () => false, isTransaction: () => false, }, conversionData: "", cookieMatchingSlots: 1, debug: 0, deserializeUrlParams: () => emptyObj, doCookieMatching() {}, ef_itp_ls: false, eventType: "", executeAfterLoad() {}, executeOnloadCallbacks() {}, expectedTrackingParams: ["ev_cl", "ev_sid"], fbIsApp: 0, fbsCM: 0, fbsPixelId: 0, filterList: () => [], getArrayIndex: -1, getConversionData: () => "", getConversionDataFromLocalStorage: cb => cb(), getDisplayClickUri: () => "", getEpochFromEfUniq: () => 0, getFirstLevelObjectCopy: () => emptyObj, getInvisibleIframeElement() {}, getInvisibleImageElement() {}, getMacroSubstitutedText: () => "", getPixelDetails: cb => cb({}), getScriptElement() {}, getScriptSrc: () => "", getServerParams: () => emptyObj, getSortedAttributes: () => [], getTrackingParams: () => emptyObj, getTransactionParams: () => emptyObj, handleConversionData() {}, impressionProperties: "", impressionTypes: ["impression", "impression_served"], inFloodlight: 0, init(config) { try { const { userId } = config; window.EF.userId = userId; pixelDetailsEvent.userId = userId; } catch (_) {} }, initializeEFVariables() {}, isArray: a => Array.isArray(a), isEmptyDictionary: () => true, isITPEnabled: () => false, isPermanentCookieSet: () => false, isSearchClick: () => 0, isXSSReady() {}, jsHost: "www.everestjs.net", jsTagAdded: 0, location: nullSrc, locationHref: nullSrc, locationSkipBang: nullSrc, log() {}, main() {}, main2() {}, newCookieMatchingEvent: () => emptyObj, newFbsCookieMatching: () => emptyObj, newImpression: () => emptyObj, newPageview: () => emptyObj, newPixelDetails: () => emptyObj, newPixelEvent: () => emptyObj, newPixelServerDisplayClickRedirectUri: () => emptyObj, newPixelServerGenericRedirectUri: () => emptyObj, newPixelServerUri: () => emptyObj, newProductSegment: () => emptyObj, newSegmentJavascript: () => emptyObj, newTransaction: () => emptyObj, newUri: () => emptyObj, onloadCallbacks: [], pageViewProperties: "", pageviewProperties: "", pixelDetails: {}, pixelDetailsAdded: 1, pixelDetailsEvent, pixelDetailsParams: [], pixelDetailsReadyCallbackFns: [], pixelDetailsRecieverCalled: 1, pixelHost: "pixel.everesttech.net", protocol: document?.location?.protocol || "", referrer: nullSrc, removeListener() {}, searchSegment: "", segment: "", serverParamsListener() {}, sid: 0, sku: "", throttleCookie: "", trackingJavascriptSrc: nullSrc, transactionObjectList: [], transactionProperties: "", userServerParams: {}, userid: 0, }; } PK !<f[BBshims/facebook-sdk.js/* 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 1226498 - Shim Facebook SDK * * This shim provides functionality to enable Facebook's authenticator on third * party sites ("continue/log in with Facebook" buttons). This includes rendering * the button as the SDK would, if sites require it. This way, if users wish to * opt into the Facebook login process regardless of the tracking consequences, * they only need to click the button as usual. * * In addition, the shim also attempts to provide placeholders for Facebook * videos, which users may click to opt into seeing the video (also despite * the increased tracking risks). This is an experimental feature enabled * that is only currently enabled on nightly builds. * * Finally, this shim also stubs out as much of the SDK as possible to prevent * breaking on sites which expect that it will always successfully load. */ if (!window.FB) { const FacebookLogoURL = "https://smartblock.firefox.etp/facebook.svg"; const PlayIconURL = "https://smartblock.firefox.etp/play.svg"; const originalUrl = document.currentScript.src; let haveUnshimmed; let initInfo; let activeOnloginAttribute; const placeholdersToRemoveOnUnshim = new Set(); const loggedGraphApiCalls = []; const eventHandlers = new Map(); function getGUID() { const v = crypto.getRandomValues(new Uint8Array(20)); return Array.from(v, c => c.toString(16)).join(""); } const sendMessageToAddon = (function () { const shimId = "FacebookSDK"; const pendingMessages = new Map(); const channel = new MessageChannel(); channel.port1.onerror = console.error; channel.port1.onmessage = event => { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = getGUID(); return new Promise(resolve => { const payload = { message, messageId, shimId }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); const isNightly = sendMessageToAddon("getOptions").then(opts => { return opts.releaseBranch === "nightly"; }); function makeLoginPlaceholder(target) { // Sites may provide their own login buttons, or rely on the Facebook SDK // to render one for them. For the latter case, we provide placeholders // which try to match the examples and documentation here: // https://developers.facebook.com/docs/facebook-login/web/login-button/ if (target.textContent || target.hasAttribute("fb-xfbml-state")) { return; } target.setAttribute("fb-xfbml-state", ""); const size = target.getAttribute("data-size") || "large"; let font, margin, minWidth, maxWidth, height, iconHeight; if (size === "small") { font = 11; margin = 8; minWidth = maxWidth = 200; height = 20; iconHeight = 12; } else if (size === "medium") { font = 13; margin = 8; minWidth = 200; maxWidth = 320; height = 28; iconHeight = 16; } else { font = 16; minWidth = 240; maxWidth = 400; margin = 12; height = 40; iconHeight = 24; } const wattr = target.getAttribute("data-width") || ""; const width = wattr === "100%" ? wattr : `${parseFloat(wattr) || minWidth}px`; const round = target.getAttribute("data-layout") === "rounded" ? 20 : 4; const text = target.getAttribute("data-button-type") === "continue_with" ? "Continue with Facebook" : "Log in with Facebook"; const button = document.createElement("div"); button.style = ` display: flex; align-items: center; justify-content: center; padding-left: ${margin + iconHeight}px; ${width}; min-width: ${minWidth}px; max-width: ${maxWidth}px; height: ${height}px; border-radius: ${round}px; -moz-text-size-adjust: none; -moz-user-select: none; color: #fff; font-size: ${font}px; font-weight: bold; font-family: Helvetica, Arial, sans-serif; letter-spacing: .25px; background-color: #1877f2; background-repeat: no-repeat; background-position: ${margin}px 50%; background-size: ${iconHeight}px ${iconHeight}px; background-image: url(${FacebookLogoURL}); `; button.textContent = text; target.appendChild(button); target.addEventListener("click", () => { activeOnloginAttribute = target.getAttribute("onlogin"); }); } async function makeVideoPlaceholder(target) { // For videos, we provide a more generic placeholder of roughly the // expected size with a play button, as well as a Facebook logo. if (!(await isNightly) || target.hasAttribute("fb-xfbml-state")) { return; } target.setAttribute("fb-xfbml-state", ""); let width = parseInt(target.getAttribute("data-width")); let height = parseInt(target.getAttribute("data-height")); if (height) { height = `${width * 0.6}px`; } else { height = `100%; min-height:${width * 0.75}px`; } if (width) { width = `${width}px`; } else { width = `100%; min-width:200px`; } const placeholder = document.createElement("div"); placeholdersToRemoveOnUnshim.add(placeholder); placeholder.style = ` width: ${width}; height: ${height}; top: 0px; left: 0px; background: #000; color: #fff; text-align: center; cursor: pointer; display: flex; align-items: center; justify-content: center; background-image: url(${FacebookLogoURL}), url(${PlayIconURL}); background-position: calc(100% - 24px) 24px, 50% 47.5%; background-repeat: no-repeat, no-repeat; background-size: 43px 42px, 25% 25%; -moz-text-size-adjust: none; -moz-user-select: none; color: #fff; align-items: center; padding-top: 200px; font-size: 14pt; `; placeholder.textContent = "Click to allow blocked Facebook content"; placeholder.addEventListener("click", evt => { if (!evt.isTrusted) { return; } allowFacebookSDK(() => { placeholdersToRemoveOnUnshim.forEach(p => p.remove()); }); }); target.innerHTML = ""; target.appendChild(placeholder); } // We monitor for XFBML objects as Facebook SDK does, so we // can provide placeholders for dynamically-added ones. const xfbmlObserver = new MutationObserver(mutations => { for (let { addedNodes, target, type } of mutations) { const nodes = type === "attributes" ? [target] : addedNodes; for (const node of nodes) { if (node?.classList?.contains("fb-login-button")) { makeLoginPlaceholder(node); } if (node?.classList?.contains("fb-video")) { makeVideoPlaceholder(node); } } } }); xfbmlObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], }); const needPopup = !/app_runner/.test(window.name) && !/iframe_canvas/.test(window.name); const popupName = getGUID(); let activePopup; if (needPopup) { const oldWindowOpen = window.open; window.open = function (href, name, params) { try { const url = new URL(href, window.location.href); if ( url.protocol === "https:" && (url.hostname === "m.facebook.com" || url.hostname === "www.facebook.com") && url.pathname.endsWith("/oauth") ) { name = popupName; } } catch (e) { console.error(e); } return oldWindowOpen.call(window, href, name, params); }; } let allowingFacebookPromise; async function allowFacebookSDK(postInitCallback) { if (allowingFacebookPromise) { return allowingFacebookPromise; } let resolve, reject; allowingFacebookPromise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); await sendMessageToAddon("optIn"); xfbmlObserver.disconnect(); const shim = window.FB; window.FB = undefined; // We need to pass the site's initialization info to the real // SDK as it loads, so we use the fbAsyncInit mechanism to // do so, also ensuring our own post-init callbacks are called. const oldInit = window.fbAsyncInit; window.fbAsyncInit = () => { try { if (typeof initInfo !== "undefined") { window.FB.init(initInfo); } else if (oldInit) { oldInit(); } } catch (e) { console.error(e); } // Also re-subscribe any SDK event listeners as early as possible. for (const [name, fns] of eventHandlers.entries()) { for (const fn of fns) { window.FB.Event.subscribe(name, fn); } } // Allow the shim to do any post-init work early as well, while the // SDK script finishes loading and we ask it to re-parse XFBML etc. postInitCallback?.(); }; const script = document.createElement("script"); script.src = originalUrl; script.addEventListener("error", () => { allowingFacebookPromise = null; script.remove(); activePopup?.close(); window.FB = shim; reject(); alert("Failed to load Facebook SDK; please try again"); }); script.addEventListener("load", () => { haveUnshimmed = true; // After the real SDK has fully loaded we re-issue any Graph API // calls the page is waiting on, as well as requesting for it to // re-parse any XBFML elements (including ones with placeholders). for (const args of loggedGraphApiCalls) { try { window.FB.api.apply(window.FB, args); } catch (e) { console.error(e); } } window.FB.XFBML.parse(document.body, resolve); }); document.head.appendChild(script); return allowingFacebookPromise; } function buildPopupParams() { // We try to match Facebook's popup size reasonably closely. const { outerWidth, outerHeight, screenX, screenY } = window; const { width, height } = window.screen; const w = Math.min(width, 400); const h = Math.min(height, 400); const ua = navigator.userAgent; const isMobile = ua.includes("Mobile") || ua.includes("Tablet"); const left = screenX + (screenX < 0 ? width : 0) + (outerWidth - w) / 2; const top = screenY + (screenY < 0 ? height : 0) + (outerHeight - h) / 2.5; let params = `left=${left},top=${top},width=${w},height=${h},scrollbars=1,toolbar=0,location=1`; if (!isMobile) { params = `${params},width=${w},height=${h}`; } return params; } // If a page stores the window.FB reference of the shim, then we // want to have it proxy calls to the real SDK once we've unshimmed. function ensureProxiedToUnshimmed(obj) { const shim = {}; for (const key in obj) { const value = obj[key]; if (typeof value === "function") { shim[key] = function () { if (haveUnshimmed) { return window.FB[key].apply(window.FB, arguments); } return value.apply(this, arguments); }; } else if (typeof value !== "object" || value === null) { shim[key] = value; } else { shim[key] = ensureProxiedToUnshimmed(value); } } return new Proxy(shim, { get: (shimmed, key) => (haveUnshimmed ? window.FB : shimmed)[key], }); } window.FB = ensureProxiedToUnshimmed({ api() { loggedGraphApiCalls.push(arguments); }, AppEvents: { activateApp() {}, clearAppVersion() {}, clearUserID() {}, EventNames: { ACHIEVED_LEVEL: "fb_mobile_level_achieved", ADDED_PAYMENT_INFO: "fb_mobile_add_payment_info", ADDED_TO_CART: "fb_mobile_add_to_cart", ADDED_TO_WISHLIST: "fb_mobile_add_to_wishlist", COMPLETED_REGISTRATION: "fb_mobile_complete_registration", COMPLETED_TUTORIAL: "fb_mobile_tutorial_completion", INITIATED_CHECKOUT: "fb_mobile_initiated_checkout", PAGE_VIEW: "fb_page_view", RATED: "fb_mobile_rate", SEARCHED: "fb_mobile_search", SPENT_CREDITS: "fb_mobile_spent_credits", UNLOCKED_ACHIEVEMENT: "fb_mobile_achievement_unlocked", VIEWED_CONTENT: "fb_mobile_content_view", }, getAppVersion: () => "", getUserID: () => "", logEvent() {}, logPageView() {}, logPurchase() {}, ParameterNames: { APP_USER_ID: "_app_user_id", APP_VERSION: "_appVersion", CONTENT_ID: "fb_content_id", CONTENT_TYPE: "fb_content_type", CURRENCY: "fb_currency", DESCRIPTION: "fb_description", LEVEL: "fb_level", MAX_RATING_VALUE: "fb_max_rating_value", NUM_ITEMS: "fb_num_items", PAYMENT_INFO_AVAILABLE: "fb_payment_info_available", REGISTRATION_METHOD: "fb_registration_method", SEARCH_STRING: "fb_search_string", SUCCESS: "fb_success", }, setAppVersion() {}, setUserID() {}, updateUserProperties() {}, }, Canvas: { getHash: () => "", getPageInfo(cb) { cb?.call(this, { clientHeight: 1, clientWidth: 1, offsetLeft: 0, offsetTop: 0, scrollLeft: 0, scrollTop: 0, }); }, Plugin: { hidePluginElement() {}, showPluginElement() {}, }, Prefetcher: { COLLECT_AUTOMATIC: 0, COLLECT_MANUAL: 1, addStaticResource() {}, setCollectionMode() {}, }, scrollTo() {}, setAutoGrow() {}, setDoneLoading() {}, setHash() {}, setSize() {}, setUrlHandler() {}, startTimer() {}, stopTimer() {}, }, Event: { subscribe(e, f) { if (!eventHandlers.has(e)) { eventHandlers.set(e, new Set()); } eventHandlers.get(e).add(f); }, unsubscribe(e, f) { eventHandlers.get(e)?.delete(f); }, }, frictionless: { init() {}, isAllowed: () => false, }, gamingservices: { friendFinder() {}, uploadImageToMediaLibrary() {}, }, getAccessToken: () => null, getAuthResponse() { return { status: "" }; }, getLoginStatus(cb) { cb?.call(this, { status: "unknown" }); }, getUserID() {}, init(_initInfo) { initInfo = _initInfo; // in case the site is not using fbAsyncInit }, login(cb, opts) { // We have to load Facebook's script, and then wait for it to call // window.open. By that time, the popup blocker will likely trigger. // So we open a popup now with about:blank, and then make sure FB // will re-use that same popup later. if (needPopup) { activePopup = window.open("about:blank", popupName, buildPopupParams()); } allowFacebookSDK(() => { activePopup = undefined; function runPostLoginCallbacks() { try { cb?.apply(this, arguments); } catch (e) { console.error(e); } if (activeOnloginAttribute) { setTimeout(activeOnloginAttribute, 1); activeOnloginAttribute = undefined; } } window.FB.login(runPostLoginCallbacks, opts); }).catch(() => { activePopup = undefined; activeOnloginAttribute = undefined; try { cb?.({}); } catch (e) { console.error(e); } }); }, logout(cb) { cb?.call(this); }, ui(params, fn) { if (params.method === "permissions.oauth") { window.FB.login(fn, params); } }, XFBML: { parse(node, cb) { node = node || document; node.querySelectorAll(".fb-login-button").forEach(makeLoginPlaceholder); node.querySelectorAll(".fb-video").forEach(makeVideoPlaceholder); try { cb?.call(this); } catch (e) { console.error(e); } }, }, }); window.FB.XFBML.parse(); window?.fbAsyncInit?.(); } PK !<{Qshims/facebook.svg PK ! { if (!proto || proto.forEach === forEach) { return; } try { Object.defineProperty(proto, "forEach", { enumerable: false, get: () => forEach, }); } catch (_) { proto.forEach = forEach; } }; for (const name of DOMIterables) { handlePrototype(window[name]?.prototype); } } if (!window.conversant?.launch) { const c = (window.conversant = window.conversant || {}); c.launch = () => {}; } PK !<Y' ' shims/firebase.js/* 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 1767407 - Shim Firebase * * Sites relying on firebase-messaging.js will break in Private * browsing mode because it assumes that they require service * workers and indexedDB, when they generally do not. */ /* globals cloneInto */ (function () { const win = window.wrappedJSObject; const emptyObj = new win.Object(); const emptyArr = new win.Array(); const emptyMsg = cloneInto({ message: "" }, window); const noOpFn = cloneInto(function () {}, window, { cloneFunctions: true }); if (!win.indexedDB) { const idb = { open: () => win.Promise.reject(emptyMsg), }; Object.defineProperty(win, "indexedDB", { value: cloneInto(idb, window, { cloneFunctions: true }), }); } // bug 1778993 for (const name of [ "IDBCursor", "IDBDatabase", "IDBIndex", "IDBOpenDBRequest", "IDBRequest", "IDBTransaction", ]) { if (!win[name]) { Object.defineProperty(win, name, { value: emptyObj }); } } if (!win.serviceWorker) { const sw = { addEventListener() {}, getRegistrations: () => win.Promise.resolve(emptyArr), register: () => win.Promise.reject(emptyMsg), }; Object.defineProperty(navigator.wrappedJSObject, "serviceWorker", { value: cloneInto(sw, window, { cloneFunctions: true }), }); // bug 1779536 Object.defineProperty(navigator.wrappedJSObject.serviceWorker, "ready", { value: new win.Promise(noOpFn), }); } // bug 1750699 if (!win.PushManager) { Object.defineProperty(win, "PushManager", { value: emptyObj }); } // bug 1750699 if (!win.PushSubscription) { const ps = { prototype: { getKey() {}, }, }; Object.defineProperty(win, "PushSubscription", { value: cloneInto(ps, window, { cloneFunctions: true }), }); } // bug 1750699 if (!win.ServiceWorkerRegistration) { const swr = { prototype: { showNotification() {}, }, }; Object.defineProperty(win, "ServiceWorkerRegistration", { value: cloneInto(swr, window, { cloneFunctions: true }), }); } })(); PK !< "", }; window.gapi = { _pl: true, additnow: stub, autocomplete: stub, backdrop: stub, blogger: stub, commentcount: stub, comments: stub, community: stub, donation: stub, family_creation: stub, follow: stub, hangout: stub, health: stub, interactivepost: stub, load() {}, logutil: { enableDebugLogging() {}, }, page: stub, partnersbadge: stub, person: stub, platform: { go() {}, }, playemm: stub, playreview: stub, plus: stub, plusone: stub, post: stub, profile: stub, ratingbadge: stub, recobar: stub, savetoandroidpay: stub, savetodrive: stub, savetowallet: stub, share: stub, sharetoclassroom: stub, shortlists: stub, signin: stub, signin2: stub, surveyoptin: stub, visibility: stub, youtube: stub, ytsubscribe: stub, zoomableimage: stub, }; } for (const e of document.querySelectorAll("ins.adsbygoogle")) { e.style.maxWidth = "0px"; } PK ! { id = id || opts?.trackerId; if (!id) { return undefined; } cookie = cookie || opts?.cookieDomain || "_ga"; name = name || opts?.name || DEFAULT_TRACKER_NAME; if (!trackers.has(name)) { let props; try { props = new Map(Object.entries(opts)); } catch (_) { props = new Map(); } trackers.set(name, { get(p) { if (p === "name") { return name; } else if (p === "trackingId") { return id; } else if (p === "cookieDomain") { return cookie; } return props.get(p); }, ma() {}, requireSync() {}, send() {}, set(p, v) { if (typeof p !== "object") { p = Object.fromEntries([[p, v]]); } for (const k in p) { props.set(k, p[k]); if (k === "hitCallback") { run(p[k]); } } }, }); } return trackers.get(name); }; const cmdRE = /((?.*?)\.)?((?.*?):)?(?.*)/; function ga(cmd, ...args) { if (arguments.length === 1 && typeof cmd === "function") { run(cmd, trackers.get(DEFAULT_TRACKER_NAME)); return undefined; } if (typeof cmd !== "string") { return undefined; } const groups = cmdRE.exec(cmd)?.groups; if (!groups) { console.error("Could not parse GA command", cmd); return undefined; } let { name, plugin, method } = groups; if (plugin) { return undefined; } if (cmd === "set") { trackers.get(name)?.set(args[0], args[1]); } if (method === "remove") { trackers.delete(name); return undefined; } if (cmd === "send") { run(args.at(-1)?.hitCallback); return undefined; } if (method === "create") { let id, cookie, fields; for (const param of args.slice(0, 4)) { if (typeof param === "object") { fields = param; break; } if (id === undefined) { id = param; } else if (cookie === undefined) { cookie = param; } else { name = param; } } return create(id, cookie, name, fields); } return undefined; } Object.assign(ga, { create: (a, b, c, d) => ga("create", a, b, c, d), getAll: () => Array.from(trackers.values()), getByName: name => trackers.get(name), loaded: true, remove: t => ga("remove", t), }); // Process any GA command queue the site pre-declares (bug 1736850) const q = window[window.GoogleAnalyticsObject || "ga"]?.q; window[window.GoogleAnalyticsObject || "ga"] = ga; if (Array.isArray(q)) { const push = o => { ga(...o); return true; }; q.push = push; q.forEach(o => push(o)); } // Also process the Google Tag Manager dataLayer (bug 1713688) const dl = window.dataLayer; if (Array.isArray(dl) && !dl.find(e => e["gtm.start"])) { const push = function (o) { setTimeout(() => run(o?.eventCallback), 1); return true; }; dl.push = push; dl.forEach(o => push(o)); } // Run dataLayer.hide.end to handle asynchide (bug 1628151) run(window.dataLayer?.hide?.end); } if (!window?.gaplugins?.Linker) { window.gaplugins = window.gaplugins || {}; window.gaplugins.Linker = class { autoLink() {} decorate(url) { return url; } passthrough() {} }; } PK ! {}; } PK !<*pU" " shims/google-analytics-legacy.js/* 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/. */ // based on https://github.com/gorhill/uBlock/blob/6f49e079db0262e669b70f4169924f796ac8db7c/src/web_accessible_resources/google-analytics_ga.js "use strict"; if (!window._gaq) { function noopfn() {} const gaq = { Na: noopfn, O: noopfn, Sa: noopfn, Ta: noopfn, Va: noopfn, _createAsyncTracker: noopfn, _getAsyncTracker: noopfn, _getPlugin: noopfn, push: a => { if (typeof a === "function") { a(); return; } if (!Array.isArray(a)) { return; } if ( typeof a[0] === "string" && /(^|\.)_link$/.test(a[0]) && typeof a[1] === "string" ) { window.location.assign(a[1]); } if ( a[0] === "_set" && a[1] === "hitCallback" && typeof a[2] === "function" ) { a[2](); } }, }; const tracker = { _addIgnoredOrganic: noopfn, _addIgnoredRef: noopfn, _addItem: noopfn, _addOrganic: noopfn, _addTrans: noopfn, _clearIgnoredOrganic: noopfn, _clearIgnoredRef: noopfn, _clearOrganic: noopfn, _cookiePathCopy: noopfn, _deleteCustomVar: noopfn, _getName: noopfn, _setAccount: noopfn, _getAccount: noopfn, _getClientInfo: noopfn, _getDetectFlash: noopfn, _getDetectTitle: noopfn, _getLinkerUrl: a => a, _getLocalGifPath: noopfn, _getServiceMode: noopfn, _getVersion: noopfn, _getVisitorCustomVar: noopfn, _initData: noopfn, _link: noopfn, _linkByPost: noopfn, _setAllowAnchor: noopfn, _setAllowHash: noopfn, _setAllowLinker: noopfn, _setCampContentKey: noopfn, _setCampMediumKey: noopfn, _setCampNameKey: noopfn, _setCampNOKey: noopfn, _setCampSourceKey: noopfn, _setCampTermKey: noopfn, _setCampaignCookieTimeout: noopfn, _setCampaignTrack: noopfn, _setClientInfo: noopfn, _setCookiePath: noopfn, _setCookiePersistence: noopfn, _setCookieTimeout: noopfn, _setCustomVar: noopfn, _setDetectFlash: noopfn, _setDetectTitle: noopfn, _setDomainName: noopfn, _setLocalGifPath: noopfn, _setLocalRemoteServerMode: noopfn, _setLocalServerMode: noopfn, _setReferrerOverride: noopfn, _setRemoteServerMode: noopfn, _setSampleRate: noopfn, _setSessionTimeout: noopfn, _setSiteSpeedSampleRate: noopfn, _setSessionCookieTimeout: noopfn, _setVar: noopfn, _setVisitorCookieTimeout: noopfn, _trackEvent: noopfn, _trackPageLoadTime: noopfn, _trackPageview: noopfn, _trackSocial: noopfn, _trackTiming: noopfn, _trackTrans: noopfn, _visitCode: noopfn, }; const gat = { _anonymizeIP: noopfn, _createTracker: noopfn, _forceSSL: noopfn, _getPlugin: noopfn, _getTracker: () => tracker, _getTrackerByName: () => tracker, _getTrackers: noopfn, aa: noopfn, ab: noopfn, hb: noopfn, la: noopfn, oa: noopfn, pa: noopfn, u: noopfn, }; window._gat = gat; const aa = window._gaq || []; if (Array.isArray(aa)) { while (aa[0]) { gaq.push(aa.shift()); } } window._gaq = gaq.qf = gaq; } PK !<\555shims/google-ima.js/* 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/. */ /** * Bug 1713690 - Shim Google Interactive Media Ads ima3.js * * Many sites use ima3.js for ad bidding and placement, often in conjunction * with Google Publisher Tags, Prebid.js and/or other scripts. This shim * provides a stubbed-out version of the API which helps work around related * site breakage, such as black bxoes where videos ought to be placed. */ if (!window.google?.ima?.VERSION) { const VERSION = "3.517.2"; const CheckCanAutoplay = (function () { // Sourced from: https://searchfox.org/mozilla-central/source/dom/media/gtest/negative_duration.mp4 const TEST_VIDEO = new Blob( [ new Uint32Array([ 469762048, 1887007846, 1752392036, 0, 913273705, 1717987696, 828601953, -1878917120, 1987014509, 1811939328, 1684567661, 0, 0, 0, -402456576, 0, 256, 1, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 33554432, -201261056, 1801548404, 1744830464, 1684564852, 251658241, 0, 0, 0, 0, 16777216, 0, -1, -1, 0, 0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 64, 5, 53250, -2080309248, 1634296941, 738197504, 1684563053, 1, 0, 0, 0, 0, -2137614336, -1, -1, 50261, 754974720, 1919706216, 0, 0, 1701079414, 0, 0, 0, 1701079382, 1851869295, 1919249508, 16777216, 1852402979, 102, 1752004116, 100, 1, 0, 0, 1852400676, 102, 1701995548, 102, 0, 1, 1819440396, 32, 1, 1651799011, 108, 1937011607, 100, 0, 1, 1668702599, 49, 0, 1, 0, 0, 0, 33555712, 4718800, 4718592, 0, 65536, 0, 0, 0, 0, 0, 0, 0, 0, 16776984, 1630601216, 21193590, -14745500, 1729626337, -1407254428, 89161945, 1049019, 9453056, -251611125, 27269507, -379058688, -1329024392, 268435456, 1937011827, 0, 0, 268435456, 1668510835, 0, 0, 335544320, 2054386803, 0, 0, 0, 268435456, 1868788851, 0, 0, 671088640, 2019915373, 536870912, 2019914356, 0, 16777216, 16777216, 0, 0, 0, ]), ], { type: "video/mp4" } ); let testVideo = undefined; return function () { if (!testVideo) { testVideo = document.createElement("video"); testVideo.style = "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0"; testVideo.setAttribute("muted", "muted"); testVideo.setAttribute("playsinline", "playsinline"); testVideo.src = URL.createObjectURL(TEST_VIDEO); document.body.appendChild(testVideo); } return testVideo.play(); }; })(); let ima = {}; class AdDisplayContainer { destroy() {} initialize() {} } class ImaSdkSettings { #c = true; #f = {}; #i = false; #l = ""; #p = ""; #r = 0; #t = ""; #v = ""; getCompanionBackfill() {} getDisableCustomPlaybackForIOS10Plus() { return this.#i; } getFeatureFlags() { return this.#f; } getLocale() { return this.#l; } getNumRedirects() { return this.#r; } getPlayerType() { return this.#t; } getPlayerVersion() { return this.#v; } getPpid() { return this.#p; } isCookiesEnabled() { return this.#c; } setAutoPlayAdBreaks() {} setCompanionBackfill() {} setCookiesEnabled(c) { this.#c = !!c; } setDisableCustomPlaybackForIOS10Plus(i) { this.#i = !!i; } setFeatureFlags(f) { this.#f = f; } setLocale(l) { this.#l = l; } setNumRedirects(r) { this.#r = r; } setPlayerType(t) { this.#t = t; } setPlayerVersion(v) { this.#v = v; } setPpid(p) { this.#p = p; } setSessionId(s) {} setVpaidAllowed(a) {} setVpaidMode(m) {} } ImaSdkSettings.CompanionBackfillMode = { ALWAYS: "always", ON_MASTER_AD: "on_master_ad", }; ImaSdkSettings.VpaidMode = { DISABLED: 0, ENABLED: 1, INSECURE: 2, }; class EventHandler { #listeners = new Map(); _dispatch(e) { const listeners = this.#listeners.get(e.type) || []; for (const listener of Array.from(listeners)) { try { listener(e); } catch (r) { console.error(r); } } } addEventListener(t, c) { if (!this.#listeners.has(t)) { this.#listeners.set(t, new Set()); } this.#listeners.get(t).add(c); } removeEventListener(t, c) { this.#listeners.get(t)?.delete(c); } } class AdsLoader extends EventHandler { #settings = new ImaSdkSettings(); contentComplete() {} destroy() {} getSettings() { return this.#settings; } getVersion() { return VERSION; } requestAds(r, c) { // If autoplay is disabled and the page is trying to autoplay a tracking // ad, then IMA fails with an error, and the page is expected to request // ads again later when the user clicks to play. CheckCanAutoplay().then( () => { const { ADS_MANAGER_LOADED } = AdsManagerLoadedEvent.Type; this._dispatch(new ima.AdsManagerLoadedEvent(ADS_MANAGER_LOADED)); }, () => { const e = new ima.AdError( "adPlayError", 1205, 1205, "The browser prevented playback initiated without user interaction." ); this._dispatch(new ima.AdErrorEvent(e)); } ); } } class AdsManager extends EventHandler { #volume = 1; collapse() {} configureAdsManager() {} destroy() {} discardAdBreak() {} expand() {} focus() {} getAdSkippableState() { return false; } getCuePoints() { return [0]; } getCurrentAd() { return currentAd; } getCurrentAdCuePoints() { return []; } getRemainingTime() { return 0; } getVolume() { return this.#volume; } init(w, h, m, e) {} isCustomClickTrackingUsed() { return false; } isCustomPlaybackUsed() { return false; } pause() {} requestNextAdBreak() {} resize(w, h, m) {} resume() {} setVolume(v) { this.#volume = v; } skip() {} start() { requestAnimationFrame(() => { for (const type of [ AdEvent.Type.LOADED, AdEvent.Type.STARTED, AdEvent.Type.CONTENT_RESUME_REQUESTED, AdEvent.Type.AD_BUFFERING, AdEvent.Type.FIRST_QUARTILE, AdEvent.Type.MIDPOINT, AdEvent.Type.THIRD_QUARTILE, AdEvent.Type.COMPLETE, AdEvent.Type.ALL_ADS_COMPLETED, ]) { try { this._dispatch(new ima.AdEvent(type)); } catch (e) { console.error(e); } } }); } stop() {} updateAdsRenderingSettings(s) {} } class AdsRenderingSettings {} class AdsRequest { setAdWillAutoPlay() {} setAdWillPlayMuted() {} setContinuousPlayback() {} } class AdPodInfo { getAdPosition() { return 1; } getIsBumper() { return false; } getMaxDuration() { return -1; } getPodIndex() { return 1; } getTimeOffset() { return 0; } getTotalAds() { return 1; } } class Ad { _pi = new AdPodInfo(); getAdId() { return ""; } getAdPodInfo() { return this._pi; } getAdSystem() { return ""; } getAdvertiserName() { return ""; } getApiFramework() { return null; } getCompanionAds() { return []; } getContentType() { return ""; } getCreativeAdId() { return ""; } getCreativeId() { return ""; } getDealId() { return ""; } getDescription() { return ""; } getDuration() { return 8.5; } getHeight() { return 0; } getMediaUrl() { return null; } getMinSuggestedDuration() { return -2; } getSkipTimeOffset() { return -1; } getSurveyUrl() { return null; } getTitle() { return ""; } getTraffickingParameters() { return {}; } getTraffickingParametersString() { return ""; } getUiElements() { return [""]; } getUniversalAdIdRegistry() { return "unknown"; } getUniversalAdIds() { return [""]; } getUniversalAdIdValue() { return "unknown"; } getVastMediaBitrate() { return 0; } getVastMediaHeight() { return 0; } getVastMediaWidth() { return 0; } getWidth() { return 0; } getWrapperAdIds() { return [""]; } getWrapperAdSystems() { return [""]; } getWrapperCreativeIds() { return [""]; } isLinear() { return true; } isSkippable() { return true; } } class CompanionAd { getAdSlotId() { return ""; } getContent() { return ""; } getContentType() { return ""; } getHeight() { return 1; } getWidth() { return 1; } } class AdError { #errorCode = -1; #message = ""; #type = ""; #vastErrorCode = -1; constructor(type, code, vast, message) { this.#errorCode = code; this.#message = message; this.#type = type; this.#vastErrorCode = vast; } getErrorCode() { return this.#errorCode; } getInnerError() {} getMessage() { return this.#message; } getType() { return this.#type; } getVastErrorCode() { return this.#vastErrorCode; } toString() { return `AdError ${this.#errorCode}: ${this.#message}`; } } AdError.ErrorCode = {}; AdError.Type = {}; const isEngadget = () => { try { for (const ctx of Object.values(window.vidible._getContexts())) { if (ctx.getPlayer()?.div?.innerHTML.includes("www.engadget.com")) { return true; } } } catch (_) {} return false; }; const currentAd = isEngadget() ? undefined : new Ad(); class AdEvent { constructor(type) { this.type = type; } getAd() { return currentAd; } getAdData() { return {}; } } AdEvent.Type = { AD_BREAK_READY: "adBreakReady", AD_BUFFERING: "adBuffering", AD_CAN_PLAY: "adCanPlay", AD_METADATA: "adMetadata", AD_PROGRESS: "adProgress", ALL_ADS_COMPLETED: "allAdsCompleted", CLICK: "click", COMPLETE: "complete", CONTENT_PAUSE_REQUESTED: "contentPauseRequested", CONTENT_RESUME_REQUESTED: "contentResumeRequested", DURATION_CHANGE: "durationChange", EXPANDED_CHANGED: "expandedChanged", FIRST_QUARTILE: "firstQuartile", IMPRESSION: "impression", INTERACTION: "interaction", LINEAR_CHANGE: "linearChange", LINEAR_CHANGED: "linearChanged", LOADED: "loaded", LOG: "log", MIDPOINT: "midpoint", PAUSED: "pause", RESUMED: "resume", SKIPPABLE_STATE_CHANGED: "skippableStateChanged", SKIPPED: "skip", STARTED: "start", THIRD_QUARTILE: "thirdQuartile", USER_CLOSE: "userClose", VIDEO_CLICKED: "videoClicked", VIDEO_ICON_CLICKED: "videoIconClicked", VIEWABLE_IMPRESSION: "viewable_impression", VOLUME_CHANGED: "volumeChange", VOLUME_MUTED: "mute", }; class AdErrorEvent { type = "adError"; #error = ""; constructor(error) { this.#error = error; } getError() { return this.#error; } getUserRequestContext() { return {}; } } AdErrorEvent.Type = { AD_ERROR: "adError", }; const manager = new AdsManager(); class AdsManagerLoadedEvent { constructor(type) { this.type = type; } getAdsManager() { return manager; } getUserRequestContext() { return {}; } } AdsManagerLoadedEvent.Type = { ADS_MANAGER_LOADED: "adsManagerLoaded", }; class CustomContentLoadedEvent {} CustomContentLoadedEvent.Type = { CUSTOM_CONTENT_LOADED: "deprecated-event", }; class CompanionAdSelectionSettings {} CompanionAdSelectionSettings.CreativeType = { ALL: "All", FLASH: "Flash", IMAGE: "Image", }; CompanionAdSelectionSettings.ResourceType = { ALL: "All", HTML: "Html", IFRAME: "IFrame", STATIC: "Static", }; CompanionAdSelectionSettings.SizeCriteria = { IGNORE: "IgnoreSize", SELECT_EXACT_MATCH: "SelectExactMatch", SELECT_NEAR_MATCH: "SelectNearMatch", }; class AdCuePoints { getCuePoints() { return []; } } class AdProgressData {} class UniversalAdIdInfo { getAdIdRegistry() { return ""; } getAdIsValue() { return ""; } } Object.assign(ima, { AdCuePoints, AdDisplayContainer, AdError, AdErrorEvent, AdEvent, AdPodInfo, AdProgressData, AdsLoader, AdsManager: manager, AdsManagerLoadedEvent, AdsRenderingSettings, AdsRequest, CompanionAd, CompanionAdSelectionSettings, CustomContentLoadedEvent, gptProxyInstance: {}, ImaSdkSettings, OmidAccessMode: { DOMAIN: "domain", FULL: "full", LIMITED: "limited", }, settings: new ImaSdkSettings(), UiElements: { AD_ATTRIBUTION: "adAttribution", COUNTDOWN: "countdown", }, UniversalAdIdInfo, VERSION, ViewMode: { FULLSCREEN: "fullscreen", NORMAL: "normal", }, }); if (!window.google) { window.google = {}; } window.google.ima = ima; } PK ! {}; } PK ! { 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); } } PK ! SafeFrame Container PK !<L//shims/history.js/* 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 1624853 - Shim Storage Access API on history.com * * history.com uses Adobe as a necessary third party to authenticating * with a TV provider. In order to accomodate this, we grant storage access * to the Adobe domain via the Storage Access API when the login or logout * buttons are clicked, then forward the click to continue as normal. */ console.warn( `When using oauth, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1624853 for details.` ); // Third-party origin we need to request storage access for. const STORAGE_ACCESS_ORIGIN = "https://sp.auth.adobe.com"; document.documentElement.addEventListener( "click", e => { const { target, isTrusted } = e; if (!isTrusted) { return; } const button = target.closest("a"); if (!button) { return; } const buttonLink = button.href; if (buttonLink?.startsWith("https://www.history.com/mvpd-auth")) { button.disabled = true; button.style.opacity = 0.5; e.stopPropagation(); e.preventDefault(); document .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) .then(() => { target.click(); }) .catch(() => { button.disabled = false; button.style.opacity = 1.0; }); } }, true ); PK ! {}, consent: () => {}, count: () => {}, ct: () => {}, deloptout: () => {}, doo: () => {}, e: () => {}, event: () => {}, getInvitation: () => {}, getPlus: () => {}, gi: () => {}, gp: () => {}, h: () => {}, hybrid: () => {}, i: () => {}, init: () => {}, oi: () => {}, optin: () => {}, setMultiIdentifier: () => {}, setoptout: () => {}, smi: () => {}, soo: () => {}, }; } PK !<\shims/iaspet.js/* 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 1713701 - Shim Integral Ad Science iaspet.js * * Some sites use iaspet to place content, often together with Google Publisher * Tags. This shim prevents breakage when the script is blocked. */ if (!window.__iasPET?.VERSION) { let queue = window?.__iasPET?.queue; if (!Array.isArray(queue)) { queue = []; } const response = JSON.stringify({ brandSafety: {}, slots: {}, }); function run(cmd) { try { cmd?.dataHandler?.(response); } catch (_) {} } queue.push = run; window.__iasPET = { VERSION: "1.16.18", queue, sessionId: "", setTargetingForAppNexus() {}, setTargetingForGPT() {}, start() {}, }; while (queue.length) { run(queue.shift()); } } PK !<5eshims/instagram.js/* 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 1804445 - instagram login broken with dFPI enabled * * Instagram login with Facebook account requires Facebook to have the storage * access under Instagram. This shim adds a request for storage access for * Facebook when the user tries to log in with a Facebook account. */ console.warn( `When logging in, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1804445 for details.` ); // Third-party origin we need to request storage access for. const STORAGE_ACCESS_ORIGIN = "https://www.facebook.com"; document.documentElement.addEventListener( "click", e => { const { target, isTrusted } = e; if (!isTrusted) { return; } const button = target.closest("button[type=button]"); if (!button) { return; } const form = target.closest("#loginForm"); if (!form) { return; } console.warn( "Calling the Storage Access API on behalf of " + STORAGE_ACCESS_ORIGIN ); button.disabled = true; e.stopPropagation(); e.preventDefault(); document .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) .then(() => { button.disabled = false; target.click(); }) .catch(() => { button.disabled = false; }); }, true ); PK ! { // Filter oauth popups. if (!url.startsWith(OAUTH_PATH_PREFIX)) { return origOpen(url, ...args); } // Request storage access for Kinja. document.requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN).then(() => { origOpen(url, ...args); }); // We don't have the window object yet which window.open returns, since the // sign-in flow is dependent on the async storage access request. This isn't // a problem as long as the website does not consume it. return null; }, window), }); PK ! { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = Math.random().toString(36).substring(2) + Date.now().toString(36); return new Promise(resolve => { const payload = { message, messageId, shimId, }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); async function go(options) { try { const o = document.getElementById("shims"); const cl = o.classList; cl.remove("red"); cl.add("green"); o.innerText = JSON.stringify(options || ""); } catch (_) {} if (window !== top) { return; } await sendMessageToAddon("optIn"); const s = document.createElement("script"); s.src = originalUrl; document.head.appendChild(s); } window[`${shimId}Promise`] = sendMessageToAddon("getOptions").then( options => { if (document.readyState !== "loading") { go(options); } else { window.addEventListener("DOMContentLoaded", () => { go(options); }); } } ); } PK !< `&&shims/maxmind-geoip.js/* 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 1754389 - Shim Maxmind GeoIP library * * Some sites rely on Maxmind's GeoIP library which gets blocked by ETP's * fingerprinter blocking. With the library window global not being defined * functionality may break or the site does not render at all. This shim * has it return the United States as the location for all users. */ if (!window.geoip2) { const continent = { code: "NA", geoname_id: 6255149, names: { de: "Nordamerika", en: "North America", es: "Norteamérica", fr: "Amérique du Nord", ja: "北アメリカ", "pt-BR": "América do Norte", ru: "Северная Америка", "zh-CN": "北美洲", }, }; const country = { geoname_id: 6252001, iso_code: "US", names: { de: "USA", en: "United States", es: "Estados Unidos", fr: "États-Unis", ja: "アメリカ合衆国", "pt-BR": "Estados Unidos", ru: "США", "zh-CN": "美国", }, }; const city = { names: { en: "", }, }; const callback = onSuccess => { requestAnimationFrame(() => { onSuccess({ city, continent, country, registered_country: country, }); }); }; window.geoip2 = { country: callback, city: callback, insights: callback, }; } PK !<shims/microsoftLogin.js/* 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/. */ const SANDBOX_ATTR = "allow-storage-access-by-user-activation"; console.warn( "Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1638383 for details." ); // Watches for MS auth iframes and adds missing sandbox attribute. The attribute // is required so the third-party iframe can gain access to its first party // storage via the Storage Access API. function init() { const observer = new MutationObserver(() => { document.body .querySelectorAll("iframe[id^='msalRenewFrame'][sandbox]") .forEach(frame => { frame.sandbox.add(SANDBOX_ATTR); }); }); observer.observe(document.body, { attributes: true, subtree: false, childList: true, }); } window.addEventListener("DOMContentLoaded", init); PK ! win.Promise.reject(emptyMsg), }; Object.defineProperty(win, "indexedDB", { value: cloneInto(idb, window, { cloneFunctions: true }), }); })(); PK ! slotConfig, getMoatTargetingForSlot(slot) { return targeting.get(slot?.getSlotElementId()); }, pageDataAvailable: () => true, safetyDataAvailable: () => true, setMoatTargetingForAllSlots() { for (const slot of window.googletag.pubads().getSlots() || []) { targeting.set(slot.getSlotElementId(), slot.getTargeting()); } }, setMoatTargetingForSlot(slot) { targeting.set(slot?.getSlotElementId(), slotConfig); }, slotDataAvailable() { return window.googletag?.pubads().getSlots().length > 0; }, }; } PK !<׎iy y shims/mochitest-shim-1.js/* 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"; /* globals browser */ if (!window.MochitestShimPromise) { const originalUrl = document.currentScript.src; const shimId = "MochitestShim"; const sendMessageToAddon = (function () { const pendingMessages = new Map(); const channel = new MessageChannel(); channel.port1.onerror = console.error; channel.port1.onmessage = event => { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = Math.random().toString(36).substring(2) + Date.now().toString(36); return new Promise(resolve => { const payload = { message, messageId, shimId, }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); async function go(options) { try { const o = document.getElementById("shims"); const cl = o.classList; cl.remove("red"); cl.add("green"); o.innerText = JSON.stringify(options || ""); } catch (_) {} window.shimPromiseResolve("shimmed"); if (window !== top) { window.optInPromiseResolve(false); return; } await sendMessageToAddon("optIn"); window.doingOptIn = true; const s = document.createElement("script"); s.src = originalUrl; s.onerror = () => window.optInPromiseResolve("error"); document.head.appendChild(s); } window[`${shimId}Promise`] = new Promise(resolve => { sendMessageToAddon("getOptions").then(options => { if (document.readyState !== "loading") { resolve(go(options)); } else { window.addEventListener("DOMContentLoaded", () => { resolve(go(options)); }); } }); }); } PK !<-  shims/mochitest-shim-2.js/* 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"; /* globals browser */ if (!window.testPromise) { const originalUrl = document.currentScript.src; const shimId = "MochitestShim2"; const sendMessageToAddon = (function () { const pendingMessages = new Map(); const channel = new MessageChannel(); channel.port1.onerror = console.error; channel.port1.onmessage = event => { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = Math.random().toString(36).substring(2) + Date.now().toString(36); return new Promise(resolve => { const payload = { message, messageId, shimId, }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); async function go(options) { try { const o = document.getElementById("shims"); const cl = o.classList; cl.remove("red"); cl.add("green"); o.innerText = JSON.stringify(options || ""); } catch (_) {} window.shimPromiseResolve("shimmed"); if (window !== top) { window.optInPromiseResolve(false); return; } await sendMessageToAddon("optIn"); window.doingOptIn = true; const s = document.createElement("script"); s.src = originalUrl; s.onerror = () => window.optInPromiseResolve("error"); document.head.appendChild(s); } sendMessageToAddon("getOptions").then(options => { if (document.readyState !== "loading") { go(options); } else { window.addEventListener("DOMContentLoaded", () => { go(options); }); } }); } PK !<$K*shims/mochitest-shim-3.js/* 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"; window.shimPromiseResolve("shimmed"); PK ! { return new NolTracker(); }; } PK !<shims/optimizely.js/* 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/. */ /** * Bug 1714431 - Shim Optimizely * * This shim stubs out window.optimizely for those sites which * break when it is not successfully loaded. */ if (!window.optimizely?.state) { const behavior = { query: () => [], }; const dcp = { getAttributeValue() {}, waitForAttributeValue: () => Promise.resolve(), }; const data = { accountId: "", audiences: {}, campaigns: {}, clientName: "js", clientVersion: "0.166.0", dcpServiceId: null, events: {}, experiments: {}, groups: {}, pages: {}, projectId: "", revision: "", snippetId: null, variations: {}, }; const activationId = String(Date.now()); const state = { getActivationId() { return activationId; }, getActiveExperimentIds() { return []; }, getCampaignStateLists() { return {}; }, getCampaignStates() { return {}; }, getDecisionObject() { return null; }, getDecisionString() { return null; }, getExperimentStates() { return {}; }, getPageStates() { return {}; }, getRedirectInfo() { return null; }, getVariationMap() { return {}; }, isGlobalHoldback() { return false; }, }; const poll = (fn, to) => { setInterval(() => { try { fn(); } catch (_) {} }, to); }; const waitUntil = test => { let interval, resolve; function check() { try { if (test()) { clearInterval(interval); resolve?.(); return true; } } catch (_) {} return false; } return new Promise(r => { resolve = r; if (check()) { resolve(); return; } interval = setInterval(check, 250); }); }; const waitForElement = sel => { return waitUntil(() => { document.querySelector(sel); }); }; const observeSelector = (sel, fn, opts) => { let interval; const observed = new Set(); function check() { try { for (const e of document.querySelectorAll(sel)) { if (observed.has(e)) { continue; } observed.add(e); try { fn(e); } catch (_) {} if (opts.once) { clearInterval(interval); } } } catch (_) {} } interval = setInterval(check, 250); const timeout = { opts }; if (timeout) { setTimeout(() => { clearInterval(interval); }, timeout); } }; const utils = { Promise: window.Promise, observeSelector, poll, waitForElement, waitUntil, }; const visitorId = { randomId: "", }; let browserVersion = ""; try { browserVersion = navigator.userAgent.match(/rv:(.*)\)/)[1]; } catch (_) {} const visitor = { browserId: "ff", browserVersion, currentTimestamp: Date.now(), custom: {}, customBehavior: {}, device: "desktop", device_type: "desktop_laptop", events: [], first_session: true, offset: 240, referrer: null, source_type: "direct", visitorId, }; window.optimizely = { data: { note: "Obsolete, use optimizely.get('data') instead", }, get(e) { switch (e) { case "behavior": return behavior; case "data": return data; case "dcp": return dcp; case "jquery": throw new Error("jQuery not implemented"); case "session": return undefined; case "state": return state; case "utils": return utils; case "visitor": return visitor; case "visitor_id": return visitorId; } return undefined; }, initialized: true, push() {}, state: {}, }; } PK ! PK ! { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = Math.random().toString(36).substring(2) + Date.now().toString(36); return new Promise(resolve => { const payload = { message, messageId, shimId, }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); const ramblerIdHelper = { getProfileInfo: (successCallback, errorCallback) => { successCallback({}); }, openAuth: () => { sendMessageToAddon("optIn").then(function () { const openAuthArgs = arguments; window.ramblerIdHelper = undefined; const s = document.createElement("script"); s.src = originalScript; document.head.appendChild(s); s.addEventListener("load", () => { const helper = window.ramblerIdHelper; for (const { fn, args } of callLog) { helper[fn].apply(helper, args); } helper.openAuth.apply(helper, openAuthArgs); }); }); }, }; const callLog = []; function addLoggedCall(fn) { ramblerIdHelper[fn] = () => { callLog.push({ fn, args: arguments }); }; } addLoggedCall("registerOnFrameCloseCallback"); addLoggedCall("registerOnFrameRedirect"); addLoggedCall("registerOnPossibleLoginCallback"); addLoggedCall("registerOnPossibleLogoutCallback"); addLoggedCall("registerOnPossibleOauthLoginCallback"); window.ramblerIdHelper = ramblerIdHelper; } PK ! { const v = crypto.getRandomValues(new Uint8Array(l)); const s = Array.from(v, c => c.toString(16)).join(""); return s.slice(0, l); }; const call = (fn, ...args) => { if (typeof fn === "function") { try { fn(...args); } catch (e) { console.error(e); } } }; class r3_generic { type = "GENERIC"; createScript() {} destroy() {} } class r3_addtocart extends r3_generic { type = "ADDTOCART"; addItemIdToCart() {} } class r3_addtoregistry extends r3_generic { type = "ADDTOREGISTRY"; addItemIdCentsQuantity() {} } class r3_brand extends r3_generic { type = "BRAND"; } class r3_cart extends r3_generic { type = "CART"; addItemId() {} addItemIdCentsQuantity() {} addItemIdDollarsAndCentsQuantity() {} addItemIdPriceQuantity() {} } class r3_category extends r3_generic { type = "CATEGORY"; addItemId() {} setId() {} setName() {} setParentId() {} setTopName() {} } class r3_common extends r3_generic { type = "COMMON"; baseUrl = "https://recs.richrelevance.com/rrserver/"; devFlags = {}; jsFileName = "p13n_generated.js"; RICHSORT = { paginate() {}, filterPrice() {}, filterAttribute() {}, }; addCategoryHintId() {} addClickthruParams() {} addContext() {} addFilter() {} addFilterBrand() {} addFilterCategory() {} addItemId() {} addItemIdToCart() {} addPlacementType() {} addRefinement() {} addSearchTerm() {} addSegment() {} blockItemId() {} enableCfrad() {} enableRad() {} forceDebugMode() {} forceDevMode() {} forceDisplayMode() {} forceLocale() {} initFromParams() {} setApiKey() {} setBaseUrl() {} setCartValue() {} setChannel() {} setClickthruServer() {} setCurrency() {} setDeviceId() {} setFilterBrandsIncludeMatchingElements() {} setForcedTreatment() {} setImageServer() {} setLanguage() {} setMVTForcedTreatment() {} setNoCookieMode() {} setPageBrand() {} setPrivateMode() {} setRefinementFallback() {} setRegionId() {} setRegistryId() {} setRegistryType() {} setSessionId() {} setUserId() {} useDummyData() {} } class r3_error extends r3_generic { type = "ERROR"; } class r3_home extends r3_generic { type = "HOME"; } class r3_item extends r3_generic { type = "ITEM"; addAttribute() {} addCategory() {} addCategoryId() {} setBrand() {} setEndDate() {} setId() {} setImageId() {} setLinkId() {} setName() {} setPrice() {} setRating() {} setRecommendable() {} setReleaseDate() {} setSalePrice() {} } class r3_personal extends r3_generic { type = "PERSONAL"; } class r3_purchased extends r3_generic { type = "PURCHASED"; addItemId() {} addItemIdCentsQuantity() {} addItemIdDollarsAndCentsQuantity() {} addItemIdPriceQuantity() {} setOrderNumber() {} setPromotionCode() {} setShippingCost() {} setTaxes() {} setTotalPrice() {} } class r3_search extends r3_generic { type = "SEARCH"; addItemId() {} setTerms() {} } class r3_wishlist extends r3_generic { type = "WISHLIST"; addItemId() {} } const RR = { add() {}, addItemId() {}, addItemIdCentsQuantity() {}, addItemIdDollarsAndCentsQuantity() {}, addItemIdPriceQuantity() {}, addItemIdToCart() {}, addObject() {}, addSearchTerm() {}, c() {}, charset: "UTF-8", checkParamCookieValue: () => null, d: document, data: { JSON: { placements: [], }, }, debugWindow() {}, set defaultCallback(fn) { call(fn); }, fixName: n => n, genericAddItemPriceQuantity() {}, get() {}, getDomElement(a) { return typeof a === "string" && a ? document.querySelector(a) : null; }, id() {}, insert() {}, insertDynamicPlacement() {}, isArray: a => Array.isArray(a), js() {}, set jsonCallback(fn) { call(fn, {}); }, l: document.location.href, lc() {}, noCookieMode: false, ol() {}, onloadCalled: true, pq() {}, rcsCookieDefaultDuration: 364, registerPageType() {}, registeredPageTypes: { ADDTOCART: r3_addtocart, ADDTOREGISTRY: r3_addtoregistry, BRAND: r3_brand, CART: r3_cart, CATEGORY: r3_category, COMMON: r3_common, ERROR: r3_error, GENERIC: r3_generic, HOME: r3_home, ITEM: r3_item, PERSONAL: r3_personal, PURCHASED: r3_purchased, SEARCH: r3_search, WISHLIST: r3_wishlist, }, renderDynamicPlacements() {}, set() {}, setCharset() {}, U: "undefined", unregisterAllPageType() {}, unregisterPageType() {}, }; Object.assign(window, { r3() {}, r3_addtocart, r3_addtoregistry, r3_brand, r3_cart, r3_category, r3_common, r3_error, r3_generic, r3_home, r3_item, r3_personal, r3_placement() {}, r3_purchased, r3_search, r3_wishlist, RR, rr_addLoadEvent() {}, rr_annotations_array: [undefined], rr_call_after_flush() {}, rr_create_script() {}, rr_dynamic: { placements: [], }, rr_flush() {}, rr_flush_onload() {}, rr_insert_placement() {}, rr_onload_called: true, rr_placement_place_holders: [], rr_placements: [], rr_recs: { placements: [], }, rr_remote_data: getRandomString(), rr_v: "1.2.6.20210212", }); call(jsonCallback); call(defaultCallback, {}); } PK !<#Ãshims/spotify-embed.js/* 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/. */ /* globals exportFunction */ "use strict"; /** * Spotify embeds default to "track preview mode". They require first-party * storage access in order to detect the login status and allow the user to play * the whole song or add it to their library. * Upon clicking the "play" button in the preview view this shim attempts to get * storage access and on success, reloads the frame and plays the full track. * This only works if the user is already logged in to Spotify in the * first-party context. */ const AUTOPLAY_FLAG = "shimPlayAfterStorageAccess"; const SELECTOR_PREVIEW_PLAY = 'div[data-testid="preview-play-pause"] > button'; const SELECTOR_FULL_PLAY = 'button[data-testid="play-pause-button"]'; /** * Promise-wrapper around DOMContentLoaded event. */ function waitForDOMContentLoaded() { return new Promise(resolve => { window.addEventListener("DOMContentLoaded", resolve, { once: true }); }); } /** * Listener for the preview playback button which requests storage access and * reloads the page. */ function previewPlayButtonListener(event) { const { target, isTrusted } = event; if (!isTrusted) { return; } const button = target.closest("button"); if (!button) { return; } // Filter for the preview playback button. This won't match the full // playback button that is shown when the user is logged in. if (!button.matches(SELECTOR_PREVIEW_PLAY)) { return; } // The storage access request below runs async so playback won't start // immediately. Mitigate this UX issue by updating the clicked element's // style so the user gets some immediate feedback. button.style.opacity = 0.5; event.stopPropagation(); event.preventDefault(); console.debug("Requesting storage access.", location.origin); document .requestStorageAccess() // When storage access is granted, reload the frame for the embedded // player to detect the login state and give us full playback // capabilities. .then(() => { // Use a flag to indicate that we want to click play after reload. // This is so the user does not have to click play twice. sessionStorage.setItem(AUTOPLAY_FLAG, "true"); console.debug("Reloading after storage access grant."); location.reload(); }) // If the user denies the storage access prompt we can't use the login // state. Attempt start preview playback instead. .catch(() => { button.click(); }) // Reset button style for both success and error case. .finally(() => { button.style.opacity = 1.0; }); } /** * Attempt to start (full) playback. Waits for the play button to appear and * become ready. */ async function startFullPlayback() { // Wait for DOMContentLoaded before looking for the playback button. await waitForDOMContentLoaded(); let numTries = 0; let intervalId = setInterval(() => { try { document.querySelector(SELECTOR_FULL_PLAY).click(); clearInterval(intervalId); console.debug("Clicked play after storage access grant."); } catch (e) {} numTries++; if (numTries >= 50) { console.debug("Can not start playback. Giving up."); clearInterval(intervalId); } }, 200); } (async () => { // Only run the shim for embedded iframes. if (window.top == window) { return; } console.warn( `When using the Spotify embedded player, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1792395 for details.` ); // Already requested storage access before the reload, trigger playback. if (sessionStorage.getItem(AUTOPLAY_FLAG) == "true") { sessionStorage.removeItem(AUTOPLAY_FLAG); await startFullPlayback(); return; } // Wait for the user to click the preview play button. If the player has // already loaded the full version, this method will do nothing. document.documentElement.addEventListener( "click", previewPlayButtonListener, { capture: true } ); })(); PK !<IJFFshims/tracking-pixel.pngPNG  IHDRĉ IDATxcIENDB`PK ! PK ! PK ! { const v = crypto.getRandomValues(new Uint8Array(20)); return Array.from(v, c => c.toString(16)).join(""); }; const sendMessageToAddon = (function () { const shimId = "Vidible"; const pendingMessages = new Map(); const channel = new MessageChannel(); channel.port1.onerror = console.error; channel.port1.onmessage = event => { const { messageId, response } = event.data; const resolve = pendingMessages.get(messageId); if (resolve) { pendingMessages.delete(messageId); resolve(response); } }; function reconnect() { const detail = { pendingMessages: [...pendingMessages.values()], port: channel.port2, shimId, }; window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); } window.addEventListener("ShimHelperReady", reconnect); reconnect(); return function (message) { const messageId = getGUID(); return new Promise(resolve => { const payload = { message, messageId, shimId }; pendingMessages.set(messageId, resolve); channel.port1.postMessage(payload); }); }; })(); const Shimmer = (function () { // If a page might store references to an object before we replace it, // ensure that it only receives proxies to that object created by // `Shimmer.proxy(obj)`. Later when the unshimmed object is created, // call `Shimmer.unshim(proxy, unshimmed)`. This way the references // will automatically "become" the unshimmed object when appropriate. const shimmedObjects = new WeakMap(); const unshimmedObjects = new Map(); function proxy(shim) { if (shimmedObjects.has(shim)) { return shimmedObjects.get(shim); } const prox = new Proxy(shim, { get: (target, k) => { if (unshimmedObjects.has(prox)) { return unshimmedObjects.get(prox)[k]; } return target[k]; }, apply: (target, thisArg, args) => { if (unshimmedObjects.has(prox)) { return unshimmedObjects.get(prox)(...args); } return target.apply(thisArg, args); }, construct: (target, args) => { if (unshimmedObjects.has(prox)) { return new unshimmedObjects.get(prox)(...args); } return new target(...args); }, }); shimmedObjects.set(shim, prox); shimmedObjects.set(prox, prox); for (const key in shim) { const value = shim[key]; if (typeof value === "function") { shim[key] = function () { const unshimmed = unshimmedObjects.get(prox); if (unshimmed) { return unshimmed[key].apply(unshimmed, arguments); } return value.apply(this, arguments); }; } else if (typeof value !== "object" || value === null) { shim[key] = value; } else { shim[key] = Shimmer.proxy(value); } } return prox; } function unshim(shim, unshimmed) { unshimmedObjects.set(shim, unshimmed); for (const prop in shim) { if (prop in unshimmed) { const un = unshimmed[prop]; if (typeof un === "object" && un !== null) { unshim(shim[prop], un); } } else { unshimmedObjects.set(shim[prop], undefined); } } } return { proxy, unshim }; })(); const extras = []; const playersByNode = new WeakMap(); const playerData = new Map(); const getJSONPVideoPlacements = () => { return document.querySelectorAll( `script[src*="delivery.vidible.tv/jsonp"]` ); }; const allowVidible = () => { if (allowVidible.promise) { return allowVidible.promise; } const shim = window.vidible; window.vidible = undefined; allowVidible.promise = sendMessageToAddon("optIn") .then(() => { return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = originalScript; script.addEventListener("load", () => { Shimmer.unshim(shim, window.vidible); for (const args of extras) { window.visible.registerExtra(...args); } for (const jsonp of getJSONPVideoPlacements()) { const { src } = jsonp; const jscript = document.createElement("script"); jscript.onload = resolve; jscript.src = src; jsonp.replaceWith(jscript); } for (const [playerShim, data] of playerData.entries()) { const { loadCalled, on, parent, placeholder, setup } = data; placeholder?.remove(); const player = window.vidible.player(parent); Shimmer.unshim(playerShim, player); for (const [type, fns] of on.entries()) { for (const fn of fns) { try { player.on(type, fn); } catch (e) { console.error(e); } } } if (setup) { player.setup(setup); } if (loadCalled) { player.load(); } } resolve(); }); script.addEventListener("error", () => { script.remove(); reject(); }); document.head.appendChild(script); }); }) .catch(() => { window.vidible = shim; delete allowVidible.promise; }); return allowVidible.promise; }; const createVideoPlaceholder = (service, callback) => { const placeholder = document.createElement("div"); placeholder.style = ` position: absolute; width: 100%; height: 100%; min-width: 160px; min-height: 100px; top: 0px; left: 0px; background: #000; color: #fff; text-align: center; cursor: pointer; display: flex; align-items: center; justify-content: center; background-image: url(${PlayIconURL}); background-position: 50% 47.5%; background-repeat: no-repeat; background-size: 25% 25%; -moz-text-size-adjust: none; -moz-user-select: none; color: #fff; align-items: center; padding-top: 200px; font-size: 14pt; `; placeholder.textContent = `Click to allow blocked ${service} content`; placeholder.addEventListener("click", evt => { evt.isTrusted && callback(); }); return placeholder; }; const Player = function (parent) { const existing = playersByNode.get(parent); if (existing) { return existing; } const player = Shimmer.proxy(this); playersByNode.set(parent, player); const placeholder = createVideoPlaceholder("Vidible", allowVidible); parent.parentNode.insertBefore(placeholder, parent); playerData.set(player, { on: new Map(), parent, placeholder, }); return player; }; const changeData = function (fn) { const data = playerData.get(this); if (data) { fn(data); playerData.set(this, data); } }; Player.prototype = { addEventListener() {}, destroy() { const { placeholder } = playerData.get(this); placeholder?.remove(); playerData.delete(this); }, dispatchEvent() {}, getAdsPassedTime() {}, getAllMacros() {}, getCurrentTime() {}, getDuration() {}, getHeight() {}, getPixelsLog() {}, getPlayerContainer() {}, getPlayerInfo() {}, getPlayerStatus() {}, getRequestsLog() {}, getStripUrl() {}, getVolume() {}, getWidth() {}, hidePlayReplayControls() {}, isMuted() {}, isPlaying() {}, load() { changeData(data => (data.loadCalled = true)); }, mute() {}, on(type, fn) { changeData(({ on }) => { if (!on.has(type)) { on.set(type, new Set()); } on.get(type).add(fn); }); }, off(type, fn) { changeData(({ on }) => { on.get(type)?.delete(fn); }); }, overrideMacro() {}, pause() {}, play() {}, playVideoByIndex() {}, removeEventListener() {}, seekTo() {}, sendBirthDate() {}, sendKey() {}, setup(s) { changeData(data => (data.setup = s)); return this; }, setVideosToPlay() {}, setVolume() {}, showPlayReplayControls() {}, toggleFullscreen() {}, toggleMute() {}, togglePlay() {}, updateBid() {}, version() {}, volume() {}, }; const vidible = { ADVERT_CLOSED: "advertClosed", AD_END: "adend", AD_META: "admeta", AD_PAUSED: "adpaused", AD_PLAY: "adplay", AD_START: "adstart", AD_TIMEUPDATE: "adtimeupdate", AD_WAITING: "adwaiting", AGE_GATE_DISPLAYED: "agegatedisplayed", BID_UPDATED: "BidUpdated", CAROUSEL_CLICK: "CarouselClick", CONTEXT_ENDED: "contextended", CONTEXT_STARTED: "contextstarted", ENTER_FULLSCREEN: "playerenterfullscreen", EXIT_FULLSCREEN: "playerexitfullscreen", FALLBACK: "fallback", FLOAT_END_ACTION: "floatended", FLOAT_START_ACTION: "floatstarted", HIDE_PLAY_REPLAY_BUTTON: "hideplayreplaybutton", LIGHTBOX_ACTIVATED: "lightboxactivated", LIGHTBOX_DEACTIVATED: "lightboxdeactivated", MUTE: "Mute", PLAYER_CONTROLS_STATE_CHANGE: "playercontrolsstatechaned", PLAYER_DOCKED: "playerDocked", PLAYER_ERROR: "playererror", PLAYER_FLOATING: "playerFloating", PLAYER_READY: "playerready", PLAYER_RESIZE: "playerresize", PLAYLIST_END: "playlistend", SEEK_END: "SeekEnd", SEEK_START: "SeekStart", SHARE_SCREEN_CLOSED: "sharescreenclosed", SHARE_SCREEN_OPENED: "sharescreenopened", SHOW_PLAY_REPLAY_BUTTON: "showplayreplaybutton", SUBTITLES_DISABLED: "subtitlesdisabled", SUBTITLES_ENABLED: "subtitlesenabled", SUBTITLES_READY: "subtitlesready", UNMUTE: "Unmute", VIDEO_DATA_LOADED: "videodataloaded", VIDEO_END: "videoend", VIDEO_META: "videometadata", VIDEO_MODULE_CREATED: "videomodulecreated", VIDEO_PAUSE: "videopause", VIDEO_PLAY: "videoplay", VIDEO_SEEKEND: "videoseekend", VIDEO_SELECTED: "videoselected", VIDEO_START: "videostart", VIDEO_TIMEUPDATE: "videotimeupdate", VIDEO_VOLUME_CHANGED: "videovolumechanged", VOLUME: "Volume", _getContexts: () => [], "content.CLICK": "content.click", "content.IMPRESSION": "content.impression", "content.QUARTILE": "content.quartile", "content.VIEW": "content.view", createPlayer: parent => new Player(parent), createPlayerAsync: parent => new Player(parent), createVPAIDPlayer: parent => new Player(parent), destroyAll() {}, extension() {}, getContext() {}, player: parent => new Player(parent), playerInceptionTime() { return { undefined: 1620149827713 }; }, registerExtra(a, b, c) { extras.push([a, b, c]); }, version: () => "21.1.313", }; window.vidible = Shimmer.proxy(vidible); for (const jsonp of getJSONPVideoPlacements()) { const player = new Player(jsonp); const { placeholder } = playerData.get(player); jsonp.parentNode.insertBefore(placeholder, jsonp); } } PK !<􅤒shims/vmad.xml PK !<2shims/webtrends.js/* 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 1766414 - Shim WebTrends Core Tag and Advanced Link Tracking * * Sites using WebTrends Core Tag or Link Tracking can break if they are * are blocked. This shim mitigates that breakage by loading an empty module. */ if (!window.dcsMultiTrack) { window.dcsMultiTrack = o => { o?.callback?.({}); }; } if (!window.WebTrends) { class dcs { addSelector() { return this; } addTransform() { return this; } DCSext = {}; init(obj) { return this; } track() { return this; } } window.Webtrends = window.WebTrends = { dcs, multiTrack: window.dcsMultiTrack, }; window.requestAnimationFrame(() => { window.webtrendsAsyncLoad?.(dcs); window.webtrendsAsyncInit?.(); }); } PKu1