summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat/shims
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /browser/extensions/webcompat/shims
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/extensions/webcompat/shims')
-rw-r--r--browser/extensions/webcompat/shims/addthis-angular.js16
-rw-r--r--browser/extensions/webcompat/shims/adform.js30
-rw-r--r--browser/extensions/webcompat/shims/adnexus-ast.js210
-rw-r--r--browser/extensions/webcompat/shims/adnexus-prebid.js68
-rw-r--r--browser/extensions/webcompat/shims/adsafeprotected-ima.js19
-rw-r--r--browser/extensions/webcompat/shims/apstag.js73
-rw-r--r--browser/extensions/webcompat/shims/blogger.js39
-rw-r--r--browser/extensions/webcompat/shims/bloggerAccount.js68
-rw-r--r--browser/extensions/webcompat/shims/bmauth.js21
-rw-r--r--browser/extensions/webcompat/shims/branch.js84
-rw-r--r--browser/extensions/webcompat/shims/chartbeat.js18
-rw-r--r--browser/extensions/webcompat/shims/crave-ca.js56
-rw-r--r--browser/extensions/webcompat/shims/criteo.js64
-rw-r--r--browser/extensions/webcompat/shims/cxense.js593
-rw-r--r--browser/extensions/webcompat/shims/doubleverify.js36
-rw-r--r--browser/extensions/webcompat/shims/eluminate.js95
-rw-r--r--browser/extensions/webcompat/shims/empty-script.js5
-rw-r--r--browser/extensions/webcompat/shims/empty-shim.txt0
-rw-r--r--browser/extensions/webcompat/shims/everest.js171
-rw-r--r--browser/extensions/webcompat/shims/facebook-sdk.js554
-rw-r--r--browser/extensions/webcompat/shims/facebook.svg3
-rw-r--r--browser/extensions/webcompat/shims/fastclick.js75
-rw-r--r--browser/extensions/webcompat/shims/firebase.js95
-rw-r--r--browser/extensions/webcompat/shims/google-ads.js77
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js187
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js13
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-legacy.js137
-rw-r--r--browser/extensions/webcompat/shims/google-ima.js620
-rw-r--r--browser/extensions/webcompat/shims/google-page-ad.js17
-rw-r--r--browser/extensions/webcompat/shims/google-publisher-tags.js509
-rw-r--r--browser/extensions/webcompat/shims/google-safeframe.html29
-rw-r--r--browser/extensions/webcompat/shims/history.js54
-rw-r--r--browser/extensions/webcompat/shims/iam.js39
-rw-r--r--browser/extensions/webcompat/shims/iaspet.js45
-rw-r--r--browser/extensions/webcompat/shims/instagram.js55
-rw-r--r--browser/extensions/webcompat/shims/kinja.js44
-rw-r--r--browser/extensions/webcompat/shims/live-test-shim.js82
-rw-r--r--browser/extensions/webcompat/shims/maxmind-geoip.js69
-rw-r--r--browser/extensions/webcompat/shims/microsoftLogin.js29
-rw-r--r--browser/extensions/webcompat/shims/microsoftVirtualAssistant.js46
-rw-r--r--browser/extensions/webcompat/shims/moat.js46
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-1.js87
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-2.js85
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-3.js7
-rw-r--r--browser/extensions/webcompat/shims/nielsen.js111
-rw-r--r--browser/extensions/webcompat/shims/optimizely.js205
-rw-r--r--browser/extensions/webcompat/shims/play.svg7
-rw-r--r--browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js17
-rw-r--r--browser/extensions/webcompat/shims/rambler-authenticator.js84
-rw-r--r--browser/extensions/webcompat/shims/rich-relevance.js288
-rw-r--r--browser/extensions/webcompat/shims/spotify-embed.js133
-rw-r--r--browser/extensions/webcompat/shims/tracking-pixel.pngbin0 -> 70 bytes
-rw-r--r--browser/extensions/webcompat/shims/vast2.xml12
-rw-r--r--browser/extensions/webcompat/shims/vast3.xml12
-rw-r--r--browser/extensions/webcompat/shims/vidible.js424
-rw-r--r--browser/extensions/webcompat/shims/vmad.xml12
-rw-r--r--browser/extensions/webcompat/shims/webtrends.js46
57 files changed, 6021 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/shims/addthis-angular.js b/browser/extensions/webcompat/shims/addthis-angular.js
new file mode 100644
index 0000000000..0f0cdd5029
--- /dev/null
+++ b/browser/extensions/webcompat/shims/addthis-angular.js
@@ -0,0 +1,16 @@
+/* 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"]);
+}
diff --git a/browser/extensions/webcompat/shims/adform.js b/browser/extensions/webcompat/shims/adform.js
new file mode 100644
index 0000000000..d6727d500e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/adform.js
@@ -0,0 +1,30 @@
+/* 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 1713695 - Shim Adform tracking
+ *
+ * Sites such as m.tim.it may gate content behind AdForm's trackpoint,
+ * breaking download links and such if blocked. This shim stubs out the
+ * script and its related tracking pixel, so the content still works.
+ */
+
+if (!window.Adform) {
+ window.Adform = {
+ Opt: {
+ disableRedirect() {},
+ getStatus(clientID, callback) {
+ callback({
+ clientID,
+ errorMessage: undefined,
+ optIn() {},
+ optOut() {},
+ status: "nocookie",
+ });
+ },
+ },
+ };
+}
diff --git a/browser/extensions/webcompat/shims/adnexus-ast.js b/browser/extensions/webcompat/shims/adnexus-ast.js
new file mode 100644
index 0000000000..ae07fa6a03
--- /dev/null
+++ b/browser/extensions/webcompat/shims/adnexus-ast.js
@@ -0,0 +1,210 @@
+/* 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 1734130 - Shim AdNexus AST
+ *
+ * Some sites expect AST to successfully load, or they break.
+ * This shim mitigates that breakage.
+ */
+
+if (!window.apntag?.loaded) {
+ const anq = window.apntag?.anq || [];
+
+ const gTags = new Map();
+ const gAds = new Map();
+ const gEventHandlers = {};
+
+ const Ad = class {
+ adType = "banner";
+ auctionId = "-";
+ banner = {
+ width: 1,
+ height: 1,
+ content: "",
+ trackers: {
+ impression_urls: [],
+ video_events: {},
+ },
+ };
+ brandCategoryId = 0;
+ buyerMemberId = 0;
+ cpm = 0.1;
+ cpm_publisher_currency = 0.1;
+ creativeId = 0;
+ dealId = undefined;
+ height = 1;
+ mediaSubtypeId = 1;
+ mediaTypeId = 1;
+ publisher_currency_code = "US";
+ source = "-";
+ tagId = -1;
+ targetId = "";
+ width = 1;
+
+ constructor(tagId, targetId) {
+ this.tagId = tagId;
+ this.targetId = targetId;
+ }
+ };
+
+ const fireAdEvent = (type, adObj) => {
+ 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);
+}
diff --git a/browser/extensions/webcompat/shims/adnexus-prebid.js b/browser/extensions/webcompat/shims/adnexus-prebid.js
new file mode 100644
index 0000000000..f0f810f0e9
--- /dev/null
+++ b/browser/extensions/webcompat/shims/adnexus-prebid.js
@@ -0,0 +1,68 @@
+/* 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);
+}
diff --git a/browser/extensions/webcompat/shims/adsafeprotected-ima.js b/browser/extensions/webcompat/shims/adsafeprotected-ima.js
new file mode 100644
index 0000000000..93cd8e1eab
--- /dev/null
+++ b/browser/extensions/webcompat/shims/adsafeprotected-ima.js
@@ -0,0 +1,19 @@
+/* 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";
+
+/**
+ *
+ * Sites relying on Ad Safe Protected's adapter for Google IMA may
+ * have broken videos when the script is blocked. This shim stubs
+ * out the API to help mitigate major breakage.
+ */
+
+if (!window.googleImaVansAdapter) {
+ window.googleImaVansAdapter = {
+ init() {},
+ dispose() {},
+ };
+}
diff --git a/browser/extensions/webcompat/shims/apstag.js b/browser/extensions/webcompat/shims/apstag.js
new file mode 100644
index 0000000000..55be05916b
--- /dev/null
+++ b/browser/extensions/webcompat/shims/apstag.js
@@ -0,0 +1,73 @@
+/* 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 1713698 - Shim Amazon Transparent Ad Marketplace's apstag.js
+ *
+ * Some sites such as politico.com rely on Amazon TAM tracker to serve ads,
+ * breaking functionality like galleries if it is blocked. This shim helps
+ * mitigate major breakage in that case.
+ */
+
+if (!window.apstag?._getSlotIdToNameMapping) {
+ const _Q = window.apstag?._Q || [];
+
+ const newBid = config => {
+ 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);
+ }
+}
diff --git a/browser/extensions/webcompat/shims/blogger.js b/browser/extensions/webcompat/shims/blogger.js
new file mode 100644
index 0000000000..a474b3c5e9
--- /dev/null
+++ b/browser/extensions/webcompat/shims/blogger.js
@@ -0,0 +1,39 @@
+/* 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 powered blogs rely on storage access to https://blogger.com to enable
+ * oauth with Google. For dFPI, sites need to use the Storage Access API to gain
+ * first party storage access. This shim calls requestStorageAccess on behalf of
+ * the site when a user wants to log in via oauth.
+ */
+
+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=1776869 for details.`
+);
+
+const GOOGLE_OAUTH_PATH_PREFIX = "https://accounts.google.com/ServiceLogin";
+
+// Overwrite the window.open method so we can detect oauth related popups.
+const origOpen = window.wrappedJSObject.open;
+Object.defineProperty(window.wrappedJSObject, "open", {
+ value: exportFunction((url, ...args) => {
+ // 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),
+});
diff --git a/browser/extensions/webcompat/shims/bloggerAccount.js b/browser/extensions/webcompat/shims/bloggerAccount.js
new file mode 100644
index 0000000000..19e80dbfbe
--- /dev/null
+++ b/browser/extensions/webcompat/shims/bloggerAccount.js
@@ -0,0 +1,68 @@
+/* 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
+);
diff --git a/browser/extensions/webcompat/shims/bmauth.js b/browser/extensions/webcompat/shims/bmauth.js
new file mode 100644
index 0000000000..944f2100d6
--- /dev/null
+++ b/browser/extensions/webcompat/shims/bmauth.js
@@ -0,0 +1,21 @@
+/* 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: () => {},
+ },
+ },
+ };
+}
diff --git a/browser/extensions/webcompat/shims/branch.js b/browser/extensions/webcompat/shims/branch.js
new file mode 100644
index 0000000000..31e8f4eeec
--- /dev/null
+++ b/browser/extensions/webcompat/shims/branch.js
@@ -0,0 +1,84 @@
+/* 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);
+}
diff --git a/browser/extensions/webcompat/shims/chartbeat.js b/browser/extensions/webcompat/shims/chartbeat.js
new file mode 100644
index 0000000000..0e57fc6da1
--- /dev/null
+++ b/browser/extensions/webcompat/shims/chartbeat.js
@@ -0,0 +1,18 @@
+/* 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() {},
+};
diff --git a/browser/extensions/webcompat/shims/crave-ca.js b/browser/extensions/webcompat/shims/crave-ca.js
new file mode 100644
index 0000000000..b4d93ccdfa
--- /dev/null
+++ b/browser/extensions/webcompat/shims/crave-ca.js
@@ -0,0 +1,56 @@
+/* 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 1746439 - crave.ca login broken with dFPI enabled
+ *
+ * Crave.ca relies upon a login page that is out-of-origin. That login page
+ * sets a cookie for https://www.crave.ca, which is then used as an proof of
+ * authentication on redirect back to the main site. This shim adds a request
+ * for storage access for https://www.crave.ca when the user tries to log in.
+ */
+
+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=1746439 for details.`
+);
+
+// Third-party origin we need to request storage access for.
+const STORAGE_ACCESS_ORIGIN = "https://www.crave.ca";
+
+document.documentElement.addEventListener(
+ "click",
+ e => {
+ 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
+);
diff --git a/browser/extensions/webcompat/shims/criteo.js b/browser/extensions/webcompat/shims/criteo.js
new file mode 100644
index 0000000000..afdc00b888
--- /dev/null
+++ b/browser/extensions/webcompat/shims/criteo.js
@@ -0,0 +1,64 @@
+/* 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,
+ };
+}
diff --git a/browser/extensions/webcompat/shims/cxense.js b/browser/extensions/webcompat/shims/cxense.js
new file mode 100644
index 0000000000..55862f4fb5
--- /dev/null
+++ b/browser/extensions/webcompat/shims/cxense.js
@@ -0,0 +1,593 @@
+/* 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: () => "<div/>",
+ renderTemplate: () => "<div/>",
+ 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: () => "<div/>",
+ renderTemplate: () => "<div/>",
+ 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();
+}
diff --git a/browser/extensions/webcompat/shims/doubleverify.js b/browser/extensions/webcompat/shims/doubleverify.js
new file mode 100644
index 0000000000..7eaf945d77
--- /dev/null
+++ b/browser/extensions/webcompat/shims/doubleverify.js
@@ -0,0 +1,36 @@
+/* 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 1771557 - Shim DoubleVerify analytics
+ *
+ * Some sites such as Sports Illustrated expect DoubleVerify's
+ * analytics script to load, otherwise odd breakage may occur.
+ * This shim helps mitigate such breakage.
+ */
+
+if (!window?.PQ?.loaded) {
+ const cmd = [];
+ cmd.push = function (c) {
+ try {
+ c?.();
+ } catch (_) {}
+ };
+
+ window.apntag = {
+ anq: [],
+ };
+
+ window.PQ = {
+ cmd,
+ loaded: true,
+ getTargeting: (_, cb) => cb?.([]),
+ init: () => {},
+ loadSignals: (_, cb) => cb?.(),
+ loadSignalsForSlots: (_, cb) => cb?.(),
+ PTS: {},
+ };
+}
diff --git a/browser/extensions/webcompat/shims/eluminate.js b/browser/extensions/webcompat/shims/eluminate.js
new file mode 100644
index 0000000000..3fa65c048c
--- /dev/null
+++ b/browser/extensions/webcompat/shims/eluminate.js
@@ -0,0 +1,95 @@
+/* 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 1606448 - Shim CoreMetrics Eluminate analytics
+ *
+ * Sites may rely on eluminate.js tracking in ways which cause breakage,
+ * which has been seen on shopping sites such as Vans.com, where the
+ * search filtering UX is broken. This shim mitigates such breakage.
+ */
+
+if (!window.CM_DDX) {
+ window.CM_DDX = {
+ domReadyFired: false,
+ headScripts: true,
+ dispatcherLoadRequested: false,
+ firstPassFunctionBinding: false,
+ BAD_PAGE_ID_ELAPSED_TIMEOUT: 5000,
+ version: -1,
+ standalone: false,
+ test: {
+ syndicate: true,
+ testCounter: "",
+ doTest: false,
+ newWin: false,
+ process: () => {},
+ },
+ 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,
+ };
+}
diff --git a/browser/extensions/webcompat/shims/empty-script.js b/browser/extensions/webcompat/shims/empty-script.js
new file mode 100644
index 0000000000..d01f2ab537
--- /dev/null
+++ b/browser/extensions/webcompat/shims/empty-script.js
@@ -0,0 +1,5 @@
+/* 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 */
diff --git a/browser/extensions/webcompat/shims/empty-shim.txt b/browser/extensions/webcompat/shims/empty-shim.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/browser/extensions/webcompat/shims/empty-shim.txt
diff --git a/browser/extensions/webcompat/shims/everest.js b/browser/extensions/webcompat/shims/everest.js
new file mode 100644
index 0000000000..259ab9033e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/everest.js
@@ -0,0 +1,171 @@
+/* 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,
+ };
+}
diff --git a/browser/extensions/webcompat/shims/facebook-sdk.js b/browser/extensions/webcompat/shims/facebook-sdk.js
new file mode 100644
index 0000000000..1e995ff047
--- /dev/null
+++ b/browser/extensions/webcompat/shims/facebook-sdk.js
@@ -0,0 +1,554 @@
+/* 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?.();
+}
diff --git a/browser/extensions/webcompat/shims/facebook.svg b/browser/extensions/webcompat/shims/facebook.svg
new file mode 100644
index 0000000000..df63700a9e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/facebook.svg
@@ -0,0 +1,3 @@
+<!-- copyright is dedicated to the Public Domain.
+ https://en.wikipedia.org/wiki/File:Facebook_f_logo_(2019).svg -->
+<svg xmlns="http://www.w3.org/2000/svg" width="1365.12" height="1365.12" viewBox="0 0 14222 14222"><circle cx="7111" cy="7112" r="7111" fill="#fff"/><path d="M9879 9168l315-2056H8222V5778c0-562 275-1111 1159-1111h897V2917s-814-139-1592-139c-1624 0-2686 984-2686 2767v1567H4194v2056h1806v4969c362 57 733 86 1111 86s749-30 1111-86V9168z" fill="#1977f3"/></svg>
diff --git a/browser/extensions/webcompat/shims/fastclick.js b/browser/extensions/webcompat/shims/fastclick.js
new file mode 100644
index 0000000000..ad6814c995
--- /dev/null
+++ b/browser/extensions/webcompat/shims/fastclick.js
@@ -0,0 +1,75 @@
+/* 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 1738220 - Shim Conversant FastClick
+ *
+ * Sites assuming FastClick will load can break if it is blocked.
+ * This shim mitigates that breakage.
+ */
+
+// FastClick bundles nodeJS packages/core-js/internals/dom-iterables.js
+// which is known to be needed by at least one site.
+if (!HTMLCollection.prototype.forEach) {
+ const DOMIterables = [
+ "CSSRuleList",
+ "CSSStyleDeclaration",
+ "CSSValueList",
+ "ClientRectList",
+ "DOMRectList",
+ "DOMStringList",
+ "DOMTokenList",
+ "DataTransferItemList",
+ "FileList",
+ "HTMLAllCollection",
+ "HTMLCollection",
+ "HTMLFormElement",
+ "HTMLSelectElement",
+ "MediaList",
+ "MimeTypeArray",
+ "NamedNodeMap",
+ "NodeList",
+ "PaintRequestList",
+ "Plugin",
+ "PluginArray",
+ "SVGLengthList",
+ "SVGNumberList",
+ "SVGPathSegList",
+ "SVGPointList",
+ "SVGStringList",
+ "SVGTransformList",
+ "SourceBufferList",
+ "StyleSheetList",
+ "TextTrackCueList",
+ "TextTrackList",
+ "TouchList",
+ ];
+
+ const forEach = Array.prototype.forEach;
+
+ const handlePrototype = proto => {
+ 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 = () => {};
+}
diff --git a/browser/extensions/webcompat/shims/firebase.js b/browser/extensions/webcompat/shims/firebase.js
new file mode 100644
index 0000000000..8ac049c5e4
--- /dev/null
+++ b/browser/extensions/webcompat/shims/firebase.js
@@ -0,0 +1,95 @@
+/* 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 }),
+ });
+ }
+})();
diff --git a/browser/extensions/webcompat/shims/google-ads.js b/browser/extensions/webcompat/shims/google-ads.js
new file mode 100644
index 0000000000..a432186f43
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-ads.js
@@ -0,0 +1,77 @@
+/* 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 1713726 - Shim Ads by Google
+ *
+ * Sites relying on window.adsbygoogle may encounter breakage if it is blocked.
+ * This shim provides a stub for that API to mitigate that breakage.
+ */
+
+if (window.adsbygoogle?.loaded === undefined) {
+ window.adsbygoogle = {
+ loaded: true,
+ push() {},
+ };
+}
+
+if (window.gapi?._pl === undefined) {
+ const stub = {
+ go() {},
+ render: () => "",
+ };
+ 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";
+}
diff --git a/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js b/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js
new file mode 100644
index 0000000000..8809fca8ec
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js
@@ -0,0 +1,187 @@
+/* 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 1713687 - Shim Google Analytics and Tag Manager
+ *
+ * Sites often rely on the Google Analytics window object and will
+ * break if it fails to load or is blocked. This shim works around
+ * such breakage.
+ *
+ * Sites also often use the Google Optimizer (asynchide) code snippet,
+ * only for it to cause multi-second delays if Google Analytics does
+ * not load. This shim also avoids such delays.
+ *
+ * They also rely on Google Tag Manager, which often goes hand-in-
+ * hand with Analytics, but is not always blocked by anti-tracking
+ * lists. Handling both in the same shim handles both cases.
+ */
+
+if (window[window.GoogleAnalyticsObject || "ga"]?.loaded === undefined) {
+ const DEFAULT_TRACKER_NAME = "t0";
+
+ const trackers = new Map();
+
+ const run = function (fn, ...args) {
+ if (typeof fn === "function") {
+ try {
+ fn(...args);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ };
+
+ const create = (id, cookie, name, opts) => {
+ 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 = /((?<name>.*?)\.)?((?<plugin>.*?):)?(?<method>.*)/;
+
+ 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() {}
+ };
+}
diff --git a/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js b/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js
new file mode 100644
index 0000000000..60b49df120
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js
@@ -0,0 +1,13 @@
+/* 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.gaplugins) {
+ window.gaplugins = {};
+}
+
+if (!window.gaplugins.EC) {
+ window.gaplugins.EC = () => {};
+}
diff --git a/browser/extensions/webcompat/shims/google-analytics-legacy.js b/browser/extensions/webcompat/shims/google-analytics-legacy.js
new file mode 100644
index 0000000000..da1a638e12
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics-legacy.js
@@ -0,0 +1,137 @@
+/* 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;
+}
diff --git a/browser/extensions/webcompat/shims/google-ima.js b/browser/extensions/webcompat/shims/google-ima.js
new file mode 100644
index 0000000000..1f5e56239d
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-ima.js
@@ -0,0 +1,620 @@
+/* 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;
+}
diff --git a/browser/extensions/webcompat/shims/google-page-ad.js b/browser/extensions/webcompat/shims/google-page-ad.js
new file mode 100644
index 0000000000..42f3a0fca5
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-page-ad.js
@@ -0,0 +1,17 @@
+/* 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 1713692 - Shim Google Page Ad conversion tracker
+ *
+ * This shim stubs out the simple API for converstion tracking with
+ * Google Page Ad, mitigating major breakage on pages which presume
+ * the API will always successfully load.
+ */
+
+if (!window.google_trackConversion) {
+ window.google_trackConversion = () => {};
+}
diff --git a/browser/extensions/webcompat/shims/google-publisher-tags.js b/browser/extensions/webcompat/shims/google-publisher-tags.js
new file mode 100644
index 0000000000..e5174d3244
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-publisher-tags.js
@@ -0,0 +1,509 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1713685 - Shim Google Publisher Tags
+ *
+ * Many sites rely on googletag to place content or drive ad bidding,
+ * and will experience major breakage if it is blocked. This shim provides
+ * enough of the API's frame To mitigate much of that breakage.
+ */
+
+if (window.googletag?.apiReady === undefined) {
+ const version = "2021050601";
+
+ const noopthisfn = function () {
+ return this;
+ };
+
+ const slots = new Map();
+ const slotsById = new Map();
+ const slotsPerPath = new Map();
+ const slotCreatives = new Map();
+ const usedCreatives = new Map();
+ const fetchedSlots = new Set();
+ const eventCallbacks = new Map();
+
+ const fireSlotEvent = (name, slot) => {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ const size = [0, 0];
+ for (const cb of eventCallbacks.get(name) || []) {
+ cb({ isEmpty: true, size, slot });
+ }
+ resolve();
+ });
+ });
+ };
+
+ const recreateIframeForSlot = slot => {
+ const eid = `google_ads_iframe_${slot.getId()}`;
+ document.getElementById(eid)?.remove();
+ const node = document.getElementById(slot.getSlotElementId());
+ if (node) {
+ const f = document.createElement("iframe");
+ f.id = eid;
+ f.srcdoc = "<body></body>";
+ f.style =
+ "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0";
+ f.setAttribute("width", 0);
+ f.setAttribute("height", 0);
+ node.appendChild(f);
+ }
+ };
+
+ const emptySlotElement = slot => {
+ const node = document.getElementById(slot.getSlotElementId());
+ while (node?.lastChild) {
+ node.lastChild.remove();
+ }
+ };
+
+ const SizeMapping = class extends Array {
+ getCreatives() {
+ const { clientWidth, clientHeight } = document.documentElement;
+ for (const [size, creatives] of this) {
+ if (clientWidth >= size[0] && clientHeight >= size[1]) {
+ return creatives;
+ }
+ }
+ return [];
+ }
+ };
+
+ const fetchSlot = slot => {
+ if (!slot) {
+ return;
+ }
+
+ const id = slot.getSlotElementId();
+
+ const node = document.getElementById(id);
+ if (!node) {
+ return;
+ }
+
+ let creatives = slotCreatives.get(id);
+ if (creatives instanceof SizeMapping) {
+ creatives = creatives.getCreatives();
+ }
+
+ if (!creatives?.length) {
+ return;
+ }
+
+ for (const creative of creatives) {
+ if (usedCreatives.has(creative)) {
+ return;
+ }
+ }
+
+ const creative = creatives[0];
+ usedCreatives.set(creative, slot);
+ fetchedSlots.add(id);
+ };
+
+ const displaySlot = async slot => {
+ if (!slot) {
+ return;
+ }
+
+ const id = slot.getSlotElementId();
+ if (!document.getElementById(id)) {
+ return;
+ }
+
+ if (!fetchedSlots.has(id)) {
+ fetchSlot(slot);
+ }
+
+ const parent = document.getElementById(id);
+ if (parent) {
+ parent.appendChild(document.createElement("div"));
+ }
+
+ emptySlotElement(slot);
+ recreateIframeForSlot(slot);
+ await fireSlotEvent("slotRenderEnded", slot);
+ await fireSlotEvent("slotRequested", slot);
+ await fireSlotEvent("slotResponseReceived", slot);
+ await fireSlotEvent("slotOnload", slot);
+ await fireSlotEvent("impressionViewable", slot);
+ };
+
+ const addEventListener = function (name, listener) {
+ if (!eventCallbacks.has(name)) {
+ eventCallbacks.set(name, new Set());
+ }
+ eventCallbacks.get(name).add(listener);
+ return this;
+ };
+
+ const removeEventListener = function (name, listener) {
+ if (eventCallbacks.has(name)) {
+ return eventCallbacks.get(name).delete(listener);
+ }
+ return false;
+ };
+
+ const companionAdsService = {
+ addEventListener,
+ enable() {},
+ fillSlot() {},
+ getAttributeKeys: () => [],
+ getDisplayAdsCorrelator: () => "",
+ getName: () => "companion_ads",
+ getSlotIdMap: () => {
+ return {};
+ },
+ getSlots: () => [],
+ getVideoStreamCorrelator() {},
+ isRoadblockingSupported: () => false,
+ isSlotAPersistentRoadblock: () => false,
+ notifyUnfilledSlots() {},
+ onImplementationLoaded() {},
+ refreshAllSlots() {
+ for (const slot of slotsById.values()) {
+ fetchSlot(slot);
+ displaySlot(slot);
+ }
+ },
+ removeEventListener,
+ set() {},
+ setRefreshUnfilledSlots() {},
+ setVideoSession() {},
+ slotRenderEnded() {},
+ };
+
+ const contentService = {
+ addEventListener,
+ setContent() {},
+ removeEventListener,
+ };
+
+ const getTargetingValue = v => {
+ if (typeof v === "string") {
+ return [v];
+ }
+ try {
+ return [Array.prototype.flat.call(v)[0]];
+ } catch (_) {}
+ return [];
+ };
+
+ const updateTargeting = (targeting, map) => {
+ if (typeof map === "object") {
+ const entries = Object.entries(map || {});
+ for (const [k, v] of entries) {
+ targeting.set(k, getTargetingValue(v));
+ }
+ }
+ };
+
+ const defineSlot = (adUnitPath, creatives, opt_div) => {
+ if (slotsById.has(opt_div)) {
+ document.getElementById(opt_div)?.remove();
+ return slotsById.get(opt_div);
+ }
+ const attributes = new Map();
+ const targeting = new Map();
+ const exclusions = new Set();
+ const response = {
+ advertiserId: undefined,
+ campaignId: undefined,
+ creativeId: undefined,
+ creativeTemplateId: undefined,
+ lineItemId: undefined,
+ };
+ const sizes = [
+ {
+ getHeight: () => 2,
+ getWidth: () => 2,
+ },
+ ];
+ const num = (slotsPerPath.get(adUnitPath) || 0) + 1;
+ slotsPerPath.set(adUnitPath, num);
+ const id = `${adUnitPath}_${num}`;
+ let clickUrl = "";
+ let collapseEmptyDiv = null;
+ let services = new Set();
+ const slot = {
+ addService(e) {
+ services.add(e);
+ return slot;
+ },
+ clearCategoryExclusions: noopthisfn,
+ clearTargeting(k) {
+ if (k === undefined) {
+ targeting.clear();
+ } else {
+ targeting.delete(k);
+ }
+ },
+ defineSizeMapping(mapping) {
+ slotCreatives.set(opt_div, mapping);
+ return this;
+ },
+ get: k => attributes.get(k),
+ getAdUnitPath: () => adUnitPath,
+ getAttributeKeys: () => Array.from(attributes.keys()),
+ getCategoryExclusions: () => Array.from(exclusions),
+ getClickUrl: () => clickUrl,
+ getCollapseEmptyDiv: () => collapseEmptyDiv,
+ getContentUrl: () => "",
+ getDivStartsCollapsed: () => null,
+ getDomId: () => opt_div,
+ getEscapedQemQueryId: () => "",
+ getFirstLook: () => 0,
+ getId: () => id,
+ getHtml: () => "",
+ getName: () => id,
+ getOutOfPage: () => false,
+ getResponseInformation: () => response,
+ getServices: () => Array.from(services),
+ getSizes: () => sizes,
+ getSlotElementId: () => opt_div,
+ getSlotId: () => slot,
+ getTargeting: k => targeting.get(k) || gTargeting.get(k) || [],
+ getTargetingKeys: () =>
+ Array.from(
+ new Set(Array.of(...gTargeting.keys(), ...targeting.keys()))
+ ),
+ getTargetingMap: () =>
+ Object.assign(
+ Object.fromEntries(gTargeting.entries()),
+ Object.fromEntries(targeting.entries())
+ ),
+ set(k, v) {
+ attributes.set(k, v);
+ return slot;
+ },
+ setCategoryExclusion(e) {
+ exclusions.add(e);
+ return slot;
+ },
+ setClickUrl(u) {
+ clickUrl = u;
+ return slot;
+ },
+ setCollapseEmptyDiv(v) {
+ collapseEmptyDiv = !!v;
+ return slot;
+ },
+ setSafeFrameConfig: noopthisfn,
+ setTagForChildDirectedTreatment: noopthisfn,
+ setTargeting(k, v) {
+ targeting.set(k, getTargetingValue(v));
+ return slot;
+ },
+ toString: () => id,
+ updateTargetingFromMap(map) {
+ updateTargeting(targeting, map);
+ return slot;
+ },
+ };
+ slots.set(adUnitPath, slot);
+ slotsById.set(opt_div, slot);
+ slotCreatives.set(opt_div, creatives);
+ return slot;
+ };
+
+ let initialLoadDisabled = false;
+
+ const gTargeting = new Map();
+ const gAttributes = new Map();
+
+ let imaContent = { vid: "", cmsid: "" };
+ let videoContent = { vid: "", cmsid: "" };
+
+ const pubadsService = {
+ addEventListener,
+ clear() {},
+ clearCategoryExclusions: noopthisfn,
+ clearTagForChildDirectedTreatment: noopthisfn,
+ clearTargeting(k) {
+ if (k === undefined) {
+ gTargeting.clear();
+ } else {
+ gTargeting.delete(k);
+ }
+ },
+ collapseEmptyDivs() {},
+ defineOutOfPagePassback: (a, o) => defineSlot(a, 0, o),
+ definePassback: (a, s, o) => defineSlot(a, s, o),
+ disableInitialLoad() {
+ initialLoadDisabled = true;
+ return this;
+ },
+ display(adUnitPath, sizes, opt_div) {
+ const slot = defineSlot(adUnitPath, sizes, opt_div);
+ displaySlot(slot);
+ },
+ enable() {},
+ enableAsyncRendering() {},
+ enableLazyLoad() {},
+ enableSingleRequest() {},
+ enableSyncRendering() {},
+ enableVideoAds() {},
+ forceExperiment() {},
+ get: k => gAttributes.get(k),
+ getAttributeKeys: () => Array.from(gAttributes.keys()),
+ getCorrelator() {},
+ getImaContent: () => imaContent,
+ getName: () => "publisher_ads",
+ getSlots: () => Array.from(slots.values()),
+ getSlotIdMap() {
+ const map = {};
+ slots.values().forEach(s => {
+ map[s.getId()] = s;
+ });
+ return map;
+ },
+ getTagSessionCorrelator() {},
+ getTargeting: k => gTargeting.get(k) || [],
+ getTargetingKeys: () => Array.from(gTargeting.keys()),
+ getTargetingMap: () => Object.fromEntries(gTargeting.entries()),
+ getVersion: () => version,
+ getVideoContent: () => videoContent,
+ isInitialLoadDisabled: () => initialLoadDisabled,
+ isSRA: () => false,
+ markAsAmp() {},
+ refresh(slts) {
+ if (!slts) {
+ slts = slots.values();
+ } else if (!Array.isArray(slts)) {
+ slts = [slts];
+ }
+ for (const slot of slts) {
+ if (slot) {
+ try {
+ fetchSlot(slot);
+ displaySlot(slot);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+ },
+ removeEventListener,
+ set(k, v) {
+ gAttributes[k] = v;
+ return this;
+ },
+ setCategoryExclusion: noopthisfn,
+ setCentering() {},
+ setCookieOptions: noopthisfn,
+ setCorrelator: noopthisfn,
+ setForceSafeFrame: noopthisfn,
+ setImaContent(vid, cmsid) {
+ imaContent = { vid, cmsid };
+ return this;
+ },
+ setLocation: noopthisfn,
+ setPrivacySettings: noopthisfn,
+ setPublisherProvidedId: noopthisfn,
+ setRequestNonPersonalizedAds: noopthisfn,
+ setSafeFrameConfig: noopthisfn,
+ setTagForChildDirectedTreatment: noopthisfn,
+ setTagForUnderAgeOfConsent: noopthisfn,
+ setTargeting(k, v) {
+ gTargeting.set(k, getTargetingValue(v));
+ return this;
+ },
+ setVideoContent(vid, cmsid) {
+ videoContent = { vid, cmsid };
+ return this;
+ },
+ updateCorrelator() {},
+ updateTargetingFromMap(map) {
+ updateTargeting(gTargeting, map);
+ return this;
+ },
+ };
+
+ const SizeMappingBuilder = class {
+ #mapping;
+ constructor() {
+ this.#mapping = new SizeMapping();
+ }
+ addSize(size, creatives) {
+ if (
+ size !== "fluid" &&
+ (!Array.isArray(size) || isNaN(size[0]) || isNaN(size[1]))
+ ) {
+ this.#mapping = null;
+ } else {
+ this.#mapping?.push([size, creatives]);
+ }
+ return this;
+ }
+ build() {
+ return this.#mapping;
+ }
+ };
+
+ let gt = window.googletag;
+ if (!gt) {
+ gt = window.googletag = {};
+ }
+
+ Object.assign(gt, {
+ apiReady: true,
+ companionAds: () => companionAdsService,
+ content: () => contentService,
+ defineOutOfPageSlot: (a, o) => defineSlot(a, 0, o),
+ defineSlot: (a, s, o) => defineSlot(a, s, o),
+ destroySlots() {
+ slots.clear();
+ slotsById.clear();
+ },
+ disablePublisherConsole() {},
+ display(arg) {
+ let id;
+ if (arg?.getSlotElementId) {
+ id = arg.getSlotElementId();
+ } else if (arg?.nodeType) {
+ id = arg.id;
+ } else {
+ id = String(arg);
+ }
+ displaySlot(slotsById.get(id));
+ },
+ enableServices() {},
+ enums: {
+ OutOfPageFormat: {
+ BOTTOM_ANCHOR: 3,
+ INTERSTITIAL: 5,
+ REWARDED: 4,
+ TOP_ANCHOR: 2,
+ },
+ },
+ getVersion: () => version,
+ pubads: () => pubadsService,
+ pubadsReady: true,
+ setAdIframeTitle() {},
+ sizeMapping: () => new SizeMappingBuilder(),
+ });
+
+ const run = function (fn) {
+ if (typeof fn === "function") {
+ try {
+ fn.call(window);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ };
+
+ const cmds = gt.cmd || [];
+ const newCmd = [];
+ newCmd.push = run;
+ gt.cmd = newCmd;
+
+ for (const cmd of cmds) {
+ run(cmd);
+ }
+}
diff --git a/browser/extensions/webcompat/shims/google-safeframe.html b/browser/extensions/webcompat/shims/google-safeframe.html
new file mode 100644
index 0000000000..34bc1d242f
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-safeframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- 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 1713691 - Shim Google SafeFrame
+
+ Some sites will break if they cannot load a Google SafeFrame. This
+ shim provides a stand-in for the frame to mitigate that breakage.
+-->
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>SafeFrame Container</title>
+ <script>
+ try {
+ const F = /^([^;]+);(\d+);([\s\S]*)$/.exec(window.name);
+ window.name = "";
+ const P = window.document;
+ P.open("text/html", "replace");
+ P.write(F[3].substr(0, +F[2]));
+ P.close();
+ } catch (e) {
+ console.error(e);
+ }
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/browser/extensions/webcompat/shims/history.js b/browser/extensions/webcompat/shims/history.js
new file mode 100644
index 0000000000..6fbd1fdedb
--- /dev/null
+++ b/browser/extensions/webcompat/shims/history.js
@@ -0,0 +1,54 @@
+/* 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
+);
diff --git a/browser/extensions/webcompat/shims/iam.js b/browser/extensions/webcompat/shims/iam.js
new file mode 100644
index 0000000000..84dee0e484
--- /dev/null
+++ b/browser/extensions/webcompat/shims/iam.js
@@ -0,0 +1,39 @@
+/* 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 1761774 - Shim INFOnline IAM tracker
+ *
+ * Sites using IAM can break if it is blocked. This shim mitigates that
+ * breakage by loading a stand-in module.
+ */
+
+if (!window.iom?.c) {
+ window.iom = {
+ c: () => {},
+ consent: () => {},
+ count: () => {},
+ ct: () => {},
+ deloptout: () => {},
+ doo: () => {},
+ e: () => {},
+ event: () => {},
+ getInvitation: () => {},
+ getPlus: () => {},
+ gi: () => {},
+ gp: () => {},
+ h: () => {},
+ hybrid: () => {},
+ i: () => {},
+ init: () => {},
+ oi: () => {},
+ optin: () => {},
+ setMultiIdentifier: () => {},
+ setoptout: () => {},
+ smi: () => {},
+ soo: () => {},
+ };
+}
diff --git a/browser/extensions/webcompat/shims/iaspet.js b/browser/extensions/webcompat/shims/iaspet.js
new file mode 100644
index 0000000000..7e19dd52ad
--- /dev/null
+++ b/browser/extensions/webcompat/shims/iaspet.js
@@ -0,0 +1,45 @@
+/* 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());
+ }
+}
diff --git a/browser/extensions/webcompat/shims/instagram.js b/browser/extensions/webcompat/shims/instagram.js
new file mode 100644
index 0000000000..5bf5014fdc
--- /dev/null
+++ b/browser/extensions/webcompat/shims/instagram.js
@@ -0,0 +1,55 @@
+/* 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
+);
diff --git a/browser/extensions/webcompat/shims/kinja.js b/browser/extensions/webcompat/shims/kinja.js
new file mode 100644
index 0000000000..d30425b42d
--- /dev/null
+++ b/browser/extensions/webcompat/shims/kinja.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/**
+ * Kinja powered blogs rely on storage access to https://kinja.com to enable
+ * oauth with external providers. For dFPI, sites need to use the Storage Access
+ * API to gain first party storage access. This shim calls requestStorageAccess
+ * on behalf of the site when a user wants to log in via oauth.
+ */
+
+// Third-party origin we need to request storage access for.
+const STORAGE_ACCESS_ORIGIN = "https://kinja.com";
+
+// Prefix of the path opened in a new window when users click the oauth login
+// buttons.
+const OAUTH_PATH_PREFIX = "/oauthlogin?provider=";
+
+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=1656171 for details.`
+);
+
+// Overwrite the window.open method so we can detect oauth related popups.
+const origOpen = window.wrappedJSObject.open;
+Object.defineProperty(window.wrappedJSObject, "open", {
+ value: exportFunction((url, ...args) => {
+ // 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),
+});
diff --git a/browser/extensions/webcompat/shims/live-test-shim.js b/browser/extensions/webcompat/shims/live-test-shim.js
new file mode 100644
index 0000000000..45ab9ba48b
--- /dev/null
+++ b/browser/extensions/webcompat/shims/live-test-shim.js
@@ -0,0 +1,82 @@
+/* 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.LiveTestShimPromise) {
+ const originalUrl = document.currentScript.src;
+
+ const shimId = "LiveTestShim";
+
+ 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 (_) {}
+
+ 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);
+ });
+ }
+ }
+ );
+}
diff --git a/browser/extensions/webcompat/shims/maxmind-geoip.js b/browser/extensions/webcompat/shims/maxmind-geoip.js
new file mode 100644
index 0000000000..e5eb1e45a3
--- /dev/null
+++ b/browser/extensions/webcompat/shims/maxmind-geoip.js
@@ -0,0 +1,69 @@
+/* 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,
+ };
+}
diff --git a/browser/extensions/webcompat/shims/microsoftLogin.js b/browser/extensions/webcompat/shims/microsoftLogin.js
new file mode 100644
index 0000000000..ebbfb2fbff
--- /dev/null
+++ b/browser/extensions/webcompat/shims/microsoftLogin.js
@@ -0,0 +1,29 @@
+/* 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);
diff --git a/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js b/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js
new file mode 100644
index 0000000000..4b4493750c
--- /dev/null
+++ b/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js
@@ -0,0 +1,46 @@
+/* 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 1801277 - Shim Microsoft virtual assistant.
+ *
+ * The microsoft virtual assistant will break when accessing the indexedDB that
+ * will throw a security error because the virtual assistant is under a
+ * third-party tracking domain 'liveperson.net'. The shim replaces the indexedDB
+ * with a fake interface that won't throw an error.
+ */
+
+/* globals cloneInto */
+
+(function () {
+ const win = window.wrappedJSObject;
+
+ try {
+ // We only replace the indexedDB when liveperson.net is loaded in a
+ // third-party context. Note that this is not strictly correct because
+ // this is a cross-origin check but not a third-party check.
+ if (win.parent == win || win.location.origin == win.top.location.origin) {
+ return;
+ }
+ } catch (e) {
+ // If we get a security error when accessing the top-level origin, this
+ // shows that the window is in a cross-origin context. In this case, we can
+ // proceed to apply the shim.
+ if (e.name != "SecurityError") {
+ throw e;
+ }
+ }
+
+ const emptyMsg = cloneInto({ message: "" }, window);
+
+ const idb = {
+ open: () => win.Promise.reject(emptyMsg),
+ };
+
+ Object.defineProperty(win, "indexedDB", {
+ value: cloneInto(idb, window, { cloneFunctions: true }),
+ });
+})();
diff --git a/browser/extensions/webcompat/shims/moat.js b/browser/extensions/webcompat/shims/moat.js
new file mode 100644
index 0000000000..9957492684
--- /dev/null
+++ b/browser/extensions/webcompat/shims/moat.js
@@ -0,0 +1,46 @@
+/* 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 1713704 - Shim Moat ad tracker
+ *
+ * Sites such as Forbes may gate content behind Moat ads, resulting in
+ * breakage like black boxes where videos should be placed. This shim
+ * helps mitigate that breakage by allowing the placement to succeed.
+ */
+
+if (!window.moatPrebidAPI?.__A) {
+ const targeting = new Map();
+
+ const slotConfig = {
+ m_categories: ["moat_safe"],
+ m_data: "0",
+ m_safety: "safe",
+ };
+
+ window.moatPrebidApi = {
+ __A() {},
+ disableLogging() {},
+ enableLogging() {},
+ getMoatTargetingForPage: () => 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;
+ },
+ };
+}
diff --git a/browser/extensions/webcompat/shims/mochitest-shim-1.js b/browser/extensions/webcompat/shims/mochitest-shim-1.js
new file mode 100644
index 0000000000..d95059cf7a
--- /dev/null
+++ b/browser/extensions/webcompat/shims/mochitest-shim-1.js
@@ -0,0 +1,87 @@
+/* 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));
+ });
+ }
+ });
+ });
+}
diff --git a/browser/extensions/webcompat/shims/mochitest-shim-2.js b/browser/extensions/webcompat/shims/mochitest-shim-2.js
new file mode 100644
index 0000000000..bc5536405e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/mochitest-shim-2.js
@@ -0,0 +1,85 @@
+/* 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);
+ });
+ }
+ });
+}
diff --git a/browser/extensions/webcompat/shims/mochitest-shim-3.js b/browser/extensions/webcompat/shims/mochitest-shim-3.js
new file mode 100644
index 0000000000..dc0a8005f5
--- /dev/null
+++ b/browser/extensions/webcompat/shims/mochitest-shim-3.js
@@ -0,0 +1,7 @@
+/* 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");
diff --git a/browser/extensions/webcompat/shims/nielsen.js b/browser/extensions/webcompat/shims/nielsen.js
new file mode 100644
index 0000000000..d34528a7c1
--- /dev/null
+++ b/browser/extensions/webcompat/shims/nielsen.js
@@ -0,0 +1,111 @@
+/* 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 1760754 - Shim Nielsen tracker
+ *
+ * Sites expecting the Nielsen tracker to load properly can break if it
+ * is blocked. This shim mitigates that breakage by loading a stand-in.
+ */
+
+if (!window.nol_t) {
+ const cid = "";
+
+ let domain = "";
+ let schemeHost = "";
+ let scriptName = "";
+ try {
+ const url = document?.currentScript?.src;
+ const { pathname, protocol, host } = new URL(url);
+ domain = host.split(".").slice(0, -2).join(".");
+ schemeHost = `${protocol}//${host}/`;
+ scriptName = pathname.split("/").pop();
+ } catch (_) {}
+
+ const NolTracker = class {
+ CONST = {
+ max_tags: 20,
+ };
+ feat = {};
+ globals = {
+ cid,
+ content: "0",
+ defaultApidFile: "config250",
+ defaultErrorParams: {
+ nol_vcid: "c00",
+ nol_clientid: "",
+ },
+ domain,
+ fpidSfCodeList: [""],
+ init() {},
+ tagCurrRetry: -1,
+ tagMaxRetry: 3,
+ wlCurrRetry: -1,
+ wlMaxRetry: 3,
+ };
+ pmap = [];
+ pvar = {
+ cid,
+ content: "0",
+ cookies_enabled: "n",
+ server: domain,
+ };
+ scriptName = [scriptName];
+ version = "6.0.107";
+
+ addScript() {}
+ catchLinkOverlay() {}
+ clickEvent() {}
+ clickTrack() {}
+ do_sample() {}
+ downloadEvent() {}
+ eventTrack() {}
+ filter() {}
+ fireToUrl() {}
+ getSchemeHost() {
+ return schemeHost;
+ }
+ getVersion() {}
+ iframe() {}
+ in_sample() {
+ return true;
+ }
+ injectBsdk() {}
+ invite() {}
+ linkTrack() {}
+ mergeFeatures() {}
+ pageEvent() {}
+ pause() {}
+ populateWhitelist() {}
+ post() {}
+ postClickTrack() {}
+ postData() {}
+ postEvent() {}
+ postEventTrack() {}
+ postLinkTrack() {}
+ prefix() {
+ return "";
+ }
+ processDdrsSvc() {}
+ random() {}
+ record() {
+ return this;
+ }
+ regLinkOverlay() {}
+ regListen() {}
+ retrieveCiFileViaCors() {}
+ sectionEvent() {}
+ sendALink() {}
+ sendForm() {}
+ sendIt() {}
+ slideEvent() {}
+ whitelistAssigned() {}
+ };
+
+ window.nol_t = () => {
+ return new NolTracker();
+ };
+}
diff --git a/browser/extensions/webcompat/shims/optimizely.js b/browser/extensions/webcompat/shims/optimizely.js
new file mode 100644
index 0000000000..dcda87421d
--- /dev/null
+++ b/browser/extensions/webcompat/shims/optimizely.js
@@ -0,0 +1,205 @@
+/* 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: {},
+ };
+}
diff --git a/browser/extensions/webcompat/shims/play.svg b/browser/extensions/webcompat/shims/play.svg
new file mode 100644
index 0000000000..df5bbcb4f1
--- /dev/null
+++ b/browser/extensions/webcompat/shims/play.svg
@@ -0,0 +1,7 @@
+<!-- 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/.
+ source: https://searchfox.org/mozilla-central/source/devtools/client/themes/images/play.svg -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="#fff" d="M20.436 11.37L5.904 2.116c-.23-.147-.523-.158-.762-.024-.24.132-.39.384-.39.657v18.5c0 .273.15.525.39.657.112.063.236.093.36.093.14 0 .28-.04.402-.117l14.53-9.248c.218-.138.35-.376.35-.633 0-.256-.132-.495-.348-.633z"/>
+</svg>
diff --git a/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js b/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js
new file mode 100644
index 0000000000..bc45aeda26
--- /dev/null
+++ b/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js
@@ -0,0 +1,17 @@
+/* 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 1714354 - Fix for site issues with web APIs in private browsing
+ *
+ * Some sites expect specific DOM APIs to work in specific ways, which
+ * is not always true, such as in private browsing mode. We work around
+ * related breakage by undefining those APIs entirely in private
+ * browsing mode for those sites.
+ */
+
+delete window.wrappedJSObject.caches;
+delete window.wrappedJSObject.indexedDB;
diff --git a/browser/extensions/webcompat/shims/rambler-authenticator.js b/browser/extensions/webcompat/shims/rambler-authenticator.js
new file mode 100644
index 0000000000..1fe074b660
--- /dev/null
+++ b/browser/extensions/webcompat/shims/rambler-authenticator.js
@@ -0,0 +1,84 @@
+/* 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.ramblerIdHelper) {
+ const originalScript = document.currentScript.src;
+
+ const sendMessageToAddon = (function () {
+ const shimId = "Rambler";
+ 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);
+ });
+ };
+ })();
+
+ 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;
+}
diff --git a/browser/extensions/webcompat/shims/rich-relevance.js b/browser/extensions/webcompat/shims/rich-relevance.js
new file mode 100644
index 0000000000..aea85c030a
--- /dev/null
+++ b/browser/extensions/webcompat/shims/rich-relevance.js
@@ -0,0 +1,288 @@
+/* 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 1713725 - Shim Rich Relevance personalized shopping
+ *
+ * Sites may expect the Rich Relevance personalized shopping API to load,
+ * breaking if it is blocked. This shim attempts to limit breakage on those
+ * site to just the personalized shopping aspects, by stubbing out the APIs.
+ */
+
+if (!window.r3_common) {
+ const jsonCallback = window.RR?.jsonCallback;
+ const defaultCallback = window.RR?.defaultCallback;
+
+ const getRandomString = (l = 66) => {
+ 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, {});
+}
diff --git a/browser/extensions/webcompat/shims/spotify-embed.js b/browser/extensions/webcompat/shims/spotify-embed.js
new file mode 100644
index 0000000000..62ad05b725
--- /dev/null
+++ b/browser/extensions/webcompat/shims/spotify-embed.js
@@ -0,0 +1,133 @@
+/* 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 }
+ );
+})();
diff --git a/browser/extensions/webcompat/shims/tracking-pixel.png b/browser/extensions/webcompat/shims/tracking-pixel.png
new file mode 100644
index 0000000000..52c591798e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/tracking-pixel.png
Binary files differ
diff --git a/browser/extensions/webcompat/shims/vast2.xml b/browser/extensions/webcompat/shims/vast2.xml
new file mode 100644
index 0000000000..3536ccfc0f
--- /dev/null
+++ b/browser/extensions/webcompat/shims/vast2.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 1713693 - Shim Doubleclick
+
+ Some sites rely on an XML VAST Ad response from Doubleclick, or will
+ break (showing black boxes instead of videos, etc). This shim mitigates
+ such breakage.
+-->
+<VAST version="2.0"></VAST>
diff --git a/browser/extensions/webcompat/shims/vast3.xml b/browser/extensions/webcompat/shims/vast3.xml
new file mode 100644
index 0000000000..ae03f0dc14
--- /dev/null
+++ b/browser/extensions/webcompat/shims/vast3.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 1713693 - Shim Doubleclick
+
+ Some sites rely on an XML VAST Ad response from Doubleclick, or will
+ break (showing black boxes instead of videos, etc). This shim mitigates
+ such breakage.
+-->
+<VAST version="3.0"></VAST>
diff --git a/browser/extensions/webcompat/shims/vidible.js b/browser/extensions/webcompat/shims/vidible.js
new file mode 100644
index 0000000000..1d45bc0f7e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/vidible.js
@@ -0,0 +1,424 @@
+/* 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 1713710 - Shim Vidible video player
+ *
+ * Sites relying on Vidible's video player may experience broken videos if that
+ * script is blocked. This shim allows users to opt into viewing those videos
+ * regardless of any tracking consequences, by providing placeholders for each.
+ */
+
+if (!window.vidible?.version) {
+ const PlayIconURL = "https://smartblock.firefox.etp/play.svg";
+
+ const originalScript = document.currentScript.src;
+
+ const getGUID = () => {
+ 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);
+ }
+}
diff --git a/browser/extensions/webcompat/shims/vmad.xml b/browser/extensions/webcompat/shims/vmad.xml
new file mode 100644
index 0000000000..5bb9a5a5d5
--- /dev/null
+++ b/browser/extensions/webcompat/shims/vmad.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 1713693 - Shim Doubleclick
+
+ Some sites rely on an XML VMAD Ad response from Doubleclick, or will
+ break (showing black boxes instead of videos, etc). This shim mitigates
+ such breakage.
+-->
+<vmap:AdBreak></vmap:AdBreak>
diff --git a/browser/extensions/webcompat/shims/webtrends.js b/browser/extensions/webcompat/shims/webtrends.js
new file mode 100644
index 0000000000..c7ef0069da
--- /dev/null
+++ b/browser/extensions/webcompat/shims/webtrends.js
@@ -0,0 +1,46 @@
+/* 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?.();
+ });
+}