summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/extensions/webcompat/about-compat/AboutCompat.jsm42
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.css187
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.html51
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.js283
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.js46
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.json6
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPageProcessScript.js34
-rw-r--r--browser/extensions/webcompat/components.conf17
-rw-r--r--browser/extensions/webcompat/data/injections.js1059
-rw-r--r--browser/extensions/webcompat/data/shims.js874
-rw-r--r--browser/extensions/webcompat/data/ua_overrides.js1371
-rw-r--r--browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js53
-rw-r--r--browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json72
-rw-r--r--browser/extensions/webcompat/experiment-apis/appConstants.js28
-rw-r--r--browser/extensions/webcompat/experiment-apis/appConstants.json15
-rw-r--r--browser/extensions/webcompat/experiment-apis/matchPatterns.js30
-rw-r--r--browser/extensions/webcompat/experiment-apis/matchPatterns.json29
-rw-r--r--browser/extensions/webcompat/experiment-apis/systemManufacturer.js23
-rw-r--r--browser/extensions/webcompat/experiment-apis/systemManufacturer.json20
-rw-r--r--browser/extensions/webcompat/experiment-apis/trackingProtection.js216
-rw-r--r--browser/extensions/webcompat/experiment-apis/trackingProtection.json102
-rw-r--r--browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css7
-rw-r--r--browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css21
-rw-r--r--browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css15
-rw-r--r--browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css16
-rw-r--r--browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css19
-rw-r--r--browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css16
-rw-r--r--browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css15
-rw-r--r--browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css12
-rw-r--r--browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css15
-rw-r--r--browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css23
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css18
-rw-r--r--browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css19
-rw-r--r--browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css13
-rw-r--r--browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js15
-rw-r--r--browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js35
-rw-r--r--browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js33
-rw-r--r--browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js38
-rw-r--r--browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js52
-rw-r--r--browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js32
-rw-r--r--browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js38
-rw-r--r--browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js22
-rw-r--r--browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js21
-rw-r--r--browser/extensions/webcompat/injections/js/bug1724764-window-print.js28
-rw-r--r--browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js29
-rw-r--r--browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js36
-rw-r--r--browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js116
-rw-r--r--browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js35
-rw-r--r--browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js26
-rw-r--r--browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js27
-rw-r--r--browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js40
-rw-r--r--browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js31
-rw-r--r--browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js37
-rw-r--r--browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js24
-rw-r--r--browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js29
-rw-r--r--browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js26
-rw-r--r--browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js45
-rw-r--r--browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js25
-rw-r--r--browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js24
-rw-r--r--browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js27
-rw-r--r--browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js23
-rw-r--r--browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js39
-rw-r--r--browser/extensions/webcompat/lib/about_compat_broker.js141
-rw-r--r--browser/extensions/webcompat/lib/custom_functions.js109
-rw-r--r--browser/extensions/webcompat/lib/injections.js165
-rw-r--r--browser/extensions/webcompat/lib/intervention_helpers.js233
-rw-r--r--browser/extensions/webcompat/lib/messaging_helper.js36
-rw-r--r--browser/extensions/webcompat/lib/module_shim.js24
-rw-r--r--browser/extensions/webcompat/lib/requestStorageAccess_helper.js30
-rw-r--r--browser/extensions/webcompat/lib/shim_messaging_helper.js65
-rw-r--r--browser/extensions/webcompat/lib/shims.js1044
-rw-r--r--browser/extensions/webcompat/lib/ua_helpers.js79
-rw-r--r--browser/extensions/webcompat/lib/ua_overrides.js210
-rw-r--r--browser/extensions/webcompat/manifest.json153
-rw-r--r--browser/extensions/webcompat/moz.build192
-rw-r--r--browser/extensions/webcompat/run.js45
-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
-rw-r--r--browser/extensions/webcompat/tests/browser/browser.ini15
-rw-r--r--browser/extensions/webcompat/tests/browser/browser_aboutcompat.js27
-rw-r--r--browser/extensions/webcompat/tests/browser/browser_shims.js73
-rw-r--r--browser/extensions/webcompat/tests/browser/head.js140
-rw-r--r--browser/extensions/webcompat/tests/browser/iframe_test.html19
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test.html21
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test.js11
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_2.html21
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_2.js11
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_3.html21
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_3.js7
164 files changed, 14951 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/about-compat/AboutCompat.jsm b/browser/extensions/webcompat/about-compat/AboutCompat.jsm
new file mode 100644
index 0000000000..7c844726f8
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/AboutCompat.jsm
@@ -0,0 +1,42 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["AboutCompat"];
+
+const Services =
+ globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+const addonID = "webcompat@mozilla.org";
+const addonPageRelativeURL = "/about-compat/aboutCompat.html";
+
+function AboutCompat() {
+ this.chromeURL =
+ WebExtensionPolicy.getByID(addonID).getURL(addonPageRelativeURL);
+}
+AboutCompat.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIAboutModule"]),
+ getURIFlags() {
+ return (
+ Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS |
+ Ci.nsIAboutModule.IS_SECURE_CHROME_UI
+ );
+ },
+
+ newChannel(aURI, aLoadInfo) {
+ const uri = Services.io.newURI(this.chromeURL);
+ const channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+ channel.originalURI = aURI;
+
+ channel.owner = (
+ Services.scriptSecurityManager.createContentPrincipal ||
+ // Handles fallback to earlier versions.
+ // eslint-disable-next-line mozilla/valid-services-property
+ Services.scriptSecurityManager.createCodebasePrincipal
+ )(uri, aLoadInfo.originAttributes);
+ return channel;
+ },
+};
diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.css b/browser/extensions/webcompat/about-compat/aboutCompat.css
new file mode 100644
index 0000000000..b51db7f9f5
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutCompat.css
@@ -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/. */
+
+@media (any-pointer: fine) {
+ :root {
+ font-family: sans-serif;
+ margin: 40px auto;
+ min-width: 30em;
+ max-width: 60em;
+ }
+
+ table {
+ width: 100%;
+ padding-bottom: 2em;
+ border-spacing: 0;
+ }
+
+ td {
+ border-bottom: 1px solid var(--in-content-border-color);
+ }
+
+ td:last-child > button {
+ float: inline-end;
+ }
+}
+
+/* Mobile UI where common.css is not loaded */
+
+@media (any-pointer: coarse), (any-pointer: none) {
+ * {
+ margin: 0;
+ padding: 0;
+ }
+
+ :root {
+ --background-color: #fff;
+ --text-color: #0c0c0d;
+ --border-color: #e1e1e2;
+ --button-background-color: #f5f5f5;
+ --selected-tab-text-color: #0061e0;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --background-color: #292833;
+ --text-color: #f9f9fa;
+ --border-color: rgba(255, 255, 255, 0.15);
+ --button-background-color: rgba(0, 0, 0, 0.15);
+ --selected-tab-text-color: #00ddff;
+ }
+ }
+
+ body {
+ background-color: var(--background-color);
+ color: var(--text-color);
+ font: message-box;
+ font-size: 14px;
+ -moz-text-size-adjust: none;
+ display: grid;
+ grid-template-areas: "a b c" "d d d";
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-template-rows: fit-content(100%) 1fr;
+ }
+
+ .tab[data-l10n-id="label-overrides"] {
+ grid-area: a;
+ }
+
+ .tab[data-l10n-id="label-interventions"] {
+ grid-area: b;
+ }
+
+ .tab[data-l10n-id="label-smartblock"] {
+ grid-area: c;
+ }
+
+ table {
+ grid-area: d;
+ }
+
+ table,
+ tr,
+ p {
+ display: block;
+ }
+
+ table {
+ border-top: 2px solid var(--border-color);
+ margin-top: -2px;
+ width: 100%;
+ z-index: 1;
+ display: none;
+ }
+
+ tr {
+ border-bottom: 1px solid var(--border-color);
+ padding: 0;
+ }
+
+ a {
+ color: inherit;
+ font-size: 94%;
+ }
+
+ .tab {
+ cursor: pointer;
+ z-index: 2;
+ display: inline-block;
+ text-align: left;
+ border-block: 2px solid transparent;
+ font-size: 1em;
+ font-weight: bold;
+ padding: 1em;
+ }
+
+ .tab.active {
+ color: var(--selected-tab-text-color);
+ border-bottom-color: currentColor;
+ margin-bottom: 0;
+ padding-bottom: calc(1em + 2px);
+ }
+
+ .tab.active + table {
+ display: block;
+ }
+
+ td {
+ grid-area: b;
+ padding-left: 1em;
+ }
+
+ td:first-child {
+ grid-area: a;
+ padding-top: 1em;
+ }
+
+ td:last-child {
+ grid-area: c;
+ padding-bottom: 1em;
+ }
+
+ tr {
+ display: grid;
+ grid-template-areas: "a c" "b c";
+ grid-template-columns: 1fr 6.5em;
+ }
+
+ td[colspan="4"] {
+ padding: 1em;
+ font-style: italic;
+ text-align: center;
+ }
+
+ td:not([colspan]):nth-child(1) {
+ font-weight: bold;
+ padding-bottom: 0.25em;
+ }
+
+ td:nth-child(2) {
+ padding-bottom: 1em;
+ }
+
+ td:nth-child(3) {
+ display: flex;
+ padding: 0;
+ }
+
+ button {
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ background: var(--button-background-color);
+ color: inherit;
+ inset-inline-end: 0;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ border-inline-start: 1px solid var(--border-color);
+ font-weight: 600;
+ appearance: none;
+ }
+
+ button::-moz-focus-inner {
+ border: 0;
+ }
+}
diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.html b/browser/extensions/webcompat/about-compat/aboutCompat.html
new file mode 100644
index 0000000000..d820f20ee2
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutCompat.html
@@ -0,0 +1,51 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <base />
+
+ <!-- If you change this script tag you must update the hash in the extension's
+ `content_security_policy` 'sha256-MmZkN2QaIHhfRWPZ8TVRjijTn5Ci1iEabtTEWrt9CCo=' -->
+ <script>
+ /* globals browser */ document.head.firstElementChild.href =
+ browser.runtime.getURL("");
+ </script>
+
+ <meta charset="utf-8" />
+ <meta name="color-scheme" content="light dark" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <link rel="stylesheet" href="about-compat/aboutCompat.css" />
+ <link
+ rel="stylesheet"
+ media="screen and (pointer:fine), projection"
+ type="text/css"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <link rel="localization" href="toolkit/about/aboutCompat.ftl" />
+ <title data-l10n-id="text-title"></title>
+ <script src="about-compat/aboutCompat.js"></script>
+ </head>
+ <body>
+ <h2 class="tab active" data-l10n-id="label-overrides"></h2>
+ <table id="overrides">
+ <col />
+ <col />
+ <col />
+ </table>
+ <h2 class="tab" data-l10n-id="label-interventions"></h2>
+ <table id="interventions">
+ <col />
+ <col />
+ <col />
+ </table>
+ <h2 class="tab" data-l10n-id="label-smartblock"></h2>
+ <table id="smartblock" class="shims">
+ <col />
+ <col />
+ <col />
+ </table>
+ </body>
+</html>
diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.js b/browser/extensions/webcompat/about-compat/aboutCompat.js
new file mode 100644
index 0000000000..e01b853877
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutCompat.js
@@ -0,0 +1,283 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser */
+
+let availablePatches;
+
+const portToAddon = (function () {
+ let port;
+
+ function connect() {
+ port = browser.runtime.connect({ name: "AboutCompatTab" });
+ port.onMessage.addListener(onMessageFromAddon);
+ port.onDisconnect.addListener(e => {
+ port = undefined;
+ });
+ }
+
+ connect();
+
+ async function send(message) {
+ if (port) {
+ return port.postMessage(message);
+ }
+ return Promise.reject("background script port disconnected");
+ }
+
+ return { send };
+})();
+
+const $ = function (sel) {
+ return document.querySelector(sel);
+};
+
+const DOMContentLoadedPromise = new Promise(resolve => {
+ document.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ resolve();
+ },
+ { once: true }
+ );
+});
+
+Promise.all([
+ browser.runtime.sendMessage("getAllInterventions"),
+ DOMContentLoadedPromise,
+]).then(([info]) => {
+ document.body.addEventListener("click", async evt => {
+ const ele = evt.target;
+ if (ele.nodeName === "BUTTON") {
+ const row = ele.closest("[data-id]");
+ if (row) {
+ evt.preventDefault();
+ ele.disabled = true;
+ const id = row.getAttribute("data-id");
+ try {
+ await browser.runtime.sendMessage({ command: "toggle", id });
+ } catch (_) {
+ ele.disabled = false;
+ }
+ }
+ } else if (ele.classList.contains("tab")) {
+ document.querySelectorAll(".tab").forEach(tab => {
+ tab.classList.remove("active");
+ });
+ ele.classList.add("active");
+ }
+ });
+
+ availablePatches = info;
+ redraw();
+});
+
+function onMessageFromAddon(msg) {
+ const alsoShowHidden = location.hash === "#all";
+
+ if ("interventionsChanged" in msg) {
+ redrawTable($("#interventions"), msg.interventionsChanged, alsoShowHidden);
+ }
+
+ if ("overridesChanged" in msg) {
+ redrawTable($("#overrides"), msg.overridesChanged, alsoShowHidden);
+ }
+
+ if ("shimsChanged" in msg) {
+ updateShimTables(msg.shimsChanged, alsoShowHidden);
+ }
+
+ const id = msg.toggling || msg.toggled;
+ const button = $(`[data-id="${id}"] button`);
+ if (!button) {
+ return;
+ }
+ const active = msg.active;
+ document.l10n.setAttributes(
+ button,
+ active ? "label-disable" : "label-enable"
+ );
+ button.disabled = !!msg.toggling;
+}
+
+function redraw() {
+ if (!availablePatches) {
+ return;
+ }
+ const { overrides, interventions, shims } = availablePatches;
+ const alsoShowHidden = location.hash === "#all";
+ redrawTable($("#overrides"), overrides, alsoShowHidden);
+ redrawTable($("#interventions"), interventions, alsoShowHidden);
+ updateShimTables(shims, alsoShowHidden);
+}
+
+function clearTableAndAddMessage(table, msgId) {
+ table.querySelectorAll("tr").forEach(tr => {
+ tr.remove();
+ });
+
+ const tr = document.createElement("tr");
+ tr.className = "message";
+ tr.id = msgId;
+
+ const td = document.createElement("td");
+ td.setAttribute("colspan", "3");
+ document.l10n.setAttributes(td, msgId);
+ tr.appendChild(td);
+
+ table.appendChild(tr);
+}
+
+function hideMessagesOnTable(table) {
+ table.querySelectorAll("tr.message").forEach(tr => {
+ tr.remove();
+ });
+}
+
+function updateShimTables(shimsChanged, alsoShowHidden) {
+ const tables = document.querySelectorAll("table.shims");
+ if (!tables.length) {
+ return;
+ }
+
+ for (const { bug, disabledReason, hidden, id, name, type } of shimsChanged) {
+ // if any shim is disabled by global pref, all of them are. just show the
+ // "disabled in about:config" message on each shim table in that case.
+ if (disabledReason === "globalPref") {
+ for (const table of tables) {
+ clearTableAndAddMessage(table, "text-disabled-in-about-config");
+ }
+ return;
+ }
+
+ // otherwise, find which table the shim belongs in. if there is none,
+ // ignore the shim (we're not showing it on the UI for whatever reason).
+ const table = document.querySelector(`table.shims#${type}`);
+ if (!table) {
+ continue;
+ }
+
+ // similarly, skip shims hidden from the UI (only for testing, etc).
+ if (!alsoShowHidden && hidden) {
+ continue;
+ }
+
+ // also, hide the shim if it is disabled because it is not meant for this
+ // platform, release (etc) rather than being disabled by pref/about:compat
+ const notApplicable =
+ disabledReason &&
+ disabledReason !== "pref" &&
+ disabledReason !== "session";
+ if (!alsoShowHidden && notApplicable) {
+ continue;
+ }
+
+ // create an updated table-row for the shim
+ const tr = document.createElement("tr");
+ tr.setAttribute("data-id", id);
+
+ let td = document.createElement("td");
+ td.innerText = name;
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ const a = document.createElement("a");
+ a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
+ document.l10n.setAttributes(a, "label-more-information", { bug });
+ a.target = "_blank";
+ td.appendChild(a);
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ tr.appendChild(td);
+ const button = document.createElement("button");
+ document.l10n.setAttributes(
+ button,
+ disabledReason ? "label-enable" : "label-disable"
+ );
+ td.appendChild(button);
+
+ // is it already in the table?
+ const row = table.querySelector(`tr[data-id="${id}"]`);
+ if (row) {
+ row.replaceWith(tr);
+ } else {
+ table.appendChild(tr);
+ }
+ }
+
+ for (const table of tables) {
+ if (!table.querySelector("tr:not(.message)")) {
+ // no shims? then add a message that none are available for this platform/config
+ clearTableAndAddMessage(table, `text-no-${table.id}`);
+ } else {
+ // otherwise hide any such message, since we have shims on the list
+ hideMessagesOnTable(table);
+ }
+ }
+}
+
+function redrawTable(table, data, alsoShowHidden) {
+ const df = document.createDocumentFragment();
+ table.querySelectorAll("tr").forEach(tr => {
+ tr.remove();
+ });
+
+ let noEntriesMessage;
+ if (data === false) {
+ noEntriesMessage = "text-disabled-in-about-config";
+ } else if (data.length === 0) {
+ noEntriesMessage = `text-no-${table.id}`;
+ }
+
+ if (noEntriesMessage) {
+ const tr = document.createElement("tr");
+ df.appendChild(tr);
+
+ const td = document.createElement("td");
+ td.setAttribute("colspan", "3");
+ document.l10n.setAttributes(td, noEntriesMessage);
+ tr.appendChild(td);
+
+ table.appendChild(df);
+ return;
+ }
+
+ for (const row of data) {
+ if (row.hidden && !alsoShowHidden) {
+ continue;
+ }
+
+ const tr = document.createElement("tr");
+ tr.setAttribute("data-id", row.id);
+ df.appendChild(tr);
+
+ let td = document.createElement("td");
+ td.innerText = row.domain;
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ const a = document.createElement("a");
+ const bug = row.bug;
+ a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
+ document.l10n.setAttributes(a, "label-more-information", { bug });
+ a.target = "_blank";
+ td.appendChild(a);
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ tr.appendChild(td);
+ const button = document.createElement("button");
+ document.l10n.setAttributes(
+ button,
+ row.active ? "label-disable" : "label-enable"
+ );
+ td.appendChild(button);
+ }
+ table.appendChild(df);
+}
+
+window.onhashchange = redraw;
diff --git a/browser/extensions/webcompat/about-compat/aboutPage.js b/browser/extensions/webcompat/about-compat/aboutPage.js
new file mode 100644
index 0000000000..0f2e7c4ad4
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutPage.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";
+
+/* global ExtensionAPI, XPCOMUtils */
+
+const Services =
+ globalThis.Services ||
+ ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "resProto",
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsISubstitutingProtocolHandler"
+);
+
+const ResourceSubstitution = "webcompat";
+const ProcessScriptURL = "resource://webcompat/aboutPageProcessScript.js";
+const ContractID = "@mozilla.org/network/protocol/about;1?what=compat";
+
+this.aboutPage = class extends ExtensionAPI {
+ onStartup() {
+ const { rootURI } = this.extension;
+
+ resProto.setSubstitution(
+ ResourceSubstitution,
+ Services.io.newURI("about-compat/", null, rootURI)
+ );
+
+ if (!(ContractID in Cc)) {
+ Services.ppmm.loadProcessScript(ProcessScriptURL, true);
+ this.processScriptRegistered = true;
+ }
+ }
+
+ onShutdown() {
+ resProto.setSubstitution(ResourceSubstitution, null);
+
+ if (this.processScriptRegistered) {
+ Services.ppmm.removeDelayedProcessScript(ProcessScriptURL);
+ }
+ }
+};
diff --git a/browser/extensions/webcompat/about-compat/aboutPage.json b/browser/extensions/webcompat/about-compat/aboutPage.json
new file mode 100644
index 0000000000..42e6114188
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutPage.json
@@ -0,0 +1,6 @@
+[
+ {
+ "namespace": "aboutCompat",
+ "description": "Enables the about:compat page"
+ }
+]
diff --git a/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js b/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js
new file mode 100644
index 0000000000..13cb4fd0bf
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js
@@ -0,0 +1,34 @@
+/* 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/. */
+
+/* eslint-env mozilla/process-script */
+
+"use strict";
+
+// Note: This script is used only when a static registration for our
+// component is not already present in the libxul binary.
+
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+const classID = Components.ID("{97bf9550-2a7b-11e9-b56e-0800200c9a66}");
+
+if (!Cm.isCIDRegistered(classID)) {
+ const { ComponentUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/ComponentUtils.sys.mjs"
+ );
+
+ const factory = ComponentUtils.generateSingletonFactory(function () {
+ const { AboutCompat } = ChromeUtils.import(
+ "resource://webcompat/AboutCompat.jsm"
+ );
+ return new AboutCompat();
+ });
+
+ Cm.registerFactory(
+ classID,
+ "about:compat",
+ "@mozilla.org/network/protocol/about;1?what=compat",
+ factory
+ );
+}
diff --git a/browser/extensions/webcompat/components.conf b/browser/extensions/webcompat/components.conf
new file mode 100644
index 0000000000..ca5a6c3dbd
--- /dev/null
+++ b/browser/extensions/webcompat/components.conf
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Note: This file will add static component registration entries for our
+# components to the libxul binary, though the actual component JSMs will be
+# packaged with the extension.
+Classes = [
+ {
+ 'cid': '{97bf9550-2a7b-11e9-b56e-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=compat'],
+ 'jsm': 'resource://webcompat/AboutCompat.jsm',
+ 'constructor': 'AboutCompat',
+ },
+]
diff --git a/browser/extensions/webcompat/data/injections.js b/browser/extensions/webcompat/data/injections.js
new file mode 100644
index 0000000000..3c0dc58bce
--- /dev/null
+++ b/browser/extensions/webcompat/data/injections.js
@@ -0,0 +1,1059 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals module, require */
+
+// This is a hack for the tests.
+if (typeof InterventionHelpers === "undefined") {
+ var InterventionHelpers = require("../lib/intervention_helpers");
+}
+
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const AVAILABLE_INJECTIONS = [
+ {
+ id: "testbed-injection",
+ platform: "all",
+ domain: "webcompat-addon-testbed.herokuapp.com",
+ bug: "0000000",
+ hidden: true,
+ contentScripts: {
+ matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
+ css: [
+ {
+ file: "injections/css/bug0000000-testbed-css-injection.css",
+ },
+ ],
+ js: [
+ {
+ file: "injections/js/bug0000000-testbed-js-injection.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1452707",
+ platform: "all",
+ domain: "ib.absa.co.za",
+ bug: "1452707",
+ contentScripts: {
+ matches: ["https://ib.absa.co.za/*"],
+ js: [
+ {
+ file: "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1457335",
+ platform: "desktop",
+ domain: "histography.io",
+ bug: "1457335",
+ contentScripts: {
+ matches: ["*://histography.io/*"],
+ js: [
+ {
+ file: "injections/js/bug1457335-histography.io-ua-change.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1472075",
+ platform: "desktop",
+ domain: "bankofamerica.com",
+ bug: "1472075",
+ contentScripts: {
+ matches: [
+ "*://*.bankofamerica.com/*",
+ "*://*.ml.com/*", // #120104
+ ],
+ js: [
+ {
+ file: "injections/js/bug1472075-bankofamerica.com-ua-change.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1579159",
+ platform: "android",
+ domain: "m.tailieu.vn",
+ bug: "1579159",
+ contentScripts: {
+ matches: ["*://m.tailieu.vn/*", "*://m.elib.vn/*"],
+ js: [
+ {
+ file: "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1583366",
+ platform: "desktop",
+ domain: "Download prompt for files with no content-type",
+ bug: "1583366",
+ data: {
+ urls: ["https://ads-us.rd.linksynergy.com/as.php*"],
+ contentType: {
+ name: "content-type",
+ value: "text/html; charset=utf-8",
+ },
+ },
+ customFunc: "noSniffFix",
+ },
+ {
+ id: "bug1570328",
+ platform: "android",
+ domain: "developer.apple.com",
+ bug: "1570328",
+ contentScripts: {
+ matches: ["*://developer.apple.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1570328-developer-apple.com-transform-scale.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1575000",
+ platform: "all",
+ domain: "apply.lloydsbank.co.uk",
+ bug: "1575000",
+ contentScripts: {
+ matches: ["*://apply.lloydsbank.co.uk/*"],
+ css: [
+ {
+ file: "injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1605611",
+ platform: "android",
+ domain: "maps.google.com",
+ bug: "1605611",
+ contentScripts: {
+ matches: InterventionHelpers.matchPatternsForGoogle(
+ "*://www.google.",
+ "/maps*"
+ ),
+ css: [
+ {
+ file: "injections/css/bug1605611-maps.google.com-directions-time.css",
+ },
+ ],
+ js: [
+ {
+ file: "injections/js/bug1605611-maps.google.com-directions-time.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1610344",
+ platform: "all",
+ domain: "directv.com.co",
+ bug: "1610344",
+ contentScripts: {
+ matches: [
+ "https://*.directv.com.co/*",
+ "https://*.directv.com.ec/*", // bug 1827706
+ ],
+ css: [
+ {
+ file: "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1644830",
+ platform: "desktop",
+ domain: "usps.com",
+ bug: "1644830",
+ contentScripts: {
+ matches: ["https://*.usps.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1651917",
+ platform: "android",
+ domain: "teletrader.com",
+ bug: "1651917",
+ contentScripts: {
+ matches: ["*://*.teletrader.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1651917-teletrader.com.body-transform-origin.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1653075",
+ platform: "desktop",
+ domain: "livescience.com",
+ bug: "1653075",
+ contentScripts: {
+ matches: ["*://*.livescience.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1653075-livescience.com-scrollbar-width.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1654877",
+ platform: "android",
+ domain: "preev.com",
+ bug: "1654877",
+ contentScripts: {
+ matches: ["*://preev.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1654877-preev.com-moz-appearance-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1654907",
+ platform: "android",
+ domain: "reactine.ca",
+ bug: "1654907",
+ contentScripts: {
+ matches: ["*://*.reactine.ca/*"],
+ css: [
+ {
+ file: "injections/css/bug1654907-reactine.ca-hide-unsupported.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1631811",
+ platform: "all",
+ domain: "datastudio.google.com",
+ bug: "1631811",
+ contentScripts: {
+ matches: ["https://datastudio.google.com/embed/reporting/*"],
+ js: [
+ {
+ file: "injections/js/bug1631811-datastudio.google.com-indexedDB.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1694470",
+ platform: "android",
+ domain: "m.myvidster.com",
+ bug: "1694470",
+ contentScripts: {
+ matches: ["https://m.myvidster.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1694470-myvidster.com-content-not-shown.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1731825",
+ platform: "desktop",
+ domain: "Office 365 email handling prompt",
+ bug: "1731825",
+ contentScripts: {
+ matches: [
+ "*://*.live.com/*",
+ "*://*.office.com/*",
+ "*://*.sharepoint.com/*",
+ "*://*.office365.com/*",
+ ],
+ js: [
+ {
+ file: "injections/js/bug1731825-office365-email-handling-prompt-autohide.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1707795",
+ platform: "desktop",
+ domain: "Office Excel spreadsheets",
+ bug: "1707795",
+ contentScripts: {
+ matches: [
+ "*://*.live.com/*",
+ "*://*.office.com/*",
+ "*://*.sharepoint.com/*",
+ ],
+ css: [
+ {
+ file: "injections/css/bug1707795-office365-sheets-overscroll-disable.css",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1712833",
+ platform: "all",
+ domain: "buskocchi.desuca.co.jp",
+ bug: "1712833",
+ contentScripts: {
+ matches: ["*://buskocchi.desuca.co.jp/*"],
+ css: [
+ {
+ file: "injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1722955",
+ platform: "android",
+ domain: "frontgate.com",
+ bug: "1722955",
+ contentScripts: {
+ matches: ["*://*.frontgate.com/*"],
+ js: [
+ {
+ file: "lib/ua_helpers.js",
+ },
+ {
+ file: "injections/js/bug1722955-frontgate.com-ua-override.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1724764",
+ platform: "android",
+ domain: "Issues related to missing window.print",
+ bug: "1724764",
+ contentScripts: {
+ matches: [
+ "*://*.edupage.org/*", // 1804477 and 1800118
+ ],
+ js: [
+ {
+ file: "injections/js/bug1724764-window-print.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1724868",
+ platform: "android",
+ domain: "news.yahoo.co.jp",
+ bug: "1724868",
+ contentScripts: {
+ matches: ["*://news.yahoo.co.jp/articles/*", "*://s.yimg.jp/*"],
+ js: [
+ {
+ file: "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1741234",
+ platform: "all",
+ domain: "patient.alphalabs.ca",
+ bug: "1741234",
+ contentScripts: {
+ matches: ["*://patient.alphalabs.ca/*"],
+ css: [
+ {
+ file: "injections/css/bug1741234-patient.alphalabs.ca-height-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1739489",
+ platform: "desktop",
+ domain: "Sites using draft.js",
+ bug: "1739489",
+ contentScripts: {
+ matches: [
+ "*://draftjs.org/*", // Bug 1739489
+ "*://www.facebook.com/*", // Bug 1739489
+ "*://twitter.com/*", // Bug 1776229
+ "*://mobile.twitter.com/*", // Bug 1776229
+ "*://*.reddit.com/*", // Bug 1829755
+ ],
+ js: [
+ {
+ file: "injections/js/bug1739489-draftjs-beforeinput.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1765947",
+ platform: "android",
+ domain: "veniceincoming.com",
+ bug: "1765947",
+ contentScripts: {
+ matches: ["*://veniceincoming.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1765947-veniceincoming.com-left-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug11769762",
+ platform: "all",
+ domain: "tiktok.com",
+ bug: "1769762",
+ contentScripts: {
+ matches: ["https://www.tiktok.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1769762-tiktok.com-plugins-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1770962",
+ platform: "all",
+ domain: "coldwellbankerhomes.com",
+ bug: "1770962",
+ contentScripts: {
+ matches: ["*://*.coldwellbankerhomes.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1770962-coldwellbankerhomes.com-image-height.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1774490",
+ platform: "all",
+ domain: "rainews.it",
+ bug: "1774490",
+ contentScripts: {
+ matches: ["*://www.rainews.it/*"],
+ css: [
+ {
+ file: "injections/css/bug1774490-rainews.it-gallery-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1774005",
+ platform: "all",
+ domain: "Sites relying on window.InstallTrigger",
+ bug: "1774005",
+ contentScripts: {
+ matches: [
+ "*://*.crunchyroll.com/*", // Bug 1777597
+ "*://*.ersthelfer.tv/*", // Bug 1817520
+ "*://*.webex.com/*", // Bug 1788934
+ "*://ifcinema.institutfrancais.com/*", // Bug 1806423
+ "*://islamionline.islamicbank.ps/*", // Bug 1821439
+ "*://*.itv.com/*", // Bug 1830203
+ "*://mobilevikings.be/*/registration/*", // Bug 1797400
+ "*://www.schoolnutritionandfitness.com/*", // Bug 1793761
+ ],
+ js: [
+ {
+ file: "injections/js/bug1774005-installtrigger-shim.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1784302",
+ platform: "android",
+ domain: "open.toutiao.com",
+ bug: "1784302",
+ contentScripts: {
+ matches: ["*://open.toutiao.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1784302-effectiveType-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1784141",
+ platform: "android",
+ domain: "aveeno.com and acuvue.com",
+ bug: "1784141",
+ contentScripts: {
+ matches: [
+ "*://*.aveeno.com/*",
+ "*://*.aveeno.ca/*",
+ "*://*.aveeno.com.au/*",
+ "*://*.aveeno.co.kr/*",
+ "*://*.aveeno.co.uk/*",
+ "*://*.aveeno.ie/*",
+ "*://*.acuvue.com/*", // 1804730
+ "*://*.acuvue.com.ar/*",
+ "*://*.acuvue.com.br/*",
+ "*://*.acuvue.ca/*",
+ "*://*.acuvue-fr.ca/*",
+ "*://*.acuvue.cl/*",
+ "*://*.acuvue.co.cr/*",
+ "*://*.acuvue.com.co/*",
+ "*://*.acuvue.com.do/*",
+ "*://*.acuvue.com.pe/*",
+ "*://*.acuvue.com.sv/*",
+ "*://*.acuvue.com.gt/*",
+ "*://*.acuvue.hn/*",
+ "*://*.acuvue.com.mx/*",
+ "*://*.acuvue.com.pa/*",
+ "*://*.acuvue.com.py/*",
+ "*://*.acuvue.com.pr/*",
+ "*://*.acuvue.com.uy/*",
+ "*://*.acuvue.com.au/*",
+ "*://*.acuvue.com.cn/*",
+ "*://*.acuvue.com.hk/*",
+ "*://*.acuvue.co.in/*",
+ "*://*.acuvue.co.id/*",
+ "*://acuvuevision.jp/*",
+ "*://*.acuvue.co.kr/*",
+ "*://*.acuvue.com.my/*",
+ "*://*.acuvue.co.nz/*",
+ "*://*.acuvue.com.sg/*",
+ "*://*.acuvue.com.tw/*",
+ "*://*.acuvue.co.th/*",
+ "*://*.acuvue.com.vn/*",
+ "*://*.acuvue.at/*",
+ "*://*.acuvue.be/*",
+ "*://*.fr.acuvue.be/*",
+ "*://*.acuvue-croatia.com/*",
+ "*://*.acuvue.cz/*",
+ "*://*.acuvue.dk/*",
+ "*://*.acuvue.fi/*",
+ "*://*.acuvue.fr/*",
+ "*://*.acuvue.de/*",
+ "*://*.acuvue.gr/*",
+ "*://*.acuvue.hu/*",
+ "*://*.acuvue.ie/*",
+ "*://*.acuvue.co.il/*",
+ "*://*.acuvue.it/*",
+ "*://*.acuvuekz.com/*",
+ "*://*.acuvue.lu/*",
+ "*://*.en.acuvuearabia.com/*",
+ "*://*.acuvuearabia.com/*",
+ "*://*.acuvue.nl/*",
+ "*://*.acuvue.no/*",
+ "*://*.acuvue.pl/*",
+ "*://*.acuvue.pt/*",
+ "*://*.acuvue.ro/*",
+ "*://*.acuvue.ru/*",
+ "*://*.acuvue.sk/*",
+ "*://*.acuvue.si/*",
+ "*://*.acuvue.co.za/*",
+ "*://*.jnjvision.com.tr/*",
+ "*://*.acuvue.co.uk/*",
+ "*://*.acuvue.ua/*",
+ "*://*.acuvue.com.pe/*",
+ "*://*.acuvue.es/*",
+ "*://*.acuvue.se/*",
+ "*://*.acuvue.ch/*",
+ ],
+ css: [
+ {
+ file: "injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1784199",
+ platform: "all",
+ domain: "Sites based on Entrata Platform",
+ bug: "1784199",
+ contentScripts: {
+ matches: [
+ "*://*.aptsovation.com/*",
+ "*://*.avanabayview.com/*", // #118617
+ "*://*.breakpointeandcoronado.com/*", // #117735
+ "*://*.liveatlasathens.com/*", // #111189
+ "*://*.liveobserverpark.com/*", // #105244
+ "*://*.midwayurban.com/*", // #116523
+ "*://*.nhcalaska.com/*",
+ "*://*.prospectportal.com/*", // #115206
+ "*://*.securityproperties.com/*",
+ "*://*.theloftsorlando.com/*",
+ "*://*.vanallenapartments.com/*", // #120056
+ ],
+ css: [
+ {
+ file: "injections/css/bug1784199-entrata-platform-unsupported.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1795490",
+ platform: "android",
+ domain: "www.china-airlines.com",
+ bug: "1795490",
+ contentScripts: {
+ matches: ["*://www.china-airlines.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1799968",
+ platform: "linux",
+ domain: "www.samsung.com",
+ bug: "1799968",
+ contentScripts: {
+ matches: ["*://www.samsung.com/*/watches/*/*"],
+ js: [
+ {
+ file: "injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1799980",
+ platform: "all",
+ domain: "healow.com",
+ bug: "1799980",
+ contentScripts: {
+ matches: ["*://healow.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1799980-healow.com-infinite-loop-fix.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1799994",
+ platform: "desktop",
+ domain: "www.vivobarefoot.com",
+ bug: "1799994",
+ contentScripts: {
+ matches: ["*://www.vivobarefoot.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1800000",
+ platform: "desktop",
+ domain: "www.honda.co.uk",
+ bug: "1800000",
+ contentScripts: {
+ matches: ["*://www.honda.co.uk/cars/book-a-service.html*"],
+ css: [
+ {
+ file: "injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1448747",
+ platform: "android",
+ domain: "FastClick breakage",
+ bug: "1448747",
+ contentScripts: {
+ matches: [
+ "*://*.co2meter.com/*", // 10959
+ "*://*.franmar.com/*", // 27273
+ "*://*.themusiclab.org/*", // 49667
+ "*://*.oregonfoodbank.org/*", // 53203
+ "*://*.fourbarrelcoffee.com/*", // 59427
+ "*://bluetokaicoffee.com/*", // 99867
+ "*://bathpublishing.com/*", // 100145
+ "*://dylantalkstone.com/*", // 101356
+ "*://renewd.com.au/*", // 104998
+ "*://*.lamudi.co.id/*", // 106767
+ "*://*.thehawksmoor.com/*", // 107549
+ "*://weaversofireland.com/*", // 116816
+ "*://*.iledefrance-mobilites.fr/*", // 117344
+ "*://*.lawnmowerpartsworld.com/*", // 117577
+ "*://*.discountcoffee.co.uk/*", // 118757
+ "*://torguard.net/*", // 120113
+ "*://*.arcsivr.com/*", // 120716
+ ],
+ js: [
+ {
+ file: "injections/js/bug1448747-fastclick-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1818818",
+ platform: "android",
+ domain: "FastClick breakage - legacy",
+ bug: "1818818",
+ contentScripts: {
+ matches: [
+ "*://*.chatiw.com/*", // 5544
+ "*://*.wellcare.com/*", // 116595
+ ],
+ js: [
+ {
+ file: "injections/js/bug1818818-fastclick-legacy-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1819476",
+ platform: "all",
+ domain: "axisbank.com",
+ bug: "1819476",
+ contentScripts: {
+ matches: ["*://*.axisbank.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1819450",
+ platform: "android",
+ domain: "cmbchina.com",
+ bug: "1819450",
+ contentScripts: {
+ matches: ["*://www.cmbchina.com/*", "*://cmbchina.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1819450-cmbchina.com-ua-change.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1819678",
+ platform: "android",
+ domain: "cnki.net",
+ bug: "1819678",
+ contentScripts: {
+ matches: ["*://*.cnki.net/*"],
+ js: [
+ {
+ file: "injections/js/bug1819678-cnki.net-undisable-search-field.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1827678-webc77727",
+ platform: "android",
+ domain: "free4talk.com",
+ bug: "1827678",
+ contentScripts: {
+ matches: ["*://www.free4talk.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1819678-free4talk.com-window-chrome-shim.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1827678-webc119017",
+ platform: "desktop",
+ domain: "nppes.cms.hhs.gov",
+ bug: "1827678",
+ contentScripts: {
+ matches: ["*://nppes.cms.hhs.gov/*"],
+ css: [
+ {
+ file: "injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830776",
+ platform: "all",
+ domain: "blueshieldca.com",
+ bug: "1830776",
+ contentScripts: {
+ matches: ["*://*.blueshieldca.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1830776-blueshieldca.com-unsupported.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1829949",
+ platform: "desktop",
+ domain: "tomshardware.com",
+ bug: "1829949",
+ contentScripts: {
+ matches: ["*://*.tomshardware.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1829949-tomshardware.com-scrollbar-width.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1829952",
+ platform: "android",
+ domain: "eventer.co.il",
+ bug: "1829952",
+ contentScripts: {
+ matches: ["*://*.eventer.co.il/*"],
+ css: [
+ {
+ file: "injections/css/bug1829952-eventer.co.il-button-height.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830747",
+ platform: "android",
+ domain: "my.babbel.com",
+ bug: "1830747",
+ contentScripts: {
+ matches: ["*://my.babbel.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1830747-babbel.com-page-height.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830752",
+ platform: "all",
+ domain: "afisha.ru",
+ bug: "1830752",
+ contentScripts: {
+ matches: ["*://*.afisha.ru/*"],
+ css: [
+ {
+ file: "injections/css/bug1830752-afisha.ru-slider-pointer-events.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830761",
+ platform: "all",
+ domain: "91mobiles.com",
+ bug: "1830761",
+ contentScripts: {
+ matches: ["*://*.91mobiles.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1830761-91mobiles.com-content-height.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830796",
+ platform: "android",
+ domain: "copyleaks.com",
+ bug: "1830796",
+ contentScripts: {
+ matches: ["*://*.copyleaks.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1830796-copyleaks.com-hide-unsupported.css",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1830810",
+ platform: "all",
+ domain: "interceramic.com",
+ bug: "1830810",
+ contentScripts: {
+ matches: ["*://interceramic.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1830810-interceramic.com-hide-unsupported.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1830813",
+ platform: "desktop",
+ domain: "onstove.com",
+ bug: "1830813",
+ contentScripts: {
+ matches: ["*://*.onstove.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1830813-page.onstove.com-hide-unsupported.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1831007",
+ platform: "all",
+ domain: "All international Nintendo domains",
+ bug: "1831007",
+ contentScripts: {
+ matches: [
+ "*://*.mojenintendo.cz/*",
+ "*://*.nintendo-europe.com/*",
+ "*://*.nintendo.at/*",
+ "*://*.nintendo.be/*",
+ "*://*.nintendo.ch/*",
+ "*://*.nintendo.co.il/*",
+ "*://*.nintendo.co.jp/*",
+ "*://*.nintendo.co.kr/*",
+ "*://*.nintendo.co.nz/*",
+ "*://*.nintendo.co.uk/*",
+ "*://*.nintendo.co.za/*",
+ "*://*.nintendo.com.au/*",
+ "*://*.nintendo.com.hk/*",
+ "*://*.nintendo.com/*",
+ "*://*.nintendo.de/*",
+ "*://*.nintendo.dk/*",
+ "*://*.nintendo.es/*",
+ "*://*.nintendo.fi/*",
+ "*://*.nintendo.fr/*",
+ "*://*.nintendo.gr/*",
+ "*://*.nintendo.hu/*",
+ "*://*.nintendo.it/*",
+ "*://*.nintendo.nl/*",
+ "*://*.nintendo.no/*",
+ "*://*.nintendo.pt/*",
+ "*://*.nintendo.ru/*",
+ "*://*.nintendo.se/*",
+ "*://*.nintendo.sk/*",
+ "*://*.nintendo.tw/*",
+ "*://*.nintendoswitch.com.cn/*",
+ ],
+ js: [
+ {
+ file: "injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1836157",
+ platform: "android",
+ domain: "thai-masszazs.net",
+ bug: "1836157",
+ contentScripts: {
+ matches: ["*://www.thai-masszazs.net/en/*"],
+ js: [
+ {
+ file: "injections/js/bug1836157-thai-masszazs-niceScroll-disable.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1836103",
+ platform: "all",
+ domain: "autostar-novoross.ru",
+ bug: "1836103",
+ contentScripts: {
+ matches: ["*://autostar-novoross.ru/*"],
+ css: [
+ {
+ file: "injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1836105",
+ platform: "all",
+ domain: "cnn.com",
+ bug: "1836105",
+ contentScripts: {
+ matches: ["*://*.cnn.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1836177",
+ platform: "desktop",
+ domain: "clalit.co.il",
+ bug: "1836177",
+ contentScripts: {
+ matches: [
+ "*://e-services.clalit.co.il/OnlineWeb/General/InfoFullLogin.aspx*",
+ ],
+ css: [
+ {
+ file: "injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1842437",
+ platform: "desktop",
+ domain: "www.youtube.com",
+ bug: "1842437",
+ contentScripts: {
+ matches: ["*://www.youtube.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1842437-www.youtube.com-performance-now-precision.js",
+ },
+ ],
+ },
+ },
+];
+
+module.exports = AVAILABLE_INJECTIONS;
diff --git a/browser/extensions/webcompat/data/shims.js b/browser/extensions/webcompat/data/shims.js
new file mode 100644
index 0000000000..8e08dd6c95
--- /dev/null
+++ b/browser/extensions/webcompat/data/shims.js
@@ -0,0 +1,874 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals module, require */
+
+const AVAILABLE_SHIMS = [
+ {
+ hiddenInAboutCompat: true,
+ id: "LiveTestShim",
+ platform: "all",
+ name: "Live test shim",
+ bug: "livetest",
+ file: "live-test-shim.js",
+ matches: ["*://webcompat-addon-testbed.herokuapp.com/shims_test.js"],
+ needsShimHelpers: ["getOptions", "optIn"],
+ },
+ {
+ hiddenInAboutCompat: true,
+ id: "MochitestShim",
+ platform: "all",
+ branch: ["all:ignoredOtherPlatform"],
+ name: "Test shim for Mochitests",
+ bug: "mochitest",
+ file: "mochitest-shim-1.js",
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test.js",
+ ],
+ needsShimHelpers: ["getOptions", "optIn"],
+ options: {
+ simpleOption: true,
+ complexOption: { a: 1, b: "test" },
+ branchValue: { value: true, branches: [] },
+ platformValue: { value: true, platform: "neverUsed" },
+ },
+ unblocksOnOptIn: ["*://trackertest.org/*"],
+ },
+ {
+ hiddenInAboutCompat: true,
+ disabled: true,
+ id: "MochitestShim2",
+ platform: "all",
+ name: "Test shim for Mochitests (disabled by default)",
+ bug: "mochitest",
+ file: "mochitest-shim-2.js",
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_2.js",
+ ],
+ needsShimHelpers: ["getOptions", "optIn"],
+ options: {
+ simpleOption: true,
+ complexOption: { a: 1, b: "test" },
+ branchValue: { value: true, branches: [] },
+ platformValue: { value: true, platform: "neverUsed" },
+ },
+ unblocksOnOptIn: ["*://trackertest.org/*"],
+ },
+ {
+ hiddenInAboutCompat: true,
+ id: "MochitestShim3",
+ platform: "all",
+ name: "Test shim for Mochitests (host)",
+ bug: "mochitest",
+ file: "mochitest-shim-3.js",
+ notHosts: ["example.com"],
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js",
+ ],
+ },
+ {
+ hiddenInAboutCompat: true,
+ id: "MochitestShim4",
+ platform: "all",
+ name: "Test shim for Mochitests (notHost)",
+ bug: "mochitest",
+ file: "mochitest-shim-3.js",
+ hosts: ["example.net"],
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js",
+ ],
+ },
+ {
+ hiddenInAboutCompat: true,
+ id: "MochitestShim5",
+ platform: "all",
+ name: "Test shim for Mochitests (branch)",
+ bug: "mochitest",
+ file: "mochitest-shim-3.js",
+ branches: ["never matches"],
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js",
+ ],
+ },
+ {
+ hiddenInAboutCompat: true,
+ id: "MochitestShim6",
+ platform: "never matches",
+ name: "Test shim for Mochitests (platform)",
+ bug: "mochitest",
+ file: "mochitest-shim-3.js",
+ matches: [
+ "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js",
+ ],
+ },
+ {
+ id: "AddThis",
+ platform: "all",
+ name: "AddThis",
+ bug: "1713694",
+ file: "addthis-angular.js",
+ matches: [
+ "*://s7.addthis.com/icons/official-addthis-angularjs/current/dist/official-addthis-angularjs.min.js*",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Adform",
+ platform: "all",
+ name: "Adform",
+ bug: "1713695",
+ file: "adform.js",
+ matches: [
+ "*://track.adform.net/serving/scripts/trackpoint/",
+ "*://track.adform.net/serving/scripts/trackpoint/async/",
+ {
+ patterns: ["*://track.adform.net/Serving/TrackPoint/*"],
+ target: "tracking-pixel.png",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AdNexusAST",
+ platform: "all",
+ name: "AdNexus AST",
+ bug: "1734130",
+ file: "adnexus-ast.js",
+ matches: ["*://*.adnxs.com/*/ast.js*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AdNexusPrebid",
+ platform: "all",
+ name: "AdNexus Prebid",
+ bug: "1713696",
+ file: "adnexus-prebid.js",
+ matches: ["*://*.adnxs.com/*/pb.js*", "*://*.adnxs.com/*/prebid*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AdobeEverestJS",
+ platform: "all",
+ name: "Adobe EverestJS",
+ bug: "1728114",
+ file: "everest.js",
+ matches: ["*://www.everestjs.net/static/st.v3.js*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ // keep this above AdSafeProtectedTrackingPixels
+ id: "AdSafeProtectedGoogleIMAAdapter",
+ platform: "all",
+ name: "Ad Safe Protected Google IMA Adapter",
+ bug: "1508639",
+ file: "adsafeprotected-ima.js",
+ matches: ["*://static.adsafeprotected.com/vans-adapter-google-ima.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AdsByGoogle",
+ platform: "all",
+ name: "Ads by Google",
+ bug: "1713726",
+ file: "google-ads.js",
+ matches: [
+ "*://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js",
+ {
+ patterns: [
+ "*://pagead2.googlesyndication.com/pagead/*.js*fcd=true",
+ "*://pagead2.googlesyndication.com/pagead/js/*.js*fcd=true",
+ ],
+ target: "empty-script.js",
+ types: ["xmlhttprequest"],
+ },
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AdvertisingCom",
+ platform: "all",
+ name: "advertising.com",
+ bug: "1701685",
+ matches: [
+ {
+ patterns: ["*://pixel.advertising.com/firefox-etp"],
+ target: "tracking-pixel.png",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ patterns: ["*://cdn.cmp.advertising.com/firefox-etp"],
+ target: "empty-script.js",
+ types: ["xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ patterns: ["*://*.advertising.com/*.js*"],
+ target: "https://cdn.cmp.advertising.com/firefox-etp",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ patterns: ["*://*.advertising.com/*"],
+ target: "https://pixel.advertising.com/firefox-etp",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ ],
+ },
+ {
+ id: "Branch",
+ platform: "all",
+ name: "Branch Web SDK",
+ bug: "1716220",
+ file: "branch.js",
+ matches: ["*://cdn.branch.io/branch-latest.min.js*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "DoubleVerify",
+ platform: "all",
+ name: "DoubleVerify",
+ bug: "1771557",
+ file: "doubleverify.js",
+ matches: ["*://pub.doubleverify.com/signals/pub.js*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "AmazonTAM",
+ platform: "all",
+ name: "Amazon Transparent Ad Marketplace",
+ bug: "1713698",
+ file: "apstag.js",
+ matches: ["*://c.amazon-adsystem.com/aax2/apstag.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "BmAuth",
+ platform: "all",
+ name: "BmAuth by 9c9media",
+ bug: "1486337",
+ file: "bmauth.js",
+ matches: ["*://auth.9c9media.ca/auth/main.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Chartbeat",
+ platform: "all",
+ name: "Chartbeat",
+ bug: "1713699",
+ file: "chartbeat.js",
+ matches: [
+ "*://static.chartbeat.com/js/chartbeat.js",
+ "*://static.chartbeat.com/js/chartbeat_video.js",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Criteo",
+ platform: "all",
+ name: "Criteo",
+ bug: "1713720",
+ file: "criteo.js",
+ matches: ["*://static.criteo.net/js/ld/publishertag.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ // keep this above AdSafeProtectedTrackingPixels
+ id: "Doubleclick",
+ platform: "all",
+ name: "Doubleclick",
+ bug: "1713693",
+ matches: [
+ {
+ patterns: [
+ "*://securepubads.g.doubleclick.net/gampad/*ad-blk*",
+ "*://pubads.g.doubleclick.net/gampad/*ad-blk*",
+ ],
+ target: "empty-shim.txt",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ {
+ patterns: [
+ "*://securepubads.g.doubleclick.net/gampad/*xml_vmap1*",
+ "*://pubads.g.doubleclick.net/gampad/*xml_vmap1*",
+ ],
+ target: "vmad.xml",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ {
+ patterns: [
+ "*://vast.adsafeprotected.com/vast*",
+ "*://securepubads.g.doubleclick.net/gampad/*xml_vmap2*",
+ "*://pubads.g.doubleclick.net/gampad/*xml_vmap2*",
+ ],
+ target: "vast2.xml",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ {
+ patterns: [
+ "*://securepubads.g.doubleclick.net/gampad/*ad*",
+ "*://pubads.g.doubleclick.net/gampad/*ad*",
+ ],
+ target: "vast3.xml",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "PBMWebAPIFixes",
+ platform: "all",
+ name: "Private Browsing Web APIs",
+ bug: "1773110",
+ runFirst: "private-browsing-web-api-fixes.js",
+ matches: [
+ "*://*.imgur.com/js/vendor.*.bundle.js",
+ "*://*.imgur.io/js/vendor.*.bundle.js",
+ "*://www.rva311.com/static/js/main.*.chunk.js",
+ "*://web-assets.toggl.com/app/assets/scripts/*.js", // bug 1783919
+ ],
+ onlyIfPrivateBrowsing: true,
+ },
+ {
+ id: "Eluminate",
+ platform: "all",
+ name: "Eluminate",
+ bug: "1503211",
+ file: "eluminate.js",
+ matches: ["*://libs.coremetrics.com/eluminate.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "FacebookSDK",
+ platform: "all",
+ branches: ["nightly:android"],
+ name: "Facebook SDK",
+ bug: "1226498",
+ file: "facebook-sdk.js",
+ logos: ["facebook.svg", "play.svg"],
+ matches: [
+ "*://connect.facebook.net/*/sdk.js*",
+ "*://connect.facebook.net/*/all.js*",
+ {
+ patterns: ["*://www.facebook.com/platform/impression.php*"],
+ target: "tracking-pixel.png",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ ],
+ needsShimHelpers: ["optIn", "getOptions"],
+ onlyIfBlockedByETP: true,
+ unblocksOnOptIn: [
+ "*://connect.facebook.net/*/sdk.js*",
+ "*://connect.facebook.net/*/all.js*",
+ "*://*.xx.fbcdn.net/*", // covers:
+ // "*://scontent-.*-\d.xx.fbcdn.net/*",
+ // "*://static.xx.fbcdn.net/rsrc.php/*",
+ "*://graph.facebook.com/v2*access_token*",
+ "*://graph.facebook.com/v*/me*",
+ "*://graph.facebook.com/*/picture*",
+ "*://www.facebook.com/*/plugins/login_button.php*",
+ "*://www.facebook.com/x/oauth/status*",
+ {
+ patterns: [
+ "*://www.facebook.com/*/plugins/video.php*",
+ "*://www.facebook.com/rsrc.php/*",
+ ],
+ branches: ["nightly"],
+ },
+ ],
+ },
+ {
+ id: "Fastclick",
+ platform: "all",
+ name: "Fastclick",
+ bug: "1738220",
+ file: "fastclick.js",
+ matches: [
+ "*://secure.cdn.fastclick.net/js/cnvr-launcher/*/launcher-stub.min.js*",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleAnalyticsAndTagManager",
+ platform: "all",
+ name: "Google Analytics and Tag Manager",
+ bug: "1713687",
+ file: "google-analytics-and-tag-manager.js",
+ matches: [
+ "*://www.google-analytics.com/analytics.js*",
+ "*://www.google-analytics.com/gtm/js*",
+ "*://www.googletagmanager.com/gtm.js*",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleAnalyticsECommercePlugin",
+ platform: "all",
+ name: "Google Analytics E-Commerce Plugin",
+ bug: "1620533",
+ file: "google-analytics-ecommerce-plugin.js",
+ matches: ["*://www.google-analytics.com/plugins/ua/ec.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleAnalyticsLegacy",
+ platform: "all",
+ name: "Google Analytics (legacy version)",
+ bug: "1487072",
+ file: "google-analytics-legacy.js",
+ matches: ["*://ssl.google-analytics.com/ga.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleIMA",
+ platform: "all",
+ name: "Google Interactive Media Ads",
+ bug: "1713690",
+ file: "google-ima.js",
+ matches: [
+ "*://s0.2mdn.net/instream/html5/ima3.js",
+ "*://imasdk.googleapis.com/js/sdkloader/ima3.js",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GooglePageAd",
+ platform: "all",
+ name: "Google Page Ad",
+ bug: "1713692",
+ file: "google-page-ad.js",
+ matches: ["*://www.googleadservices.com/pagead/conversion_async.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GooglePublisherTags",
+ platform: "all",
+ name: "Google Publisher Tags",
+ bug: "1713685",
+ file: "google-publisher-tags.js",
+ matches: [
+ "*://www.googletagservices.com/tag/js/gpt.js*",
+ "*://pagead2.googlesyndication.com/tag/js/gpt.js*",
+ "*://pagead2.googlesyndication.com/gpt/pubads_impl_*.js*",
+ "*://securepubads.g.doubleclick.net/tag/js/gpt.js*",
+ "*://securepubads.g.doubleclick.net/gpt/pubads_impl_*.js*",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Google SafeFrame",
+ platform: "all",
+ name: "Google SafeFrame",
+ bug: "1713691",
+ matches: [
+ {
+ patterns: [
+ "*://tpc.googlesyndication.com/safeframe/*/html/container.html",
+ "*://*.safeframe.googlesyndication.com/safeframe/*/html/container.html",
+ ],
+ target: "google-safeframe.html",
+ types: ["sub_frame"],
+ },
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleTrends",
+ platform: "all",
+ name: "Google Trends",
+ bug: "1624914",
+ custom: "google-trends-dfpi-fix",
+ onlyIfDFPIActive: true,
+ matches: [
+ {
+ patterns: ["*://trends.google.com/trends/embed*"],
+ types: ["sub_frame"],
+ },
+ ],
+ },
+ {
+ id: "IAM",
+ platform: "all",
+ name: "INFOnline IAM",
+ bug: "1761774",
+ file: "iam.js",
+ matches: ["*://script.ioam.de/iam.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ // keep this above AdSafeProtectedTrackingPixels
+ id: "IASPET",
+ platform: "all",
+ name: "Integral Ad Science PET",
+ bug: "1713701",
+ file: "iaspet.js",
+ matches: [
+ "*://cdn.adsafeprotected.com/iasPET.1.js",
+ "*://static.adsafeprotected.com/iasPET.1.js",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "MNet",
+ platform: "all",
+ name: "Media.net Ads",
+ bug: "1713703",
+ file: "empty-script.js",
+ matches: ["*://adservex.media.net/videoAds.js*"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Moat",
+ platform: "all",
+ name: "Moat",
+ bug: "1713704",
+ file: "moat.js",
+ matches: [
+ "*://*.moatads.com/*/moatad.js*",
+ "*://*.moatads.com/*/moatapi.js*",
+ "*://*.moatads.com/*/moatheader.js*",
+ "*://*.moatads.com/*/yi.js*",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Nielsen",
+ platform: "all",
+ name: "Nielsen",
+ bug: "1760754",
+ file: "nielsen.js",
+ matches: ["*://*.imrworldwide.com/v60.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Optimizely",
+ platform: "all",
+ name: "Optimizely",
+ bug: "1714431",
+ file: "optimizely.js",
+ matches: [
+ "*://cdn.optimizely.com/js/*.js",
+ "*://cdn.optimizely.com/public/*.js",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Rambler",
+ platform: "all",
+ name: "Rambler Authenticator",
+ bug: "1606428",
+ file: "rambler-authenticator.js",
+ matches: ["*://id.rambler.ru/rambler-id-helper/auth_events.js"],
+ needsShimHelpers: ["optIn"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "RichRelevance",
+ platform: "all",
+ name: "Rich Relevance",
+ bug: "1713725",
+ file: "rich-relevance.js",
+ matches: ["*://media.richrelevance.com/rrserver/js/1.2/p13n.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Firebase",
+ platform: "all",
+ name: "Firebase",
+ bug: "1771783",
+ onlyIfPrivateBrowsing: true,
+ runFirst: "firebase.js",
+ matches: [
+ // bugs 1750699, 1767407
+ "*://www.gstatic.com/firebasejs/*/firebase-messaging.js*",
+ ],
+ contentScripts: [
+ {
+ cookieStoreId: "firefox-private",
+ js: "firebase.js",
+ runAt: "document_start",
+ matches: [
+ "*://www.homedepot.ca/*", // bug 1778993
+ "*://orangerie.eu/*", // bug 1758442
+ "*://web.whatsapp.com/*", // bug 1767407
+ "*://www.tripadvisor.com/*", // bug 1779536
+ "*://www.office.com/*", // bug 1783921
+ ],
+ },
+ ],
+ },
+ {
+ id: "StickyAdsTV",
+ platform: "all",
+ name: "StickyAdsTV",
+ bug: "1717806",
+ matches: [
+ {
+ patterns: ["https://ads.stickyadstv.com/firefox-etp"],
+ target: "tracking-pixel.png",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ patterns: [
+ "*://ads.stickyadstv.com/auto-user-sync*",
+ "*://ads.stickyadstv.com/user-matching*",
+ ],
+ target: "https://ads.stickyadstv.com/firefox-etp",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ ],
+ },
+ {
+ id: "Vidible",
+ branch: ["nightly"],
+ platform: "all",
+ name: "Vidible",
+ bug: "1713710",
+ file: "vidible.js",
+ logos: ["play.svg"],
+ matches: [
+ "*://*.vidible.tv/*/vidible-min.js*",
+ "*://vdb-cdn-files.s3.amazonaws.com/*/vidible-min.js*",
+ ],
+ needsShimHelpers: ["optIn"],
+ onlyIfBlockedByETP: true,
+ unblocksOnOptIn: [
+ "*://delivery.vidible.tv/jsonp/pid=*/vid=*/*.js*",
+ "*://delivery.vidible.tv/placement/*",
+ "*://img.vidible.tv/prod/*",
+ "*://cdn-ssl.vidible.tv/prod/player/js/*.js",
+ "*://hlsrv.vidible.tv/prod/*.m3u8*",
+ "*://videos.vidible.tv/prod/*.key*",
+ "*://videos.vidible.tv/prod/*.mp4*",
+ "*://videos.vidible.tv/prod/*.webm*",
+ "*://videos.vidible.tv/prod/*.ts*",
+ ],
+ },
+ {
+ id: "Kinja",
+ platform: "all",
+ name: "Kinja",
+ bug: "1656171",
+ contentScripts: [
+ {
+ js: "kinja.js",
+ matches: [
+ "*://www.avclub.com/*",
+ "*://deadspin.com/*",
+ "*://gizmodo.com/*",
+ "*://jalopnik.com/*",
+ "*://jezebel.com/*",
+ "*://kotaku.com/*",
+ "*://lifehacker.com/*",
+ "*://www.theonion.com/*",
+ "*://www.theroot.com/*",
+ "*://thetakeout.com/*",
+ "*://theinventory.com/*",
+ ],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ id: "MicrosoftLogin",
+ platform: "desktop",
+ name: "Microsoft Login",
+ bug: "1638383",
+ requestStorageAccessForRedirect: [
+ ["*://web.powerva.microsoft.com/*", "*://login.microsoftonline.com/*"],
+ ["*://teams.microsoft.com/*", "*://login.microsoftonline.com/*"],
+ ["*://*.teams.microsoft.us/*", "*://login.microsoftonline.us/*"],
+ ],
+ contentScripts: [
+ {
+ js: "microsoftLogin.js",
+ matches: [
+ "*://web.powerva.microsoft.com/*",
+ "*://teams.microsoft.com/*",
+ "*://*.teams.microsoft.us/*",
+ ],
+ runAt: "document_start",
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ id: "MicrosoftVirtualAssistant",
+ platform: "all",
+ name: "Microsoft Virtual Assistant",
+ bug: "1801277",
+ contentScripts: [
+ {
+ js: "microsoftVirtualAssistant.js",
+ matches: ["*://publisher.liveperson.net/*"],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ ],
+ },
+ {
+ id: "History",
+ platform: "all",
+ name: "History.com",
+ bug: "1624853",
+ contentScripts: [
+ {
+ js: "history.js",
+ matches: ["*://play.history.com/*"],
+ runAt: "document_start",
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ id: "Crave.ca",
+ platform: "all",
+ name: "Crave.ca",
+ bug: "1746439",
+ contentScripts: [
+ {
+ js: "crave-ca.js",
+ matches: ["*://account.bellmedia.ca/login*"],
+ runAt: "document_start",
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ id: "Instagram.com",
+ platform: "android",
+ name: "Instagram.com",
+ bug: "1804445",
+ contentScripts: [
+ {
+ js: "instagram.js",
+ matches: ["*://www.instagram.com/*"],
+ runAt: "document_start",
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ id: "MaxMindGeoIP",
+ platform: "all",
+ name: "MaxMind GeoIP",
+ bug: "1754389",
+ file: "maxmind-geoip.js",
+ matches: ["*://js.maxmind.com/js/apis/geoip2/*/geoip2.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "WebTrends",
+ platform: "all",
+ name: "WebTrends",
+ bug: "1766414",
+ file: "webtrends.js",
+ matches: [
+ "*://s.webtrends.com/js/advancedLinkTracking.js",
+ "*://s.webtrends.com/js/webtrends.js",
+ "*://s.webtrends.com/js/webtrends.min.js",
+ ],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Blogger",
+ platform: "all",
+ name: "Blogger",
+ bug: "1776869",
+ contentScripts: [
+ {
+ js: "blogger.js",
+ matches: ["*://www.blogger.com/comment/frame/*"],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ {
+ js: "bloggerAccount.js",
+ matches: ["*://www.blogger.com/blog/*"],
+ runAt: "document_end",
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+ {
+ // keep this below any other shims checking adsafeprotected URLs
+ id: "AdSafeProtectedTrackingPixels",
+ platform: "all",
+ name: "Ad Safe Protected tracking pixels",
+ bug: "1717806",
+ matches: [
+ {
+ patterns: ["https://static.adsafeprotected.com/firefox-etp-pixel"],
+ target: "tracking-pixel.png",
+ types: ["image", "imageset", "xmlhttprequest"],
+ },
+ {
+ patterns: ["https://static.adsafeprotected.com/firefox-etp-js"],
+ target: "empty-script.js",
+ types: ["xmlhttprequest"],
+ },
+ {
+ patterns: [
+ "*://*.adsafeprotected.com/*.gif*",
+ "*://*.adsafeprotected.com/*.png*",
+ ],
+ target: "https://static.adsafeprotected.com/firefox-etp-pixel",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ patterns: [
+ "*://*.adsafeprotected.com/*.js*",
+ "*://*.adsafeprotected.com/*/adj*",
+ "*://*.adsafeprotected.com/*/imp/*",
+ "*://*.adsafeprotected.com/*/Serving/*",
+ "*://*.adsafeprotected.com/*/unit/*",
+ "*://*.adsafeprotected.com/jload",
+ "*://*.adsafeprotected.com/jload?*",
+ "*://*.adsafeprotected.com/jsvid",
+ "*://*.adsafeprotected.com/jsvid?*",
+ "*://*.adsafeprotected.com/mon*",
+ "*://*.adsafeprotected.com/tpl",
+ "*://*.adsafeprotected.com/tpl?*",
+ "*://*.adsafeprotected.com/services/pub*",
+ ],
+ target: "https://static.adsafeprotected.com/firefox-etp-js",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ // note, fallback case seems to be an image
+ patterns: ["*://*.adsafeprotected.com/*"],
+ target: "https://static.adsafeprotected.com/firefox-etp-pixel",
+ types: ["image", "imageset", "xmlhttprequest"],
+ onlyIfBlockedByETP: true,
+ },
+ ],
+ },
+ {
+ id: "SpotifyEmbed",
+ platform: "all",
+ name: "SpotifyEmbed",
+ bug: "1792395",
+ contentScripts: [
+ {
+ js: "spotify-embed.js",
+ matches: ["*://open.spotify.com/embed/*"],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ ],
+ onlyIfDFPIActive: true,
+ },
+];
+
+module.exports = AVAILABLE_SHIMS;
diff --git a/browser/extensions/webcompat/data/ua_overrides.js b/browser/extensions/webcompat/data/ua_overrides.js
new file mode 100644
index 0000000000..150d13465d
--- /dev/null
+++ b/browser/extensions/webcompat/data/ua_overrides.js
@@ -0,0 +1,1371 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser, module, require */
+
+// This is a hack for the tests.
+if (typeof InterventionHelpers === "undefined") {
+ var InterventionHelpers = require("../lib/intervention_helpers");
+}
+if (typeof UAHelpers === "undefined") {
+ var UAHelpers = require("../lib/ua_helpers");
+}
+
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const AVAILABLE_UA_OVERRIDES = [
+ {
+ id: "testbed-override",
+ platform: "all",
+ domain: "webcompat-addon-testbed.herokuapp.com",
+ bug: "0000000",
+ config: {
+ hidden: true,
+ matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 for WebCompat"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1577519 - directv.com - Create a UA override for directv.com for playback on desktop
+ * WebCompat issue #3846 - https://webcompat.com/issues/3846
+ *
+ * directv.com (attwatchtv.com) is blocking Firefox via UA sniffing. Spoofing as Chrome allows
+ * to access the site and playback works fine. This is former directvnow.com
+ */
+ id: "bug1577519",
+ platform: "desktop",
+ domain: "directv.com",
+ bug: "1577519",
+ config: {
+ matches: [
+ "*://*.attwatchtv.com/*",
+ "*://*.directv.com.ec/*", // bug 1827706
+ "*://*.directv.com/*",
+ ],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1570108 - steamcommunity.com - UA override for steamcommunity.com
+ * WebCompat issue #34171 - https://webcompat.com/issues/34171
+ *
+ * steamcommunity.com blocks chat feature for Firefox users showing unsupported browser message.
+ * When spoofing as Chrome the chat works fine
+ */
+ id: "bug1570108",
+ platform: "desktop",
+ domain: "steamcommunity.com",
+ bug: "1570108",
+ config: {
+ matches: ["*://steamcommunity.com/chat*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1582582 - sling.com - UA override for sling.com
+ * WebCompat issue #17804 - https://webcompat.com/issues/17804
+ *
+ * sling.com blocks Firefox users showing unsupported browser message.
+ * When spoofing as Chrome playing content works fine
+ */
+ id: "bug1582582",
+ platform: "desktop",
+ domain: "sling.com",
+ bug: "1582582",
+ config: {
+ matches: ["https://watch.sling.com/*", "https://www.sling.com/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1610026 - www.mobilesuica.com - UA override for www.mobilesuica.com
+ * WebCompat issue #4608 - https://webcompat.com/issues/4608
+ *
+ * mobilesuica.com showing unsupported message for Firefox users
+ * Spoofing as Chrome allows to access the page
+ */
+ id: "bug1610026",
+ platform: "all",
+ domain: "www.mobilesuica.com",
+ bug: "1610026",
+ config: {
+ matches: ["https://www.mobilesuica.com/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1385206 - Create UA override for rakuten.co.jp on Firefox Android
+ * (Imported from ua-update.json.in)
+ *
+ * rakuten.co.jp serves a Desktop version if Firefox is included in the UA.
+ */
+ id: "bug1385206",
+ platform: "android",
+ domain: "rakuten.co.jp",
+ bug: "1385206",
+ config: {
+ matches: ["*://*.rakuten.co.jp/*"],
+ uaTransformer: originalUA => {
+ return originalUA.replace(/Firefox.+$/, "");
+ },
+ },
+ },
+ {
+ /*
+ * Bug 969844 - mobile.de sends desktop site to Firefox on Android
+ *
+ * mobile.de sends the desktop site to Firefox Mobile.
+ * Spoofing as Chrome works fine.
+ */
+ id: "bug969844",
+ platform: "android",
+ domain: "mobile.de",
+ bug: "969844",
+ config: {
+ matches: ["*://*.mobile.de/*"],
+ uaTransformer: _ => {
+ return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1509873 - zmags.com - Add UA override for secure.viewer.zmags.com
+ * WebCompat issue #21576 - https://webcompat.com/issues/21576
+ *
+ * The zmags viewer locks out Firefox Mobile with a "Browser unsupported"
+ * message, but tests showed that it works just fine with a Chrome UA.
+ * Outreach attempts were unsuccessful, and as the site has a relatively
+ * high rank, we alter the UA.
+ */
+ id: "bug1509873",
+ platform: "android",
+ domain: "zmags.com",
+ bug: "1509873",
+ config: {
+ matches: ["*://*.viewer.zmags.com/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1574522 - UA override for enuri.com on Firefox for Android
+ * WebCompat issue #37139 - https://webcompat.com/issues/37139
+ *
+ * enuri.com returns a different template for Firefox on Android
+ * based on server side UA detection. This results in page content cut offs.
+ * Spoofing as Chrome fixes the issue
+ */
+ id: "bug1574522",
+ platform: "android",
+ domain: "enuri.com",
+ bug: "1574522",
+ config: {
+ matches: ["*://enuri.com/*"],
+ uaTransformer: _ => {
+ return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1574564 - UA override for ceskatelevize.cz on Firefox for Android
+ * WebCompat issue #15467 - https://webcompat.com/issues/15467
+ *
+ * ceskatelevize sets streamingProtocol depending on the User-Agent it sees
+ * in the request headers, returning DASH for Chrome, HLS for iOS,
+ * and Flash for Firefox Mobile. Since Mobile has no Flash, the video
+ * doesn't work. Spoofing as Chrome makes the video play
+ */
+ id: "bug1574564",
+ platform: "android",
+ domain: "ceskatelevize.cz",
+ bug: "1574564",
+ config: {
+ matches: ["*://*.ceskatelevize.cz/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1577267 - UA override for metfone.com.kh on Firefox for Android
+ * WebCompat issue #16363 - https://webcompat.com/issues/16363
+ *
+ * metfone.com.kh has a server side UA detection which returns desktop site
+ * for Firefox for Android. Spoofing as Chrome allows to receive mobile version
+ */
+ id: "bug1577267",
+ platform: "android",
+ domain: "metfone.com.kh",
+ bug: "1577267",
+ config: {
+ matches: ["*://*.metfone.com.kh/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1598198 - User Agent extension for Samsung's galaxy.store URLs
+ *
+ * Samsung's galaxy.store shortlinks are supposed to redirect to a Samsung
+ * intent:// URL on Samsung devices, but to an error page on other brands.
+ * As we do not provide device info in our user agent string, this check
+ * fails, and even Samsung users land on an error page if they use Firefox
+ * for Android.
+ * This intervention adds a simple "Samsung" identifier to the User Agent
+ * on only the Galaxy Store URLs if the device happens to be a Samsung.
+ */
+ id: "bug1598198",
+ platform: "android",
+ domain: "galaxy.store",
+ bug: "1598198",
+ config: {
+ matches: [
+ "*://galaxy.store/*",
+ "*://dev.galaxy.store/*",
+ "*://stg.galaxy.store/*",
+ ],
+ uaTransformer: originalUA => {
+ if (!browser.systemManufacturer) {
+ return originalUA;
+ }
+
+ const manufacturer = browser.systemManufacturer.getManufacturer();
+ if (manufacturer && manufacturer.toLowerCase() === "samsung") {
+ return originalUA.replace("Mobile;", "Mobile; Samsung;");
+ }
+
+ return originalUA;
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1595215 - UA overrides for Uniqlo sites
+ * Webcompat issue #38825 - https://webcompat.com/issues/38825
+ *
+ * To receive the proper mobile version instead of the desktop version or
+ * avoid redirect loop, the UA is spoofed.
+ */
+ id: "bug1595215",
+ platform: "android",
+ domain: "uniqlo.com",
+ bug: "1595215",
+ config: {
+ matches: ["*://*.uniqlo.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Mobile Safari";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1622063 - UA override for wp1-ext.usps.gov
+ * Webcompat issue #29867 - https://webcompat.com/issues/29867
+ *
+ * The Job Search site for USPS does not work for Firefox Mobile
+ * browsers (a 500 is returned).
+ */
+ id: "bug1622063",
+ platform: "android",
+ domain: "wp1-ext.usps.gov",
+ bug: "1622063",
+ config: {
+ matches: ["*://wp1-ext.usps.gov/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1697324 - Update the override for mobile2.bmo.com
+ * Previously Bug 1622081 - UA override for mobile2.bmo.com
+ * Webcompat issue #45019 - https://webcompat.com/issues/45019
+ *
+ * Unless the UA string contains "Chrome", mobile2.bmo.com will
+ * display a modal saying the browser is out-of-date.
+ */
+ id: "bug1697324",
+ platform: "android",
+ domain: "mobile2.bmo.com",
+ bug: "1697324",
+ config: {
+ matches: ["*://mobile2.bmo.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1628455 - UA override for autotrader.ca
+ * Webcompat issue #50961 - https://webcompat.com/issues/50961
+ *
+ * autotrader.ca is showing desktop site for Firefox on Android
+ * based on server side UA detection. Spoofing as Chrome allows to
+ * get mobile experience
+ */
+ id: "bug1628455",
+ platform: "android",
+ domain: "autotrader.ca",
+ bug: "1628455",
+ config: {
+ matches: ["https://*.autotrader.ca/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1646791 - bancosantander.es - Re-add UA override.
+ * Bug 1665129 - *.gruposantander.es - Add wildcard domains.
+ * WebCompat issue #33462 - https://webcompat.com/issues/33462
+ * SuMo request - https://support.mozilla.org/es/questions/1291085
+ *
+ * santanderbank expects UA to have 'like Gecko', otherwise it runs
+ * xmlDoc.onload whose support has been dropped. It results in missing labels in forms
+ * and some other issues. Adding 'like Gecko' fixes those issues.
+ */
+ id: "bug1646791",
+ platform: "all",
+ domain: "santanderbank.com",
+ bug: "1646791",
+ config: {
+ matches: [
+ "*://*.bancosantander.es/*",
+ "*://*.gruposantander.es/*",
+ "*://*.santander.co.uk/*",
+ ],
+ uaTransformer: originalUA => {
+ // The first line related to Firefox 100 is for Bug 1743445.
+ // [TODO]: Remove when bug 1743429 gets backed out.
+ return UAHelpers.capVersionTo99(originalUA).replace(
+ "Gecko",
+ "like Gecko"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1651292 - UA override for www.jp.square-enix.com
+ * Webcompat issue #53018 - https://webcompat.com/issues/53018
+ *
+ * Unless the UA string contains "Chrome 66+", a section of
+ * www.jp.square-enix.com will show a never ending LOADING
+ * page.
+ */
+ id: "bug1651292",
+ platform: "android",
+ domain: "www.jp.square-enix.com",
+ bug: "1651292",
+ config: {
+ matches: ["*://www.jp.square-enix.com/music/sem/page/FF7R/ost/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome/83";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1666754 - Mobile UA override for lffl.org
+ * Bug 1665720 - lffl.org article page takes 2x as much time to load on Moto G
+ *
+ * This site returns desktop site based on server side UA detection.
+ * Spoofing as Chrome allows to get mobile experience
+ */
+ id: "bug1666754",
+ platform: "android",
+ domain: "lffl.org",
+ bug: "1666754",
+ config: {
+ matches: ["*://*.lffl.org/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1704673 - Add UA override for app.xiaomi.com
+ * Webcompat issue #66163 - https://webcompat.com/issues/66163
+ *
+ * The page isn’t redirecting properly error message received.
+ * Spoofing as Chrome makes the page load
+ */
+ id: "bug1704673",
+ platform: "android",
+ domain: "app.xiaomi.com",
+ bug: "1704673",
+ config: {
+ matches: ["*://app.xiaomi.com/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1712807 - Add UA override for www.dealnews.com
+ * Webcompat issue #39341 - https://webcompat.com/issues/39341
+ *
+ * The sites shows Firefox a different layout compared to Chrome.
+ * Spoofing as Chrome fixes this.
+ */
+ id: "bug1712807",
+ platform: "android",
+ domain: "www.dealnews.com",
+ bug: "1712807",
+ config: {
+ matches: ["*://www.dealnews.com/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1719859 - Add UA override for saxoinvestor.fr
+ * Webcompat issue #74678 - https://webcompat.com/issues/74678
+ *
+ * The site blocks Firefox with a server-side UA sniffer. Appending a
+ * Chrome version segment to the UA makes it work.
+ */
+ id: "bug1719859",
+ platform: "all",
+ domain: "saxoinvestor.fr",
+ bug: "1719859",
+ config: {
+ matches: ["*://*.saxoinvestor.fr/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome/91.0.4472.114";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1722954 - Add UA override for game.granbluefantasy.jp
+ * Webcompat issue #34310 - https://github.com/webcompat/web-bugs/issues/34310
+ *
+ * The website is sending a version of the site which is too small. Adding a partial
+ * safari iOS version of the UA sends us the right layout.
+ */
+ id: "bug1722954",
+ platform: "android",
+ domain: "granbluefantasy.jp",
+ bug: "1722954",
+ config: {
+ matches: ["*://*.granbluefantasy.jp/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " iPhone OS 12_0 like Mac OS X";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1738317 - Add UA override for vmos.cn
+ * Webcompat issue #90432 - https://github.com/webcompat/web-bugs/issues/90432
+ *
+ * Firefox for Android receives a desktop-only layout based on server-side
+ * UA sniffing. Spoofing as Chrome works fine.
+ */
+ id: "bug1738317",
+ platform: "android",
+ domain: "vmos.cn",
+ bug: "1738317",
+ config: {
+ matches: ["*://*.vmos.cn/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1743627 - Add UA override for renaud-bray.com
+ * Webcompat issue #55276 - https://github.com/webcompat/web-bugs/issues/55276
+ *
+ * Firefox for Android depends on "Version/" being there in the UA string,
+ * or it'll throw a runtime error.
+ */
+ id: "bug1743627",
+ platform: "android",
+ domain: "renaud-bray.com",
+ bug: "1743627",
+ config: {
+ matches: ["*://*.renaud-bray.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Version/0";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1743751 - Add UA override for slrclub.com
+ * Webcompat issue #91373 - https://github.com/webcompat/web-bugs/issues/91373
+ *
+ * On Firefox Android, the browser is receiving the desktop layout.
+ * Spoofing as Chrome works fine.
+ */
+ id: "bug1743751",
+ platform: "android",
+ domain: "slrclub.com",
+ bug: "1743751",
+ config: {
+ matches: ["*://*.slrclub.com/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1743754 - Add UA override for slrclub.com
+ * Webcompat issue #86839 - https://github.com/webcompat/web-bugs/issues/86839
+ *
+ * On Firefox Android, the browser is failing a UA parsing on Firefox UA.
+ */
+ id: "bug1743754",
+ platform: "android",
+ domain: "workflow.base.vn",
+ bug: "1743754",
+ config: {
+ matches: ["*://workflow.base.vn/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1743429 - Add UA override for sites broken with the Version 100 User Agent
+ *
+ * Some sites have issues with a UA string with Firefox version 100 or higher,
+ * so present as version 99 for now.
+ */
+ id: "bug1743429",
+ platform: "all",
+ domain: "Sites with known Version 100 User Agent breakage",
+ bug: "1743429",
+ config: {
+ matches: [
+ "*://411.ca/", // #121332
+ "*://*.commerzbank.de/*", // Bug 1767630
+ "*://*.mms.telekom.de/*", // #1800241
+ "*://ubank.com.au/*", // #104099
+ "*://wifi.sncf/*", // #100194
+ ],
+ uaTransformer: originalUA => {
+ return UAHelpers.capVersionTo99(originalUA);
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1753461 - UA override for serieson.naver.com
+ * Webcompat issue #99993 - https://webcompat.com/issues/97298
+ *
+ * The site locks out Firefox users unless a Chrome UA is given,
+ * and locks out Linux users as well (so we use Windows+Chrome).
+ */
+ id: "bug1753461",
+ platform: "desktop",
+ domain: "serieson.naver.com",
+ bug: "1753461",
+ config: {
+ matches: ["*://serieson.naver.com/*"],
+ uaTransformer: originalUA => {
+ return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1756872 - UA override for www.dolcegabbana.com
+ * Webcompat issue #99993 - https://webcompat.com/issues/99993
+ *
+ * The site's layout is broken on Firefox for Android
+ * without a full Chrome user-agent string.
+ */
+ id: "bug1756872",
+ platform: "android",
+ domain: "www.dolcegabbana.com",
+ bug: "1756872",
+ config: {
+ matches: ["*://www.dolcegabbana.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1771200 - UA override for animalplanet.com
+ * Webcompat issue #99993 - https://webcompat.com/issues/103727
+ *
+ * The videos are not playing and an error message is displayed
+ * in Firefox for Android, but work with Chrome UA
+ */
+ id: "bug1771200",
+ platform: "android",
+ domain: "animalplanet.com",
+ bug: "1771200",
+ config: {
+ matches: ["*://*.animalplanet.com/video/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1771200 - UA override for lazada.co.id
+ * Webcompat issue #106229 - https://webcompat.com/issues/106229
+ *
+ * The map is not playing and an error message is displayed
+ * in Firefox for Android, but work with Chrome UA
+ */
+ id: "bug1779059",
+ platform: "android",
+ domain: "lazada.co.id",
+ bug: "1779059",
+ config: {
+ matches: ["*://member-m.lazada.co.id/address/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1778168 - UA override for watch.antennaplus.gr
+ * Webcompat issue #106529 - https://webcompat.com/issues/106529
+ *
+ * The site's content is not loaded unless a Chrome UA is used,
+ * and breaks on Linux (so we claim Windows instead in that case).
+ */
+ id: "bug1778168",
+ platform: "desktop",
+ domain: "watch.antennaplus.gr",
+ bug: "1778168",
+ config: {
+ matches: ["*://watch.antennaplus.gr/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA({
+ desktopOS: "nonLinux",
+ });
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1776897 - UA override for www.edencast.fr
+ * Webcompat issue #106545 - https://webcompat.com/issues/106545
+ *
+ * The site's podcast audio player does not load unless a Chrome UA is used.
+ */
+ id: "bug1776897",
+ platform: "all",
+ domain: "www.edencast.fr",
+ bug: "1776897",
+ config: {
+ matches: ["*://www.edencast.fr/zoomcast*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1784361 - UA override for coldwellbankerhomes.com
+ * Webcompat issue #108535 - https://webcompat.com/issues/108535
+ *
+ * An error is thrown due to missing element, unless Chrome UA is used
+ */
+ id: "bug1784361",
+ platform: "android",
+ domain: "coldwellbankerhomes.com",
+ bug: "1784361",
+ config: {
+ matches: ["*://*.coldwellbankerhomes.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1786404 - UA override for business.help.royalmail.com
+ * Webcompat issue #109070 - https://webcompat.com/issues/109070
+ *
+ * Replacing `Firefox` with `FireFox` to evade one of their UA tests...
+ */
+ id: "bug1786404",
+ platform: "all",
+ domain: "business.help.royalmail.com",
+ bug: "1786404",
+ config: {
+ matches: ["*://business.help.royalmail.com/app/webforms/*"],
+ uaTransformer: originalUA => {
+ return originalUA.replace("Firefox", "FireFox");
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1790698 - UA override for wolf777.com
+ * Webcompat issue #103981 - https://webcompat.com/issues/103981
+ *
+ * Add 'Linux; ' next to the Android version or the site breaks
+ */
+ id: "bug1790698",
+ platform: "android",
+ domain: "wolf777.com",
+ bug: "1790698",
+ config: {
+ matches: ["*://wolf777.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA.replace("Android", "Linux; Android");
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1800936 - UA override for cov19ent.kdca.go.kr
+ * Webcompat issue #110655 - https://webcompat.com/issues/110655
+ *
+ * Add 'Chrome;' to the UA for the site to load styles
+ */
+ id: "bug1800936",
+ platform: "all",
+ domain: "cov19ent.kdca.go.kr",
+ bug: "1800936",
+ config: {
+ matches: ["*://cov19ent.kdca.go.kr/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1803131 - UA override for argaam.com
+ * Webcompat issue #113638 - https://webcompat.com/issues/113638
+ *
+ * To receive the proper mobile version instead of the desktop version
+ * the UA is spoofed.
+ */
+ id: "bug1803131",
+ platform: "android",
+ domain: "argaam.com",
+ bug: "1803131",
+ config: {
+ matches: ["*://*.argaam.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1819702 - UA override for feelgoodcontacts.com
+ * Webcompat issue #118030 - https://webcompat.com/issues/118030
+ *
+ * Spoof the UA to receive the mobile version instead
+ * of the broken desktop version for Android.
+ */
+ id: "bug1819702",
+ platform: "android",
+ domain: "feelgoodcontacts.com",
+ bug: "1819702",
+ config: {
+ matches: ["*://*.feelgoodcontacts.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1823966 - UA override for elearning.dmv.ca.gov
+ * Original report: https://bugzilla.mozilla.org/show_bug.cgi?id=1823785
+ */
+ id: "bug1823966",
+ platform: "all",
+ domain: "elearning.dmv.ca.gov",
+ bug: "1823966",
+ config: {
+ matches: ["*://*.elearning.dmv.ca.gov/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for admissions.nid.edu
+ * Webcompat issue #65753 - https://webcompat.com/issues/65753
+ */
+ id: "bug1827678-webc65753",
+ platform: "all",
+ domain: "admissions.nid.edu",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.admissions.nid.edu/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for www.hepsiburada.com
+ * Webcompat issue #66888 - https://webcompat.com/issues/66888
+ */
+ id: "bug1827678-webc66888",
+ platform: "android",
+ domain: "www.hepsiburada.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://www.hepsiburada.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for bankmandiri.co.id
+ * Webcompat issue #67924 - https://webcompat.com/issues/67924
+ */
+ id: "bug1827678-webc67924",
+ platform: "android",
+ domain: "bankmandiri.co.id",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.bankmandiri.co.id/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for frankfred.com
+ * Webcompat issue #68007 - https://webcompat.com/issues/68007
+ */
+ id: "bug1827678-webc68007",
+ platform: "android",
+ domain: "frankfred.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.frankfred.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for static.slots.lv
+ * Webcompat issue #68379 - https://webcompat.com/issues/68379
+ */
+ id: "bug1827678-webc68379",
+ platform: "android",
+ domain: "static.slots.lv",
+ bug: "1827678",
+ config: {
+ matches: ["*://static.slots.lv/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for mobile.onvue.com
+ * Webcompat issue #68520 - https://webcompat.com/issues/68520
+ */
+ id: "bug1827678-webc68520",
+ platform: "android",
+ domain: "mobile.onvue.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://mobile.onvue.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for avizia.com
+ * Webcompat issue #68635 - https://webcompat.com/issues/68635
+ */
+ id: "bug1827678-webc68635",
+ platform: "all",
+ domain: "avizia.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.avizia.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for www.yourtexasbenefits.com
+ * Webcompat issue #76785 - https://webcompat.com/issues/76785
+ */
+ id: "bug1827678-webc76785",
+ platform: "android",
+ domain: "www.yourtexasbenefits.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://www.yourtexasbenefits.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for www.free4talk.com
+ * Webcompat issue #77727 - https://webcompat.com/issues/77727
+ */
+ id: "bug1827678-webc77727",
+ platform: "android",
+ domain: "www.free4talk.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://www.free4talk.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for watch.indee.tv
+ * Webcompat issue #77912 - https://webcompat.com/issues/77912
+ */
+ id: "bug1827678-webc77912",
+ platform: "all",
+ domain: "watch.indee.tv",
+ bug: "1827678",
+ config: {
+ matches: ["*://watch.indee.tv/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for viewer-ebook.books.com.tw
+ * Webcompat issue #80180 - https://webcompat.com/issues/80180
+ */
+ id: "bug1827678-webc80180",
+ platform: "all",
+ domain: "viewer-ebook.books.com.tw",
+ bug: "1827678",
+ config: {
+ matches: ["*://viewer-ebook.books.com.tw/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for jelly.jd.com
+ * Webcompat issue #83269 - https://webcompat.com/issues/83269
+ */
+ id: "bug1827678-webc83269",
+ platform: "android",
+ domain: "jelly.jd.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://jelly.jd.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for f2bbs.com
+ * Webcompat issue #84932 - https://webcompat.com/issues/84932
+ */
+ id: "bug1827678-webc84932",
+ platform: "android",
+ domain: "f2bbs.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://f2bbs.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for kt.com
+ * Webcompat issue #119012 - https://webcompat.com/issues/119012
+ */
+ id: "bug1827678-webc119012",
+ platform: "all",
+ domain: "kt.com",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.kt.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for oirsa.org
+ * Webcompat issue #119402 - https://webcompat.com/issues/119402
+ */
+ id: "bug1827678-webc119402",
+ platform: "all",
+ domain: "oirsa.org",
+ bug: "1827678",
+ config: {
+ matches: ["*://*.oirsa.org/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for sistema.ibglbrasil.com.br
+ * Webcompat issue #119785 - https://webcompat.com/issues/119785
+ */
+ id: "bug1827678-webc119785",
+ platform: "all",
+ domain: "sistema.ibglbrasil.com.br",
+ bug: "1827678",
+ config: {
+ matches: ["*://sistema.ibglbrasil.com.br/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1827678 - UA override for onp.cloud.waterloo.ca
+ * Webcompat issue #120450 - https://webcompat.com/issues/120450
+ */
+ id: "bug1827678-webc120450",
+ platform: "all",
+ domain: "onp.cloud.waterloo.ca",
+ bug: "1827678",
+ config: {
+ matches: ["*://onp.cloud.waterloo.ca/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1830739 - UA override for casino sites
+ *
+ * The sites are showing unsupported message with the same UI
+ */
+ id: "bug1830739",
+ platform: "android",
+ domain: "casino sites",
+ bug: "1830739",
+ config: {
+ matches: [
+ "*://*.captainjackcasino.com/*", // 79490
+ "*://*.casinoextreme.eu/*", // 118175
+ "*://*.cryptoloko.com/*", // 117911
+ "*://*.heapsowins.com/*", // 120027
+ "*://*.planet7casino.com/*", // 120609
+ "*://*.yebocasino.co.za/*", // 88409
+ ],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1830821 - UA override for m.tworld.co.kr
+ * Webcompat issue #118998 - https://webcompat.com/issues/118998
+ */
+ id: "bug1830821-webc118998",
+ platform: "android",
+ domain: "m.tworld.co.kr",
+ bug: "1830821",
+ config: {
+ matches: ["*://m.tworld.co.kr/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1830821 - UA override for webcartop.jp
+ * Webcompat issue #113663 - https://webcompat.com/issues/113663
+ */
+ id: "bug1830821-webc113663",
+ platform: "android",
+ domain: "webcartop.jp",
+ bug: "1830821",
+ config: {
+ matches: ["*://*.webcartop.jp/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1830821 - UA override for enjoy.point.auone.jp
+ * Webcompat issue #90981 - https://webcompat.com/issues/90981
+ */
+ id: "bug1830821-webc90981",
+ platform: "android",
+ domain: "enjoy.point.auone.jp",
+ bug: "1830821",
+ config: {
+ matches: ["*://enjoy.point.auone.jp/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1751604 - UA override for /www.otsuka.co.jp/fib/
+ *
+ * The site's content is not loaded on mobile unless a Chrome UA is used.
+ */
+ id: "bug1829126",
+ platform: "android",
+ domain: "www.otsuka.co.jp",
+ bug: "1829126",
+ config: {
+ matches: ["*://www.otsuka.co.jp/fib/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1831441 - UA override for luna.amazon.com
+ *
+ * Games are unplayable unless a Chrome UA is used.
+ */
+ id: "bug1831441",
+ platform: "all",
+ domain: "luna.amazon.com",
+ bug: "1831441",
+ config: {
+ matches: ["*://luna.amazon.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836109 - UA override for watch.tonton.com.my
+ *
+ * The site's content is not loaded unless a Chrome UA is used.
+ */
+ id: "bug1836109",
+ platform: "all",
+ domain: "watch.tonton.com.my",
+ bug: "1836109",
+ config: {
+ matches: ["*://watch.tonton.com.my/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836112 - UA override for www.capcut.cn
+ *
+ * The site's content is not loaded unless a Chrome UA is used.
+ */
+ id: "bug1836112",
+ platform: "all",
+ domain: "www.capcut.cn",
+ bug: "1836112",
+ config: {
+ matches: ["*://www.capcut.cn/editor*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836116 - UA override for www.slushy.com
+ *
+ * The site's content is not loaded without a Chrome UA spoof.
+ */
+ id: "bug1836116",
+ platform: "all",
+ domain: "www.slushy.com",
+ bug: "1836116",
+ config: {
+ matches: ["*://www.slushy.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome/113.0.0.0";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836135 - UA override for gts-pro.sdimedia.com
+ *
+ * The site's content is not loaded without a Chrome UA spoof.
+ */
+ id: "bug1836135",
+ platform: "all",
+ domain: "gts-pro.sdimedia.com",
+ bug: "1836135",
+ config: {
+ matches: ["*://gts-pro.sdimedia.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA.replace("Firefox/", "Fx/") + " Chrome/113.0.0.0";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836140 - UA override for indices.iriworldwide.com
+ *
+ * The site's content is not loaded without a UA spoof.
+ */
+ id: "bug1836140",
+ platform: "all",
+ domain: "indices.iriworldwide.com",
+ bug: "1836140",
+ config: {
+ matches: ["*://indices.iriworldwide.com/covid19/*"],
+ uaTransformer: originalUA => {
+ return originalUA.replace("Firefox/", "Fx/");
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836178 - UA override for atracker.pro
+ *
+ * The site's content is not loaded without a Chrome UA spoof.
+ */
+ id: "bug1836178",
+ platform: "all",
+ domain: "atracker.pro",
+ bug: "1836178",
+ config: {
+ matches: ["*://atracker.pro/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome/113.0.0.0";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836181 - UA override for conference.amwell.com
+ *
+ * The site's content is not loaded unless a Chrome UA is used.
+ */
+ id: "bug1836181",
+ platform: "all",
+ domain: "conference.amwell.com",
+ bug: "1836181",
+ config: {
+ matches: ["*://conference.amwell.com/*"],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1836182 - UA override for www.flatsatshadowglen.com
+ *
+ * The site's content is not loaded without a Chrome UA spoof.
+ */
+ id: "bug1836182",
+ platform: "all",
+ domain: "www.flatsatshadowglen.com",
+ bug: "1836182",
+ config: {
+ matches: ["*://www.flatsatshadowglen.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome/113.0.0.0";
+ },
+ },
+ },
+];
+
+module.exports = AVAILABLE_UA_OVERRIDES;
diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js
new file mode 100644
index 0000000000..21ad297dc1
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global ExtensionAPI, ExtensionCommon, Services, XPCOMUtils */
+
+this.aboutConfigPrefs = class extends ExtensionAPI {
+ getAPI(context) {
+ const EventManager = ExtensionCommon.EventManager;
+ const extensionIDBase = context.extension.id.split("@")[0];
+ const extensionPrefNameBase = `extensions.${extensionIDBase}.`;
+
+ return {
+ aboutConfigPrefs: {
+ onPrefChange: new EventManager({
+ context,
+ name: "aboutConfigPrefs.onUAOverridesPrefChange",
+ register: (fire, name) => {
+ const prefName = `${extensionPrefNameBase}${name}`;
+ const callback = () => {
+ fire.async(name).catch(() => {}); // ignore Message Manager disconnects
+ };
+ Services.prefs.addObserver(prefName, callback);
+ return () => {
+ Services.prefs.removeObserver(prefName, callback);
+ };
+ },
+ }).api(),
+ async getBranch(branchName) {
+ const branch = `${extensionPrefNameBase}${branchName}.`;
+ return Services.prefs.getChildList(branch).map(pref => {
+ const name = pref.replace(branch, "");
+ return { name, value: Services.prefs.getBoolPref(pref) };
+ });
+ },
+ async getPref(name) {
+ try {
+ return Services.prefs.getBoolPref(
+ `${extensionPrefNameBase}${name}`
+ );
+ } catch (_) {
+ return undefined;
+ }
+ },
+ async setPref(name, value) {
+ Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value);
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json
new file mode 100644
index 0000000000..44284f199c
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json
@@ -0,0 +1,72 @@
+[
+ {
+ "namespace": "aboutConfigPrefs",
+ "description": "experimental API extension to allow access to about:config preferences",
+ "events": [
+ {
+ "name": "onPrefChange",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The preference which changed"
+ }
+ ],
+ "extraParameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The preference to monitor"
+ }
+ ]
+ }
+ ],
+ "functions": [
+ {
+ "name": "getBranch",
+ "type": "function",
+ "description": "Get all child prefs for a branch",
+ "parameters": [
+ {
+ "name": "branchName",
+ "type": "string",
+ "description": "The branch name"
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "getPref",
+ "type": "function",
+ "description": "Get a preference's value",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The preference name"
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "setPref",
+ "type": "function",
+ "description": "Set a preference's value",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The preference name"
+ },
+ {
+ "name": "value",
+ "type": "boolean",
+ "description": "The new value"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.js b/browser/extensions/webcompat/experiment-apis/appConstants.js
new file mode 100644
index 0000000000..2869f299a4
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/appConstants.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global AppConstants, ExtensionAPI, XPCOMUtils */
+
+this.appConstants = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ appConstants: {
+ getReleaseBranch: () => {
+ if (AppConstants.NIGHTLY_BUILD) {
+ return "nightly";
+ } else if (AppConstants.MOZ_DEV_EDITION) {
+ return "dev_edition";
+ } else if (AppConstants.EARLY_BETA_OR_EARLIER) {
+ return "early_beta_or_earlier";
+ } else if (AppConstants.RELEASE_OR_BETA) {
+ return "release_or_beta";
+ }
+ return "unknown";
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.json b/browser/extensions/webcompat/experiment-apis/appConstants.json
new file mode 100644
index 0000000000..cf04915eca
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/appConstants.json
@@ -0,0 +1,15 @@
+[
+ {
+ "namespace": "appConstants",
+ "description": "experimental API to expose some app constants",
+ "functions": [
+ {
+ "name": "getReleaseBranch",
+ "type": "function",
+ "description": "",
+ "async": true,
+ "parameters": []
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.js b/browser/extensions/webcompat/experiment-apis/matchPatterns.js
new file mode 100644
index 0000000000..422cba5fc4
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.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";
+
+/* global ExtensionAPI */
+
+this.matchPatterns = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ matchPatterns: {
+ getMatcher(patterns) {
+ const set = new MatchPatternSet(patterns);
+ return Cu.cloneInto(
+ {
+ matches: url => {
+ return set.matches(url);
+ },
+ },
+ context.cloneScope,
+ {
+ cloneFunctions: true,
+ }
+ );
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.json b/browser/extensions/webcompat/experiment-apis/matchPatterns.json
new file mode 100644
index 0000000000..6fb4dc10fc
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.json
@@ -0,0 +1,29 @@
+[
+ {
+ "namespace": "matchPatterns",
+ "description": "experimental API extension to expose MatchPattern functionality",
+ "functions": [
+ {
+ "name": "getMatcher",
+ "type": "function",
+ "description": "get a MatchPatternSet",
+ "parameters": [
+ {
+ "name": "patterns",
+ "description": "Array of string URL patterns to match",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ],
+ "returns": {
+ "type": "object",
+ "properties": {
+ "matches": { "type": "function" }
+ }
+ }
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.js b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js
new file mode 100644
index 0000000000..b7dc68415c
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global ExtensionAPI, Services, XPCOMUtils */
+
+this.systemManufacturer = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ systemManufacturer: {
+ getManufacturer() {
+ try {
+ return Services.sysinfo.getProperty("manufacturer");
+ } catch (_) {
+ return undefined;
+ }
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.json b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json
new file mode 100644
index 0000000000..c64fccc46d
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json
@@ -0,0 +1,20 @@
+[
+ {
+ "namespace": "systemManufacturer",
+ "description": "experimental API extension to allow reading the device's manufacturer",
+ "functions": [
+ {
+ "name": "getManufacturer",
+ "type": "function",
+ "description": "Get the device's manufacturer",
+ "parameters": [],
+ "returns": {
+ "type": "string",
+ "properties": {},
+ "additionalProperties": { "type": "any" },
+ "description": "The manufacturer's name."
+ }
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.js b/browser/extensions/webcompat/experiment-apis/trackingProtection.js
new file mode 100644
index 0000000000..0f5d9a4233
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global ExtensionAPI, ExtensionCommon, ExtensionParent, Services, XPCOMUtils */
+
+// eslint-disable-next-line mozilla/reject-importGlobalProperties
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]);
+
+class AllowList {
+ constructor(id) {
+ this._id = id;
+ }
+
+ setShims(patterns, notHosts) {
+ this._shimPatterns = patterns;
+ this._shimMatcher = new MatchPatternSet(patterns || []);
+ this._shimNotHosts = notHosts || [];
+ return this;
+ }
+
+ setAllows(patterns, hosts) {
+ this._allowPatterns = patterns;
+ this._allowMatcher = new MatchPatternSet(patterns || []);
+ this._allowHosts = hosts || [];
+ return this;
+ }
+
+ shims(url, topHost) {
+ return (
+ this._shimMatcher?.matches(url) && !this._shimNotHosts?.includes(topHost)
+ );
+ }
+
+ allows(url, topHost) {
+ return (
+ this._allowMatcher?.matches(url) && this._allowHosts?.includes(topHost)
+ );
+ }
+}
+
+class Manager {
+ constructor() {
+ this._allowLists = new Map();
+ }
+
+ _getAllowList(id) {
+ if (!this._allowLists.has(id)) {
+ this._allowLists.set(id, new AllowList(id));
+ }
+ return this._allowLists.get(id);
+ }
+
+ _ensureStarted() {
+ if (this._classifierObserver) {
+ return;
+ }
+
+ this._unblockedChannelIds = new Set();
+ this._channelClassifier = Cc[
+ "@mozilla.org/url-classifier/channel-classifier-service;1"
+ ].getService(Ci.nsIChannelClassifierService);
+ this._classifierObserver = {};
+ this._classifierObserver.observe = (subject, topic, data) => {
+ switch (topic) {
+ case "http-on-stop-request": {
+ const { channelId } = subject.QueryInterface(Ci.nsIIdentChannel);
+ this._unblockedChannelIds.delete(channelId);
+ break;
+ }
+ case "urlclassifier-before-block-channel": {
+ const channel = subject.QueryInterface(
+ Ci.nsIUrlClassifierBlockedChannel
+ );
+ const { channelId, url } = channel;
+ let topHost;
+ try {
+ topHost = new URL(channel.topLevelUrl).hostname;
+ } catch (_) {
+ return;
+ }
+ // If anti-tracking webcompat is disabled, we only permit replacing
+ // channels, not fully unblocking them.
+ if (Manager.ENABLE_WEBCOMPAT) {
+ // if any allowlist unblocks the request entirely, we allow it
+ for (const allowList of this._allowLists.values()) {
+ if (allowList.allows(url, topHost)) {
+ this._unblockedChannelIds.add(channelId);
+ channel.allow();
+ return;
+ }
+ }
+ }
+ // otherwise, if any allowlist shims the request we say it's replaced
+ for (const allowList of this._allowLists.values()) {
+ if (allowList.shims(url, topHost)) {
+ this._unblockedChannelIds.add(channelId);
+ channel.replace();
+ return;
+ }
+ }
+ break;
+ }
+ }
+ };
+ Services.obs.addObserver(this._classifierObserver, "http-on-stop-request");
+ this._channelClassifier.addListener(this._classifierObserver);
+ }
+
+ stop() {
+ if (!this._classifierObserver) {
+ return;
+ }
+
+ Services.obs.removeObserver(
+ this._classifierObserver,
+ "http-on-stop-request"
+ );
+ this._channelClassifier.removeListener(this._classifierObserver);
+ delete this._channelClassifier;
+ delete this._classifierObserver;
+ }
+
+ wasChannelIdUnblocked(channelId) {
+ return this._unblockedChannelIds?.has(channelId);
+ }
+
+ allow(allowListId, patterns, hosts) {
+ this._ensureStarted();
+ this._getAllowList(allowListId).setAllows(patterns, hosts);
+ }
+
+ shim(allowListId, patterns, notHosts) {
+ this._ensureStarted();
+ this._getAllowList(allowListId).setShims(patterns, notHosts);
+ }
+
+ revoke(allowListId) {
+ this._allowLists.delete(allowListId);
+ }
+}
+var manager = new Manager();
+
+function getChannelId(context, requestId) {
+ const wrapper = ChannelWrapper.getRegisteredChannel(
+ requestId,
+ context.extension.policy,
+ context.xulBrowser.frameLoader.remoteTab
+ );
+ return wrapper?.channel?.QueryInterface(Ci.nsIIdentChannel)?.channelId;
+}
+
+var dFPIPrefName = "network.cookie.cookieBehavior";
+var dFPIPbPrefName = "network.cookie.cookieBehavior.pbmode";
+var dFPIStatus;
+function updateDFPIStatus() {
+ dFPIStatus = {
+ nonPbMode: 5 == Services.prefs.getIntPref(dFPIPrefName),
+ pbMode: 5 == Services.prefs.getIntPref(dFPIPbPrefName),
+ };
+}
+
+this.trackingProtection = class extends ExtensionAPI {
+ onShutdown(isAppShutdown) {
+ if (manager) {
+ manager.stop();
+ }
+ Services.prefs.removeObserver(dFPIPrefName, updateDFPIStatus);
+ Services.prefs.removeObserver(dFPIPbPrefName, updateDFPIStatus);
+ }
+
+ getAPI(context) {
+ Services.prefs.addObserver(dFPIPrefName, updateDFPIStatus);
+ Services.prefs.addObserver(dFPIPbPrefName, updateDFPIStatus);
+ updateDFPIStatus();
+
+ return {
+ trackingProtection: {
+ async shim(allowListId, patterns, notHosts) {
+ manager.shim(allowListId, patterns, notHosts);
+ },
+ async allow(allowListId, patterns, hosts) {
+ manager.allow(allowListId, patterns, hosts);
+ },
+ async revoke(allowListId) {
+ manager.revoke(allowListId);
+ },
+ async wasRequestUnblocked(requestId) {
+ if (!manager) {
+ return false;
+ }
+ const channelId = getChannelId(context, requestId);
+ if (!channelId) {
+ return false;
+ }
+ return manager.wasChannelIdUnblocked(channelId);
+ },
+ async isDFPIActive(isPrivate) {
+ if (isPrivate) {
+ return dFPIStatus.pbMode;
+ }
+ return dFPIStatus.nonPbMode;
+ },
+ },
+ };
+ }
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ Manager,
+ "ENABLE_WEBCOMPAT",
+ "privacy.antitracking.enableWebcompat",
+ false
+);
diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.json b/browser/extensions/webcompat/experiment-apis/trackingProtection.json
new file mode 100644
index 0000000000..c495f39add
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.json
@@ -0,0 +1,102 @@
+[
+ {
+ "namespace": "trackingProtection",
+ "description": "experimental API allow requests through ETP",
+ "functions": [
+ {
+ "name": "isDFPIActive",
+ "type": "function",
+ "description": "Returns whether dFPI is active for private/non-private browsing tabs",
+ "parameters": [
+ {
+ "type": "boolean",
+ "name": "isPrivate"
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "shim",
+ "type": "function",
+ "description": "Set specified URL patterns as intended to be shimmed",
+ "parameters": [
+ {
+ "name": "allowlistId",
+ "description": "Identfier for the allow-list, so it may be added-to or revoked",
+ "type": "string"
+ },
+ {
+ "name": "patterns",
+ "description": "Array of match patterns",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "notHosts",
+ "description": "Hosts on which to not shim these patterns",
+ "type": "array",
+ "optional": true,
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ {
+ "name": "allow",
+ "type": "function",
+ "description": "Set specified URL patterns as intended to be allowed through the content blocker for the specified top hosts",
+ "parameters": [
+ {
+ "name": "allowlistId",
+ "description": "Identfier for the allow-list, so it may be added-to or revoked",
+ "type": "string"
+ },
+ {
+ "name": "patterns",
+ "description": "Array of match patterns",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "hosts",
+ "description": "Hosts to allow the patterns on",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "revoke",
+ "type": "function",
+ "description": "Revokes the given allow-list entirely (both shims and allows)",
+ "parameters": [
+ {
+ "name": "allowListId",
+ "type": "string"
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "wasRequestUnblocked",
+ "type": "function",
+ "description": "Whether the given requestId was unblocked by any allowList",
+ "parameters": [
+ {
+ "name": "requestId",
+ "type": "string"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css b/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css
new file mode 100644
index 0000000000..566685c2da
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css
@@ -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/. */
+
+#css-injection.red {
+ background-color: #0f0;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css b/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css
new file mode 100644
index 0000000000..05f7e685b9
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css
@@ -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/. */
+
+/**
+ * developer.apple.com - content of the page is shifted to the left
+ * Bug #1570328 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570328
+ * WebCompat issue #4070 - https://webcompat.com/issues/4070
+ *
+ * The site is relying on zoom property which is not supported by Mozilla,
+ * see https://bugzilla.mozilla.org/show_bug.cgi?id=390936. Adding a combination
+ * of transform: scale(1.4), transform-origin and width fixes the issue
+ */
+@media only screen and (min-device-width: 320px) and (max-device-width: 980px),
+ (min-device-width: 1024px) and (max-device-width: 1024px) and (min-device-height: 1366px) and (max-device-height: 1366px) and (min-width: 320px) and (max-width: 980px) {
+ #tocContainer {
+ transform-origin: 0 0;
+ transform: scale(1.4);
+ width: 71.4%;
+ }
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css b/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css
new file mode 100644
index 0000000000..e157dc6920
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css
@@ -0,0 +1,15 @@
+/* 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/. */
+
+/**
+ * apply.lloydsbank.co.uk - radio buttons are misplaced
+ * Bug #1575000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575000
+ * WebCompat issue #34969 - https://webcompat.com/issues/34969
+ *
+ * Radio buttons are displaced to the left due to positioning issue of ::before
+ * pseudo element, adding position relative to it's parent fixes the issue.
+ */
+.radio-content-field .radio.inline label span.text {
+ position: relative;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css b/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css
new file mode 100644
index 0000000000..75ca4a8723
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css
@@ -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/. */
+
+/**
+ * Bug 1605611 - Cannot change Departure/arrival dates in Google Maps on Android
+ *
+ * Google Maps hides a datetime-local in its directions picker by giving it
+ * z-index:-50000, which causes it to be unclickable in Firefox. Here we
+ * use opacity:0 instead to hide it, while letting it remain clickable.
+ */
+
+.ml-route-options-picker-container input[type="datetime-local"] {
+ z-index: unset;
+ opacity: 0;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css b/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css
new file mode 100644
index 0000000000..9f673bee95
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css
@@ -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/. */
+
+/**
+ * directv.com.co - Browser is not supported message
+ * Bug #1610344 - https://bugzilla.mozilla.org/show_bug.cgi?id=1610344
+ * WebCompat issue #41822 - https://webcompat.com/issues/41822
+ *
+ * directv.com.co is showing a "This browser is not supported" message in
+ * Firefox. Our tests indicated that everything is working just fine, and our
+ * previous contact attempts have not been successful. This intervention
+ * hides the large red unsupported banner.
+ */
+.browser-compatible.compatible.incompatible {
+ display: none;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css b/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css
new file mode 100644
index 0000000000..fc1cb7489a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css
@@ -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/. */
+
+/**
+ * missingmail.usps.com - Unable to mark the check-boxes from "Disclaimer and
+ * Terms and Conditions" section
+ * Bug #1644830 - https://bugzilla.mozilla.org/show_bug.cgi?id=1644830
+ * WebCompat issue #53950 - https://webcompat.com/issues/53950
+ *
+ * missingmail.usps.com runs into a case of bug 997189, where an absolutely
+ * positioned inline-block element with floating siblings is shifter to the
+ * right, and thus invisible.
+ */
+.mrc-custom-checkbox-container input {
+ margin-left: -3rem;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css b/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css
new file mode 100644
index 0000000000..2c4429a301
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css
@@ -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/. */
+
+/**
+ * teletrader.com - content is shifted down and right
+ * Bug #1651917 - https://bugzilla.mozilla.org/show_bug.cgi?id=1651917
+ * WebCompat issue #55217 - https://webcompat.com/issues/55217
+ *
+ * The content is shifted down and right, because they use webkit prefixes
+ * for scaling and redefining the origin. Firefox doesn't support
+ * -webkit-transform-origin-x/y
+ * This is the object of https://bugzilla.mozilla.org/show_bug.cgi?id=1584881
+ * Adding transform-origin: 0 0; to body fixes the issue
+ */
+body {
+ transform-origin: 0 0;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css b/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css
new file mode 100644
index 0000000000..ae14d1ec13
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css
@@ -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/. */
+
+/**
+ * livescience.com - a scrollbar covering navigation menu
+ * Bug #1653075 - https://bugzilla.mozilla.org/show_bug.cgi?id=1653075
+ *
+ * The scrollbar is covering navigation items and that makes them half hidden.
+ * There are some ::-webkit-scrollbar css rules applied to the scrollbar,
+ * making it thinner. Adding similar rules for Firefox fixes the issue.
+ */
+
+.trending__list {
+ scrollbar-width: thin;
+ scrollbar-color: #f9ae3b #f5f5f5;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css b/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css
new file mode 100644
index 0000000000..b13c3052f3
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css
@@ -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/. */
+
+/**
+ * preev.com - typed numbers are not fully visible
+ * Bug #1654877 - https://bugzilla.mozilla.org/show_bug.cgi?id=1654877
+ * WebCompat issue #55099 - https://webcompat.com/issues/55099
+ *
+ * It's hard to see the entered number because the spin button is
+ * taking too much space. While there is -moz-appearance: textfield,
+ * -webkit-appearance: none; underneath supersedes it,
+ * leaving the spin button visible. Adding -moz-appearance: textfield;
+ * as a separate rule fixes the issue
+ */
+input[type="number"],
+input[type="text"] {
+ -moz-appearance: textfield;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css
new file mode 100644
index 0000000000..6cecb6658a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css
@@ -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/. */
+
+/**
+ * reactine.ca - Unsupported browser message
+ * Bug #1654907 - https://bugzilla.mozilla.org/show_bug.cgi?id=1654907
+ * WebCompat issue #55481 - https://webcompat.com/issues/55481
+ *
+ * reactine.ca is showing "Sorry this browser is not supported."
+ * message if Firefox for Android based on UA detection. Site seems
+ * to be working fine, so this intervention is to hide this message
+ */
+#browser-alert {
+ display: none !important;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css b/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css
new file mode 100644
index 0000000000..adec7101ba
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css
@@ -0,0 +1,15 @@
+/* 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/. */
+
+/**
+ * m.myvidster.com - Content is not shown
+ * Bug #1694470 - https://bugzilla.mozilla.org/show_bug.cgi?id=1694470
+ * WebCompat issue #67308 - https://webcompat.com/issues/67308
+ *
+ * The site depends on Sencha Touch and should receive some specific
+ * -webkit-box-flex be working.
+ */
+#home_refresh_var {
+ -webkit-box-flex: 1;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css b/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css
new file mode 100644
index 0000000000..7165ceb70f
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css
@@ -0,0 +1,12 @@
+/* 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/. */
+
+/**
+ * www.office.com - There is an overscroll effect on Excel sheets which is
+ * not a very pleasant user experience. This invention disables it.
+ */
+
+.ewr-sheetcontainer {
+ overscroll-behavior: none;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css b/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css
new file mode 100644
index 0000000000..b4b8ca4a34
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css
@@ -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/. */
+
+/**
+ * buskocchi.desuca.co.jp - Ensure that the map has a height so it is visible.
+ * Bug #1712833 - https://bugzilla.mozilla.org/show_bug.cgi?id=1712833
+ * WebCompat issue #50837 - https://webcompat.com/issues/50837
+ */
+
+form[name="main"] {
+ height: 100%;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css b/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css
new file mode 100644
index 0000000000..3765d1de1e
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css
@@ -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/. */
+
+/**
+ * patient.alphalabs.ca - "Continue" button is overlapped by displaced page footer
+ * Bug #1741234 - https://bugzilla.mozilla.org/show_bug.cgi?id=1741234
+ * WebCompat issue #93156 - https://webcompat.com/issues/93156
+ */
+
+body {
+ height: 100%;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css b/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css
new file mode 100644
index 0000000000..8c1ab7b2ae
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css
@@ -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/. */
+
+/**
+ * veniceincoming.com - site is not usable
+ * Bug #1765947 - https://bugzilla.mozilla.org/show_bug.cgi?id=1765947
+ * WebCompat issue #102133 - https://webcompat.com/issues/102133
+ */
+
+.tour-list .single-tour .mobile-link {
+ left: 0;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css b/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css
new file mode 100644
index 0000000000..f9460951af
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css
@@ -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/. */
+
+/**
+ * coldwellbankerhomes.com - Property images are displayed squeezed
+ * Bug #1770962 - https://bugzilla.mozilla.org/show_bug.cgi?id=1770962
+ * WebCompat issue #102872 - https://webcompat.com/issues/102872
+ */
+
+.property-snapshot-psr-panel
+ .prop-pix
+ .photo-carousel.owl
+ .owl-stage-outer
+ .owl-item
+ img {
+ height: -moz-available;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css b/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css
new file mode 100644
index 0000000000..840e4ad7fb
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css
@@ -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/. */
+
+/**
+ * rainews.it - Image slideshow is not shown
+ * Bug #1774490 - https://bugzilla.mozilla.org/show_bug.cgi?id=1774490
+ * WebCompat issue #105402 - https://webcompat.com/issues/105402
+ */
+
+.photogallery-swiper .swiper-slide {
+ height: auto;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css b/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css
new file mode 100644
index 0000000000..50b5bb2e90
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css
@@ -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/. */
+
+/**
+ * aveeno.com and acuvue.com - Unsupported message is displayed
+ *
+ * Bug #1784141 - aveeno.com - https://bugzilla.mozilla.org/show_bug.cgi?id=1784141
+ * Bug #1804730 - acuvue.com - https://bugzilla.mozilla.org/show_bug.cgi?id=1804730
+ *
+ * WebCompat issue #103557 - https://webcompat.com/issues/103557
+ * WebCompat issue #103557 - https://webcompat.com/issues/110797
+ */
+
+#browser-alert {
+ display: none !important;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css b/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css
new file mode 100644
index 0000000000..d20b84a99b
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css
@@ -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/. */
+
+/**
+ * aptsovation.com - Unsupported message is displayed on sites based on Entrata platform
+ * Bug #1784199 - https://bugzilla.mozilla.org/show_bug.cgi?id=1784199
+ * WebCompat issue #100131 - https://webcompat.com/issues/100131
+ */
+
+* {
+ color: unset;
+}
+
+#propertyProduct,
+.banner_overlay {
+ display: none;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css b/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css
new file mode 100644
index 0000000000..fd42fd7648
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css
@@ -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/. */
+
+/**
+ * www.vivobarefoot.com - product filters cannot be interacted-with
+ * Bug #1799994 - https://bugzilla.mozilla.org/show_bug.cgi?id=1799994
+ * WebCompat issue #108752 - https://webcompat.com/issues/108752
+ *
+ * The breakage is actually correct behavior, but because of Chrome
+ * bug https://bugs.chromium.org/p/chromium/issues/detail?id=606208
+ * it is currently not breaking on Chrome. We can work around it by
+ * bumping the z-index of the filter options.
+ */
+.page-products .filter-options {
+ z-index: 2;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css b/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css
new file mode 100644
index 0000000000..5ce3d9f15c
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css
@@ -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/. */
+
+/**
+ * www.honda.co.uk- "choose dealer" buttons cannot be interacted-with
+ * Bug #1800000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1800000
+ * WebCompat issue #109242 - https://webcompat.com/issues/109242
+ *
+ * The breakage is actually correct behavior, but because of Chrome
+ * bug https://bugs.chromium.org/p/chromium/issues/detail?id=606208
+ * it is currently not breaking on Chrome. We can work around it by
+ * bumping the z-index of the filter options.
+ */
+.cta-button {
+ z-index: 2;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css b/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css
new file mode 100644
index 0000000000..ab9788ddc1
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css
@@ -0,0 +1,15 @@
+/* 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/. */
+
+/**
+ * nppes.cms.hhs.gov - Firefox is an unsupported browser
+ * WebCompat issue #119017 - https://github.com/webcompat/web-bugs/issues/119017
+ *
+ * As everything seems to work just fine, this intervention simply hides the
+ * banner.
+ */
+
+#unsupportedDiv {
+ display: none !important;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css b/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css
new file mode 100644
index 0000000000..f6bee8c878
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css
@@ -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/. */
+
+/**
+ * tomshardware.com - a scrollbar covering navigation menu
+ * Bug #1829949 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829949
+ * WebCompat issue #121170 - https://github.com/webcompat/web-bugs/issues/121170
+ *
+ * The scrollbar is covering navigation items and that makes them half hidden.
+ * There are some ::-webkit-scrollbar css rules applied to the scrollbar,
+ * making it thinner. Adding similar rules for Firefox fixes the issue.
+ */
+
+.trending__list {
+ scrollbar-width: thin;
+ scrollbar-color: #000 #f5f5f5;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css b/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css
new file mode 100644
index 0000000000..54de51589a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css
@@ -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/. */
+
+/**
+ * eventer.co.il - a button is covering entire page
+ * Bug #1829952 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829952
+ * WebCompat issue #121296 - https://github.com/webcompat/web-bugs/issues/121296
+ *
+ * The button is covering the page only in Firefox on mobile
+ * because of additional styles applied via @-moz-document url-prefix.
+ * Resetting the height makes the button normal size
+ */
+
+#purchasePageRedesignContainer .mobileStripButton {
+ height: auto;
+ min-height: auto;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css b/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css
new file mode 100644
index 0000000000..1f7585e637
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css
@@ -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/. */
+
+/**
+ * my.babbel.com - "Next" button is not visible
+ * Bug #1830747 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830747
+ * WebCompat issue #119212 - https://github.com/webcompat/web-bugs/issues/119212
+ *
+ * The next button on the bottom of the page is not visible in Firefox,
+ * but visible in Chrome since the site is using -webkit-fill-available rule.
+ * Adding height: 100% to the page wrapper allows to see the button.
+ */
+
+[data-main] {
+ height: 100%;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css b/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css
new file mode 100644
index 0000000000..64974c46e4
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css
@@ -0,0 +1,23 @@
+/* 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/. */
+
+/**
+ * afisha.ru - Slider not working
+ * Bug #1830752 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830752
+ * WebCompat issue #120455 - https://github.com/webcompat/web-bugs/issues/120455
+ *
+ * The range slider for price filtering is not working because of pointer-events:none applied
+ * on the slider element. It's working in Chrome because of webkit specific rules
+ * set with -moz-range-thumb that override the pointer events on the slider thumb to auto.
+ * Setting the same rule with -moz-range-thumb makes the slider to work.
+ */
+
+.gNPvK::-moz-range-thumb,
+.y5iHc::-moz-range-thumb {
+ background-color: #0050ff;
+ border-color: #0050ff;
+ border-radius: 50%;
+ cursor: pointer;
+ pointer-events: auto;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css b/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css
new file mode 100644
index 0000000000..f2e24346e1
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css
@@ -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/. */
+
+/**
+ * 91mobiles.com - Text overlapping
+ * Bug #1830761 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830761
+ * WebCompat issue #117029 - https://github.com/webcompat/web-bugs/issues/117029
+ *
+ * The content overlaps dedicated space since Firefox honors small heights on <td>
+ * due to https://bugzilla.mozilla.org/show_bug.cgi?id=1461852. Setting the height to
+ * fit-content makes it work as expected.
+ */
+
+#fixed-table tr td .cmp-summary-box,
+.cmpr-table .textpanel {
+ height: fit-content;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css
new file mode 100644
index 0000000000..753835de6a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css
@@ -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/. */
+
+/**
+ * copyleaks.com - Unsupported message
+ * Bug #1830796 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830796
+ * WebCompat issue #121395 - https://github.com/webcompat/web-bugs/issues/121395
+ */
+
+#outdated {
+ display: none !important;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css
new file mode 100644
index 0000000000..7726140d0e
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css
@@ -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/. */
+
+/**
+ * interceramic.com - Unsupported message
+ * Bug #1830810 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830810
+ * WebCompat issue #117807 - https://github.com/webcompat/web-bugs/issues/117807
+ */
+
+#ff-modal {
+ display: none !important;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css
new file mode 100644
index 0000000000..707e75765e
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css
@@ -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/. */
+
+/**
+ * onstove.com - Unsupported message
+ * Bug #1830813 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830813
+ * WebCompat issue #116760 - https://github.com/webcompat/web-bugs/issues/116760
+ */
+
+.gnb-alerts.gnb-old-browser {
+ height: 0;
+}
+
+.isCampaign .gnb-stove.gnb-default-fixed,
+.isCampaign .layout.layout-base .layout-header {
+ height: 68px;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css b/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css
new file mode 100644
index 0000000000..70fc01b86f
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css
@@ -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/. */
+
+/**
+ * autostar-novoross.ru - Map is not as tall as expected
+ * Bug #1836103 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836103
+ * WebCompat issue #80763 - https://github.com/webcompat/web-bugs/issues/80763
+ */
+
+.t396 .tn-atom {
+ height: 100%;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css b/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css
new file mode 100644
index 0000000000..db6f018619
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css
@@ -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/. */
+
+/**
+ * cnn.com - Printing in portrait mode results in blank pages
+ * Bug #1836105 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836105
+ *
+ * Disable some of CNN's giant styles for height, top and margin-bottom,
+ * since they break print layout in Firefox (as per bug 1830307)
+ */
+
+@media print {
+ .header__wrapper-outer {
+ height: initial !important;
+ top: initial !important;
+ margin-bottom: initial !important;
+ }
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css b/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css
new file mode 100644
index 0000000000..ef255c7b89
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css
@@ -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/. */
+
+/**
+ * clalit.co.il - Hide number input spinners as page intends
+ * Bug #1836177 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836177
+ * WebCompat issue #109468 - https://github.com/webcompat/web-bugs/issues/109468
+ */
+
+input[type="number"] {
+ -moz-appearance: textfield;
+}
diff --git a/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js b/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js
new file mode 100644
index 0000000000..7a192d6c41
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals exportFunction */
+
+Object.defineProperty(window.wrappedJSObject, "isTestFeatureSupported", {
+ get: exportFunction(function () {
+ return true;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js b/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js
new file mode 100644
index 0000000000..7a8e85f538
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js
@@ -0,0 +1,35 @@
+/* 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 1448747 - Neutralize FastClick
+ *
+ * The patch is applied on sites using FastClick library
+ * to make sure `FastClick.notNeeded` returns `true`.
+ * This allows to disable FastClick and fix various breakage caused
+ * by the library (mainly non-functioning drop-down lists).
+ */
+
+/* globals exportFunction */
+
+(function () {
+ const proto = CSS2Properties.prototype.wrappedJSObject;
+ const descriptor = Object.getOwnPropertyDescriptor(proto, "touchAction");
+ const { get } = descriptor;
+
+ descriptor.get = exportFunction(function () {
+ try {
+ throw Error();
+ } catch (e) {
+ if (e.stack?.includes("notNeeded")) {
+ return "none";
+ }
+ }
+ return get.call(this);
+ }, window);
+
+ Object.defineProperty(proto, "touchAction", descriptor);
+})();
diff --git a/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js b/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
new file mode 100644
index 0000000000..40e17b4a36
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
@@ -0,0 +1,33 @@
+/* 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 1452707 - Build site patch for ib.absa.co.za
+ * WebCompat issue #16401 - https://webcompat.com/issues/16401
+ *
+ * The online banking at ib.absa.co.za detect if window.controllers is a
+ * non-falsy value to detect if the current browser is Firefox or something
+ * else. In bug 1448045, this shim has been disabled for Firefox Nightly 61+,
+ * which breaks the UA detection on this site and results in a "Browser
+ * unsuppored" error message.
+ *
+ * This site patch simply sets window.controllers to a string, resulting in
+ * their check to work again.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "window.controllers has been shimmed for compatibility reasons. See https://webcompat.com/issues/16401 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "controllers", {
+ get: exportFunction(function () {
+ return true;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js b/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js
new file mode 100644
index 0000000000..06085acc5a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js
@@ -0,0 +1,38 @@
+/* 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 1457335 - histography.io - Override UA & navigator.vendor
+ * WebCompat issue #1804 - https://webcompat.com/issues/1804
+ *
+ * This site is using a strict matching of navigator.userAgent and
+ * navigator.vendor to allow access for Safari or Chrome. Here, we set the
+ * values appropriately so we get recognized as Chrome.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/1804 for details."
+);
+
+const CHROME_UA = navigator.userAgent + " Chrome for WebCompat";
+
+Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(function () {
+ return CHROME_UA;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
+
+Object.defineProperty(window.navigator.wrappedJSObject, "vendor", {
+ get: exportFunction(function () {
+ return "Google Inc.";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js b/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js
new file mode 100644
index 0000000000..5aa72e75ae
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js
@@ -0,0 +1,52 @@
+/* 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 1472075 - Build UA override for Bank of America for OSX & Linux
+ * WebCompat issue #2787 - https://webcompat.com/issues/2787
+ *
+ * BoA is showing a red warning to Linux and macOS users, while accepting
+ * Windows users without warning. From our side, there is no difference here
+ * and we receive a lot of user complains about the warnings, so we spoof
+ * as Firefox on Windows in those cases.
+ */
+
+/* globals exportFunction */
+
+if (!navigator.platform.includes("Win")) {
+ console.info(
+ "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/2787 for details."
+ );
+
+ const WINDOWS_UA = navigator.userAgent.replace(
+ /\(.*; rv:/i,
+ "(Windows NT 10.0; Win64; x64; rv:"
+ );
+
+ Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(function () {
+ return WINDOWS_UA;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+ });
+
+ Object.defineProperty(window.navigator.wrappedJSObject, "appVersion", {
+ get: exportFunction(function () {
+ return "appVersion";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+ });
+
+ Object.defineProperty(window.navigator.wrappedJSObject, "platform", {
+ get: exportFunction(function () {
+ return "Win64";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+ });
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js b/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js
new file mode 100644
index 0000000000..5c757466c6
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js
@@ -0,0 +1,32 @@
+/* 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";
+
+/**
+ * m.tailieu.vn - Override PDFJS.disableWorker to be true
+ * WebCompat issue #39057 - https://webcompat.com/issues/39057
+ *
+ * Custom viewer built with PDF.js is not working in Firefox for Android
+ * Disabling worker to match Chrome behavior fixes the issue
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "window.PDFJS.disableWorker has been set to true for compatibility reasons. See https://webcompat.com/issues/39057 for details."
+);
+
+let globals = {};
+
+Object.defineProperty(window.wrappedJSObject, "PDFJS", {
+ get: exportFunction(function () {
+ return globals;
+ }, window),
+
+ set: exportFunction(function (value = {}) {
+ globals = value;
+ globals.disableWorker = true;
+ }, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js b/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js
new file mode 100644
index 0000000000..a068e8dcd5
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js
@@ -0,0 +1,38 @@
+/* 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 1605611 - Cannot change Departure/arrival dates in Google Maps on Android
+ *
+ * This patch re-enables the disabled "Leave now" button.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1800498 and
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1605611 for details.
+ */
+
+const selector =
+ ".ml-directions-searchbox-parent [aria-haspopup=dialog][disabled=true]";
+
+document.addEventListener("DOMContentLoaded", () => {
+ // In case the element appeared before the MutationObserver was activated.
+ for (const elem of document.querySelectorAll(selector)) {
+ elem.disabled = false;
+ }
+ // Start watching for the insertion of the "Leave now" button.
+ const moOptions = {
+ attributeFilter: ["disabled"],
+ attributes: true,
+ subtree: true,
+ };
+ const mo = new MutationObserver(function (records) {
+ for (const { target } of records) {
+ if (target.matches(selector)) {
+ target.disabled = false;
+ }
+ }
+ });
+ mo.observe(document.body, moOptions);
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js b/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js
new file mode 100644
index 0000000000..fb9be74039
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1631811 - disable indexedDB for datastudio.google.com iframes
+ *
+ * Indexed DB is disabled already for these iframes due to cookie blocking.
+ * This intervention changes the functionality from throwing a SecurityError
+ * when indexedDB is accessed to removing it from the window object
+ */
+
+console.info(
+ "window.indexedDB has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1631811 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "indexedDB", {
+ get: undefined,
+ set: undefined,
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js b/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js
new file mode 100644
index 0000000000..577a55450a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.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";
+
+/*
+ * Bug 1722955 - Add UA override for frontgate.com
+ * Webcompat issue #36277 - https://github.com/webcompat/web-bugs/issues/36277
+ *
+ * The website is sending the desktop version to Firefox on mobile devices
+ * based on UA sniffing. Spoofing as Chrome fixes this.
+ */
+
+/* globals exportFunction, UAHelpers */
+
+console.info(
+ "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/36277 for details."
+);
+
+UAHelpers.overrideWithDeviceAppropriateChromeUA();
diff --git a/browser/extensions/webcompat/injections/js/bug1724764-window-print.js b/browser/extensions/webcompat/injections/js/bug1724764-window-print.js
new file mode 100644
index 0000000000..20e9ecf22d
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1724764-window-print.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Generic window.print shim
+ *
+ * Issues related to an error caused by missing window.print() method on Android.
+ * Adding print to the window object allows to unbreak the sites.
+ */
+
+/* globals exportFunction */
+
+if (typeof window.print === "undefined") {
+ console.info(
+ "window.print has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1659818 for details."
+ );
+
+ Object.defineProperty(window.wrappedJSObject, "print", {
+ get: exportFunction(function () {
+ return true;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+ });
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js b/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js
new file mode 100644
index 0000000000..ab7b76c799
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.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/. */
+
+"use strict";
+
+/**
+ * Bug 1724868 - news.yahoo.co.jp - Override UA
+ * WebCompat issue #82605 - https://webcompat.com/issues/82605
+ *
+ * Yahoo Japan news doesn't allow playing video in Firefox on Android
+ * as they don't have it in their support matrix. They check UA override twice
+ * and display different ui with the same error. Changing UA to Chrome via
+ * content script allows playing the videos.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/82605 for details."
+);
+
+Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(function () {
+ return "Mozilla/5.0 (Linux; Android 11; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js b/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js
new file mode 100644
index 0000000000..d1823aec74
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.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 1731825 - Office 365 email handling prompt autohide
+ *
+ * This site patch prevents the notification bar on Office 365
+ * apps from popping up on each page-load, offering to handle
+ * email with Outlook.
+ */
+
+/* globals exportFunction */
+
+const warning =
+ "Office 365 Outlook email handling prompt has been hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=1731825 for details.";
+
+const localStorageKey = "mailProtocolHandlerAlreadyOffered";
+
+const nav = navigator.wrappedJSObject;
+const { registerProtocolHandler } = nav;
+const { localStorage } = window.wrappedJSObject;
+
+Object.defineProperty(navigator.wrappedJSObject, "registerProtocolHandler", {
+ value: exportFunction(function (scheme, url, title) {
+ if (localStorage.getItem(localStorageKey)) {
+ console.info(warning);
+ return undefined;
+ }
+ registerProtocolHandler.call(nav, scheme, url, title);
+ localStorage.setItem(localStorageKey, true);
+ return undefined;
+ }, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js
new file mode 100644
index 0000000000..5ae55ec6f3
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js
@@ -0,0 +1,116 @@
+/* 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 1739489 - Entering an emoji using the MacOS IME "crashes" Draft.js editors.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "textInput event has been remapped to beforeinput for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1739489 for details."
+);
+
+window.wrappedJSObject.TextEvent = window.wrappedJSObject.InputEvent;
+
+const { CustomEvent, Event, EventTarget } = window.wrappedJSObject;
+var Remapped = [
+ [CustomEvent, "constructor"],
+ [Event, "constructor"],
+ [Event, "initEvent"],
+ [EventTarget, "addEventListener"],
+ [EventTarget, "removeEventListener"],
+];
+
+for (const [obj, name] of Remapped) {
+ const { prototype } = obj;
+ const orig = prototype[name];
+ Object.defineProperty(prototype, name, {
+ value: exportFunction(function (type, b, c, d) {
+ if (type?.toLowerCase() === "textinput") {
+ type = "beforeinput";
+ }
+ return orig.call(this, type, b, c, d);
+ }, window),
+ });
+}
+
+if (location.host === "www.reddit.com") {
+ (function () {
+ const EditorCSS = ".public-DraftEditor-content[contenteditable=true]";
+ let obsEditor, obsStart, obsText, obsKey, observer;
+ const obsConfig = { characterData: true, childList: true, subtree: true };
+ const obsHandler = () => {
+ observer.disconnect();
+ const finalTextNode = obsEditor.querySelector(
+ `[data-offset-key="${obsKey}"] [data-text='true']`
+ ).firstChild;
+ const end = obsStart + obsText.length;
+ window
+ .getSelection()
+ .setBaseAndExtent(finalTextNode, end, finalTextNode, end);
+ };
+ observer = new MutationObserver(obsHandler);
+
+ document.documentElement.addEventListener(
+ "beforeinput",
+ e => {
+ if (e.inputType != "insertFromPaste") {
+ return;
+ }
+ const { target } = e;
+ obsEditor = target.closest(EditorCSS);
+ if (!obsEditor) {
+ return;
+ }
+ const items = e?.dataTransfer.items;
+ for (let item of items) {
+ if (item.type === "text/plain") {
+ e.preventDefault();
+ item.getAsString(text => {
+ obsText = text;
+
+ // find the editor-managed <span> which contains the text node the
+ // cursor starts on, and the cursor's location (or the selection start)
+ const sel = window.getSelection();
+ obsStart = sel.anchorOffset;
+ let anchor = sel.anchorNode;
+ if (!anchor.closest) {
+ anchor = anchor.parentElement;
+ }
+ anchor = anchor.closest("[data-offset-key]");
+ obsKey = anchor.getAttribute("data-offset-key");
+
+ // set us up to wait for the editor to either update or replace the
+ // <span> with that key (the one containing the text to be changed).
+ // we will then make sure the cursor is after the pasted text, as if
+ // the editor recreates the node, the cursor position is lost
+ observer.observe(obsEditor, obsConfig);
+
+ // force the editor to "paste". sending paste or other events will not
+ // work, nor using execCommand (adding HTML will screw up the DOM that
+ // the editor expects, and adding plain text will make it ignore newlines).
+ target.dispatchEvent(
+ new InputEvent("beforeinput", {
+ inputType: "insertText",
+ data: text,
+ bubbles: true,
+ cancelable: true,
+ })
+ );
+
+ // blur the editor to force it to update/flush its state, because otherwise
+ // the paste works, but the editor doesn't show it (until it is re-focused).
+ obsEditor.blur();
+ });
+ break;
+ }
+ }
+ },
+ true
+ );
+ })();
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js b/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js
new file mode 100644
index 0000000000..7383a4e567
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1769762 - Empty out navigator.plugins
+ * WebCompat issue #103612 - https://webcompat.com/issues/103612
+ *
+ * Certain features of the site are breaking if navigator.plugins array is not empty:
+ *
+ * 1. "Likes" on the comments are not saved
+ * 2. Can't reply to other people's comments
+ * 3. "Likes" on the videos are not saved
+ * 4. Can't follow an account (after refreshing "Follow" button is visible again)
+ *
+ * (note that the first 2 are still broken if you open devtools even with this intervention)
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The PluginArray has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1753874 for details."
+);
+
+const pluginsArray = new window.wrappedJSObject.Array();
+Object.setPrototypeOf(pluginsArray, PluginArray.prototype);
+
+Object.defineProperty(navigator.wrappedJSObject, "plugins", {
+ get: exportFunction(function () {
+ return pluginsArray;
+ }, window),
+ set: exportFunction(function (val) {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js b/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js
new file mode 100644
index 0000000000..ca7ef5b6c5
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js
@@ -0,0 +1,26 @@
+/* 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 1774005 - Generic window.InstallTrigger shim
+ *
+ * This interventions shims window.InstallTrigger to a string, which evaluates
+ * as `true` in web developers browser sniffing code. This intervention will
+ * be applied to multiple domains, see bug 1774005 for more information.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The InstallTrigger has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1774005 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "InstallTrigger", {
+ get: exportFunction(function () {
+ return "This property has been shimed for Web Compatibility reasons.";
+ }, window),
+ set: exportFunction(function (_) {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js b/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js
new file mode 100644
index 0000000000..7bde4a7d82
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js
@@ -0,0 +1,27 @@
+/* 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 1784302 - Issues due to missing navigator.connection after
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1637922 landed.
+ * Webcompat issue #104838 - https://github.com/webcompat/web-bugs/issues/104838
+ */
+
+/* globals cloneInto, exportFunction */
+
+console.info(
+ "navigator.connection has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1756692 for details."
+);
+
+var connection = {
+ addEventListener: () => {},
+ removeEventListener: () => {},
+ effectiveType: "4g",
+};
+
+window.navigator.wrappedJSObject.connection = cloneInto(connection, window, {
+ cloneFunctions: true,
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js b/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js
new file mode 100644
index 0000000000..f3c0e03513
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js
@@ -0,0 +1,40 @@
+/* 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 1795490 - Cannot use date fields on China Airlines mobile page
+ *
+ * This patch ensures that the search input never has the [disabled]
+ * attribute, so that users may tap/click on it to search.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1795490 for details.
+ */
+
+const SELECTOR = `#departureDateMobile[disabled], #returnDateMobile[disabled]`;
+
+function check(target) {
+ if (target.nodeName === "INPUT" && target.matches(SELECTOR)) {
+ target.removeAttribute("disabled");
+ return true;
+ }
+ return false;
+}
+
+new MutationObserver(mutations => {
+ for (const { addedNodes, target, attributeName } of mutations) {
+ if (attributeName === "disabled") {
+ check(target);
+ } else {
+ addedNodes?.forEach(node => {
+ if (!check(node)) {
+ node
+ .querySelectorAll?.(SELECTOR)
+ ?.forEach(n => n.removeAttribute("disabled"));
+ }
+ });
+ }
+ }
+}).observe(document, { attributes: true, childList: true, subtree: true });
diff --git a/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js b/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js
new file mode 100644
index 0000000000..941f071e2c
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1799968 - Build site patch for www.samsung.com
+ * WebCompat issue #108993 - https://webcompat.com/issues/108993
+ *
+ * Samsung's Watch pages try to detect the OS via navigator.appVersion,
+ * but fail with Linux because they expect it to contain the literal
+ * string "linux", and their JS breaks.
+ *
+ * As such this site patch sets appVersion to "5.0 (Linux)", and is
+ * only meant to be applied on Linux.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "navigator.appVersion has been shimmed for compatibility reasons. See https://webcompat.com/issues/108993 for details."
+);
+
+Object.defineProperty(navigator.wrappedJSObject, "appVersion", {
+ get: exportFunction(function () {
+ return "5.0 (Linux)";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js b/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js
new file mode 100644
index 0000000000..191e97dec1
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1799980 - Healow gets stuck in an infinite loop while pages load
+ *
+ * This patch keeps Healow's localization scripts from getting stuck in
+ * an infinite loop while their pages are loading.
+ *
+ * This happens because they use synchronous XMLHttpRequests to fetch a
+ * JSON file with their localized text on the first call to their i18n
+ * function, and then force subsequent calls to wait for it by waiting
+ * in an infinite loop.
+ *
+ * But since they're in an infinite loop, the code after the syncXHR will
+ * never be able to run, so this ultimately triggers a slow script warning.
+ *
+ * We can improve this by just preventing the infinite loop from happening,
+ * though since they disable caching on their JSON files it means that more
+ * XHRs may happen. But since those files are small, this seems like a
+ * reasonable compromise until they migrate to a better i18n solution.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1799980 for details.
+ */
+
+/* globals exportFunction */
+
+Object.defineProperty(window.wrappedJSObject, "ajaxRequestProcessing", {
+ get: exportFunction(function () {
+ return false;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js b/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js
new file mode 100644
index 0000000000..91f1c1a19a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1818818 - Neutralize FastClick
+ *
+ * The patch is applied on sites using older version of FastClick library.
+ * This allows to disable FastClick and fix various breakage caused
+ * by the library.
+ */
+
+/* globals exportFunction */
+
+const proto = CSS2Properties.prototype.wrappedJSObject;
+Object.defineProperty(proto, "msTouchAction", {
+ get: exportFunction(function () {
+ return "none";
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js b/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js
new file mode 100644
index 0000000000..bbe76c465f
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.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/. */
+
+"use strict";
+
+/**
+ * Bug 1819450 - cmbchina.com - Override UA
+ *
+ * The site is using UA detection to redirect to
+ * m.cmbchina.com (mobile version of the site). Adding `SAMSUNG` allows
+ * to bypass the detection of mobile browser.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The user agent has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1081239 for details."
+);
+
+const MODIFIED_UA = navigator.userAgent + " SAMSUNG";
+
+Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(function () {
+ return MODIFIED_UA;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js b/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js
new file mode 100644
index 0000000000..a72e938e4f
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js
@@ -0,0 +1,26 @@
+/* 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";
+
+/**
+ * axisbank.com - Shim webkitSpeechRecognition
+ * WebCompat issue #117770 - https://webcompat.com/issues/117770
+ *
+ * The page with bank offerings is not loading options due to the
+ * site relying on webkitSpeechRecognition, which is undefined in Firefox.
+ * Shimming it to `class {}` makes the pages work.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "webkitSpeechRecognition was shimmed for compatibility reasons. See https://webcompat.com/issues/117770 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "webkitSpeechRecognition", {
+ value: exportFunction(function () {
+ return class {};
+ }, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js b/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js
new file mode 100644
index 0000000000..c230feb43d
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.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 1819678 - cnki.net - Cannot use search field
+ * WebCompat issue #115777 - https://webcompat.com/issues/115777
+ *
+ * This patch ensures that the search input never has the [disabled]
+ * attribute, so that users may tap/click on it to search.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1819678 for details.
+ */
+
+console.info(
+ "search input disabled attribute was removed for compatibility reasons. See https://webcompat.com/issues/115777 for details."
+);
+
+const SELECTOR = `.searchimg[disabled]`;
+
+function check(target) {
+ if (target.nodeName === "INPUT" && target.matches(SELECTOR)) {
+ target.removeAttribute("disabled");
+ return true;
+ }
+ return false;
+}
+
+new MutationObserver(mutations => {
+ for (const { addedNodes, target, attributeName } of mutations) {
+ if (attributeName === "disabled") {
+ check(target);
+ } else {
+ addedNodes?.forEach(node => {
+ if (!check(node)) {
+ node
+ .querySelectorAll?.(SELECTOR)
+ ?.forEach(n => n.removeAttribute("disabled"));
+ }
+ });
+ }
+ }
+}).observe(document, { attributes: true, childList: true, subtree: true });
diff --git a/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js b/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js
new file mode 100644
index 0000000000..6e6b5823cb
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1827678 - UA spoof for www.free4talk.com
+ *
+ * This site is checking for window.chrome, so let's spoof that.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "window.chrome has been shimmed for compatibility reasons. See https://github.com/webcompat/web-bugs/issues/77727 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "chrome", {
+ get: exportFunction(function () {
+ return true;
+ }, window),
+
+ set: exportFunction(function () {}, window),
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js b/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js
new file mode 100644
index 0000000000..2b1eb11baf
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js
@@ -0,0 +1,24 @@
+/* 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 1830776 - blueshieldca.com
+ * WebCompat issue #112630 - https://webcompat.com/issues/112630
+ *
+ * The site is showing unsupported message in Firefox.
+ * They're also checking for "browserCollapsed" item in sessionStorage
+ * before showing the message, to only show it once. Adding this
+ * item to sessionStorage will make sure the message is not shown
+ * on the initial load.
+ */
+
+console.info(
+ "browserCollapsed in sessionStorage has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1830776 for details."
+);
+
+if (!sessionStorage.getItem("browserCollapsed")) {
+ sessionStorage.setItem("browserCollapsed", "true");
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js b/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js
new file mode 100644
index 0000000000..433c416770
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js
@@ -0,0 +1,27 @@
+/* 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 1831007 - Shim window.OnetrustActiveGroups for Nintendo sites
+ *
+ * Nintendo relies on `window.OnetrustActiveGroups` being defined. If it's not,
+ * users may have intermittent issues signing into their account, as they're
+ * then trying to call `.split()` on `undefined`.
+ *
+ * This intervention sets a default value (an empty string), but still allows
+ * the value to be overwritten at any time.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The window.OnetrustActiveGroups property has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1831007 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "OnetrustActiveGroups", {
+ value: "",
+ writable: true,
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js b/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js
new file mode 100644
index 0000000000..719267748b
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js
@@ -0,0 +1,23 @@
+/* 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 1836157 - Shim navigator.platform on www.thai-massaszs.net/en/
+ *
+ * This page adds niceScroll on Android, which breaks scrolling and
+ * zooming on Firefox. Adding ` Mac` to `navigator.platform` makes
+ * the page avoid adding niceScroll entirely, unbreaking the page.
+ */
+
+var plat = navigator.platform;
+if (!plat.includes("Mac")) {
+ console.info(
+ "The navigator.platform property has been shimmed to include 'Mac' for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1836157 for details."
+ );
+
+ Object.defineProperty(navigator.__proto__.wrappedJSObject, "platform", {
+ value: plat + " Mac",
+ writable: true,
+ });
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js b/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js
new file mode 100644
index 0000000000..2d328de108
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.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 1842437 - When attempting to go back on youtube.com, the content remains the same
+ *
+ * If consecutive session history entries had history.state.entryTime set to same value,
+ * back button doesn't work as expected. The entryTime value is coming from performance.now()
+ * and modifying its return value slightly to make sure two close consecutive calls don't
+ * get the same result helped with resolving the issue.
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "performance.now precision has been modified for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1756970 for details."
+);
+
+const origPerf = performance.wrappedJSObject;
+const origNow = origPerf.now;
+
+let counter = 0;
+let previousVal = 0;
+
+Object.defineProperty(window.performance.wrappedJSObject, "now", {
+ value: exportFunction(function () {
+ let originalVal = origNow.call(origPerf);
+ if (originalVal === previousVal) {
+ originalVal += 0.00000003 * ++counter;
+ } else {
+ previousVal = originalVal;
+ counter = 0;
+ }
+ return originalVal;
+ }, window),
+});
diff --git a/browser/extensions/webcompat/lib/about_compat_broker.js b/browser/extensions/webcompat/lib/about_compat_broker.js
new file mode 100644
index 0000000000..faaa56a38e
--- /dev/null
+++ b/browser/extensions/webcompat/lib/about_compat_broker.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global browser, module, onMessageFromTab */
+
+class AboutCompatBroker {
+ constructor(bindings) {
+ this._injections = bindings.injections;
+ this._uaOverrides = bindings.uaOverrides;
+ this._shims = bindings.shims;
+
+ if (!this._injections && !this._uaOverrides && !this._shims) {
+ throw new Error("No interventions; about:compat broker is not needed");
+ }
+
+ this.portsToAboutCompatTabs = this.buildPorts();
+ this._injections?.bindAboutCompatBroker(this);
+ this._uaOverrides?.bindAboutCompatBroker(this);
+ this._shims?.bindAboutCompatBroker(this);
+ }
+
+ buildPorts() {
+ const ports = new Set();
+
+ browser.runtime.onConnect.addListener(port => {
+ ports.add(port);
+ port.onDisconnect.addListener(function () {
+ ports.delete(port);
+ });
+ });
+
+ async function broadcast(message) {
+ for (const port of ports) {
+ port.postMessage(message);
+ }
+ }
+
+ return { broadcast };
+ }
+
+ filterOverrides(overrides) {
+ return overrides
+ .filter(override => override.availableOnPlatform)
+ .map(override => {
+ const { id, active, bug, domain, hidden } = override;
+ return { id, active, bug, domain, hidden };
+ });
+ }
+
+ getInterventionById(id) {
+ for (const [type, things] of Object.entries({
+ overrides: this._uaOverrides?.getAvailableOverrides() || [],
+ interventions: this._injections?.getAvailableInjections() || [],
+ shims: this._shims?.getAvailableShims() || [],
+ })) {
+ for (const what of things) {
+ if (what.id === id) {
+ return { type, what };
+ }
+ }
+ }
+ return {};
+ }
+
+ bootup() {
+ onMessageFromTab(msg => {
+ switch (msg.command || msg) {
+ case "toggle": {
+ const id = msg.id;
+ const { type, what } = this.getInterventionById(id);
+ if (!what) {
+ return Promise.reject(
+ `No such override or intervention to toggle: ${id}`
+ );
+ }
+ const active = type === "shims" ? !what.disabledReason : what.active;
+ this.portsToAboutCompatTabs
+ .broadcast({ toggling: id, active })
+ .then(async () => {
+ switch (type) {
+ case "interventions": {
+ if (active) {
+ await this._injections?.disableInjection(what);
+ } else {
+ await this._injections?.enableInjection(what);
+ }
+ break;
+ }
+ case "overrides": {
+ if (active) {
+ await this._uaOverrides?.disableOverride(what);
+ } else {
+ await this._uaOverrides?.enableOverride(what);
+ }
+ break;
+ }
+ case "shims": {
+ if (active) {
+ await this._shims?.disableShimForSession(id);
+ } else {
+ await this._shims?.enableShimForSession(id);
+ }
+ // no need to broadcast the "toggled" signal for shims, as
+ // they send a shimsUpdated message themselves instead
+ return;
+ }
+ }
+ this.portsToAboutCompatTabs.broadcast({
+ toggled: id,
+ active: !active,
+ });
+ });
+ break;
+ }
+ case "getAllInterventions": {
+ return Promise.resolve({
+ overrides:
+ (this._uaOverrides?.isEnabled() &&
+ this.filterOverrides(
+ this._uaOverrides?.getAvailableOverrides()
+ )) ||
+ false,
+ interventions:
+ (this._injections?.isEnabled() &&
+ this.filterOverrides(
+ this._injections?.getAvailableInjections()
+ )) ||
+ false,
+ shims: this._shims?.getAvailableShims() || false,
+ });
+ }
+ }
+ return undefined;
+ });
+ }
+}
+
+module.exports = AboutCompatBroker;
diff --git a/browser/extensions/webcompat/lib/custom_functions.js b/browser/extensions/webcompat/lib/custom_functions.js
new file mode 100644
index 0000000000..97603e0424
--- /dev/null
+++ b/browser/extensions/webcompat/lib/custom_functions.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser, module */
+
+const replaceStringInRequest = (
+ requestId,
+ inString,
+ outString,
+ inEncoding = "utf-8"
+) => {
+ const filter = browser.webRequest.filterResponseData(requestId);
+ const decoder = new TextDecoder(inEncoding);
+ const encoder = new TextEncoder();
+ const RE = new RegExp(inString, "g");
+ const carryoverLength = inString.length;
+ let carryover = "";
+
+ filter.ondata = event => {
+ const replaced = (
+ carryover + decoder.decode(event.data, { stream: true })
+ ).replace(RE, outString);
+ filter.write(encoder.encode(replaced.slice(0, -carryoverLength)));
+ carryover = replaced.slice(-carryoverLength);
+ };
+
+ filter.onstop = event => {
+ if (carryover.length) {
+ filter.write(encoder.encode(carryover));
+ }
+ filter.close();
+ };
+};
+
+const CUSTOM_FUNCTIONS = {
+ detectSwipeFix: injection => {
+ const { urls, types } = injection.data;
+ const listener = (injection.data.listener = ({ requestId }) => {
+ replaceStringInRequest(
+ requestId,
+ "preventDefault:true",
+ "preventDefault:false"
+ );
+ return {};
+ });
+ browser.webRequest.onBeforeRequest.addListener(listener, { urls, types }, [
+ "blocking",
+ ]);
+ },
+ detectSwipeFixDisable: injection => {
+ const { listener } = injection.data;
+ browser.webRequest.onBeforeRequest.removeListener(listener);
+ delete injection.data.listener;
+ },
+ noSniffFix: injection => {
+ const { urls, contentType } = injection.data;
+ const listener = (injection.data.listener = e => {
+ e.responseHeaders.push(contentType);
+ return { responseHeaders: e.responseHeaders };
+ });
+
+ browser.webRequest.onHeadersReceived.addListener(listener, { urls }, [
+ "blocking",
+ "responseHeaders",
+ ]);
+ },
+ noSniffFixDisable: injection => {
+ const { listener } = injection.data;
+ browser.webRequest.onHeadersReceived.removeListener(listener);
+ delete injection.data.listener;
+ },
+ runScriptBeforeRequest: injection => {
+ const { bug, message, request, script, types } = injection;
+ const warning = `${message} See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`;
+
+ const listener = (injection.listener = e => {
+ const { tabId, frameId } = e;
+ return browser.tabs
+ .executeScript(tabId, {
+ file: script,
+ frameId,
+ runAt: "document_start",
+ })
+ .then(() => {
+ browser.tabs.executeScript(tabId, {
+ code: `console.warn(${JSON.stringify(warning)})`,
+ runAt: "document_start",
+ });
+ })
+ .catch(_ => {});
+ });
+
+ browser.webRequest.onBeforeRequest.addListener(
+ listener,
+ { urls: request, types: types || ["script"] },
+ ["blocking"]
+ );
+ },
+ runScriptBeforeRequestDisable: injection => {
+ const { listener } = injection;
+ browser.webRequest.onBeforeRequest.removeListener(listener);
+ delete injection.data.listener;
+ },
+};
+
+module.exports = CUSTOM_FUNCTIONS;
diff --git a/browser/extensions/webcompat/lib/injections.js b/browser/extensions/webcompat/lib/injections.js
new file mode 100644
index 0000000000..8760f551c7
--- /dev/null
+++ b/browser/extensions/webcompat/lib/injections.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser, module */
+
+class Injections {
+ constructor(availableInjections, customFunctions) {
+ this.INJECTION_PREF = "perform_injections";
+
+ this._injectionsEnabled = true;
+
+ this._availableInjections = availableInjections;
+ this._activeInjections = new Map();
+ this._customFunctions = customFunctions;
+ }
+
+ bindAboutCompatBroker(broker) {
+ this._aboutCompatBroker = broker;
+ }
+
+ bootup() {
+ browser.aboutConfigPrefs.onPrefChange.addListener(() => {
+ this.checkInjectionPref();
+ }, this.INJECTION_PREF);
+ this.checkInjectionPref();
+ }
+
+ checkInjectionPref() {
+ browser.aboutConfigPrefs.getPref(this.INJECTION_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(this.INJECTION_PREF, true);
+ } else if (value === false) {
+ this.unregisterContentScripts();
+ } else {
+ this.registerContentScripts();
+ }
+ });
+ }
+
+ getAvailableInjections() {
+ return this._availableInjections;
+ }
+
+ isEnabled() {
+ return this._injectionsEnabled;
+ }
+
+ async registerContentScripts() {
+ const platformInfo = await browser.runtime.getPlatformInfo();
+ const platformMatches = [
+ "all",
+ platformInfo.os,
+ platformInfo.os == "android" ? "android" : "desktop",
+ ];
+ for (const injection of this._availableInjections) {
+ if (platformMatches.includes(injection.platform)) {
+ injection.availableOnPlatform = true;
+ await this.enableInjection(injection);
+ }
+ }
+
+ this._injectionsEnabled = true;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ interventionsChanged: this._aboutCompatBroker.filterOverrides(
+ this._availableInjections
+ ),
+ });
+ }
+
+ assignContentScriptDefaults(contentScripts) {
+ let finalConfig = Object.assign({}, contentScripts);
+
+ if (!finalConfig.runAt) {
+ finalConfig.runAt = "document_start";
+ }
+
+ return finalConfig;
+ }
+
+ async enableInjection(injection) {
+ if (injection.active) {
+ return undefined;
+ }
+
+ if (injection.customFunc) {
+ return this.enableCustomInjection(injection);
+ }
+
+ return this.enableContentScripts(injection);
+ }
+
+ enableCustomInjection(injection) {
+ if (injection.customFunc in this._customFunctions) {
+ this._customFunctions[injection.customFunc](injection);
+ injection.active = true;
+ } else {
+ console.error(
+ `Provided function ${injection.customFunc} wasn't found in functions list`
+ );
+ }
+ }
+
+ async enableContentScripts(injection) {
+ try {
+ const handle = await browser.contentScripts.register(
+ this.assignContentScriptDefaults(injection.contentScripts)
+ );
+ this._activeInjections.set(injection, handle);
+ injection.active = true;
+ } catch (ex) {
+ console.error(
+ "Registering WebCompat GoFaster content scripts failed: ",
+ ex
+ );
+ }
+ }
+
+ unregisterContentScripts() {
+ for (const injection of this._availableInjections) {
+ this.disableInjection(injection);
+ }
+
+ this._injectionsEnabled = false;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ interventionsChanged: false,
+ });
+ }
+
+ async disableInjection(injection) {
+ if (!injection.active) {
+ return undefined;
+ }
+
+ if (injection.customFunc) {
+ return this.disableCustomInjections(injection);
+ }
+
+ return this.disableContentScripts(injection);
+ }
+
+ disableCustomInjections(injection) {
+ const disableFunc = injection.customFunc + "Disable";
+
+ if (disableFunc in this._customFunctions) {
+ this._customFunctions[disableFunc](injection);
+ injection.active = false;
+ } else {
+ console.error(
+ `Provided function ${disableFunc} for disabling injection wasn't found in functions list`
+ );
+ }
+ }
+
+ async disableContentScripts(injection) {
+ const contentScript = this._activeInjections.get(injection);
+ await contentScript.unregister();
+ this._activeInjections.delete(injection);
+ injection.active = false;
+ }
+}
+
+module.exports = Injections;
diff --git a/browser/extensions/webcompat/lib/intervention_helpers.js b/browser/extensions/webcompat/lib/intervention_helpers.js
new file mode 100644
index 0000000000..16ea6572f2
--- /dev/null
+++ b/browser/extensions/webcompat/lib/intervention_helpers.js
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals module */
+
+const GOOGLE_TLDS = [
+ "com",
+ "ac",
+ "ad",
+ "ae",
+ "com.af",
+ "com.ag",
+ "com.ai",
+ "al",
+ "am",
+ "co.ao",
+ "com.ar",
+ "as",
+ "at",
+ "com.au",
+ "az",
+ "ba",
+ "com.bd",
+ "be",
+ "bf",
+ "bg",
+ "com.bh",
+ "bi",
+ "bj",
+ "com.bn",
+ "com.bo",
+ "com.br",
+ "bs",
+ "bt",
+ "co.bw",
+ "by",
+ "com.bz",
+ "ca",
+ "com.kh",
+ "cc",
+ "cd",
+ "cf",
+ "cat",
+ "cg",
+ "ch",
+ "ci",
+ "co.ck",
+ "cl",
+ "cm",
+ "cn",
+ "com.co",
+ "co.cr",
+ "com.cu",
+ "cv",
+ "com.cy",
+ "cz",
+ "de",
+ "dj",
+ "dk",
+ "dm",
+ "com.do",
+ "dz",
+ "com.ec",
+ "ee",
+ "com.eg",
+ "es",
+ "com.et",
+ "fi",
+ "com.fj",
+ "fm",
+ "fr",
+ "ga",
+ "ge",
+ "gf",
+ "gg",
+ "com.gh",
+ "com.gi",
+ "gl",
+ "gm",
+ "gp",
+ "gr",
+ "com.gt",
+ "gy",
+ "com.hk",
+ "hn",
+ "hr",
+ "ht",
+ "hu",
+ "co.id",
+ "iq",
+ "ie",
+ "co.il",
+ "im",
+ "co.in",
+ "io",
+ "is",
+ "it",
+ "je",
+ "com.jm",
+ "jo",
+ "co.jp",
+ "co.ke",
+ "ki",
+ "kg",
+ "co.kr",
+ "com.kw",
+ "kz",
+ "la",
+ "com.lb",
+ "com.lc",
+ "li",
+ "lk",
+ "co.ls",
+ "lt",
+ "lu",
+ "lv",
+ "com.ly",
+ "co.ma",
+ "md",
+ "me",
+ "mg",
+ "mk",
+ "ml",
+ "com.mm",
+ "mn",
+ "ms",
+ "com.mt",
+ "mu",
+ "mv",
+ "mw",
+ "com.mx",
+ "com.my",
+ "co.mz",
+ "com.na",
+ "ne",
+ "com.nf",
+ "com.ng",
+ "com.ni",
+ "nl",
+ "no",
+ "com.np",
+ "nr",
+ "nu",
+ "co.nz",
+ "com.om",
+ "com.pk",
+ "com.pa",
+ "com.pe",
+ "com.ph",
+ "pl",
+ "com.pg",
+ "pn",
+ "com.pr",
+ "ps",
+ "pt",
+ "com.py",
+ "com.qa",
+ "ro",
+ "rs",
+ "ru",
+ "rw",
+ "com.sa",
+ "com.sb",
+ "sc",
+ "se",
+ "com.sg",
+ "sh",
+ "si",
+ "sk",
+ "com.sl",
+ "sn",
+ "sm",
+ "so",
+ "st",
+ "sr",
+ "com.sv",
+ "td",
+ "tg",
+ "co.th",
+ "com.tj",
+ "tk",
+ "tl",
+ "tm",
+ "to",
+ "tn",
+ "com.tr",
+ "tt",
+ "com.tw",
+ "co.tz",
+ "com.ua",
+ "co.ug",
+ "co.uk",
+ "com",
+ "com.uy",
+ "co.uz",
+ "com.vc",
+ "co.ve",
+ "vg",
+ "co.vi",
+ "com.vn",
+ "vu",
+ "ws",
+ "co.za",
+ "co.zm",
+ "co.zw",
+];
+
+var InterventionHelpers = {
+ /**
+ * Useful helper to generate a list of domains with a fixed base domain and
+ * multiple country-TLDs or other cases with various TLDs.
+ *
+ * Example:
+ * matchPatternsForTLDs("*://mozilla.", "/*", ["com", "org"])
+ * => ["*://mozilla.com/*", "*://mozilla.org/*"]
+ */
+ matchPatternsForTLDs(base, suffix, tlds) {
+ return tlds.map(tld => base + tld + suffix);
+ },
+
+ /**
+ * A modified version of matchPatternsForTLDs that always returns the match
+ * list for all known Google country TLDs.
+ */
+ matchPatternsForGoogle(base, suffix = "/*") {
+ return InterventionHelpers.matchPatternsForTLDs(base, suffix, GOOGLE_TLDS);
+ },
+};
+
+module.exports = InterventionHelpers;
diff --git a/browser/extensions/webcompat/lib/messaging_helper.js b/browser/extensions/webcompat/lib/messaging_helper.js
new file mode 100644
index 0000000000..d978ed384f
--- /dev/null
+++ b/browser/extensions/webcompat/lib/messaging_helper.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";
+
+/* globals browser */
+
+// By default, only the first handler for browser.runtime.onMessage which
+// returns a value will get to return one. As such, we need to let them all
+// receive the message, and all have a chance to return a response (with the
+// first non-undefined result being the one that is ultimately returned).
+// This way, about:compat and the shims library can both get a chance to
+// process a message, and just return undefined if they wish to ignore it.
+
+const onMessageFromTab = (function () {
+ const handlers = new Set();
+
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ const promises = [...handlers.values()].map(fn => fn(msg, sender));
+ return Promise.allSettled(promises).then(results => {
+ for (const { reason, value } of results) {
+ if (reason) {
+ console.error(reason);
+ } else if (value !== undefined) {
+ return value;
+ }
+ }
+ return undefined;
+ });
+ });
+
+ return function (handler) {
+ handlers.add(handler);
+ };
+})();
diff --git a/browser/extensions/webcompat/lib/module_shim.js b/browser/extensions/webcompat/lib/module_shim.js
new file mode 100644
index 0000000000..2fd39fdbbd
--- /dev/null
+++ b/browser/extensions/webcompat/lib/module_shim.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * We cannot yet use proper JS modules within webextensions, as support for them
+ * is highly experimental and highly instable. So we end up just including all
+ * the JS files we need as separate background scripts, and since they all are
+ * executed within the same context, this works for our in-browser deployment.
+ *
+ * However, this code is tracked outside of mozilla-central, and we work on
+ * shipping this code in other products, like android-components as well.
+ * Because of that, we have automated tests running within that repository. To
+ * make our lives easier, we add `module.exports` statements to the JS source
+ * files, so we can easily import their contents into our NodeJS-based test
+ * suite.
+ *
+ * This works fine, but obviously, `module` is not defined when running
+ * in-browser. So let's use this empty object as a shim, so we don't run into
+ * runtime exceptions because of that.
+ */
+var module = {};
diff --git a/browser/extensions/webcompat/lib/requestStorageAccess_helper.js b/browser/extensions/webcompat/lib/requestStorageAccess_helper.js
new file mode 100644
index 0000000000..032225bb78
--- /dev/null
+++ b/browser/extensions/webcompat/lib/requestStorageAccess_helper.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/. */
+
+/* globals browser */
+
+// Helper for calling the internal requestStorageAccessForOrigin method. The
+// method is called on the first-party document for the third-party which needs
+// first-party storage access.
+browser.runtime.onMessage.addListener(request => {
+ let { requestStorageAccessOrigin, warning } = request;
+ if (!requestStorageAccessOrigin) {
+ return false;
+ }
+
+ // Log a warning to the web console, informing about the shim.
+ console.warn(warning);
+
+ // Call the internal storage access API. Passing false means we don't require
+ // user activation, but will always show the storage access prompt. The user
+ // has to explicitly allow storage access.
+ return document
+ .requestStorageAccessForOrigin(requestStorageAccessOrigin, false)
+ .then(() => {
+ return { success: true };
+ })
+ .catch(() => {
+ return { success: false };
+ });
+});
diff --git a/browser/extensions/webcompat/lib/shim_messaging_helper.js b/browser/extensions/webcompat/lib/shim_messaging_helper.js
new file mode 100644
index 0000000000..ee109713a5
--- /dev/null
+++ b/browser/extensions/webcompat/lib/shim_messaging_helper.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser */
+
+if (!window.Shims) {
+ window.Shims = new Map();
+}
+
+if (!window.ShimsHelperReady) {
+ window.ShimsHelperReady = true;
+
+ browser.runtime.onMessage.addListener(details => {
+ const { shimId, warning } = details;
+ if (!shimId) {
+ return;
+ }
+ window.Shims.set(shimId, details);
+ if (warning) {
+ console.warn(warning);
+ }
+ });
+
+ async function handleMessage(port, shimId, messageId, message) {
+ let response;
+ const shim = window.Shims.get(shimId);
+ if (shim) {
+ const { needsShimHelpers, origin } = shim;
+ if (origin === location.origin) {
+ if (needsShimHelpers?.includes(message)) {
+ const msg = { shimId, message };
+ try {
+ response = await browser.runtime.sendMessage(msg);
+ } catch (_) {}
+ }
+ }
+ }
+ port.postMessage({ messageId, response });
+ }
+
+ window.addEventListener(
+ "ShimConnects",
+ e => {
+ e.stopPropagation();
+ e.preventDefault();
+ const { port, pendingMessages, shimId } = e.detail;
+ const shim = window.Shims.get(shimId);
+ if (!shim) {
+ return;
+ }
+ port.onmessage = ({ data }) => {
+ handleMessage(port, shimId, data.messageId, data.message);
+ };
+ for (const [messageId, message] of pendingMessages) {
+ handleMessage(port, shimId, messageId, message);
+ }
+ },
+ true
+ );
+
+ window.dispatchEvent(new CustomEvent("ShimHelperReady"));
+}
diff --git a/browser/extensions/webcompat/lib/shims.js b/browser/extensions/webcompat/lib/shims.js
new file mode 100644
index 0000000000..ee33627c57
--- /dev/null
+++ b/browser/extensions/webcompat/lib/shims.js
@@ -0,0 +1,1044 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals browser, module, onMessageFromTab */
+
+// To grant shims access to bundled logo images without risking
+// exposing our moz-extension URL, we have the shim request them via
+// nonsense URLs which we then redirect to the actual files (but only
+// on tabs where a shim using a given logo happens to be active).
+const LogosBaseURL = "https://smartblock.firefox.etp/";
+
+const releaseBranchPromise = browser.appConstants.getReleaseBranch();
+
+const platformPromise = browser.runtime.getPlatformInfo().then(info => {
+ return info.os === "android" ? "android" : "desktop";
+});
+
+let debug = async function () {
+ if ((await releaseBranchPromise) !== "release_or_beta") {
+ console.debug.apply(this, arguments);
+ }
+};
+let error = async function () {
+ if ((await releaseBranchPromise) !== "release_or_beta") {
+ console.error.apply(this, arguments);
+ }
+};
+let warn = async function () {
+ if ((await releaseBranchPromise) !== "release_or_beta") {
+ console.warn.apply(this, arguments);
+ }
+};
+
+class Shim {
+ constructor(opts, manager) {
+ this.manager = manager;
+
+ const { contentScripts, matches, unblocksOnOptIn } = opts;
+
+ this.branches = opts.branches;
+ this.bug = opts.bug;
+ this.isGoogleTrendsDFPIFix = opts.custom == "google-trends-dfpi-fix";
+ this.file = opts.file;
+ this.hiddenInAboutCompat = opts.hiddenInAboutCompat;
+ this.hosts = opts.hosts;
+ this.id = opts.id;
+ this.logos = opts.logos || [];
+ this.matches = [];
+ this.name = opts.name;
+ this.notHosts = opts.notHosts;
+ this.onlyIfBlockedByETP = opts.onlyIfBlockedByETP;
+ this.onlyIfDFPIActive = opts.onlyIfDFPIActive;
+ this.onlyIfPrivateBrowsing = opts.onlyIfPrivateBrowsing;
+ this._options = opts.options || {};
+ this.needsShimHelpers = opts.needsShimHelpers;
+ this.platform = opts.platform || "all";
+ this.runFirst = opts.runFirst;
+ this.unblocksOnOptIn = unblocksOnOptIn;
+ this.requestStorageAccessForRedirect = opts.requestStorageAccessForRedirect;
+
+ this._hostOptIns = new Set();
+
+ this._disabledByConfig = opts.disabled;
+ this._disabledGlobally = false;
+ this._disabledForSession = false;
+ this._disabledByPlatform = false;
+ this._disabledByReleaseBranch = false;
+
+ this._activeOnTabs = new Set();
+ this._showedOptInOnTabs = new Set();
+
+ const pref = `disabled_shims.${this.id}`;
+
+ this.redirectsRequests = !!this.file && matches?.length;
+
+ this._contentScriptRegistrations = [];
+ this.contentScripts = contentScripts || [];
+ for (const script of this.contentScripts) {
+ if (typeof script.css === "string") {
+ script.css = [{ file: `/shims/${script.css}` }];
+ }
+ if (typeof script.js === "string") {
+ script.js = [{ file: `/shims/${script.js}` }];
+ }
+ }
+
+ for (const match of matches || []) {
+ if (!match.types) {
+ this.matches.push({ patterns: [match], types: ["script"] });
+ } else {
+ this.matches.push(match);
+ }
+ if (match.target) {
+ this.redirectsRequests = true;
+ }
+ }
+
+ browser.aboutConfigPrefs.onPrefChange.addListener(async () => {
+ const value = await browser.aboutConfigPrefs.getPref(pref);
+ this._disabledPrefValue = value;
+ this._onEnabledStateChanged();
+ }, pref);
+
+ this.ready = Promise.all([
+ browser.aboutConfigPrefs.getPref(pref),
+ platformPromise,
+ releaseBranchPromise,
+ ]).then(([disabledPrefValue, platform, branch]) => {
+ this._disabledPrefValue = disabledPrefValue;
+
+ this._disabledByPlatform =
+ this.platform !== "all" && this.platform !== platform;
+
+ this._disabledByReleaseBranch = false;
+ for (const supportedBranchAndPlatform of this.branches || []) {
+ const [supportedBranch, supportedPlatform] =
+ supportedBranchAndPlatform.split(":");
+ if (
+ (!supportedPlatform || supportedPlatform == platform) &&
+ supportedBranch != branch
+ ) {
+ this._disabledByReleaseBranch = true;
+ }
+ }
+
+ this._preprocessOptions(platform, branch);
+ this._onEnabledStateChanged();
+ });
+ }
+
+ _preprocessOptions(platform, branch) {
+ // options may be any value, but can optionally be gated for specified
+ // platform/branches, if in the format `{value, branches, platform}`
+ this.options = {};
+ for (const [k, v] of Object.entries(this._options)) {
+ if (v?.value) {
+ if (
+ (!v.platform || v.platform === platform) &&
+ (!v.branches || v.branches.includes(branch))
+ ) {
+ this.options[k] = v.value;
+ }
+ } else {
+ this.options[k] = v;
+ }
+ }
+ }
+
+ get enabled() {
+ if (this._disabledGlobally || this._disabledForSession) {
+ return false;
+ }
+
+ if (this._disabledPrefValue !== undefined) {
+ return !this._disabledPrefValue;
+ }
+
+ return (
+ !this._disabledByConfig &&
+ !this._disabledByPlatform &&
+ !this._disabledByReleaseBranch
+ );
+ }
+
+ get disabledReason() {
+ if (this._disabledGlobally) {
+ return "globalPref";
+ }
+
+ if (this._disabledForSession) {
+ return "session";
+ }
+
+ if (this._disabledPrefValue !== undefined) {
+ if (this._disabledPrefValue === true) {
+ return "pref";
+ }
+ return false;
+ }
+
+ if (this._disabledByConfig) {
+ return "config";
+ }
+
+ if (this._disabledByPlatform) {
+ return "platform";
+ }
+
+ if (this._disabledByReleaseBranch) {
+ return "releaseBranch";
+ }
+
+ return false;
+ }
+
+ onAllShimsEnabled() {
+ const wasEnabled = this.enabled;
+ this._disabledGlobally = false;
+ if (!wasEnabled) {
+ this._onEnabledStateChanged();
+ }
+ }
+
+ onAllShimsDisabled() {
+ const wasEnabled = this.enabled;
+ this._disabledGlobally = true;
+ if (wasEnabled) {
+ this._onEnabledStateChanged();
+ }
+ }
+
+ enableForSession() {
+ const wasEnabled = this.enabled;
+ this._disabledForSession = false;
+ if (!wasEnabled) {
+ this._onEnabledStateChanged();
+ }
+ }
+
+ disableForSession() {
+ const wasEnabled = this.enabled;
+ this._disabledForSession = true;
+ if (wasEnabled) {
+ this._onEnabledStateChanged();
+ }
+ }
+
+ async _onEnabledStateChanged() {
+ this.manager?.onShimStateChanged(this.id);
+ if (!this.enabled) {
+ await this._unregisterContentScripts();
+ return this._revokeRequestsInETP();
+ }
+ await this._registerContentScripts();
+ return this._allowRequestsInETP();
+ }
+
+ async _registerContentScripts() {
+ if (
+ this.contentScripts.length &&
+ !this._contentScriptRegistrations.length
+ ) {
+ const matches = [];
+ for (const options of this.contentScripts) {
+ matches.push(options.matches);
+ const reg = await browser.contentScripts.register(options);
+ this._contentScriptRegistrations.push(reg);
+ }
+ const urls = Array.from(new Set(matches.flat()));
+ debug("Enabling content scripts for these URLs:", urls);
+ }
+ }
+
+ async _unregisterContentScripts() {
+ for (const registration of this._contentScriptRegistrations) {
+ registration.unregister();
+ }
+ this._contentScriptRegistrations = [];
+ }
+
+ async _allowRequestsInETP() {
+ const matches = this.matches.map(m => m.patterns).flat();
+ if (matches.length) {
+ await browser.trackingProtection.shim(this.id, matches);
+ }
+
+ if (this._hostOptIns.size) {
+ const optIns = this.getApplicableOptIns();
+ if (optIns.length) {
+ await browser.trackingProtection.allow(
+ this.id,
+ this._optInPatterns,
+ Array.from(this._hostOptIns)
+ );
+ }
+ }
+ }
+
+ _revokeRequestsInETP() {
+ return browser.trackingProtection.revoke(this.id);
+ }
+
+ setActiveOnTab(tabId, active = true) {
+ if (active) {
+ this._activeOnTabs.add(tabId);
+ } else {
+ this._activeOnTabs.delete(tabId);
+ this._showedOptInOnTabs.delete(tabId);
+ }
+ }
+
+ isActiveOnTab(tabId) {
+ return this._activeOnTabs.has(tabId);
+ }
+
+ meantForHost(host) {
+ const { hosts, notHosts } = this;
+ if (hosts || notHosts) {
+ if (
+ (notHosts && notHosts.includes(host)) ||
+ (hosts && !hosts.includes(host))
+ ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ async unblocksURLOnOptIn(url) {
+ if (!this._optInPatterns) {
+ this._optInPatterns = await this.getApplicableOptIns();
+ }
+
+ if (!this._optInMatcher) {
+ this._optInMatcher = browser.matchPatterns.getMatcher(
+ Array.from(this._optInPatterns)
+ );
+ }
+
+ return this._optInMatcher.matches(url);
+ }
+
+ isTriggeredByURLAndType(url, type) {
+ for (const entry of this.matches || []) {
+ if (!entry.types.includes(type)) {
+ continue;
+ }
+ if (!entry.matcher) {
+ entry.matcher = browser.matchPatterns.getMatcher(
+ Array.from(entry.patterns)
+ );
+ }
+ if (entry.matcher.matches(url)) {
+ return entry;
+ }
+ }
+
+ return undefined;
+ }
+
+ async getApplicableOptIns() {
+ if (this._applicableOptIns) {
+ return this._applicableOptIns;
+ }
+ const optins = [];
+ for (const unblock of this.unblocksOnOptIn || []) {
+ if (typeof unblock === "string") {
+ optins.push(unblock);
+ continue;
+ }
+ const { branches, patterns, platforms } = unblock;
+ if (platforms?.length) {
+ const platform = await platformPromise;
+ if (platform !== "all" && !platforms.includes(platform)) {
+ continue;
+ }
+ }
+ if (branches?.length) {
+ const branch = await releaseBranchPromise;
+ if (!branches.includes(branch)) {
+ continue;
+ }
+ }
+ optins.push.apply(optins, patterns);
+ }
+ this._applicableOptIns = optins;
+ return optins;
+ }
+
+ async onUserOptIn(host) {
+ const optins = await this.getApplicableOptIns();
+ if (optins.length) {
+ this.userHasOptedIn = true;
+ this._hostOptIns.add(host);
+ await browser.trackingProtection.allow(
+ this.id,
+ optins,
+ Array.from(this._hostOptIns)
+ );
+ }
+ }
+
+ hasUserOptedInAlready(host) {
+ return this._hostOptIns.has(host);
+ }
+
+ showOptInWarningOnce(tabId, origin) {
+ if (this._showedOptInOnTabs.has(tabId)) {
+ return Promise.resolve();
+ }
+ this._showedOptInOnTabs.add(tabId);
+
+ const { bug, name } = this;
+ const warning = `${name} is allowed on ${origin} for this browsing session due to user opt-in. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`;
+ return browser.tabs
+ .executeScript(tabId, {
+ code: `console.warn(${JSON.stringify(warning)})`,
+ runAt: "document_start",
+ })
+ .catch(() => {});
+ }
+}
+
+class Shims {
+ constructor(availableShims) {
+ if (!browser.trackingProtection) {
+ console.error("Required experimental add-on APIs for shims unavailable");
+ return;
+ }
+
+ this._registerShims(availableShims);
+
+ onMessageFromTab(this._onMessageFromShim.bind(this));
+
+ this.ENABLED_PREF = "enable_shims";
+ browser.aboutConfigPrefs.onPrefChange.addListener(() => {
+ this._checkEnabledPref();
+ }, this.ENABLED_PREF);
+ this._haveCheckedEnabledPref = this._checkEnabledPref();
+ }
+
+ bindAboutCompatBroker(broker) {
+ this._aboutCompatBroker = broker;
+ }
+
+ getShimInfoForAboutCompat(shim) {
+ const { bug, disabledReason, hiddenInAboutCompat, id, name } = shim;
+ const type = "smartblock";
+ return { bug, disabledReason, hidden: hiddenInAboutCompat, id, name, type };
+ }
+
+ disableShimForSession(id) {
+ const shim = this.shims.get(id);
+ shim?.disableForSession();
+ }
+
+ enableShimForSession(id) {
+ const shim = this.shims.get(id);
+ shim?.enableForSession();
+ }
+
+ onShimStateChanged(id) {
+ if (!this._aboutCompatBroker) {
+ return;
+ }
+
+ const shim = this.shims.get(id);
+ if (!shim) {
+ return;
+ }
+
+ const shimsChanged = [this.getShimInfoForAboutCompat(shim)];
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ shimsChanged });
+ }
+
+ getAvailableShims() {
+ const shims = Array.from(this.shims.values()).map(
+ this.getShimInfoForAboutCompat
+ );
+ shims.sort((a, b) => a.name.localeCompare(b.name));
+ return shims;
+ }
+
+ _registerShims(shims) {
+ if (this.shims) {
+ throw new Error("_registerShims has already been called");
+ }
+
+ this.shims = new Map();
+ for (const shimOpts of shims) {
+ const { id } = shimOpts;
+ if (!this.shims.has(id)) {
+ this.shims.set(shimOpts.id, new Shim(shimOpts, this));
+ }
+ }
+
+ // Register onBeforeRequest listener which handles storage access requests
+ // on matching redirects.
+ let redirectTargetUrls = Array.from(shims.values())
+ .filter(shim => shim.requestStorageAccessForRedirect)
+ .flatMap(shim => shim.requestStorageAccessForRedirect)
+ .map(([, dstUrl]) => dstUrl);
+
+ // Unique target urls.
+ redirectTargetUrls = Array.from(new Set(redirectTargetUrls));
+
+ if (redirectTargetUrls.length) {
+ debug("Registering redirect listener for requestStorageAccess helper", {
+ redirectTargetUrls,
+ });
+ browser.webRequest.onBeforeRequest.addListener(
+ this._onRequestStorageAccessRedirect.bind(this),
+ { urls: redirectTargetUrls, types: ["main_frame"] },
+ ["blocking"]
+ );
+ }
+
+ function addTypePatterns(type, patterns, set) {
+ if (!set.has(type)) {
+ set.set(type, { patterns: new Set() });
+ }
+ const allSet = set.get(type).patterns;
+ for (const pattern of patterns) {
+ allSet.add(pattern);
+ }
+ }
+
+ const allMatchTypePatterns = new Map();
+ const allHeaderChangingMatchTypePatterns = new Map();
+ const allLogos = [];
+ for (const shim of this.shims.values()) {
+ const { logos, matches } = shim;
+ allLogos.push(...logos);
+ for (const { patterns, target, types } of matches || []) {
+ for (const type of types) {
+ if (shim.isGoogleTrendsDFPIFix) {
+ addTypePatterns(type, patterns, allHeaderChangingMatchTypePatterns);
+ }
+ if (target || shim.file || shim.runFirst) {
+ addTypePatterns(type, patterns, allMatchTypePatterns);
+ }
+ }
+ }
+ }
+
+ if (allLogos.length) {
+ const urls = Array.from(new Set(allLogos)).map(l => {
+ return `${LogosBaseURL}${l}`;
+ });
+ debug("Allowing access to these logos:", urls);
+ const unmarkShimsActive = tabId => {
+ for (const shim of this.shims.values()) {
+ shim.setActiveOnTab(tabId, false);
+ }
+ };
+ browser.tabs.onRemoved.addListener(unmarkShimsActive);
+ browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
+ if (changeInfo.discarded || changeInfo.url) {
+ unmarkShimsActive(tabId);
+ }
+ });
+ browser.webRequest.onBeforeRequest.addListener(
+ this._redirectLogos.bind(this),
+ { urls, types: ["image"] },
+ ["blocking"]
+ );
+ }
+
+ if (allHeaderChangingMatchTypePatterns) {
+ for (const [
+ type,
+ { patterns },
+ ] of allHeaderChangingMatchTypePatterns.entries()) {
+ const urls = Array.from(patterns);
+ debug("Shimming these", type, "URLs:", urls);
+ browser.webRequest.onBeforeSendHeaders.addListener(
+ this._onBeforeSendHeaders.bind(this),
+ { urls, types: [type] },
+ ["blocking", "requestHeaders"]
+ );
+ browser.webRequest.onHeadersReceived.addListener(
+ this._onHeadersReceived.bind(this),
+ { urls, types: [type] },
+ ["blocking", "responseHeaders"]
+ );
+ }
+ }
+
+ if (!allMatchTypePatterns.size) {
+ debug("Skipping shims; none enabled");
+ return;
+ }
+
+ for (const [type, { patterns }] of allMatchTypePatterns.entries()) {
+ const urls = Array.from(patterns);
+ debug("Shimming these", type, "URLs:", urls);
+
+ browser.webRequest.onBeforeRequest.addListener(
+ this._ensureShimForRequestOnTab.bind(this),
+ { urls, types: [type] },
+ ["blocking"]
+ );
+ }
+ }
+
+ async _checkEnabledPref() {
+ await browser.aboutConfigPrefs.getPref(this.ENABLED_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(this.ENABLED_PREF, true);
+ } else if (value === false) {
+ this.enabled = false;
+ } else {
+ this.enabled = true;
+ }
+ });
+ }
+
+ get enabled() {
+ return this._enabled;
+ }
+
+ set enabled(enabled) {
+ if (enabled === this._enabled) {
+ return;
+ }
+
+ this._enabled = enabled;
+
+ for (const shim of this.shims.values()) {
+ if (enabled) {
+ shim.onAllShimsEnabled();
+ } else {
+ shim.onAllShimsDisabled();
+ }
+ }
+ }
+
+ async _onRequestStorageAccessRedirect({
+ originUrl: srcUrl,
+ url: dstUrl,
+ tabId,
+ }) {
+ debug("Detected redirect", { srcUrl, dstUrl, tabId });
+
+ // Check if a shim needs to request storage access for this redirect. This
+ // handler is called when the *source url* matches a shims redirect pattern,
+ // but we still need to check if the *destination url* matches.
+ const matchingShims = Array.from(this.shims.values()).filter(shim => {
+ const { enabled, requestStorageAccessForRedirect } = shim;
+
+ if (!enabled || !requestStorageAccessForRedirect) {
+ return false;
+ }
+
+ return requestStorageAccessForRedirect.some(
+ ([srcPattern, dstPattern]) =>
+ browser.matchPatterns.getMatcher([srcPattern]).matches(srcUrl) &&
+ browser.matchPatterns.getMatcher([dstPattern]).matches(dstUrl)
+ );
+ });
+
+ // For each matching shim, find out if its enabled in regard to dFPI state.
+ const bugNumbers = new Set();
+ let isDFPIActive = null;
+ await Promise.all(
+ matchingShims.map(async shim => {
+ if (shim.onlyIfDFPIActive) {
+ // Only get the dFPI state for the first shim which requires it.
+ if (isDFPIActive === null) {
+ const tabIsPB = (await browser.tabs.get(tabId)).incognito;
+ isDFPIActive = await browser.trackingProtection.isDFPIActive(
+ tabIsPB
+ );
+ }
+ if (!isDFPIActive) {
+ return;
+ }
+ }
+ bugNumbers.add(shim.bug);
+ })
+ );
+
+ // If there is no shim which needs storage access for this redirect src/dst
+ // pair, resume it.
+ if (!bugNumbers.size) {
+ return;
+ }
+
+ // Inject the helper to call requestStorageAccessForOrigin on the document.
+ await browser.tabs.executeScript(tabId, {
+ file: "/lib/requestStorageAccess_helper.js",
+ runAt: "document_start",
+ });
+
+ const bugUrls = Array.from(bugNumbers)
+ .map(bugNo => `https://bugzilla.mozilla.org/show_bug.cgi?id=${bugNo}`)
+ .join(", ");
+ const warning = `Firefox calls the Storage Access API for ${dstUrl} on behalf of ${srcUrl}. See the following bugs for details: ${bugUrls}`;
+
+ // Request storage access for the origin of the destination url of the
+ // redirect.
+ const { origin: requestStorageAccessOrigin } = new URL(dstUrl);
+
+ // Wait for the requestStorageAccess request to finish before resuming the
+ // redirect.
+ const { success } = await browser.tabs.sendMessage(tabId, {
+ requestStorageAccessOrigin,
+ warning,
+ });
+ debug("requestStorageAccess callback", {
+ success,
+ requestStorageAccessOrigin,
+ srcUrl,
+ dstUrl,
+ bugNumbers,
+ });
+ }
+
+ async _onMessageFromShim(payload, sender, sendResponse) {
+ const { tab, frameId } = sender;
+ const { id, url } = tab;
+ const { shimId, message } = payload;
+
+ // Ignore unknown messages (for instance, from about:compat).
+ if (message !== "getOptions" && message !== "optIn") {
+ return undefined;
+ }
+
+ if (sender.id !== browser.runtime.id || id === -1) {
+ throw new Error("not allowed");
+ }
+
+ // Important! It is entirely possible for sites to spoof
+ // these messages, due to shims allowing web pages to
+ // communicate with the extension.
+
+ const shim = this.shims.get(shimId);
+ if (!shim?.needsShimHelpers?.includes(message)) {
+ throw new Error("not allowed");
+ }
+
+ if (message === "getOptions") {
+ return Object.assign(
+ {
+ platform: await platformPromise,
+ releaseBranch: await releaseBranchPromise,
+ },
+ shim.options
+ );
+ } else if (message === "optIn") {
+ try {
+ await shim.onUserOptIn(new URL(url).hostname);
+ const origin = new URL(tab.url).origin;
+ warn(
+ "** User opted in for",
+ shim.name,
+ "shim on",
+ origin,
+ "on tab",
+ id,
+ "frame",
+ frameId
+ );
+ await shim.showOptInWarningOnce(id, origin);
+ } catch (err) {
+ console.error(err);
+ throw new Error("error");
+ }
+ }
+
+ return undefined;
+ }
+
+ async _redirectLogos(details) {
+ await this._haveCheckedEnabledPref;
+
+ if (!this.enabled) {
+ return { cancel: true };
+ }
+
+ const { tabId, url } = details;
+ const logo = new URL(url).pathname.slice(1);
+
+ for (const shim of this.shims.values()) {
+ await shim.ready;
+
+ if (!shim.enabled) {
+ continue;
+ }
+
+ if (shim.onlyIfDFPIActive) {
+ const isPB = (await browser.tabs.get(details.tabId)).incognito;
+ if (!(await browser.trackingProtection.isDFPIActive(isPB))) {
+ continue;
+ }
+ }
+
+ if (!shim.logos.includes(logo)) {
+ continue;
+ }
+
+ if (shim.isActiveOnTab(tabId)) {
+ return { redirectUrl: browser.runtime.getURL(`shims/${logo}`) };
+ }
+ }
+
+ return { cancel: true };
+ }
+
+ async _onHeadersReceived(details) {
+ await this._haveCheckedEnabledPref;
+
+ for (const shim of this.shims.values()) {
+ await shim.ready;
+
+ if (!shim.enabled) {
+ continue;
+ }
+
+ if (shim.onlyIfDFPIActive) {
+ const isPB = (await browser.tabs.get(details.tabId)).incognito;
+ if (!(await browser.trackingProtection.isDFPIActive(isPB))) {
+ continue;
+ }
+ }
+
+ if (shim.isGoogleTrendsDFPIFix) {
+ if (shim.GoogleNidCookieToUse) {
+ continue;
+ }
+
+ for (const header of details.responseHeaders) {
+ if (header.name == "set-cookie") {
+ shim.GoogleNidCookieToUse = header.value;
+ return { redirectUrl: details.url };
+ }
+ }
+ }
+ }
+
+ return undefined;
+ }
+
+ async _onBeforeSendHeaders(details) {
+ await this._haveCheckedEnabledPref;
+
+ const { frameId, requestHeaders, tabId } = details;
+
+ if (!this.enabled) {
+ return { requestHeaders };
+ }
+
+ for (const shim of this.shims.values()) {
+ await shim.ready;
+
+ if (!shim.enabled) {
+ continue;
+ }
+
+ if (shim.isGoogleTrendsDFPIFix) {
+ const value = shim.GoogleNidCookieToUse;
+
+ if (!value) {
+ continue;
+ }
+
+ let found;
+ for (let header of requestHeaders) {
+ if (header.name.toLowerCase() === "cookie") {
+ header.value = value;
+ found = true;
+ }
+ }
+ if (!found) {
+ requestHeaders.push({ name: "Cookie", value });
+ }
+
+ browser.tabs
+ .get(tabId)
+ .then(({ url }) => {
+ debug(
+ `Google Trends dFPI fix used on tab ${tabId} frame ${frameId} (${url})`
+ );
+ })
+ .catch(() => {});
+
+ const warning = `Working around Google Trends tracking protection breakage. See https://bugzilla.mozilla.org/show_bug.cgi?id=${shim.bug} for details.`;
+ browser.tabs
+ .executeScript(tabId, {
+ code: `console.warn(${JSON.stringify(warning)})`,
+ runAt: "document_start",
+ })
+ .catch(() => {});
+ }
+ }
+
+ return { requestHeaders };
+ }
+
+ async _ensureShimForRequestOnTab(details) {
+ await this._haveCheckedEnabledPref;
+
+ if (!this.enabled) {
+ return undefined;
+ }
+
+ // We only ever reach this point if a request is for a URL which ought to
+ // be shimmed. We never get here if a request is blocked, and we only
+ // unblock requests if at least one shim matches it.
+
+ const { frameId, originUrl, requestId, tabId, type, url } = details;
+
+ // Ignore requests unrelated to tabs
+ if (tabId < 0) {
+ return undefined;
+ }
+
+ // We need to base our checks not on the frame's host, but the tab's.
+ const topHost = new URL((await browser.tabs.get(tabId)).url).hostname;
+ const unblocked = await browser.trackingProtection.wasRequestUnblocked(
+ requestId
+ );
+
+ let match;
+ let shimToApply;
+ for (const shim of this.shims.values()) {
+ await shim.ready;
+
+ if (!shim.enabled || (!shim.redirectsRequests && !shim.runFirst)) {
+ continue;
+ }
+
+ if (shim.onlyIfDFPIActive || shim.onlyIfPrivateBrowsing) {
+ const isPB = (await browser.tabs.get(details.tabId)).incognito;
+ if (!isPB && shim.onlyIfPrivateBrowsing) {
+ continue;
+ }
+ if (
+ shim.onlyIfDFPIActive &&
+ !(await browser.trackingProtection.isDFPIActive(isPB))
+ ) {
+ continue;
+ }
+ }
+
+ // Do not apply the shim if it is only meant to apply when strict mode ETP
+ // (content blocking) was going to block the request.
+ if (!unblocked && shim.onlyIfBlockedByETP) {
+ continue;
+ }
+
+ if (!shim.meantForHost(topHost)) {
+ continue;
+ }
+
+ // If this URL and content type isn't meant for this shim, don't apply it.
+ match = shim.isTriggeredByURLAndType(url, type);
+ if (match) {
+ if (!unblocked && match.onlyIfBlockedByETP) {
+ continue;
+ }
+
+ // If the user has already opted in for this shim, all requests it covers
+ // should be allowed; no need for a shim anymore.
+ if (shim.hasUserOptedInAlready(topHost)) {
+ warn(
+ `Allowing tracking ${type} ${url} on tab ${tabId} frame ${frameId} due to opt-in`
+ );
+ shim.showOptInWarningOnce(tabId, new URL(originUrl).origin);
+ return undefined;
+ }
+ shimToApply = shim;
+ break;
+ }
+ }
+
+ let runFirst = false;
+
+ if (shimToApply) {
+ // Note that sites may request the same shim twice, but because the requests
+ // may differ enough for some to fail (CSP/CORS/etc), we always let the request
+ // complete via local redirect. Shims should gracefully handle this as well.
+
+ const { target } = match;
+ const { bug, file, id, name, needsShimHelpers } = shimToApply;
+ runFirst = shimToApply.runFirst;
+
+ const redirect = target || file;
+
+ warn(
+ `Shimming tracking ${type} ${url} on tab ${tabId} frame ${frameId} with ${
+ redirect || runFirst
+ }`
+ );
+
+ const warning = `${name} is being shimmed by Firefox. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`;
+
+ let needConsoleMessage = true;
+
+ if (runFirst) {
+ try {
+ await browser.tabs.executeScript(tabId, {
+ file: `/shims/${runFirst}`,
+ frameId,
+ runAt: "document_start",
+ });
+ } catch (_) {}
+ }
+
+ // For scripts, we also set up any needed shim helpers.
+ if (type === "script" && needsShimHelpers?.length) {
+ try {
+ await browser.tabs.executeScript(tabId, {
+ file: "/lib/shim_messaging_helper.js",
+ frameId,
+ runAt: "document_start",
+ });
+ const origin = new URL(originUrl).origin;
+ await browser.tabs.sendMessage(
+ tabId,
+ { origin, shimId: id, needsShimHelpers, warning },
+ { frameId }
+ );
+ needConsoleMessage = false;
+ shimToApply.setActiveOnTab(tabId);
+ } catch (_) {}
+ }
+
+ if (needConsoleMessage) {
+ try {
+ await browser.tabs.executeScript(tabId, {
+ code: `console.warn(${JSON.stringify(warning)})`,
+ runAt: "document_start",
+ });
+ } catch (_) {}
+ }
+
+ if (!redirect.indexOf("http://") || !redirect.indexOf("https://")) {
+ return { redirectUrl: redirect };
+ }
+
+ // If any shims matched the request to replace it, then redirect to the local
+ // file bundled with SmartBlock, so the request never hits the network.
+ return { redirectUrl: browser.runtime.getURL(`shims/${redirect}`) };
+ }
+
+ // Sanity check: if no shims end up handling this request,
+ // yet it was meant to be blocked by ETP, then block it now.
+ if (unblocked) {
+ error(`unexpected: ${url} not shimmed on tab ${tabId} frame ${frameId}`);
+ return { cancel: true };
+ }
+
+ if (!runFirst) {
+ debug(`ignoring ${url} on tab ${tabId} frame ${frameId}`);
+ }
+ return undefined;
+ }
+}
+
+module.exports = Shims;
diff --git a/browser/extensions/webcompat/lib/ua_helpers.js b/browser/extensions/webcompat/lib/ua_helpers.js
new file mode 100644
index 0000000000..e2ab29c628
--- /dev/null
+++ b/browser/extensions/webcompat/lib/ua_helpers.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals exportFunction, module */
+
+var UAHelpers = {
+ _deviceAppropriateChromeUAs: {},
+ getDeviceAppropriateChromeUA(config = {}) {
+ const { version = "103.0.5060.71", androidDevice, desktopOS } = config;
+ const key = `${version}:${androidDevice}:${desktopOS}`;
+ if (!UAHelpers._deviceAppropriateChromeUAs[key]) {
+ const userAgent =
+ typeof navigator !== "undefined" ? navigator.userAgent : "";
+ const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [
+ "",
+ "58.0",
+ ])[1];
+
+ if (userAgent.includes("Android")) {
+ const RunningAndroidVersion =
+ userAgent.match(/Android [0-9.]+/) || "Android 6.0";
+ if (androidDevice) {
+ UAHelpers._deviceAppropriateChromeUAs[
+ key
+ ] = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; ${androidDevice}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`;
+ } else {
+ const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`;
+ const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`;
+ const IsPhone = userAgent.includes("Mobile");
+ UAHelpers._deviceAppropriateChromeUAs[key] = IsPhone
+ ? ChromePhoneUA
+ : ChromeTabletUA;
+ }
+ } else {
+ let osSegment = "Windows NT 10.0; Win64; x64";
+ if (desktopOS === "macOS" || userAgent.includes("Macintosh")) {
+ osSegment = "Macintosh; Intel Mac OS X 10_15_7";
+ }
+ if (
+ desktopOS !== "nonLinux" &&
+ (desktopOS === "linux" || userAgent.includes("Linux"))
+ ) {
+ osSegment = "X11; Ubuntu; Linux x86_64";
+ }
+
+ UAHelpers._deviceAppropriateChromeUAs[
+ key
+ ] = `Mozilla/5.0 (${osSegment}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`;
+ }
+ }
+ return UAHelpers._deviceAppropriateChromeUAs[key];
+ },
+ getPrefix(originalUA) {
+ return originalUA.substr(0, originalUA.indexOf(")") + 1);
+ },
+ overrideWithDeviceAppropriateChromeUA(config) {
+ const chromeUA = UAHelpers.getDeviceAppropriateChromeUA(config);
+ Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(() => chromeUA, window),
+ set: exportFunction(function () {}, window),
+ });
+ },
+ capVersionTo99(originalUA) {
+ const ver = originalUA.match(/Firefox\/(\d+\.\d+)/);
+ if (!ver || parseFloat(ver[1]) < 100) {
+ return originalUA;
+ }
+ return originalUA
+ .replace(`Firefox/${ver[1]}`, "Firefox/99.0")
+ .replace(`rv:${ver[1]}`, "rv:99.0");
+ },
+};
+
+if (typeof module !== "undefined") {
+ module.exports = UAHelpers;
+}
diff --git a/browser/extensions/webcompat/lib/ua_overrides.js b/browser/extensions/webcompat/lib/ua_overrides.js
new file mode 100644
index 0000000000..2426293f3f
--- /dev/null
+++ b/browser/extensions/webcompat/lib/ua_overrides.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";
+
+/* globals browser, module */
+
+class UAOverrides {
+ constructor(availableOverrides) {
+ this.OVERRIDE_PREF = "perform_ua_overrides";
+
+ this._overridesEnabled = true;
+
+ this._availableOverrides = availableOverrides;
+ this._activeListeners = new Map();
+ }
+
+ bindAboutCompatBroker(broker) {
+ this._aboutCompatBroker = broker;
+ }
+
+ bootup() {
+ browser.aboutConfigPrefs.onPrefChange.addListener(() => {
+ this.checkOverridePref();
+ }, this.OVERRIDE_PREF);
+ this.checkOverridePref();
+ }
+
+ checkOverridePref() {
+ browser.aboutConfigPrefs.getPref(this.OVERRIDE_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(this.OVERRIDE_PREF, true);
+ } else if (value === false) {
+ this.unregisterUAOverrides();
+ } else {
+ this.registerUAOverrides();
+ }
+ });
+ }
+
+ getAvailableOverrides() {
+ return this._availableOverrides;
+ }
+
+ isEnabled() {
+ return this._overridesEnabled;
+ }
+
+ enableOverride(override) {
+ if (override.active) {
+ return;
+ }
+
+ const { blocks, matches, uaTransformer } = override.config;
+ const listener = details => {
+ // Don't actually override the UA for an experiment if the user is not
+ // part of the experiment (unless they force-enabed the override).
+ if (
+ !override.config.experiment ||
+ override.permanentPrefEnabled === true
+ ) {
+ for (const header of details.requestHeaders) {
+ if (header.name.toLowerCase() === "user-agent") {
+ // Don't override the UA if we're on a mobile device that has the
+ // "Request Desktop Site" mode enabled. The UA for the desktop mode
+ // is set inside Gecko with a simple string replace, so we can use
+ // that as a check, see https://searchfox.org/mozilla-central/rev/89d33e1c3b0a57a9377b4815c2f4b58d933b7c32/mobile/android/chrome/geckoview/GeckoViewSettingsChild.js#23-28
+ let isMobileWithDesktopMode =
+ override.currentPlatform == "android" &&
+ header.value.includes("X11; Linux x86_64");
+
+ if (!isMobileWithDesktopMode) {
+ header.value = uaTransformer(header.value);
+ }
+ }
+ }
+ }
+ return { requestHeaders: details.requestHeaders };
+ };
+
+ browser.webRequest.onBeforeSendHeaders.addListener(
+ listener,
+ { urls: matches },
+ ["blocking", "requestHeaders"]
+ );
+
+ const listeners = { onBeforeSendHeaders: listener };
+ if (blocks) {
+ const blistener = details => {
+ return { cancel: true };
+ };
+
+ browser.webRequest.onBeforeRequest.addListener(
+ blistener,
+ { urls: blocks },
+ ["blocking"]
+ );
+
+ listeners.onBeforeRequest = blistener;
+ }
+ this._activeListeners.set(override, listeners);
+ override.active = true;
+ }
+
+ onOverrideConfigChanged(override) {
+ // Check whether the override should be hidden from about:compat.
+ override.hidden = override.config.hidden;
+
+ // Setting the override's permanent pref overrules whether it is hidden.
+ if (override.permanentPrefEnabled !== undefined) {
+ override.hidden = !override.permanentPrefEnabled;
+ }
+
+ // Also check whether the override should be active.
+ let shouldBeActive = true;
+
+ // Overrides can be force-deactivated by their permanent preference.
+ if (override.permanentPrefEnabled === false) {
+ shouldBeActive = false;
+ }
+
+ // Overrides gated behind an experiment the user is not part of do not
+ // have to be activated, unless they are gathering telemetry, or the
+ // user has force-enabled them with their permanent pref.
+ if (override.config.experiment && override.permanentPrefEnabled !== true) {
+ shouldBeActive = false;
+ }
+
+ if (shouldBeActive) {
+ this.enableOverride(override);
+ } else {
+ this.disableOverride(override);
+ }
+
+ if (this._overridesEnabled) {
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: this._aboutCompatBroker.filterOverrides(
+ this._availableOverrides
+ ),
+ });
+ }
+ }
+
+ async registerUAOverrides() {
+ const platformMatches = ["all"];
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ platformMatches.push(platformInfo.os == "android" ? "android" : "desktop");
+
+ for (const override of this._availableOverrides) {
+ if (platformMatches.includes(override.platform)) {
+ override.availableOnPlatform = true;
+ override.currentPlatform = platformInfo.os;
+
+ // If there is a specific about:config preference governing
+ // this override, monitor its state.
+ const pref = override.config.permanentPref;
+ override.permanentPrefEnabled =
+ pref && (await browser.aboutConfigPrefs.getPref(pref));
+ if (pref) {
+ const checkOverridePref = () => {
+ browser.aboutConfigPrefs.getPref(pref).then(value => {
+ override.permanentPrefEnabled = value;
+ this.onOverrideConfigChanged(override);
+ });
+ };
+ browser.aboutConfigPrefs.onPrefChange.addListener(
+ checkOverridePref,
+ pref
+ );
+ }
+
+ this.onOverrideConfigChanged(override);
+ }
+ }
+
+ this._overridesEnabled = true;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: this._aboutCompatBroker.filterOverrides(
+ this._availableOverrides
+ ),
+ });
+ }
+
+ unregisterUAOverrides() {
+ for (const override of this._availableOverrides) {
+ this.disableOverride(override);
+ }
+
+ this._overridesEnabled = false;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: false,
+ });
+ }
+
+ disableOverride(override) {
+ if (!override.active) {
+ return;
+ }
+
+ const listeners = this._activeListeners.get(override);
+ for (const [name, listener] of Object.entries(listeners)) {
+ browser.webRequest[name].removeListener(listener);
+ }
+ override.active = false;
+ this._activeListeners.delete(override);
+ }
+}
+
+module.exports = UAOverrides;
diff --git a/browser/extensions/webcompat/manifest.json b/browser/extensions/webcompat/manifest.json
new file mode 100644
index 0000000000..c4a2592f3d
--- /dev/null
+++ b/browser/extensions/webcompat/manifest.json
@@ -0,0 +1,153 @@
+{
+ "manifest_version": 2,
+ "name": "Web Compatibility Interventions",
+ "description": "Urgent post-release fixes for web compatibility.",
+ "version": "115.1.0",
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "webcompat@mozilla.org",
+ "strict_min_version": "102.0"
+ }
+ },
+
+ "experiment_apis": {
+ "aboutConfigPrefs": {
+ "schema": "experiment-apis/aboutConfigPrefs.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/aboutConfigPrefs.js",
+ "paths": [["aboutConfigPrefs"]]
+ }
+ },
+ "appConstants": {
+ "schema": "experiment-apis/appConstants.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/appConstants.js",
+ "paths": [["appConstants"]]
+ }
+ },
+ "aboutPage": {
+ "schema": "about-compat/aboutPage.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "about-compat/aboutPage.js",
+ "events": ["startup"]
+ }
+ },
+ "matchPatterns": {
+ "schema": "experiment-apis/matchPatterns.json",
+ "child": {
+ "scopes": ["addon_child"],
+ "script": "experiment-apis/matchPatterns.js",
+ "paths": [["matchPatterns"]]
+ }
+ },
+ "systemManufacturer": {
+ "schema": "experiment-apis/systemManufacturer.json",
+ "child": {
+ "scopes": ["addon_child"],
+ "script": "experiment-apis/systemManufacturer.js",
+ "paths": [["systemManufacturer"]]
+ }
+ },
+ "trackingProtection": {
+ "schema": "experiment-apis/trackingProtection.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/trackingProtection.js",
+ "paths": [["trackingProtection"]]
+ }
+ }
+ },
+
+ "content_security_policy": "script-src 'self' 'sha256-PeZc2H1vv7M8NXqlFyNbN4y4oM6wXmYEbf73m+Aqpak='; default-src 'self'; base-uri moz-extension://*; object-src 'none'",
+
+ "permissions": [
+ "mozillaAddons",
+ "tabs",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+
+ "background": {
+ "scripts": [
+ "lib/module_shim.js",
+ "lib/messaging_helper.js",
+ "lib/intervention_helpers.js",
+ "lib/requestStorageAccess_helper.js",
+ "lib/ua_helpers.js",
+ "data/injections.js",
+ "data/shims.js",
+ "data/ua_overrides.js",
+ "lib/about_compat_broker.js",
+ "lib/custom_functions.js",
+ "lib/injections.js",
+ "lib/shims.js",
+ "lib/ua_overrides.js",
+ "run.js"
+ ]
+ },
+
+ "web_accessible_resources": [
+ "shims/addthis-angular.js",
+ "shims/adform.js",
+ "shims/adnexus-ast.js",
+ "shims/adnexus-prebid.js",
+ "shims/adsafeprotected-ima.js",
+ "shims/apstag.js",
+ "shims/blogger.js",
+ "shims/bloggerAccount.js",
+ "shims/bmauth.js",
+ "shims/branch.js",
+ "shims/chartbeat.js",
+ "shims/crave-ca.js",
+ "shims/criteo.js",
+ "shims/cxense.js",
+ "shims/doubleverify.js",
+ "shims/eluminate.js",
+ "shims/empty-script.js",
+ "shims/empty-shim.txt",
+ "shims/everest.js",
+ "shims/facebook-sdk.js",
+ "shims/facebook.svg",
+ "shims/fastclick.js",
+ "shims/firebase.js",
+ "shims/google-ads.js",
+ "shims/google-analytics-and-tag-manager.js",
+ "shims/google-analytics-ecommerce-plugin.js",
+ "shims/google-analytics-legacy.js",
+ "shims/google-ima.js",
+ "shims/google-page-ad.js",
+ "shims/google-publisher-tags.js",
+ "shims/google-safeframe.html",
+ "shims/history.js",
+ "shims/iam.js",
+ "shims/iaspet.js",
+ "shims/instagram.js",
+ "shims/kinja.js",
+ "shims/live-test-shim.js",
+ "shims/maxmind-geoip.js",
+ "shims/microsoftLogin.js",
+ "shims/microsoftVirtualAssistant.js",
+ "shims/moat.js",
+ "shims/mochitest-shim-1.js",
+ "shims/mochitest-shim-2.js",
+ "shims/mochitest-shim-3.js",
+ "shims/nielsen.js",
+ "shims/optimizely.js",
+ "shims/play.svg",
+ "shims/private-browsing-web-api-fixes.js",
+ "shims/rambler-authenticator.js",
+ "shims/rich-relevance.js",
+ "shims/spotify-embed.js",
+ "shims/tracking-pixel.png",
+ "shims/vast2.xml",
+ "shims/vast3.xml",
+ "shims/vidible.js",
+ "shims/vmad.xml",
+ "shims/webtrends.js"
+ ]
+}
diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build
new file mode 100644
index 0000000000..0a75a15c90
--- /dev/null
+++ b/browser/extensions/webcompat/moz.build
@@ -0,0 +1,192 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"]
+DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"] += [
+ "manifest.json",
+ "run.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["about-compat"] += [
+ "about-compat/aboutCompat.css",
+ "about-compat/aboutCompat.html",
+ "about-compat/aboutCompat.js",
+ "about-compat/AboutCompat.jsm",
+ "about-compat/aboutPage.js",
+ "about-compat/aboutPage.json",
+ "about-compat/aboutPageProcessScript.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["data"] += [
+ "data/injections.js",
+ "data/shims.js",
+ "data/ua_overrides.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["experiment-apis"] += [
+ "experiment-apis/aboutConfigPrefs.js",
+ "experiment-apis/aboutConfigPrefs.json",
+ "experiment-apis/appConstants.js",
+ "experiment-apis/appConstants.json",
+ "experiment-apis/matchPatterns.js",
+ "experiment-apis/matchPatterns.json",
+ "experiment-apis/systemManufacturer.js",
+ "experiment-apis/systemManufacturer.json",
+ "experiment-apis/trackingProtection.js",
+ "experiment-apis/trackingProtection.json",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["css"] += [
+ "injections/css/bug0000000-testbed-css-injection.css",
+ "injections/css/bug1570328-developer-apple.com-transform-scale.css",
+ "injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css",
+ "injections/css/bug1605611-maps.google.com-directions-time.css",
+ "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css",
+ "injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css",
+ "injections/css/bug1651917-teletrader.com.body-transform-origin.css",
+ "injections/css/bug1653075-livescience.com-scrollbar-width.css",
+ "injections/css/bug1654877-preev.com-moz-appearance-fix.css",
+ "injections/css/bug1654907-reactine.ca-hide-unsupported.css",
+ "injections/css/bug1694470-myvidster.com-content-not-shown.css",
+ "injections/css/bug1707795-office365-sheets-overscroll-disable.css",
+ "injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css",
+ "injections/css/bug1741234-patient.alphalabs.ca-height-fix.css",
+ "injections/css/bug1765947-veniceincoming.com-left-fix.css",
+ "injections/css/bug1770962-coldwellbankerhomes.com-image-height.css",
+ "injections/css/bug1774490-rainews.it-gallery-fix.css",
+ "injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css",
+ "injections/css/bug1784199-entrata-platform-unsupported.css",
+ "injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css",
+ "injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css",
+ "injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css",
+ "injections/css/bug1829949-tomshardware.com-scrollbar-width.css",
+ "injections/css/bug1829952-eventer.co.il-button-height.css",
+ "injections/css/bug1830747-babbel.com-page-height.css",
+ "injections/css/bug1830752-afisha.ru-slider-pointer-events.css",
+ "injections/css/bug1830761-91mobiles.com-content-height.css",
+ "injections/css/bug1830796-copyleaks.com-hide-unsupported.css",
+ "injections/css/bug1830810-interceramic.com-hide-unsupported.css",
+ "injections/css/bug1830813-page.onstove.com-hide-unsupported.css",
+ "injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css",
+ "injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css",
+ "injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["js"] += [
+ "injections/js/bug0000000-testbed-js-injection.js",
+ "injections/js/bug1448747-fastclick-shim.js",
+ "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js",
+ "injections/js/bug1457335-histography.io-ua-change.js",
+ "injections/js/bug1472075-bankofamerica.com-ua-change.js",
+ "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
+ "injections/js/bug1605611-maps.google.com-directions-time.js",
+ "injections/js/bug1631811-datastudio.google.com-indexedDB.js",
+ "injections/js/bug1722955-frontgate.com-ua-override.js",
+ "injections/js/bug1724764-window-print.js",
+ "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js",
+ "injections/js/bug1731825-office365-email-handling-prompt-autohide.js",
+ "injections/js/bug1739489-draftjs-beforeinput.js",
+ "injections/js/bug1769762-tiktok.com-plugins-shim.js",
+ "injections/js/bug1774005-installtrigger-shim.js",
+ "injections/js/bug1784302-effectiveType-shim.js",
+ "injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js",
+ "injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js",
+ "injections/js/bug1799980-healow.com-infinite-loop-fix.js",
+ "injections/js/bug1818818-fastclick-legacy-shim.js",
+ "injections/js/bug1819450-cmbchina.com-ua-change.js",
+ "injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js",
+ "injections/js/bug1819678-cnki.net-undisable-search-field.js",
+ "injections/js/bug1819678-free4talk.com-window-chrome-shim.js",
+ "injections/js/bug1830776-blueshieldca.com-unsupported.js",
+ "injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js",
+ "injections/js/bug1836157-thai-masszazs-niceScroll-disable.js",
+ "injections/js/bug1842437-www.youtube.com-performance-now-precision.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [
+ "shims/addthis-angular.js",
+ "shims/adform.js",
+ "shims/adnexus-ast.js",
+ "shims/adnexus-prebid.js",
+ "shims/adsafeprotected-ima.js",
+ "shims/apstag.js",
+ "shims/blogger.js",
+ "shims/bloggerAccount.js",
+ "shims/bmauth.js",
+ "shims/branch.js",
+ "shims/chartbeat.js",
+ "shims/crave-ca.js",
+ "shims/criteo.js",
+ "shims/cxense.js",
+ "shims/doubleverify.js",
+ "shims/eluminate.js",
+ "shims/empty-script.js",
+ "shims/empty-shim.txt",
+ "shims/everest.js",
+ "shims/facebook-sdk.js",
+ "shims/facebook.svg",
+ "shims/fastclick.js",
+ "shims/firebase.js",
+ "shims/google-ads.js",
+ "shims/google-analytics-and-tag-manager.js",
+ "shims/google-analytics-ecommerce-plugin.js",
+ "shims/google-analytics-legacy.js",
+ "shims/google-ima.js",
+ "shims/google-page-ad.js",
+ "shims/google-publisher-tags.js",
+ "shims/google-safeframe.html",
+ "shims/history.js",
+ "shims/iam.js",
+ "shims/iaspet.js",
+ "shims/instagram.js",
+ "shims/kinja.js",
+ "shims/live-test-shim.js",
+ "shims/maxmind-geoip.js",
+ "shims/microsoftLogin.js",
+ "shims/microsoftVirtualAssistant.js",
+ "shims/moat.js",
+ "shims/mochitest-shim-1.js",
+ "shims/mochitest-shim-2.js",
+ "shims/mochitest-shim-3.js",
+ "shims/nielsen.js",
+ "shims/optimizely.js",
+ "shims/play.svg",
+ "shims/private-browsing-web-api-fixes.js",
+ "shims/rambler-authenticator.js",
+ "shims/rich-relevance.js",
+ "shims/spotify-embed.js",
+ "shims/tracking-pixel.png",
+ "shims/vast2.xml",
+ "shims/vast3.xml",
+ "shims/vidible.js",
+ "shims/vmad.xml",
+ "shims/webtrends.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["lib"] += [
+ "lib/about_compat_broker.js",
+ "lib/custom_functions.js",
+ "lib/injections.js",
+ "lib/intervention_helpers.js",
+ "lib/messaging_helper.js",
+ "lib/module_shim.js",
+ "lib/requestStorageAccess_helper.js",
+ "lib/shim_messaging_helper.js",
+ "lib/shims.js",
+ "lib/ua_helpers.js",
+ "lib/ua_overrides.js",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Web Compatibility", "Tooling & Investigations")
diff --git a/browser/extensions/webcompat/run.js b/browser/extensions/webcompat/run.js
new file mode 100644
index 0000000000..5822dbb2ca
--- /dev/null
+++ b/browser/extensions/webcompat/run.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";
+
+/* globals AboutCompatBroker, AVAILABLE_INJECTIONS, AVAILABLE_SHIMS,
+ AVAILABLE_PIP_OVERRIDES, AVAILABLE_UA_OVERRIDES, CUSTOM_FUNCTIONS,
+ Injections, Shims, UAOverrides */
+
+let injections, shims, uaOverrides;
+
+try {
+ injections = new Injections(AVAILABLE_INJECTIONS, CUSTOM_FUNCTIONS);
+ injections.bootup();
+} catch (e) {
+ console.error("Injections failed to start", e);
+ injections = undefined;
+}
+
+try {
+ uaOverrides = new UAOverrides(AVAILABLE_UA_OVERRIDES);
+ uaOverrides.bootup();
+} catch (e) {
+ console.error("UA overrides failed to start", e);
+ uaOverrides = undefined;
+}
+
+try {
+ shims = new Shims(AVAILABLE_SHIMS);
+} catch (e) {
+ console.error("Shims failed to start", e);
+ shims = undefined;
+}
+
+try {
+ const aboutCompatBroker = new AboutCompatBroker({
+ injections,
+ shims,
+ uaOverrides,
+ });
+ aboutCompatBroker.bootup();
+} catch (e) {
+ console.error("about:compat broker failed to start", e);
+}
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?.();
+ });
+}
diff --git a/browser/extensions/webcompat/tests/browser/browser.ini b/browser/extensions/webcompat/tests/browser/browser.ini
new file mode 100644
index 0000000000..f15b901240
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files =
+ head.js
+ shims_test.js
+ shims_test_2.js
+ shims_test_3.js
+ iframe_test.html
+ shims_test.html
+ shims_test_2.html
+ shims_test_3.html
+
+[browser_aboutcompat.js]
+[browser_shims.js]
+https_first_disabled = true
+skip-if = verify
diff --git a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
new file mode 100644
index 0000000000..a448269294
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
@@ -0,0 +1,27 @@
+"use strict";
+
+add_task(async function test_about_compat_loads_properly() {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:compat",
+ waitForLoad: true,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.querySelector("#overrides tr[data-id]"),
+ "UA overrides are listed"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.querySelector("#interventions tr[data-id]"),
+ "interventions are listed"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.querySelector("#smartblock tr[data-id]"),
+ "SmartBlock shims are listed"
+ );
+ ok(true, "Interventions are listed");
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/extensions/webcompat/tests/browser/browser_shims.js b/browser/extensions/webcompat/tests/browser/browser_shims.js
new file mode 100644
index 0000000000..4de900a4c6
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_shims.js
@@ -0,0 +1,73 @@
+"use strict";
+
+registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(TRACKING_PREF);
+});
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+});
+
+add_task(async function test_shim_disabled_by_own_pref() {
+ // Test that a shim will not apply if disabled in about:config
+
+ Services.prefs.setBoolPref(DISABLE_SHIM1_PREF, true);
+ Services.prefs.setBoolPref(TRACKING_PREF, true);
+
+ await testShimDoesNotRun();
+
+ Services.prefs.clearUserPref(DISABLE_SHIM1_PREF);
+ Services.prefs.clearUserPref(TRACKING_PREF);
+});
+
+add_task(async function test_shim_disabled_by_global_pref() {
+ // Test that a shim will not apply if disabled in about:config
+
+ Services.prefs.setBoolPref(GLOBAL_PREF, false);
+ Services.prefs.setBoolPref(DISABLE_SHIM1_PREF, false);
+ Services.prefs.setBoolPref(TRACKING_PREF, true);
+
+ await testShimDoesNotRun();
+
+ Services.prefs.clearUserPref(GLOBAL_PREF);
+ Services.prefs.clearUserPref(DISABLE_SHIM1_PREF);
+ Services.prefs.clearUserPref(TRACKING_PREF);
+});
+
+add_task(async function test_shim_disabled_hosts_notHosts() {
+ Services.prefs.setBoolPref(TRACKING_PREF, true);
+
+ await testShimDoesNotRun(false, SHIMMABLE_TEST_PAGE_3);
+
+ Services.prefs.clearUserPref(TRACKING_PREF);
+});
+
+add_task(async function test_shim_disabled_overridden_by_pref() {
+ Services.prefs.setBoolPref(TRACKING_PREF, true);
+
+ await testShimDoesNotRun(false, SHIMMABLE_TEST_PAGE_2);
+
+ Services.prefs.setBoolPref(DISABLE_SHIM2_PREF, false);
+
+ await testShimRuns(SHIMMABLE_TEST_PAGE_2);
+
+ Services.prefs.clearUserPref(TRACKING_PREF);
+ Services.prefs.clearUserPref(DISABLE_SHIM2_PREF);
+});
+
+add_task(async function test_shim() {
+ // Test that a shim which only runs in strict mode works, and that it
+ // is permitted to opt into showing normally-blocked tracking content.
+
+ Services.prefs.setBoolPref(TRACKING_PREF, true);
+
+ await testShimRuns(SHIMMABLE_TEST_PAGE);
+
+ // test that if the user opts in on one domain, they will still have to opt
+ // in on another domain which embeds an iframe to the first one.
+
+ await testShimRuns(EMBEDDING_TEST_PAGE, 0, false, false);
+
+ Services.prefs.clearUserPref(TRACKING_PREF);
+});
diff --git a/browser/extensions/webcompat/tests/browser/head.js b/browser/extensions/webcompat/tests/browser/head.js
new file mode 100644
index 0000000000..7fe8c3c171
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/head.js
@@ -0,0 +1,140 @@
+"use strict";
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const THIRD_PARTY_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.net"
+);
+
+const SHIMMABLE_TEST_PAGE = `${TEST_ROOT}shims_test.html`;
+const SHIMMABLE_TEST_PAGE_2 = `${TEST_ROOT}shims_test_2.html`;
+const SHIMMABLE_TEST_PAGE_3 = `${TEST_ROOT}shims_test_3.html`;
+const EMBEDDING_TEST_PAGE = `${THIRD_PARTY_ROOT}iframe_test.html`;
+
+const BLOCKED_TRACKER_URL =
+ "//trackertest.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js";
+
+const DISABLE_SHIM1_PREF = "extensions.webcompat.disabled_shims.MochitestShim";
+const DISABLE_SHIM2_PREF = "extensions.webcompat.disabled_shims.MochitestShim2";
+const DISABLE_SHIM3_PREF = "extensions.webcompat.disabled_shims.MochitestShim3";
+const DISABLE_SHIM4_PREF = "extensions.webcompat.disabled_shims.MochitestShim4";
+const GLOBAL_PREF = "extensions.webcompat.enable_shims";
+const TRACKING_PREF = "privacy.trackingprotection.enabled";
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+async function testShimRuns(
+ testPage,
+ frame,
+ trackersAllowed = true,
+ expectOptIn = true
+) {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: testPage,
+ waitForLoad: true,
+ });
+
+ const TrackingProtection =
+ tab.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the tab");
+ ok(TrackingProtection.enabled, "TP is enabled");
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [[trackersAllowed, BLOCKED_TRACKER_URL, expectOptIn], frame],
+ async (args, _frame) => {
+ const window = _frame === undefined ? content : content.frames[_frame];
+
+ await SpecialPowers.spawn(
+ window,
+ args,
+ async (_trackersAllowed, trackerUrl, _expectOptIn) => {
+ const shimResult = await content.wrappedJSObject.shimPromise;
+ is("shimmed", shimResult, "Shim activated");
+
+ const optInResult = await content.wrappedJSObject.optInPromise;
+ is(_expectOptIn, optInResult, "Shim allowed opt in if appropriate");
+
+ const o = content.document.getElementById("shims");
+ const cl = o.classList;
+ const opts = JSON.parse(o.innerText);
+ is(
+ undefined,
+ opts.branchValue,
+ "Shim script did not receive option for other branch"
+ );
+ is(
+ undefined,
+ opts.platformValue,
+ "Shim script did not receive option for other platform"
+ );
+ is(
+ true,
+ opts.simpleOption,
+ "Shim script received simple option correctly"
+ );
+ ok(opts.complexOption, "Shim script received complex option");
+ is(
+ 1,
+ opts.complexOption.a,
+ "Shim script received complex options correctly #1"
+ );
+ is(
+ "test",
+ opts.complexOption.b,
+ "Shim script received complex options correctly #2"
+ );
+ ok(cl.contains("green"), "Shim affected page correctly");
+ }
+ );
+ }
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function testShimDoesNotRun(
+ trackersAllowed = false,
+ testPage = SHIMMABLE_TEST_PAGE
+) {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: testPage,
+ waitForLoad: true,
+ });
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [trackersAllowed, BLOCKED_TRACKER_URL],
+ async (_trackersAllowed, trackerUrl) => {
+ const shimResult = await content.wrappedJSObject.shimPromise;
+ is("did not shim", shimResult, "Shim did not activate");
+
+ ok(
+ !content.document.getElementById("shims").classList.contains("green"),
+ "Shim script did not run"
+ );
+
+ is(
+ _trackersAllowed ? "ALLOWED" : "BLOCKED",
+ await new Promise(resolve => {
+ const s = content.document.createElement("script");
+ s.src = trackerUrl;
+ s.onload = () => resolve("ALLOWED");
+ s.onerror = () => resolve("BLOCKED");
+ content.document.head.appendChild(s);
+ }),
+ "Normally-blocked resources blocked if appropriate"
+ );
+ }
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+}
diff --git a/browser/extensions/webcompat/tests/browser/iframe_test.html b/browser/extensions/webcompat/tests/browser/iframe_test.html
new file mode 100644
index 0000000000..baf1ee9024
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/iframe_test.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf8" />
+ <script>
+ window.shimPromise = new Promise(resolve => {
+ window.shimPromiseResolve = resolve;
+ });
+ window.optInPromise = new Promise(resolve => {
+ window.optInPromiseResolve = resolve;
+ });
+ </script>
+ </head>
+ <body>
+ <iframe
+ src="http://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test.html"
+ ></iframe>
+ </body>
+</html>
diff --git a/browser/extensions/webcompat/tests/browser/shims_test.html b/browser/extensions/webcompat/tests/browser/shims_test.html
new file mode 100644
index 0000000000..ebe877316d
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf8" />
+ <script>
+ window.shimPromise = new Promise(resolve => {
+ window.shimPromiseResolve = resolve;
+ });
+ window.optInPromise = new Promise(resolve => {
+ window.optInPromiseResolve = resolve;
+ });
+ </script>
+ <script
+ onerror="window.shimPromiseResolve('error')"
+ src="shims_test.js"
+ ></script>
+ </head>
+ <body>
+ <div id="shims"></div>
+ </body>
+</html>
diff --git a/browser/extensions/webcompat/tests/browser/shims_test.js b/browser/extensions/webcompat/tests/browser/shims_test.js
new file mode 100644
index 0000000000..4a55bee7ed
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test.js
@@ -0,0 +1,11 @@
+/* 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.doingOptIn) {
+ window.optInPromiseResolve(true);
+} else {
+ window.shimPromiseResolve("did not shim");
+}
diff --git a/browser/extensions/webcompat/tests/browser/shims_test_2.html b/browser/extensions/webcompat/tests/browser/shims_test_2.html
new file mode 100644
index 0000000000..b080f74f6e
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf8" />
+ <script>
+ window.shimPromise = new Promise(resolve => {
+ window.shimPromiseResolve = resolve;
+ });
+ window.optInPromise = new Promise(resolve => {
+ window.optInPromiseResolve = resolve;
+ });
+ </script>
+ <script
+ onerror="window.shimPromiseResolve('error')"
+ src="shims_test_2.js"
+ ></script>
+ </head>
+ <body>
+ <div id="shims"></div>
+ </body>
+</html>
diff --git a/browser/extensions/webcompat/tests/browser/shims_test_2.js b/browser/extensions/webcompat/tests/browser/shims_test_2.js
new file mode 100644
index 0000000000..4a55bee7ed
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_2.js
@@ -0,0 +1,11 @@
+/* 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.doingOptIn) {
+ window.optInPromiseResolve(true);
+} else {
+ window.shimPromiseResolve("did not shim");
+}
diff --git a/browser/extensions/webcompat/tests/browser/shims_test_3.html b/browser/extensions/webcompat/tests/browser/shims_test_3.html
new file mode 100644
index 0000000000..bcb6f12043
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_3.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf8" />
+ <script>
+ window.shimPromise = new Promise(resolve => {
+ window.shimPromiseResolve = resolve;
+ });
+ window.optInPromise = new Promise(resolve => {
+ window.optInPromiseResolve = resolve;
+ });
+ </script>
+ <script
+ onerror="window.shimPromiseResolve('error')"
+ src="shims_test_3.js"
+ ></script>
+ </head>
+ <body>
+ <div id="shims"></div>
+ </body>
+</html>
diff --git a/browser/extensions/webcompat/tests/browser/shims_test_3.js b/browser/extensions/webcompat/tests/browser/shims_test_3.js
new file mode 100644
index 0000000000..9acb6cdcf1
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_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("did not shim");