summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/extensions/webcompat/about-compat/AboutCompat.jsm36
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.css187
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.html37
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.js171
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.js48
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.json6
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPageProcessScript.js32
-rw-r--r--browser/extensions/webcompat/components.conf17
-rw-r--r--browser/extensions/webcompat/data/injections.js467
-rw-r--r--browser/extensions/webcompat/data/picture_in_picture_overrides.js60
-rw-r--r--browser/extensions/webcompat/data/shims.js253
-rw-r--r--browser/extensions/webcompat/data/ua_overrides.js686
-rw-r--r--browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js57
-rw-r--r--browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json72
-rw-r--r--browser/extensions/webcompat/experiment-apis/appConstants.js32
-rw-r--r--browser/extensions/webcompat/experiment-apis/appConstants.json15
-rw-r--r--browser/extensions/webcompat/experiment-apis/experiments.js34
-rw-r--r--browser/extensions/webcompat/experiment-apis/experiments.json21
-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/pictureInPicture.js90
-rw-r--r--browser/extensions/webcompat/experiment-apis/pictureInPicture.json51
-rw-r--r--browser/extensions/webcompat/experiment-apis/sharedPreferences.js33
-rw-r--r--browser/extensions/webcompat/experiment-apis/sharedPreferences.json44
-rw-r--r--browser/extensions/webcompat/experiment-apis/systemManufacturer.js27
-rw-r--r--browser/extensions/webcompat/experiment-apis/systemManufacturer.json20
-rw-r--r--browser/extensions/webcompat/experiment-apis/trackingProtection.js168
-rw-r--r--browser/extensions/webcompat/experiment-apis/trackingProtection.json75
-rw-r--r--browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css3
-rw-r--r--browser/extensions/webcompat/injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css12
-rw-r--r--browser/extensions/webcompat/injections/css/bug1570119-teamcoco.com-scrollbar-width.css11
-rw-r--r--browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css17
-rw-r--r--browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css11
-rw-r--r--browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css24
-rw-r--r--browser/extensions/webcompat/injections/css/bug1610016-gaana.com-input-position-fix.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1645064-s-kanava.fi-invisible-charts.css12
-rw-r--r--browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css14
-rw-r--r--browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css14
-rw-r--r--browser/extensions/webcompat/injections/css/bug1654865-sports.ndtv.com-float-fix.css13
-rw-r--r--browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css15
-rw-r--r--browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css12
-rw-r--r--browser/extensions/webcompat/injections/css/bug1655049-dev.to-unclickable-button-fix.css12
-rw-r--r--browser/extensions/webcompat/injections/css/bug1666771-zilow-map-overdraw.css17
-rw-r--r--browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js11
-rw-r--r--browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js29
-rw-r--r--browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js34
-rw-r--r--browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js48
-rw-r--r--browser/extensions/webcompat/injections/js/bug1570856-medium.com-menu-isTier1.js34
-rw-r--r--browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js28
-rw-r--r--browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js82
-rw-r--r--browser/extensions/webcompat/injections/js/bug1610358-pcloud.com-appVersion-change.js25
-rw-r--r--browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js18
-rw-r--r--browser/extensions/webcompat/injections/js/bug1665035-dckids.com-cookieEnabled.js30
-rw-r--r--browser/extensions/webcompat/injections/js/bug1677442-store.hp.com-disable-indexeddb.js20
-rw-r--r--browser/extensions/webcompat/injections/js/bug1682238-gamearter.com-ua-change.js27
-rw-r--r--browser/extensions/webcompat/lib/about_compat_broker.js123
-rw-r--r--browser/extensions/webcompat/lib/custom_functions.js96
-rw-r--r--browser/extensions/webcompat/lib/injections.js163
-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/picture_in_picture_overrides.js74
-rw-r--r--browser/extensions/webcompat/lib/shim_messaging_helper.js65
-rw-r--r--browser/extensions/webcompat/lib/shims.js415
-rw-r--r--browser/extensions/webcompat/lib/ua_overrides.js265
-rw-r--r--browser/extensions/webcompat/manifest.json144
-rw-r--r--browser/extensions/webcompat/moz.build125
-rw-r--r--browser/extensions/webcompat/run.js24
-rw-r--r--browser/extensions/webcompat/shims/adsafeprotected-ima.js69
-rw-r--r--browser/extensions/webcompat/shims/bmauth.js21
-rw-r--r--browser/extensions/webcompat/shims/eluminate.js68
-rw-r--r--browser/extensions/webcompat/shims/empty-script.js5
-rw-r--r--browser/extensions/webcompat/shims/facebook-sdk.js198
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js13
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-legacy.js133
-rw-r--r--browser/extensions/webcompat/shims/google-analytics-tag-manager.js24
-rw-r--r--browser/extensions/webcompat/shims/google-analytics.js45
-rw-r--r--browser/extensions/webcompat/shims/google-publisher-tags.js163
-rw-r--r--browser/extensions/webcompat/shims/live-test-shim.js84
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-1.js89
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-2.js87
-rw-r--r--browser/extensions/webcompat/shims/mochitest-shim-3.js7
-rw-r--r--browser/extensions/webcompat/shims/rambler-authenticator.js86
-rw-r--r--browser/extensions/webcompat/shims/rich-relevance.js30
-rw-r--r--browser/extensions/webcompat/tests/browser/browser.ini13
-rw-r--r--browser/extensions/webcompat/tests/browser/browser_shims.js73
-rw-r--r--browser/extensions/webcompat/tests/browser/head.js139
-rw-r--r--browser/extensions/webcompat/tests/browser/iframe_test.html17
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test.html18
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test.js11
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_2.html18
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_2.js11
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_3.html18
-rw-r--r--browser/extensions/webcompat/tests/browser/shims_test_3.js7
96 files changed, 6644 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..a90bd8ed80
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/AboutCompat.jsm
@@ -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";
+
+var EXPORTED_SYMBOLS = ["AboutCompat"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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;
+ },
+
+ 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 ||
+ 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..492296dfcf
--- /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;
+ }
+
+ .hidden {
+ display: none;
+ }
+
+ .table-title-container {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ }
+
+ .wide-button {
+ display: block;
+ min-height: 32px;
+ padding-inline: 30px;
+ }
+
+ .submitting {
+ background-image: url(chrome://global/skin/icons/loading.png);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 16px;
+ }
+
+ .submitting .submit-crash-button-label {
+ display: none;
+ }
+
+ .failed-to-submit {
+ color: #ca8695;
+ }
+
+ a.button-as-link {
+ appearance: none;
+ min-height: 30px;
+ color: var(--in-content-text-color) !important;
+ border: 1px solid var(--in-content-box-border-color) !important;
+ border-radius: 2px;
+ background-color: var(--in-content-page-background);
+ line-height: 30px;
+ margin: 4px 8px;
+ /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
+ font-size: 1em;
+ }
+
+ a.button-as-link:hover {
+ background-color: var(--in-content-box-background-hover) !important;
+ text-decoration: none;
+ }
+
+ h2.lighter-font-weight {
+ font-weight: lighter;
+ }
+
+ th {
+ text-align: start;
+ }
+}
+
+@media (any-pointer: coarse), (any-pointer: none) {
+ * {
+ margin: 0;
+ padding: 0;
+ }
+
+ html {
+ font-family: sans-serif;
+ font-size: 14px;
+ -moz-text-size-adjust: none;
+ background-color: #f5f5f5;
+ }
+
+ table,
+ tr,
+ p {
+ display: block;
+ background: #fff;
+ }
+
+ table {
+ border-top: 2px solid #0a84ff;
+ margin-top: -2px;
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+ display: none;
+ }
+
+ tr {
+ position: relative;
+ border-bottom: 1px solid #d7d9db;
+ padding: 1em;
+ }
+
+ a {
+ color: #000;
+ font-size: 94%;
+ }
+
+ .tab {
+ cursor: pointer;
+ position: relative;
+ z-index: 2;
+ display: inline-block;
+ text-align: left;
+ padding: 1em;
+ font-weight: bold;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border: 1px solid #d7d9db;
+ border-bottom: 0;
+ margin-bottom: 2px;
+ background: #f5f5f5;
+ color: #363b40;
+ font-size: 1em;
+ font-weight: bold;
+ padding: 1em;
+ }
+
+ .tab.active {
+ border-bottom-color: #fff;
+ background: #fff;
+ margin-bottom: 0;
+ padding-bottom: calc(1em + 2px);
+ }
+
+ .tab.active + table {
+ display: block;
+ }
+
+ td {
+ display: block;
+ position: relative;
+ padding-inline-end: 6.5em;
+ }
+
+ td[colspan="4"] {
+ padding: 1em;
+ font-style: italic;
+ text-align: center;
+ }
+
+ td:not([colspan]):nth-child(1) {
+ font-weight: bold;
+ }
+
+ td:not([colspan]):nth-child(1) {
+ padding-bottom: 0.25em;
+ }
+
+ td:nth-child(3) {
+ display: contents;
+ }
+
+ button {
+ background: #e8e8e7;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ inset-inline-end: 0;
+ width: 6em;
+ border: 0;
+ border-inline-start: 1px solid #d7d9db;
+ appearance: none;
+ color: #000;
+ }
+
+ 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..31180f0b30
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutCompat.html
@@ -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/. -->
+
+<!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="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>
+</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..e5c9c4ff17
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutCompat.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";
+
+/* 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("getOverridesAndInterventions"),
+ 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) {
+ if ("interventionsChanged" in msg) {
+ redrawTable($("#interventions"), msg.interventionsChanged);
+ }
+
+ if ("overridesChanged" in msg) {
+ redrawTable($("#overrides"), msg.overridesChanged);
+ }
+
+ 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 } = availablePatches;
+ const showHidden = location.hash === "#all";
+ redrawTable($("#overrides"), overrides, showHidden);
+ redrawTable($("#interventions"), interventions, showHidden);
+}
+
+function redrawTable(table, data, showHidden = false) {
+ 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 =
+ table.id === "overrides" ? "text-no-overrides" : "text-no-interventions";
+ }
+
+ 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 && !showHidden) {
+ 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..e719551332
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutPage.js
@@ -0,0 +1,48 @@
+/* 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 */
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "Services",
+ "resource://gre/modules/Services.jsm"
+);
+
+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..e2e9866eb1
--- /dev/null
+++ b/browser/extensions/webcompat/about-compat/aboutPageProcessScript.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";
+
+// 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.import(
+ "resource://gre/modules/ComponentUtils.jsm"
+ );
+
+ 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..d97deca4d6
--- /dev/null
+++ b/browser/extensions/webcompat/data/injections.js
@@ -0,0 +1,467 @@
+/* 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: "desktop",
+ 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/*"],
+ js: [
+ {
+ file: "injections/js/bug1472075-bankofamerica.com-ua-change.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1570856",
+ platform: "android",
+ domain: "medium.com",
+ bug: "1570856",
+ contentScripts: {
+ matches: ["*://medium.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1570856-medium.com-menu-isTier1.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ 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: "bug1551672",
+ platform: "android",
+ domain: "Sites using PDK 5 video",
+ bug: "1551672",
+ data: {
+ urls: ["https://*/*/tpPdk.js", "https://*/*/pdk/js/*/*.js"],
+ types: ["script"],
+ },
+ customFunc: "pdk5fix",
+ },
+ {
+ 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: "bug1561371",
+ platform: "android",
+ domain: "mail.google.com",
+ bug: "1561371",
+ contentScripts: {
+ matches: ["*://mail.google.com/*"],
+ css: [
+ {
+ file:
+ "injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1570119",
+ platform: "desktop",
+ domain: "teamcoco.com",
+ bug: "1570119",
+ contentScripts: {
+ matches: ["*://teamcoco.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1570119-teamcoco.com-scrollbar-width.css",
+ },
+ ],
+ },
+ },
+ {
+ 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: "bug1610016",
+ platform: "android",
+ domain: "gaana.com",
+ bug: "1610016",
+ contentScripts: {
+ matches: ["https://gaana.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1610016-gaana.com-input-position-fix.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1610358",
+ platform: "android",
+ domain: "pcloud.com",
+ bug: "1610358",
+ contentScripts: {
+ matches: ["https://www.pcloud.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1610358-pcloud.com-appVersion-change.js",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1610344",
+ platform: "all",
+ domain: "directv.com.co",
+ bug: "1610344",
+ contentScripts: {
+ matches: ["https://*.directv.com.co/*"],
+ css: [
+ {
+ file:
+ "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css",
+ },
+ ],
+ },
+ },
+ {
+ id: "bug1622062",
+ platform: "android",
+ domain: "$.detectSwipe fix",
+ bug: "1622062",
+ data: {
+ urls: ["https://eu.stemwijzer.nl/public/js/votematch.vendors.js"],
+ types: ["script"],
+ },
+ customFunc: "detectSwipeFix",
+ },
+ {
+ 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: "bug1645064",
+ platform: "desktop",
+ domain: "s-kanava.fi",
+ bug: "1645064",
+ contentScripts: {
+ matches: ["https://www.s-kanava.fi/*"],
+ css: [
+ {
+ file: "injections/css/bug1645064-s-kanava.fi-invisible-charts.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: "bug1654865",
+ platform: "android",
+ domain: "sports.ndtv.com",
+ bug: "1654865",
+ contentScripts: {
+ matches: ["*://sports.ndtv.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1654865-sports.ndtv.com-float-fix.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: "bug1655049",
+ platform: "android",
+ domain: "dev.to",
+ bug: "1655049",
+ contentScripts: {
+ matches: ["*://dev.to/*"],
+ css: [
+ {
+ file: "injections/css/bug1655049-dev.to-unclickable-button-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: "bug1666771",
+ platform: "desktop",
+ domain: "zillow.com",
+ bug: "1666771",
+ contentScripts: {
+ allFrames: true,
+ matches: ["*://*.zillow.com/*"],
+ css: [
+ {
+ file: "injections/css/bug1666771-zilow-map-overdraw.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: "bug1665035",
+ platform: "desktop",
+ domain: "dckids.com",
+ bug: "1665035",
+ contentScripts: {
+ matches: [
+ "https://d3qlaywcwingl6.cloudfront.net/content/*/Html5Game/*",
+ "https://d3qlaywcwingl6.cloudfront.net/*/game/content/*",
+ ],
+ js: [
+ {
+ file: "injections/js/bug1665035-dckids.com-cookieEnabled.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1677442",
+ platform: "desktop",
+ domain: "store.hp.com",
+ bug: "1677442",
+ contentScripts: {
+ matches: ["*://d3nkfb7815bs43.cloudfront.net/*forstore.hp.com*"],
+ js: [
+ {
+ file: "injections/js/bug1677442-store.hp.com-disable-indexeddb.js",
+ },
+ ],
+ allFrames: true,
+ },
+ },
+ {
+ id: "bug1682238",
+ platform: "desktop",
+ domain: "gamearter.com",
+ bug: "1682238",
+ contentScripts: {
+ matches: ["*://*.gamearter.com/*"],
+ js: [
+ {
+ file: "injections/js/bug1682238-gamearter.com-ua-change.js",
+ },
+ ],
+ },
+ },
+];
+
+module.exports = AVAILABLE_INJECTIONS;
diff --git a/browser/extensions/webcompat/data/picture_in_picture_overrides.js b/browser/extensions/webcompat/data/picture_in_picture_overrides.js
new file mode 100644
index 0000000000..e9a4e88657
--- /dev/null
+++ b/browser/extensions/webcompat/data/picture_in_picture_overrides.js
@@ -0,0 +1,60 @@
+/* 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 AVAILABLE_PIP_OVERRIDES;
+
+{
+ // See PictureInPictureControls.jsm for these values.
+ // eslint-disable-next-line no-unused-vars
+ const TOGGLE_POLICIES = browser.pictureInPictureChild.getPolicies();
+ const KEYBOARD_CONTROLS = browser.pictureInPictureChild.getKeyboardControls();
+
+ AVAILABLE_PIP_OVERRIDES = {
+ // The keys of this object are match patterns for URLs, as documented in
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
+ //
+ // Example:
+ //
+ // "https://*.youtube.com/*": {
+ // policy: TOGGLE_POLICIES.THREE_QUARTERS,
+ // keyboardControls: KEYBOARD_CONTROLS.PLAY_PAUSE | KEYBOARD_CONTROLS.VOLUME,
+ // },
+ // "https://*.twitch.tv/mikeconley_dot_ca/*": {
+ // policy: TOGGLE_POLICIES.TOP,
+ // keyboardControls: KEYBOARD_CONTROLS.NONE,
+ // },
+
+ instagram: {
+ "https://www.instagram.com/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ },
+
+ laracasts: {
+ "https://*.laracasts.com/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ },
+
+ netflix: {
+ "https://*.netflix.com/*": { keyboardControls: ~KEYBOARD_CONTROLS.SEEK },
+ "https://*.netflix.com/browse": { policy: TOGGLE_POLICIES.HIDDEN },
+ "https://*.netflix.com/latest": { policy: TOGGLE_POLICIES.HIDDEN },
+ },
+
+ twitch: {
+ "https://*.twitch.tv/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ "https://*.twitch.tech/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ "https://*.twitch.a2z.com/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ },
+
+ udemy: {
+ "https://*.udemy.com/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
+ },
+
+ youtube: {
+ "https://*.youtube.com/*": { visibilityThreshold: 0.9 },
+ },
+ };
+}
diff --git a/browser/extensions/webcompat/data/shims.js b/browser/extensions/webcompat/data/shims.js
new file mode 100644
index 0000000000..8abdffac39
--- /dev/null
+++ b/browser/extensions/webcompat/data/shims.js
@@ -0,0 +1,253 @@
+/* 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 = [
+ {
+ 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"],
+ },
+ {
+ id: "MochitestShim",
+ platform: "all",
+ 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/*"],
+ },
+ {
+ 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/*"],
+ },
+ {
+ 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",
+ ],
+ },
+ {
+ 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",
+ ],
+ },
+ {
+ 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",
+ ],
+ },
+ {
+ 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: "AdSafeProtectedGoogleIMAAdapter",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Ad Safe Protected Google IMA Adapter",
+ bug: "1508639",
+ file: "adsafeprotected-ima.js",
+ matches: ["*://static.adsafeprotected.com/vans-adapter-google-ima.js"],
+ needsShimHelpers: ["optIn"],
+ onlyIfBlockedByETP: true,
+ unblocksOnOptIn: ["*://pubads.g.doubleclick.net/gampad/ads"],
+ },
+ {
+ id: "AdsByGoogle",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Ads by Google",
+ bug: "1629644",
+ file: "empty-script.js",
+ matches: ["*://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "BmAuth",
+ platform: "all",
+ branches: ["nightly"],
+ name: "BmAuth by 9c9media",
+ bug: "1486337",
+ file: "bmauth.js",
+ matches: ["*://auth.9c9media.ca/auth/main.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "Eluminate",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Eluminate",
+ bug: "1503211",
+ file: "eluminate.js",
+ matches: ["*://libs.coremetrics.com/eluminate.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "FacebookSDK",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Facebook SDK",
+ bug: "1226498",
+ file: "facebook-sdk.js",
+ matches: [
+ "*://connect.facebook.net/*/sdk.js*",
+ "*://connect.facebook.net/*/all.js*",
+ ],
+ needsShimHelpers: ["optIn"],
+ onlyIfBlockedByETP: true,
+ unblocksOnOptIn: [
+ "*://*.xx.fbcdn.net/*", // covers:
+ // "*://scontent-.*-\d.xx.fbcdn.net/*",
+ // "*://static.xx.fbcdn.net/rsrc.php/*",
+
+ "*://www.facebook.com/plugins/comments.php*",
+ "*://www.facebook.com/plugins/comments/async/*",
+ "*://www.facebook.com/plugins/feedback.php*",
+ "*://www.facebook.com/plugins/like_box.php*",
+ ],
+ },
+ {
+ id: "GoogleAnalytics",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Google Analytics",
+ bug: "1493602",
+ file: "google-analytics.js",
+ matches: ["*://www.google-analytics.com/analytics.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleAnalyticsECommercePlugin",
+ platform: "all",
+ branches: ["nightly"],
+ 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: "GoogleAnalyticsTagManager",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Google Analytics Tag Manager",
+ bug: "1478593",
+ file: "google-analytics-tag-manager.js",
+ matches: ["*://www.google-analytics.com/gtm/js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GoogleAnalyticsLegacy",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Legacy Google Analytics",
+ bug: "1487072",
+ file: "google-analytics-legacy.js",
+ matches: ["*://ssl.google-analytics.com/ga.js"],
+ onlyIfBlockedByETP: true,
+ },
+ {
+ id: "GooglePublisherTags",
+ platform: "all",
+ branches: ["nightly"],
+ name: "Google Publisher Tags",
+ bug: "1600538",
+ file: "google-publisher-tags.js",
+ matches: [
+ "*://www.googletagservices.com/tag/js/gpt.js",
+ "*://securepubads.g.doubleclick.net/tag/js/gpt.js",
+ "*://securepubads.g.doubleclick.net/gpt/pubads_impl_*.js",
+ ],
+ onlyIfBlockedByETP: true,
+ unblocksOnOptIn: ["*://pubads.g.doubleclick.net/ssai/event/*/streams"],
+ },
+ {
+ id: "IMA3",
+ platform: "all",
+ branches: ["nightly"],
+ name: "IMA3",
+ bug: "1487373",
+ file: "empty-script.js",
+ onlyIfBlockedByETP: true,
+ matches: ["*://s0.2mdn.net/instream/html5/ima3.js"],
+ },
+ {
+ id: "Rambler",
+ platform: "all",
+ branches: ["nightly"],
+ 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",
+ branches: ["nightly"],
+ name: "Rich Relevance",
+ bug: "1449347",
+ file: "rich-relevance.js",
+ matches: ["*://media.richrelevance.com/rrserver/js/1.2/p13n.js"],
+ onlyIfBlockedByETP: 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..daa947989c
--- /dev/null
+++ b/browser/extensions/webcompat/data/ua_overrides.js
@@ -0,0 +1,686 @@
+/* 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");
+}
+
+/**
+ * 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 - att.tv - Create a UA override for att.tv for playback on desktop
+ * WebCompat issue #3846 - https://webcompat.com/issues/3846
+ *
+ * att.tv (atttvnow.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: "att.tv",
+ bug: "1577519",
+ config: {
+ matches: ["*://*.att.tv/*"],
+ 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/76.0.3809.132 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 945963 - tieba.baidu.com serves simplified mobile content to Firefox Android
+ * additionally, Bug 1525839 for more domains
+ * WebCompat issue #18455 - https://webcompat.com/issues/18455
+ *
+ * tieba.baidu.com and tiebac.baidu.com serve a heavily simplified and less functional
+ * mobile experience to Firefox for Android users. Adding the AppleWebKit indicator
+ * to the User Agent gets us the same experience.
+ */
+ id: "bug945963",
+ platform: "android",
+ domain: "tieba.baidu.com",
+ bug: "945963",
+ config: {
+ matches: [
+ "*://baike.baidu.com/*",
+ "*://image.baidu.com/*",
+ "*://news.baidu.com/*",
+ "*://tieba.baidu.com/*",
+ "*://tiebac.baidu.com/*",
+ "*://wenku.baidu.com/*",
+ "*://zhidao.baidu.com/*",
+ ],
+ uaTransformer: originalUA => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1177298 - Write UA overrides for top Japanese Sites
+ * (Imported from ua-update.json.in)
+ *
+ * To receive the proper mobile version instead of the desktop version or
+ * a lower grade mobile experience, the UA is spoofed.
+ */
+ id: "bug1177298-2",
+ platform: "android",
+ domain: "lohaco.jp",
+ bug: "1177298",
+ config: {
+ matches: ["*://*.lohaco.jp/*"],
+ uaTransformer: _ => {
+ return "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36";
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1177298 - Write UA overrides for top Japanese Sites
+ * (Imported from ua-update.json.in)
+ *
+ * To receive the proper mobile version instead of the desktop version or
+ * a lower grade mobile experience, the UA is spoofed.
+ */
+ id: "bug1177298-3",
+ platform: "android",
+ domain: "nhk.or.jp",
+ bug: "1177298",
+ config: {
+ matches: ["*://*.nhk.or.jp/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " AppleWebKit";
+ },
+ },
+ },
+ {
+ /*
+ * 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 1509831 - cc.com - Add UA override for CC.com
+ * WebCompat issue #329 - https://webcompat.com/issues/329
+ *
+ * ComedyCentral blocks Firefox for not being able to play HLS, which was
+ * true in previous versions, but no longer is. With a spoofed Chrome UA,
+ * the site works just fine.
+ */
+ id: "bug1509831",
+ platform: "android",
+ domain: "cc.com",
+ bug: "1509831",
+ config: {
+ matches: ["*://*.cc.com/*"],
+ 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 1566253 - posts.google.com - Add UA override for posts.google.com
+ * WebCompat issue #17870 - https://webcompat.com/issues/17870
+ *
+ * posts.google.com displaying "Your browser doesn't support this page".
+ * Spoofing as Chrome works fine.
+ */
+ id: "bug1566253",
+ platform: "android",
+ domain: "posts.google.com",
+ bug: "1566253",
+ config: {
+ matches: ["*://posts.google.com/*"],
+ uaTransformer: _ => {
+ return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 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.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1577250 - UA override for homebook.pl on Firefox for Android
+ * WebCompat issue #24044 - https://webcompat.com/issues/24044
+ *
+ * homebook.pl shows desktop site on Firefox for Android based on
+ * UA detection. Spoofing as Chrome allows to get mobile site.
+ */
+ id: "bug1577250",
+ platform: "android",
+ domain: "homebook.pl",
+ bug: "1577250",
+ config: {
+ matches: ["*://*.homebook.pl/*"],
+ uaTransformer: originalUA => {
+ return (
+ UAHelpers.getPrefix(originalUA) +
+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
+ );
+ },
+ },
+ },
+ {
+ /*
+ * 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 1621065 - UA overrides for bracketchallenge.ncaa.com
+ * Webcompat issue #49886 - https://webcompat.com/issues/49886
+ *
+ * The NCAA bracket challenge website mistakenly classifies
+ * any non-Chrome browser on Android as "is_old_android". As a result,
+ * a modal is shown telling them they have security flaws. We have
+ * attempted to reach out for a fix (and clarification).
+ */
+ id: "bug1621065",
+ platform: "android",
+ domain: "bracketchallenge.ncaa.com",
+ bug: "1621065",
+ config: {
+ matches: ["*://bracketchallenge.ncaa.com/*"],
+ uaTransformer: originalUA => {
+ return originalUA + " Chrome";
+ },
+ },
+ },
+ {
+ /*
+ * 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 1622081 - UA override for m2.bmo.com
+ * Webcompat issue #45019 - https://webcompat.com/issues/45019
+ *
+ * Unless the UA string contains "Chrome", m2.bmo.com will
+ * display a modal saying the browser is out-of-date.
+ */
+ id: "bug1622081",
+ platform: "android",
+ domain: "m2.bmo.com",
+ bug: "1622081",
+ config: {
+ matches: ["*://m2.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 1630280 - UA override for dominos.ch
+ * Webcompat issue #48273 - https://webcompat.com/issues/48273
+ *
+ * dominos.ch is suggesting downloading their native app and showing
+ * an overlay that can't be removed in Firefox for Android. Spoofing
+ * as Chrome allows to continue to the site
+ */
+ id: "bug1630280",
+ platform: "android",
+ domain: "dominos.ch",
+ bug: "1630280",
+ config: {
+ matches: ["https://*.dominos.ch/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1563839 - rolb.santanderbank.com - Build UA override
+ * 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/*",
+ "*://bob.santanderbank.com/*",
+ "*://rolb.santanderbank.com/*",
+ ],
+ uaTransformer: originalUA => {
+ return 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 1654888 - UA override for ebuyer.com
+ * Webcompat issue #52463 - https://webcompat.com/issues/52463
+ *
+ * This site returns desktop site based on server side UA detection.
+ * Spoofing as Chrome allows to get mobile experience
+ */
+ id: "bug1654888",
+ platform: "android",
+ domain: "ebuyer.com",
+ bug: "1654888",
+ config: {
+ matches: ["*://*.ebuyer.com/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * 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 1679847 - Add UA override for avto.pro
+ * Webcompat issue #60043 - https://webcompat.com/issues/60043
+ *
+ * Unless Chrome is in the UA, the site serves a desktop version
+ * on catalog pages
+ */
+ id: "bug1679847",
+ platform: "android",
+ domain: "avto.pro",
+ bug: "1679847",
+ config: {
+ matches: ["https://avto.pro/catalog/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+ {
+ /*
+ * Bug 1679869 - Add UA override for vh1.com
+ * Webcompat issue #52755 - https://webcompat.com/issues/52755
+ *
+ * The site is not showing videos on Firefox on mobile stating
+ * that android 4.4.4 and chrome browser required
+ */
+ id: "bug1679869",
+ platform: "android",
+ domain: "vh1.com",
+ bug: "1679869",
+ config: {
+ matches: ["*://*.vh1.com/*"],
+ uaTransformer: () => {
+ return UAHelpers.getDeviceAppropriateChromeUA();
+ },
+ },
+ },
+];
+
+const UAHelpers = {
+ getDeviceAppropriateChromeUA() {
+ if (!UAHelpers._deviceAppropriateChromeUA) {
+ const userAgent =
+ typeof navigator !== "undefined" ? navigator.userAgent : "";
+ const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [
+ "",
+ "58.0",
+ ])[1];
+ const RunningAndroidVersion =
+ userAgent.match(/Android\/[0-9.]+/) || "Android 6.0";
+ const ChromeVersionToMimic = "76.0.3809.111";
+ const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Mobile Safari/537.36`;
+ const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Safari/537.36`;
+ const IsPhone = userAgent.includes("Mobile");
+ UAHelpers._deviceAppropriateChromeUA = IsPhone
+ ? ChromePhoneUA
+ : ChromeTabletUA;
+ }
+ return UAHelpers._deviceAppropriateChromeUA;
+ },
+ getPrefix(originalUA) {
+ return originalUA.substr(0, originalUA.indexOf(")") + 1);
+ },
+};
+
+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..08a4c3d091
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js
@@ -0,0 +1,57 @@
+/* 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 */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+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..7019eb6215
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/appConstants.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";
+
+/* global ExtensionAPI, XPCOMUtils */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+});
+
+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.BETA_OR_RELEASE) {
+ return "beta_or_release";
+ }
+ 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/experiments.js b/browser/extensions/webcompat/experiment-apis/experiments.js
new file mode 100644
index 0000000000..d1ab77c312
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/experiments.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/. */
+
+"use strict";
+
+/* global ExtensionAPI, Services, XPCOMUtils */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ EventDispatcher: "resource://gre/modules/Messaging.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+this.experiments = class extends ExtensionAPI {
+ getAPI(context) {
+ function promiseActiveExperiments() {
+ return EventDispatcher.instance.sendRequestForResult({
+ type: "Experiments:GetActive",
+ });
+ }
+ return {
+ experiments: {
+ async isActive(name) {
+ if (!Services.androidBridge || !Services.androidBridge.isFennec) {
+ return undefined;
+ }
+ return promiseActiveExperiments().then(experiments => {
+ return experiments.includes(name);
+ });
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/experiments.json b/browser/extensions/webcompat/experiment-apis/experiments.json
new file mode 100644
index 0000000000..44f833215c
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/experiments.json
@@ -0,0 +1,21 @@
+[
+ {
+ "namespace": "experiments",
+ "description": "experimental API extension to allow checking the status of Fennec experiments via Switchboard",
+ "functions": [
+ {
+ "name": "isActive",
+ "type": "function",
+ "description": "Determine if a given experiment is active.",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The experiment's name"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
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/pictureInPicture.js b/browser/extensions/webcompat/experiment-apis/pictureInPicture.js
new file mode 100644
index 0000000000..e78ddbf315
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/pictureInPicture.js
@@ -0,0 +1,90 @@
+/* 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 ChromeUtils, ExtensionAPI, Services */
+ChromeUtils.defineModuleGetter(
+ this,
+ "Services",
+ "resource://gre/modules/Services.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "KEYBOARD_CONTROLS",
+ "resource://gre/modules/PictureInPictureControls.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TOGGLE_POLICIES",
+ "resource://gre/modules/PictureInPictureControls.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "AppConstants",
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+const TOGGLE_ENABLED_PREF =
+ "media.videocontrols.picture-in-picture.video-toggle.enabled";
+
+/**
+ * This API is expected to be running in the parent process.
+ */
+this.pictureInPictureParent = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ pictureInPictureParent: {
+ setOverrides(overrides) {
+ // The Picture-in-Picture toggle is only implemented for Desktop, so make
+ // this a no-op for non-Desktop builds.
+ if (AppConstants.platform == "android") {
+ return;
+ }
+
+ Services.ppmm.sharedData.set(
+ "PictureInPicture:SiteOverrides",
+ overrides
+ );
+ },
+ },
+ };
+ }
+};
+
+/**
+ * This API is expected to be running in a content process - specifically,
+ * the WebExtension content process that the background scripts run in. We
+ * split these out so that they can return values synchronously to the
+ * background scripts.
+ */
+this.pictureInPictureChild = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ pictureInPictureChild: {
+ getKeyboardControls() {
+ // The Picture-in-Picture toggle is only implemented for Desktop, so make
+ // this return nothing for non-Desktop builds.
+ if (AppConstants.platform == "android") {
+ return Cu.cloneInto({}, context.cloneScope);
+ }
+
+ return Cu.cloneInto(KEYBOARD_CONTROLS, context.cloneScope);
+ },
+ getPolicies() {
+ // The Picture-in-Picture toggle is only implemented for Desktop, so make
+ // this return nothing for non-Desktop builds.
+ if (AppConstants.platform == "android") {
+ return Cu.cloneInto({}, context.cloneScope);
+ }
+
+ return Cu.cloneInto(TOGGLE_POLICIES, context.cloneScope);
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/pictureInPicture.json b/browser/extensions/webcompat/experiment-apis/pictureInPicture.json
new file mode 100644
index 0000000000..5f34616b6e
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/pictureInPicture.json
@@ -0,0 +1,51 @@
+[
+ {
+ "namespace": "pictureInPictureParent",
+ "description": "Parent process methods for controlling the Picture-in-Picture feature.",
+ "functions": [
+ {
+ "name": "setOverrides",
+ "type": "function",
+ "description": "Set Picture-in-Picture toggle position overrides",
+ "parameters": [
+ {
+ "name": "overrides",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "The Picture-in-Picture toggle position overrides to set"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "namespace": "pictureInPictureChild",
+ "description": "WebExtension process methods for querying the Picture-in-Picture feature.",
+ "functions": [
+ {
+ "name": "getKeyboardControls",
+ "type": "function",
+ "description": "Get the Picture-in-Picture keyboard control override constants",
+ "parameters": [],
+ "returns": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": { "type": "any" },
+ "description": "The Picture-in-Picture keyboard control override constants"
+ }
+ },
+ {
+ "name": "getPolicies",
+ "type": "function",
+ "description": "Get the Picture-in-Picture toggle position override constants",
+ "parameters": [],
+ "returns": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": { "type": "any" },
+ "description": "The Picture-in-Picture toggle position override constants"
+ }
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/sharedPreferences.js b/browser/extensions/webcompat/experiment-apis/sharedPreferences.js
new file mode 100644
index 0000000000..b2a19b3a6c
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/sharedPreferences.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";
+
+/* global ExtensionAPI, Services, XPCOMUtils */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ Services: "resource://gre/modules/Services.jsm",
+ SharedPreferences: "resource://gre/modules/SharedPreferences.jsm",
+});
+
+this.sharedPreferences = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ sharedPreferences: {
+ async setCharPref(name, value) {
+ if (!Services.androidBridge || !Services.androidBridge.isFennec) {
+ return;
+ }
+ SharedPreferences.forApp().setCharPref(name, value);
+ },
+ async setBoolPref(name, value) {
+ if (!Services.androidBridge || !Services.androidBridge.isFennec) {
+ return;
+ }
+ SharedPreferences.forApp().setBoolPref(name, value);
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/sharedPreferences.json b/browser/extensions/webcompat/experiment-apis/sharedPreferences.json
new file mode 100644
index 0000000000..cf73414cf4
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/sharedPreferences.json
@@ -0,0 +1,44 @@
+[
+ {
+ "namespace": "sharedPreferences",
+ "description": "experimental API extension to allow setting SharedPreferences on Fennec",
+ "functions": [
+ {
+ "name": "setBoolPref",
+ "type": "function",
+ "description": "Set the value of a boolean Fennec SharedPreference",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The key name"
+ },
+ {
+ "name": "value",
+ "type": "boolean",
+ "description": "The new value"
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "setCharPref",
+ "type": "function",
+ "description": "Set the value of a string Fennec SharedPreference",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The key name"
+ },
+ {
+ "name": "value",
+ "type": "string",
+ "description": "The new value"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.js b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js
new file mode 100644
index 0000000000..c3819c1128
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.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";
+
+/* global ExtensionAPI, Services, XPCOMUtils */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+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..557090e974
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.js
@@ -0,0 +1,168 @@
+/* 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 */
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]);
+
+class Manager {
+ constructor() {
+ this._allowLists = new Map();
+ }
+
+ _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;
+ }
+ for (const allowList of this._allowLists.values()) {
+ for (const entry of allowList.values()) {
+ const { matcher, hosts, notHosts } = entry;
+ if (matcher.matches(url)) {
+ if (
+ !notHosts?.has(topHost) &&
+ (hosts === true || hosts.has(topHost))
+ ) {
+ this._unblockedChannelIds.add(channelId);
+ channel.unblock();
+ 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, notHosts }) {
+ this._ensureStarted();
+
+ if (!this._allowLists.has(allowListId)) {
+ this._allowLists.set(allowListId, new Map());
+ }
+ const allowList = this._allowLists.get(allowListId);
+ for (const pattern of patterns) {
+ if (!allowList.has(pattern)) {
+ allowList.set(pattern, {
+ matcher: new MatchPattern(pattern),
+ });
+ }
+ const allowListPattern = allowList.get(pattern);
+ if (!hosts) {
+ allowListPattern.hosts = true;
+ } else {
+ if (!allowListPattern.hosts) {
+ allowListPattern.hosts = new Set();
+ }
+ for (const host of hosts) {
+ allowListPattern.hosts.add(host);
+ }
+ }
+ if (notHosts) {
+ if (!allowListPattern.notHosts) {
+ allowListPattern.notHosts = new Set();
+ }
+ for (const notHost of notHosts) {
+ allowListPattern.notHosts.add(notHost);
+ }
+ }
+ }
+ }
+
+ 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;
+}
+
+this.trackingProtection = class extends ExtensionAPI {
+ onShutdown(isAppShutdown) {
+ if (manager) {
+ manager.stop();
+ }
+ }
+
+ getAPI(context) {
+ return {
+ trackingProtection: {
+ async allow(allowListId, patterns, options) {
+ manager.allow(allowListId, patterns, options);
+ },
+ 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);
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.json b/browser/extensions/webcompat/experiment-apis/trackingProtection.json
new file mode 100644
index 0000000000..2627d1ea0f
--- /dev/null
+++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.json
@@ -0,0 +1,75 @@
+[
+ {
+ "namespace": "trackingProtection",
+ "description": "experimental API allow requests through ETP",
+ "functions": [
+ {
+ "name": "allow",
+ "type": "function",
+ "description": "Add specific requests to a given allow-list",
+ "parameters": [
+ {
+ "name": "allowlistId",
+ "type": "string"
+ },
+ {
+ "name": "patterns",
+ "description": "Array of match patterns",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "options",
+ "type": "object",
+ "optional": true,
+ "properties": {
+ "hosts": {
+ "description": "Hosts to limit this bypass to (optional)",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "notHosts": {
+ "description": "Hosts to not allow this bypass for (optional)",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "optional": true
+ }
+ }
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "revoke",
+ "type": "function",
+ "description": "Revokes the given allow-list",
+ "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..1e82ee9722
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css
@@ -0,0 +1,3 @@
+#css-injection.red {
+ background-color: #0f0;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css b/browser/extensions/webcompat/injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css
new file mode 100644
index 0000000000..15a7fe1484
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css
@@ -0,0 +1,12 @@
+/**
+ * mail.google.com - The HTML email view does not allow horizontal scrolling
+ * on Firefox mobile due to a missing CSS rule which is only served to Chrome.
+ * Bug #1561371 - https://bugzilla.mozilla.org/show_bug.cgi?id=1561371
+ *
+ * HTML emails may sometimes contain content that does not wrap, yet the
+ * CSS served to Firefox Mobile does not permit scrolling horizontally.
+ * To prevent this UX frustration, we enable horizontal scrolling.
+ */
+body > #views {
+ overflow: auto;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1570119-teamcoco.com-scrollbar-width.css b/browser/extensions/webcompat/injections/css/bug1570119-teamcoco.com-scrollbar-width.css
new file mode 100644
index 0000000000..7a6c2c07c2
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1570119-teamcoco.com-scrollbar-width.css
@@ -0,0 +1,11 @@
+/**
+ * teamcoco.com - a scrollbar at the top covering navigation menu
+ * Bug #1570119 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570119
+ *
+ * The scrollbar is covering navigation items making them unusable.
+ * There are ::-webkit-scrollbar css rules already applied to the scrollbar,
+ * hiding it in Chrome. Adding the scrollbar-width: none fixes the issue in Firefox.
+ */
+.css-bdnz85 {
+ scrollbar-width: none;
+}
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..2ffd45a361
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css
@@ -0,0 +1,17 @@
+/**
+ * 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..d6c7e82e26
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css
@@ -0,0 +1,11 @@
+/**
+ * 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..c0f4fae1f5
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css
@@ -0,0 +1,24 @@
+/**
+ * Bug 1605611 - Cannot change Departure/arrival dates in Google Maps on Android
+ *
+ * This is step 3 - see injections/js/bug1605611-maps.google.com-directions-time.js.
+ * Google Maps calls .click() on a datetime-local input element, with the intent
+ * to show the native date picker. But the native date picker does not appear,
+ * because that only happens when a user initiated the click.
+ * To fix the problem of the date picker not appearing in Google Maps, alter the
+ * styles of the datetime-local input element, to be rendered on top of the
+ * usual UI (i.e. the icon and date/time text). This allows the user to summon
+ * the native date picker when they tap on the relevant UI in Google Maps.
+ */
+
+.ml-route-options-picker-content-button
+ > #ml-route-options-time-selector-time-input {
+ z-index: 1; /* overrides -5000, to show on top of the icon AND the rendered date */
+ opacity: 0; /* let the input element be fully transparent */
+ width: 100vw; /* render over the rendered date from Maps' dialog */
+ /* position this (absolute) element to fully cover the parent container */
+ left: 0;
+ bottom: 0;
+ top: 0;
+ height: 100%;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1610016-gaana.com-input-position-fix.css b/browser/extensions/webcompat/injections/css/bug1610016-gaana.com-input-position-fix.css
new file mode 100644
index 0000000000..258841fdda
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1610016-gaana.com-input-position-fix.css
@@ -0,0 +1,13 @@
+/**
+ * gaana.com - unable to accept T&C and Privacy Policy
+ * Bug #1610016 - https://bugzilla.mozilla.org/show_bug.cgi?id=1610016
+ * WebCompat issue #29886 - https://webcompat.com/issues/29886
+ *
+ * Unable to click on checkboxes due to input element floating to the right.
+ * More info https://bugzilla.mozilla.org/show_bug.cgi?id=997189. Adding explicit
+ * positioning to the input fixes the issue
+ */
+.agree_btns input {
+ top: 0;
+ left: 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..80f94aa306
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css
@@ -0,0 +1,13 @@
+/**
+ * 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..191985b691
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css
@@ -0,0 +1,13 @@
+/**
+ * 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/bug1645064-s-kanava.fi-invisible-charts.css b/browser/extensions/webcompat/injections/css/bug1645064-s-kanava.fi-invisible-charts.css
new file mode 100644
index 0000000000..d5c348ad25
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1645064-s-kanava.fi-invisible-charts.css
@@ -0,0 +1,12 @@
+/**
+ * s-kanava.fi - The tables carousel is missing
+ * Bug #1645064 - https://bugzilla.mozilla.org/show_bug.cgi?id=1645064
+ * WebCompat issue #53584 - https://webcompat.com/issues/53584
+ *
+ * This site runs into a known Flex issue, see bug 1469649. However, the issue
+ * is easy to workaround in this case by explicitly specifying the width of
+ * the flex container.
+ */
+.carousel .slider-wrapper.axis-horizontal .slider .slide {
+ max-width: 100%;
+}
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..e7a44a93d7
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css
@@ -0,0 +1,14 @@
+/**
+ * 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..3d7a069676
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ */
+@media screen and (max-width: 900px) {
+ .trending-wrapper .trending-items {
+ scrollbar-width: thin;
+ scrollbar-color: #f9ae3b #f5f5f5;
+ }
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1654865-sports.ndtv.com-float-fix.css b/browser/extensions/webcompat/injections/css/bug1654865-sports.ndtv.com-float-fix.css
new file mode 100644
index 0000000000..f9edefb735
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1654865-sports.ndtv.com-float-fix.css
@@ -0,0 +1,13 @@
+/**
+ * sports.ndtv.com - content is of the articles is not displayed
+ * Bug #1654865 - https://bugzilla.mozilla.org/show_bug.cgi?id=1654865
+ * WebCompat issue #55377 - https://webcompat.com/issues/55377
+ *
+ * The content has width:0, due to uncleared float and negative margin combination,
+ * which is https://bugzilla.mozilla.org/show_bug.cgi?id=1400958
+ * Adding clear: both; to the element located above the affected div
+ * the fixes the issue
+ */
+.t-brd {
+ clear: both;
+}
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..111ec522df
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css
@@ -0,0 +1,15 @@
+/**
+ * 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..2893e873ed
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css
@@ -0,0 +1,12 @@
+/**
+ * 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/bug1655049-dev.to-unclickable-button-fix.css b/browser/extensions/webcompat/injections/css/bug1655049-dev.to-unclickable-button-fix.css
new file mode 100644
index 0000000000..2e40f14955
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1655049-dev.to-unclickable-button-fix.css
@@ -0,0 +1,12 @@
+/**
+ * dev.to - not possible to open social buttons menu
+ * Bug #1655049 - https://bugzilla.mozilla.org/show_bug.cgi?id=1655049
+ * WebCompat issue #55782 - https://webcompat.com/issues/55782
+ *
+ * Social buttons menu is not opening due to svg receiving the click
+ * instead of the button. See https://bugzilla.mozilla.org/show_bug.cgi?id=1654934.
+ * Adding pointer-events: none to the svg allows to open the menu
+ */
+#article-show-more-button > * {
+ pointer-events: none;
+}
diff --git a/browser/extensions/webcompat/injections/css/bug1666771-zilow-map-overdraw.css b/browser/extensions/webcompat/injections/css/bug1666771-zilow-map-overdraw.css
new file mode 100644
index 0000000000..382ed99b50
--- /dev/null
+++ b/browser/extensions/webcompat/injections/css/bug1666771-zilow-map-overdraw.css
@@ -0,0 +1,17 @@
+/**
+ * zillow.com - Zillow using massive amounts of memory.
+ * Bug #1666771 - https://bugzilla.mozilla.org/show_bug.cgi?id=1666771
+ * Bug #1662297 - https://bugzilla.mozilla.org/show_bug.cgi?id=1662297
+ *
+ * Zillow's map is using a lot of memory, caused by large amounts of overdraw
+ * inside the map while rendering object boundaries. Setting `overflow: hidden`
+ * is a workaround until Zillow addressed this in a more permanent way.
+ *
+ * Note that this override is not without side effects: some lines in the map
+ * may/will be cut off. There is no side-effect free solution to this, and
+ * not intervening means the browser just freezes.
+ */
+
+.zillow-map-layer svg.full-boundary-svg {
+ overflow: hidden !important;
+}
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..4e7db8c5f9
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js
@@ -0,0 +1,11 @@
+"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/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..d04dcd7638
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
@@ -0,0 +1,29 @@
+"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..8bbab329c4
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js
@@ -0,0 +1,34 @@
+"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..0f61422494
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js
@@ -0,0 +1,48 @@
+"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/bug1570856-medium.com-menu-isTier1.js b/browser/extensions/webcompat/injections/js/bug1570856-medium.com-menu-isTier1.js
new file mode 100644
index 0000000000..f8bb926b60
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1570856-medium.com-menu-isTier1.js
@@ -0,0 +1,34 @@
+"use strict";
+
+/**
+ * medium.com - Override window.GLOBALS.useragent.isTier1 to be true
+ * WebCompat issue #25844 - https://webcompat.com/issues/25844
+ *
+ * This site is not showing main menu when scrolling. There is a GLOBALS variable
+ * at the bottom of the template being defined based on a server side UA detection.
+ * Setting window.GLOBALS.useragent.isTier1 to true makes the menu appear when scrolling
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "window.GLOBALS.useragent.isTier1 has been set to true for compatibility reasons. See https://webcompat.com/issues/25844 for details."
+);
+
+let globals = {};
+
+Object.defineProperty(window.wrappedJSObject, "GLOBALS", {
+ get: exportFunction(function() {
+ return globals;
+ }, window),
+
+ set: exportFunction(function(value = {}) {
+ globals = value;
+
+ if (!globals.useragent) {
+ globals.useragent = {};
+ }
+
+ globals.useragent.isTier1 = true;
+ }, 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..b6600e93f8
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js
@@ -0,0 +1,28 @@
+"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..aee07df0cc
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js
@@ -0,0 +1,82 @@
+"use strict";
+
+/* globals exportFunction */
+
+/**
+ * Bug 1605611 - Cannot change Departure/arrival dates in Google Maps on Android
+ *
+ * This patch does the following:
+ * 1. Re-enable the disabled "Leave now" button.
+ * 2. Fix the precision of datetime-local inputs (to minutes).
+ * 3. Fixup side effect from enabling the date picker UI via
+ * injections/css/bug1605611-maps.google.com-directions-time.css
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=1605611#c0 for details.
+ */
+
+// Step 1.
+document.addEventListener("DOMContentLoaded", () => {
+ // In case the element appeared before the MutationObserver was activated.
+ for (const elem of document.querySelectorAll(
+ ".ml-directions-time[disabled]"
+ )) {
+ 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) {
+ let restore = false;
+ for (const { target } of records) {
+ if (target.classList.contains("ml-directions-time")) {
+ if (!restore) {
+ restore = true;
+ mo.disconnect();
+ }
+ target.disabled = false;
+ }
+ }
+ if (restore) {
+ mo.observe(document.body, moOptions);
+ }
+ });
+ mo.observe(document.body, moOptions);
+});
+
+// Step 2.
+const originalValueAsNumberGetter = Object.getOwnPropertyDescriptor(
+ HTMLInputElement.prototype.wrappedJSObject,
+ "valueAsNumber"
+).get;
+Object.defineProperty(
+ HTMLInputElement.prototype.wrappedJSObject,
+ "valueAsNumber",
+ {
+ configurable: true,
+ enumerable: true,
+ get: originalValueAsNumberGetter,
+ set: exportFunction(function(v) {
+ if (this.type === "datetime-local" && v) {
+ const d = new Date(v);
+ d.setSeconds(0);
+ d.setMilliseconds(0);
+ v = d.getTime();
+ }
+ this.valueAsNumber = v;
+ }, window),
+ }
+);
+
+// Step 3.
+// injections/css/bug1605611-maps.google.com-directions-time.css fixes the bug,
+// but a side effect of allowing the user to click on the datetime-local input
+// is that the keyboard appears when the native date picker is closed.
+// Fix this by unfocusing the datetime-local input upon focus.
+document.addEventListener("focusin", ({ target }) => {
+ if (target.id === "ml-route-options-time-selector-time-input") {
+ target.blur();
+ }
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1610358-pcloud.com-appVersion-change.js b/browser/extensions/webcompat/injections/js/bug1610358-pcloud.com-appVersion-change.js
new file mode 100644
index 0000000000..c7654227e1
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1610358-pcloud.com-appVersion-change.js
@@ -0,0 +1,25 @@
+"use strict";
+
+/**
+ * Bug 1610358 - Add "mobile" to navigator.appVersion
+ * WebCompat issue #40353 - https://webcompat.com/issues/40353
+ *
+ * the site expecting navigator.appVersion to contain "mobile",
+ * otherwise it's serving a tablet version for Firefox mobile
+ */
+
+/* globals exportFunction */
+
+console.info(
+ "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/40353 for details."
+);
+
+const APP_VERSION = navigator.appVersion + " mobile";
+
+Object.defineProperty(window.navigator.wrappedJSObject, "appVersion", {
+ get: exportFunction(function() {
+ return APP_VERSION;
+ }, window),
+
+ set: exportFunction(function() {}, window),
+});
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..63bb420d8d
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js
@@ -0,0 +1,18 @@
+"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/bug1665035-dckids.com-cookieEnabled.js b/browser/extensions/webcompat/injections/js/bug1665035-dckids.com-cookieEnabled.js
new file mode 100644
index 0000000000..a243fae54a
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1665035-dckids.com-cookieEnabled.js
@@ -0,0 +1,30 @@
+"use strict";
+
+/**
+ * Bug 1665035 - enable navigator.cookieEnabled and spoof window.navigator on Linux
+ *
+ * Some of the games are not starting because navigator.cookieEnabled
+ * returns false for trackers with ETP strict. Overwriting the value allows
+ * to play the games. In addition, Linux desktop devices are incorrectly
+ * flagged as mobile devices (even if ETP is disabled), so spoofing
+ * window.navigator.platform here.
+ */
+
+console.info(
+ "window.cookieEnabled has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1665035 for details."
+);
+
+Object.defineProperty(window.navigator.wrappedJSObject, "cookieEnabled", {
+ value: true,
+ writable: false,
+});
+
+if (navigator.platform.includes("Linux")) {
+ console.info(
+ "navigator.platform has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1665035 for details."
+ );
+ Object.defineProperty(window.navigator.wrappedJSObject, "platform", {
+ value: "Win64",
+ writable: false,
+ });
+}
diff --git a/browser/extensions/webcompat/injections/js/bug1677442-store.hp.com-disable-indexeddb.js b/browser/extensions/webcompat/injections/js/bug1677442-store.hp.com-disable-indexeddb.js
new file mode 100644
index 0000000000..507e39cff5
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1677442-store.hp.com-disable-indexeddb.js
@@ -0,0 +1,20 @@
+"use strict";
+
+/**
+ * Bug 1677442 - disable indexedDB for d3nkfb7815bs43.cloudfront.net
+ *
+ * The site embeds an iframe with a 3D viewer. The request fails
+ * because BabylonJS (the 3d library) tries to access indexedDB
+ * from the third party context (d3nkfb7815bs43.cloudfront.net)
+ * Disabling indexedDB fixes it, causing it to fetch the 3d resource
+ * via network.
+ */
+
+console.info(
+ "window.indexedDB has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1677442 for details."
+);
+
+Object.defineProperty(window.wrappedJSObject, "indexedDB", {
+ get: undefined,
+ set: undefined,
+});
diff --git a/browser/extensions/webcompat/injections/js/bug1682238-gamearter.com-ua-change.js b/browser/extensions/webcompat/injections/js/bug1682238-gamearter.com-ua-change.js
new file mode 100644
index 0000000000..8475a9486f
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1682238-gamearter.com-ua-change.js
@@ -0,0 +1,27 @@
+"use strict";
+
+/*
+ * Bug 1682238 - Override navigator.userAgent for gamearter.com on macOS 11.0
+ * Bug 1680516 - Game is not loaded on gamearter.com
+ *
+ * Unity < 2021.1.0a2 is unable to correctly parse User Agents with
+ * "Mac OS X 11.0" in them, so let's override to "Mac OS X 10.16" instead
+ * for now.
+ */
+
+/* globals exportFunction */
+
+if (navigator.userAgent.includes("Mac OS X 11.")) {
+ console.info(
+ "The user agent has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1680516 for details."
+ );
+
+ let originalUA = navigator.userAgent;
+ Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
+ get: exportFunction(function() {
+ return originalUA.replace(/Mac OS X 11\.(\d)+;/, "Mac OS X 10.16;");
+ }, window),
+
+ set: exportFunction(function() {}, 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..dc939b2b06
--- /dev/null
+++ b/browser/extensions/webcompat/lib/about_compat_broker.js
@@ -0,0 +1,123 @@
+/* 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.portsToAboutCompatTabs = this.buildPorts();
+
+ this._injections = bindings.injections;
+ this._injections.bindAboutCompatBroker(this);
+
+ this._uaOverrides = bindings.uaOverrides;
+ this._uaOverrides.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 };
+ });
+ }
+
+ getOverrideOrInterventionById(id) {
+ for (const [type, things] of Object.entries({
+ overrides: this._uaOverrides.getAvailableOverrides(),
+ interventions: this._injections.getAvailableInjections(),
+ })) {
+ 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.getOverrideOrInterventionById(id);
+ if (!what) {
+ return Promise.reject(
+ `No such override or intervention to toggle: ${id}`
+ );
+ }
+ this.portsToAboutCompatTabs
+ .broadcast({ toggling: id, active: what.active })
+ .then(async () => {
+ switch (type) {
+ case "interventions": {
+ if (what.active) {
+ await this._injections.disableInjection(what);
+ } else {
+ await this._injections.enableInjection(what);
+ }
+ break;
+ }
+ case "overrides": {
+ if (what.active) {
+ await this._uaOverrides.disableOverride(what);
+ } else {
+ await this._uaOverrides.enableOverride(what);
+ }
+ break;
+ }
+ }
+ this.portsToAboutCompatTabs.broadcast({
+ toggled: id,
+ active: what.active,
+ });
+ });
+ break;
+ }
+ case "getOverridesAndInterventions": {
+ return Promise.resolve({
+ overrides:
+ (this._uaOverrides.isEnabled() &&
+ this.filterOverrides(
+ this._uaOverrides.getAvailableOverrides()
+ )) ||
+ false,
+ interventions:
+ (this._injections.isEnabled() &&
+ this.filterOverrides(
+ this._injections.getAvailableInjections()
+ )) ||
+ 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..9bfc6fbdb5
--- /dev/null
+++ b/browser/extensions/webcompat/lib/custom_functions.js
@@ -0,0 +1,96 @@
+/* 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;
+ },
+ pdk5fix: injection => {
+ const { urls, types } = injection.data;
+ const listener = (injection.data.listener = ({ requestId }) => {
+ replaceStringInRequest(
+ requestId,
+ "VideoContextChromeAndroid",
+ "VideoContextAndroid"
+ );
+ return {};
+ });
+ browser.webRequest.onBeforeRequest.addListener(listener, { urls, types }, [
+ "blocking",
+ ]);
+ },
+ pdk5fixDisable: injection => {
+ const { listener } = injection.data;
+ 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..6a7a6e8d4a
--- /dev/null
+++ b/browser/extensions/webcompat/lib/injections.js
@@ -0,0 +1,163 @@
+/* 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 platformMatches = ["all"];
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ platformMatches.push(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..793fa03139
--- /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/picture_in_picture_overrides.js b/browser/extensions/webcompat/lib/picture_in_picture_overrides.js
new file mode 100644
index 0000000000..febee193aa
--- /dev/null
+++ b/browser/extensions/webcompat/lib/picture_in_picture_overrides.js
@@ -0,0 +1,74 @@
+/* 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 PictureInPictureOverrides {
+ constructor(availableOverrides) {
+ this.pref = "enable_picture_in_picture_overrides";
+ this._prefEnabledOverrides = new Set();
+ this._availableOverrides = availableOverrides;
+ this.policies = browser.pictureInPictureChild.getPolicies();
+ }
+
+ async _checkGlobalPref() {
+ await browser.aboutConfigPrefs.getPref(this.pref).then(value => {
+ if (value === false) {
+ this._enabled = false;
+ } else {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(this.pref, true);
+ }
+ this._enabled = true;
+ }
+ });
+ }
+
+ async _checkSpecificOverridePref(id, pref) {
+ const isDisabled = await browser.aboutConfigPrefs.getPref(pref);
+ if (isDisabled === true) {
+ this._prefEnabledOverrides.delete(id);
+ } else {
+ this._prefEnabledOverrides.add(id);
+ }
+ }
+
+ bootup() {
+ const checkGlobal = async () => {
+ await this._checkGlobalPref();
+ this._onAvailableOverridesChanged();
+ };
+ browser.aboutConfigPrefs.onPrefChange.addListener(checkGlobal, this.pref);
+
+ const bootupPrefCheckPromises = [this._checkGlobalPref()];
+
+ for (const id of Object.keys(this._availableOverrides)) {
+ const pref = `disabled_picture_in_picture_overrides.${id}`;
+ const checkSingle = async () => {
+ await this._checkSpecificOverridePref(id, pref);
+ this._onAvailableOverridesChanged();
+ };
+ browser.aboutConfigPrefs.onPrefChange.addListener(checkSingle, pref);
+ bootupPrefCheckPromises.push(this._checkSpecificOverridePref(id, pref));
+ }
+
+ Promise.all(bootupPrefCheckPromises).then(() => {
+ this._onAvailableOverridesChanged();
+ });
+ }
+
+ async _onAvailableOverridesChanged() {
+ const policies = await this.policies;
+ let enabledOverrides = {};
+ for (const [id, override] of Object.entries(this._availableOverrides)) {
+ const enabled = this._enabled && this._prefEnabledOverrides.has(id);
+ for (const [url, policy] of Object.entries(override)) {
+ enabledOverrides[url] = enabled ? policy : policies.DEFAULT;
+ }
+ }
+ browser.pictureInPictureParent.setOverrides(enabledOverrides);
+ }
+}
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..638411007b
--- /dev/null
+++ b/browser/extensions/webcompat/lib/shims.js
@@ -0,0 +1,415 @@
+/* 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 */
+
+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) !== "beta_or_release") {
+ console.debug.apply(this, arguments);
+ }
+};
+let error = async function() {
+ if ((await releaseBranchPromise) !== "beta_or_release") {
+ console.error.apply(this, arguments);
+ }
+};
+let warn = async function() {
+ if ((await releaseBranchPromise) !== "beta_or_release") {
+ console.warn.apply(this, arguments);
+ }
+};
+
+class Shim {
+ constructor(opts) {
+ const { matches, unblocksOnOptIn } = opts;
+
+ this.branches = opts.branches;
+ this.bug = opts.bug;
+ this.file = opts.file;
+ this.hosts = opts.hosts;
+ this.id = opts.id;
+ this.matches = matches;
+ this.name = opts.name;
+ this.notHosts = opts.notHosts;
+ this.onlyIfBlockedByETP = opts.onlyIfBlockedByETP;
+ this._options = opts.options || {};
+ this.needsShimHelpers = opts.needsShimHelpers;
+ this.platform = opts.platform || "all";
+ this.unblocksOnOptIn = unblocksOnOptIn;
+
+ this._hostOptIns = new Set();
+
+ this._disabledByConfig = opts.disabled;
+ this._disabledGlobally = false;
+ this._disabledByPlatform = false;
+ this._disabledByReleaseBranch = false;
+
+ const pref = `disabled_shims.${this.id}`;
+
+ 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).then(value => {
+ this._disabledPrefValue = value;
+ }),
+ platformPromise.then(platform => {
+ this._disabledByPlatform =
+ this.platform !== "all" && this.platform !== platform;
+ return platform;
+ }),
+ releaseBranchPromise.then(branch => {
+ this._disabledByReleaseBranch =
+ this.branches && !this.branches.includes(branch);
+ return branch;
+ }),
+ ]).then(([_, platform, branch]) => {
+ 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) {
+ return false;
+ }
+
+ if (this._disabledPrefValue !== undefined) {
+ return !this._disabledPrefValue;
+ }
+
+ return (
+ !this._disabledByConfig &&
+ !this._disabledByPlatform &&
+ !this._disabledByReleaseBranch
+ );
+ }
+
+ enable() {
+ this._disabledGlobally = false;
+ this._onEnabledStateChanged();
+ }
+
+ disable() {
+ this._disabledGlobally = true;
+ this._onEnabledStateChanged();
+ }
+
+ _onEnabledStateChanged() {
+ if (!this.enabled) {
+ return this._revokeRequestsInETP();
+ }
+ return this._allowRequestsInETP();
+ }
+
+ _allowRequestsInETP() {
+ return browser.trackingProtection.allow(this.id, this.matches, {
+ hosts: this.hosts,
+ notHosts: this.notHosts,
+ });
+ }
+
+ _revokeRequestsInETP() {
+ return browser.trackingProtection.revoke(this.id);
+ }
+
+ meantForHost(host) {
+ const { hosts, notHosts } = this;
+ if (hosts || notHosts) {
+ if (
+ (notHosts && notHosts.includes(host)) ||
+ (hosts && !hosts.includes(host))
+ ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ isTriggeredByURL(url) {
+ if (!this.matches) {
+ return false;
+ }
+
+ if (!this._matcher) {
+ this._matcher = browser.matchPatterns.getMatcher(this.matches);
+ }
+
+ return this._matcher.matches(url);
+ }
+
+ async onUserOptIn(host) {
+ const { unblocksOnOptIn } = this;
+ if (unblocksOnOptIn) {
+ await browser.trackingProtection.allow(this.id, unblocksOnOptIn, {
+ hosts: [host],
+ });
+ }
+
+ this._hostOptIns.add(host);
+ }
+
+ hasUserOptedInAlready(host) {
+ return this._hostOptIns.has(host);
+ }
+}
+
+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();
+ }
+
+ _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));
+ }
+ }
+
+ const allShimPatterns = new Set();
+ for (const { matches } of this.shims.values()) {
+ for (const matchPattern of matches) {
+ allShimPatterns.add(matchPattern);
+ }
+ }
+
+ if (!allShimPatterns.size) {
+ debug("Skipping shims; none enabled");
+ return;
+ }
+
+ const urls = [...allShimPatterns];
+ debug("Shimming these match patterns", urls);
+
+ browser.webRequest.onBeforeRequest.addListener(
+ this._ensureShimForRequestOnTab.bind(this),
+ { urls, types: ["script"] },
+ ["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.enable();
+ } else {
+ shim.disable();
+ }
+ }
+ }
+
+ async _onMessageFromShim(payload, sender, sendResponse) {
+ const { tab } = 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 shim.options;
+ } else if (message === "optIn") {
+ try {
+ await shim.onUserOptIn(new URL(url).hostname);
+ warn("** User opted in on tab ", id, "for", shimId);
+ } catch (err) {
+ console.error(err);
+ throw new Error("error");
+ }
+ }
+
+ return undefined;
+ }
+
+ 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, 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 shimToApply;
+ for (const shim of this.shims.values()) {
+ await shim.ready;
+
+ if (!shim.enabled) {
+ 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 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)) {
+ return undefined;
+ }
+
+ // If this URL isn't meant for this shim, don't apply it.
+ if (!shim.isTriggeredByURL(url)) {
+ continue;
+ }
+
+ shimToApply = shim;
+ break;
+ }
+
+ 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 re-run the
+ // shim JS just in case. Shims should gracefully handle this as well.
+ const { bug, file, id, name, needsShimHelpers } = shimToApply;
+ warn("Shimming", name, "on tabId", tabId, "frameId", frameId);
+
+ const warning = `${name} is being shimmed by Firefox. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`;
+
+ try {
+ if (needsShimHelpers?.length) {
+ 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 }
+ );
+ } else {
+ await browser.tabs.executeScript(tabId, {
+ code: `console.warn(${JSON.stringify(warning)})`,
+ frameId,
+ runAt: "document_start",
+ });
+ }
+ } catch (_) {}
+
+ // If any shims matched the script to replace it, then let the original
+ // request complete without ever hitting the network, with a blank script.
+ return { redirectUrl: browser.runtime.getURL(`shims/${file}`) };
+ }
+
+ // Sanity check: if no shims are over-riding a given URL and it was meant to
+ // be blocked by ETP, then block it.
+ if (unblocked) {
+ error("unexpected:", url, "was not shimmed, and had to be re-blocked");
+ return { cancel: true };
+ }
+
+ debug("allowing", url);
+ return undefined;
+ }
+}
+
+module.exports = Shims;
diff --git a/browser/extensions/webcompat/lib/ua_overrides.js b/browser/extensions/webcompat/lib/ua_overrides.js
new file mode 100644
index 0000000000..7024583e4e
--- /dev/null
+++ b/browser/extensions/webcompat/lib/ua_overrides.js
@@ -0,0 +1,265 @@
+/* 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, telemetryKey, uaTransformer } = override.config;
+ const listener = details => {
+ // We set the "used" telemetry key if the user would have had the
+ // override applied, regardless of whether it is actually applied.
+ if (!details.frameId && override.shouldSendDetailedTelemetry) {
+ // For now, we only care about Telemetry on Fennec, where telemetry
+ // is sent in Java code (as part of the core ping). That code must
+ // be aware of each key we send, which we send as a SharedPreference.
+ browser.sharedPreferences.setBoolPref(`${telemetryKey}Used`, true);
+ }
+
+ // 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.experimentActive ||
+ 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;
+
+ // If telemetry is being collected, note the addon version.
+ if (telemetryKey) {
+ const { version } = browser.runtime.getManifest();
+ browser.sharedPreferences.setCharPref(`${telemetryKey}Version`, version);
+ }
+
+ // If collecting detailed telemetry on the override, note that it was activated.
+ if (override.shouldSendDetailedTelemetry) {
+ browser.sharedPreferences.setBoolPref(`${telemetryKey}Ready`, true);
+ }
+ }
+
+ onOverrideConfigChanged(override) {
+ // Check whether the override should be hidden from about:compat.
+ override.hidden = override.config.hidden;
+
+ // Also hide if the override is in an experiment the user is not part of.
+ if (override.config.experiment && !override.experimentActive) {
+ override.hidden = true;
+ }
+
+ // 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;
+ }
+
+ // Only send detailed telemetry if the user is actively in an experiment or
+ // has opted into an experimental feature.
+ override.shouldSendDetailedTelemetry =
+ override.config.telemetryKey &&
+ (override.experimentActive || override.permanentPrefEnabled);
+
+ // 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.experimentActive &&
+ !override.config.telemetryKey &&
+ 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;
+
+ // Note whether the user is actively in the override's experiment (if any).
+ override.experimentActive = false;
+ const experiment = override.config.experiment;
+ if (experiment) {
+ // We expect the definition to have either one string for 'experiment'
+ // (just one branch) or an array of strings (multiple branches). So
+ // here we turn the string case into a one-element array for the loop.
+ const branches = Array.isArray(experiment)
+ ? experiment
+ : [experiment];
+ for (const branch of branches) {
+ if (await browser.experiments.isActive(branch)) {
+ override.experimentActive = true;
+ break;
+ }
+ }
+ }
+
+ // 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..2f4c84aeed
--- /dev/null
+++ b/browser/extensions/webcompat/manifest.json
@@ -0,0 +1,144 @@
+{
+ "manifest_version": 2,
+ "name": "Web Compatibility Interventions",
+ "description": "Urgent post-release fixes for web compatibility.",
+ "version": "19.0.0",
+
+ "applications": {
+ "gecko": {
+ "id": "webcompat@mozilla.org",
+ "strict_min_version": "59.0b5"
+ }
+ },
+
+ "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"]
+ }
+ },
+ "experiments": {
+ "schema": "experiment-apis/experiments.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/experiments.js",
+ "paths": [["experiments"]]
+ }
+ },
+ "matchPatterns": {
+ "schema": "experiment-apis/matchPatterns.json",
+ "child": {
+ "scopes": ["addon_child"],
+ "script": "experiment-apis/matchPatterns.js",
+ "paths": [["matchPatterns"]]
+ }
+ },
+ "pictureInPictureChild": {
+ "schema": "experiment-apis/pictureInPicture.json",
+ "child": {
+ "scopes": ["addon_child"],
+ "script": "experiment-apis/pictureInPicture.js",
+ "paths": [["pictureInPictureChild"]]
+ }
+ },
+ "pictureInPictureParent": {
+ "schema": "experiment-apis/pictureInPicture.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/pictureInPicture.js",
+ "paths": [["pictureInPictureParent"]]
+ }
+ },
+ "sharedPreferences": {
+ "schema": "experiment-apis/sharedPreferences.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experiment-apis/sharedPreferences.js",
+ "paths": [["sharedPreferences"]]
+ }
+ },
+ "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-MmZkN2QaIHhfRWPZ8TVRjijTn5Ci1iEabtTEWrt9CCo='; default-src 'self'; base-uri moz-extension://*; object-src 'none'",
+
+ "permissions": [
+ "tabs",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+
+ "background": {
+ "scripts": [
+ "lib/module_shim.js",
+ "lib/messaging_helper.js",
+ "lib/intervention_helpers.js",
+ "data/injections.js",
+ "data/picture_in_picture_overrides.js",
+ "data/shims.js",
+ "data/ua_overrides.js",
+ "lib/about_compat_broker.js",
+ "lib/custom_functions.js",
+ "lib/injections.js",
+ "lib/picture_in_picture_overrides.js",
+ "lib/shims.js",
+ "lib/ua_overrides.js",
+ "run.js"
+ ]
+ },
+
+ "web_accessible_resources": [
+ "shims/adsafeprotected-ima.js",
+ "shims/bmauth.js",
+ "shims/eluminate.js",
+ "shims/empty-script.js",
+ "shims/facebook-sdk.js",
+ "shims/google-analytics-ecommerce-plugin.js",
+ "shims/google-analytics-legacy.js",
+ "shims/google-analytics-tag-manager.js",
+ "shims/google-analytics.js",
+ "shims/google-publisher-tags.js",
+ "shims/live-test-shim.js",
+ "shims/mochitest-shim-1.js",
+ "shims/mochitest-shim-2.js",
+ "shims/mochitest-shim-3.js",
+ "shims/rambler-authenticator.js",
+ "shims/rich-relevance.js"
+ ]
+}
diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build
new file mode 100644
index 0000000000..624db15176
--- /dev/null
+++ b/browser/extensions/webcompat/moz.build
@@ -0,0 +1,125 @@
+# -*- 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/picture_in_picture_overrides.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/experiments.js",
+ "experiment-apis/experiments.json",
+ "experiment-apis/matchPatterns.js",
+ "experiment-apis/matchPatterns.json",
+ "experiment-apis/pictureInPicture.js",
+ "experiment-apis/pictureInPicture.json",
+ "experiment-apis/sharedPreferences.js",
+ "experiment-apis/sharedPreferences.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/bug1561371-mail.google.com-allow-horizontal-scrolling.css",
+ "injections/css/bug1570119-teamcoco.com-scrollbar-width.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/bug1610016-gaana.com-input-position-fix.css",
+ "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css",
+ "injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css",
+ "injections/css/bug1645064-s-kanava.fi-invisible-charts.css",
+ "injections/css/bug1651917-teletrader.com.body-transform-origin.css",
+ "injections/css/bug1653075-livescience.com-scrollbar-width.css",
+ "injections/css/bug1654865-sports.ndtv.com-float-fix.css",
+ "injections/css/bug1654877-preev.com-moz-appearance-fix.css",
+ "injections/css/bug1654907-reactine.ca-hide-unsupported.css",
+ "injections/css/bug1655049-dev.to-unclickable-button-fix.css",
+ "injections/css/bug1666771-zilow-map-overdraw.css",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["js"] += [
+ "injections/js/bug0000000-testbed-js-injection.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/bug1570856-medium.com-menu-isTier1.js",
+ "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
+ "injections/js/bug1605611-maps.google.com-directions-time.js",
+ "injections/js/bug1610358-pcloud.com-appVersion-change.js",
+ "injections/js/bug1631811-datastudio.google.com-indexedDB.js",
+ "injections/js/bug1665035-dckids.com-cookieEnabled.js",
+ "injections/js/bug1677442-store.hp.com-disable-indexeddb.js",
+ "injections/js/bug1682238-gamearter.com-ua-change.js",
+]
+
+FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [
+ "shims/adsafeprotected-ima.js",
+ "shims/bmauth.js",
+ "shims/eluminate.js",
+ "shims/empty-script.js",
+ "shims/facebook-sdk.js",
+ "shims/google-analytics-ecommerce-plugin.js",
+ "shims/google-analytics-legacy.js",
+ "shims/google-analytics-tag-manager.js",
+ "shims/google-analytics.js",
+ "shims/google-publisher-tags.js",
+ "shims/live-test-shim.js",
+ "shims/mochitest-shim-1.js",
+ "shims/mochitest-shim-2.js",
+ "shims/mochitest-shim-3.js",
+ "shims/rambler-authenticator.js",
+ "shims/rich-relevance.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/picture_in_picture_overrides.js",
+ "lib/shim_messaging_helper.js",
+ "lib/shims.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..923f9d7446
--- /dev/null
+++ b/browser/extensions/webcompat/run.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";
+
+/* globals AboutCompatBroker, AVAILABLE_INJECTIONS, AVAILABLE_SHIMS,
+ AVAILABLE_PIP_OVERRIDES, AVAILABLE_UA_OVERRIDES, CUSTOM_FUNCTIONS,
+ Injections, PictureInPictureOverrides, Shims, UAOverrides */
+
+const injections = new Injections(AVAILABLE_INJECTIONS, CUSTOM_FUNCTIONS);
+const uaOverrides = new UAOverrides(AVAILABLE_UA_OVERRIDES);
+const pipOverrides = new PictureInPictureOverrides(AVAILABLE_PIP_OVERRIDES);
+const shims = new Shims(AVAILABLE_SHIMS);
+
+const aboutCompatBroker = new AboutCompatBroker({
+ injections,
+ uaOverrides,
+});
+
+aboutCompatBroker.bootup();
+injections.bootup();
+uaOverrides.bootup();
+pipOverrides.bootup();
diff --git a/browser/extensions/webcompat/shims/adsafeprotected-ima.js b/browser/extensions/webcompat/shims/adsafeprotected-ima.js
new file mode 100644
index 0000000000..de273de7d8
--- /dev/null
+++ b/browser/extensions/webcompat/shims/adsafeprotected-ima.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 1508639 - Shim Ad Safe Protected's Google IMA adapter
+ */
+
+if (!window.googleImaVansAdapter) {
+ const shimId = "AdSafeProtectedGoogleIMAAdapter";
+
+ 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);
+ });
+ };
+ })();
+
+ window.googleImaVansAdapter = {
+ init: () => {},
+ dispose: () => {},
+ };
+
+ // Treat it as an opt-in when the user clicks on a video
+ // TODO: Improve this! It races to tell the bg script to unblock the ad from
+ // https://pubads.g.doubleclick.net/gampad/ads before the page loads them.
+ async function click(e) {
+ if (e.isTrusted && e.target.closest("#video-player")) {
+ document.documentElement.removeEventListener("click", click, true);
+ await sendMessageToAddon("optIn");
+ // TODO: reload ima3.js?
+ }
+ }
+ document.documentElement.addEventListener("click", 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/eluminate.js b/browser/extensions/webcompat/shims/eluminate.js
new file mode 100644
index 0000000000..863d4ef2da
--- /dev/null
+++ b/browser/extensions/webcompat/shims/eluminate.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";
+
+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.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;
+
+ function cmExecuteTagQueue() {
+ var b = window.cmTagQueue;
+ if (b) {
+ if (!Array.isArray(b)) {
+ return undefined;
+ }
+ for (var a = 0; a < b.length; ++a) {
+ window[b[a][0]].apply(window, b[a].slice(1));
+ }
+ }
+ return true;
+ }
+ cmExecuteTagQueue();
+}
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/facebook-sdk.js b/browser/extensions/webcompat/shims/facebook-sdk.js
new file mode 100644
index 0000000000..95928be857
--- /dev/null
+++ b/browser/extensions/webcompat/shims/facebook-sdk.js
@@ -0,0 +1,198 @@
+/* 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
+ *
+ * The Facebook SDK is commonly used by sites to allow users to authenticate
+ * for logins, but it is blocked by strict tracking protection. It is possible
+ * to shim the SDK and allow users to still opt into logging in via Facebook.
+ * It is also possible to replace any Facebook widgets or comments with
+ * placeholders that the user may click to opt into loading the content.
+ */
+
+if (!window.FB) {
+ const originalUrl = document.currentScript.src;
+ const pendingParses = [];
+
+ function getGUID() {
+ return (
+ Math.random()
+ .toString(36)
+ .substring(2) + Date.now().toString(36)
+ );
+ }
+
+ const shimId = "FacebookSDK";
+
+ 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 = getGUID();
+ return new Promise(resolve => {
+ const payload = {
+ message,
+ messageId,
+ shimId,
+ };
+ pendingMessages.set(messageId, resolve);
+ channel.port1.postMessage(payload);
+ });
+ };
+ })();
+
+ let ready = false;
+ let initInfo;
+ const needPopup =
+ !/app_runner/.test(window.name) && !/iframe_canvas/.test(window.name);
+ const popupName = getGUID();
+
+ if (needPopup) {
+ const oldWindowOpen = window.open;
+ window.open = function(href, name, params) {
+ try {
+ const url = new URL(href);
+ if (
+ url.protocol === "https:" &&
+ (url.hostname === "m.facebook.com" ||
+ url.hostname === "www.facebook.com") &&
+ url.pathname.endsWith("/oauth")
+ ) {
+ name = popupName;
+ }
+ } catch (_) {}
+ return oldWindowOpen.call(window, href, name, params);
+ };
+ }
+
+ async function allowFacebookSDK(callback) {
+ await sendMessageToAddon("optIn");
+
+ window.FB = undefined;
+ const oldInit = window.fbAsyncInit;
+ window.fbAsyncInit = () => {
+ ready = true;
+ if (typeof initInfo !== "undefined") {
+ window.FB.init(initInfo);
+ } else if (oldInit) {
+ oldInit();
+ }
+ if (callback) {
+ callback();
+ }
+ };
+
+ const s = document.createElement("script");
+ s.src = originalUrl;
+ await new Promise((resolve, reject) => {
+ s.onerror = reject;
+ s.onload = function() {
+ for (const args of pendingParses) {
+ window.FB.XFBML.parse.apply(window.FB.XFBML, args);
+ }
+ resolve();
+ };
+ document.head.appendChild(s);
+ });
+ }
+
+ function buildPopupParams() {
+ 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;
+ }
+
+ async function doLogin(a, b) {
+ window.FB.login(a, b);
+ }
+
+ function proxy(name, fn) {
+ return function() {
+ if (ready) {
+ return window.FB[name].apply(this, arguments);
+ }
+ return fn.apply(this, arguments);
+ };
+ }
+
+ window.FB = {
+ api: proxy("api", () => {}),
+ AppEvents: {
+ EventNames: {},
+ logPageView: () => {},
+ },
+ Event: {
+ subscribe: () => {},
+ },
+ getAccessToken: proxy("getAccessToken", () => null),
+ getAuthResponse: proxy("getAuthResponse", () => {
+ return { status: "" };
+ }),
+ getLoginStatus: proxy("getLoginStatus", cb => {
+ cb({ status: "" });
+ }),
+ getUserID: proxy("getUserID", () => {}),
+ init: _initInfo => {
+ if (ready) {
+ doLogin(_initInfo);
+ } else {
+ initInfo = _initInfo; // in case the site is not using fbAsyncInit
+ }
+ },
+ login: (a, b) => {
+ // 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) {
+ window.open("about:blank", popupName, buildPopupParams());
+ }
+ allowFacebookSDK(() => {
+ doLogin(a, b);
+ });
+ },
+ logout: proxy("logout", cb => cb()),
+ XFBML: {
+ parse: e => {
+ pendingParses.push([e]);
+ },
+ },
+ };
+
+ if (window.fbAsyncInit) {
+ window.fbAsyncInit();
+ }
+}
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..056186f75c
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics-legacy.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/. */
+
+// based on https://github.com/gorhill/uBlock/blob/caa8e7d35ba61214a9d13e7d324b2bd2aa73237f/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 (a[0] === "_link" && 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-analytics-tag-manager.js b/browser/extensions/webcompat/shims/google-analytics-tag-manager.js
new file mode 100644
index 0000000000..b8398f91b6
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics-tag-manager.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/. */
+
+// based on https://github.com/gorhill/uBlock/blob/caa8e7d35ba61214a9d13e7d324b2bd2aa73237f/src/web_accessible_resources/googletagmanager_gtm.js
+
+"use strict";
+
+if (!window.ga) {
+ window.ga = () => {};
+
+ try {
+ window.dataLayer.hide.end();
+ } catch (_) {}
+
+ const dl = window.dataLayer;
+ if (typeof dl.push === "function") {
+ dl.push = o => {
+ if (o instanceof Object && typeof o.eventCallback === "function") {
+ setTimeout(o.eventCallback, 1);
+ }
+ };
+ }
+}
diff --git a/browser/extensions/webcompat/shims/google-analytics.js b/browser/extensions/webcompat/shims/google-analytics.js
new file mode 100644
index 0000000000..f0162b1f63
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-analytics.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/. */
+
+// based on https://github.com/gorhill/uBlock/blob/8a1a8b103f56e4fcef1264e02dfd718a29bda006/src/web_accessible_resources/google-analytics_analytics.js
+
+"use strict";
+
+if (!window[window.GoogleAnalyticsObject || "ga"]) {
+ function ga() {
+ const len = arguments.length;
+ if (!len) {
+ return;
+ }
+ const args = Array.from(arguments);
+ let fn;
+ let a = args[len - 1];
+ if (a instanceof Object && a.hitCallback instanceof Function) {
+ fn = a.hitCallback;
+ } else {
+ const pos = args.indexOf("hitCallback");
+ if (pos !== -1 && args[pos + 1] instanceof Function) {
+ fn = args[pos + 1];
+ }
+ }
+ if (!(fn instanceof Function)) {
+ return;
+ }
+ try {
+ fn();
+ } catch (_) {}
+ }
+ ga.create = () => {};
+ ga.getByName = () => null;
+ ga.getAll = () => [];
+ ga.remove = () => {};
+ ga.loaded = true;
+
+ const gaName = window.GoogleAnalyticsObject || "ga";
+ window[gaName] = ga;
+}
+
+try {
+ window.dataLayer.hide.end();
+} catch (_) {}
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..756e29bac5
--- /dev/null
+++ b/browser/extensions/webcompat/shims/google-publisher-tags.js
@@ -0,0 +1,163 @@
+/* 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 1600538 - Shim Google Publisher Tags
+ */
+
+"use strict";
+
+if (!window.googletag?.apiReady) {
+ const noopfn = function() {};
+ const noopthisfn = function() {
+ return this;
+ };
+ const noopnullfn = function() {
+ return null;
+ };
+ const nooparrayfn = function() {
+ return [];
+ };
+ const noopstrfn = function() {
+ return "";
+ };
+
+ function newPassbackSlot() {
+ return {
+ display: noopfn,
+ get: noopnullfn,
+ set: noopthisfn,
+ setClickUrl: noopthisfn,
+ setTagForChildDirectedTreatment: noopthisfn,
+ setTargeting: noopthisfn,
+ updateTargetingFromMap: noopthisfn,
+ };
+ }
+
+ function display(id) {
+ const parent = document.getElementById(id);
+ if (parent) {
+ parent.appendChild(document.createElement("div"));
+ }
+ }
+
+ const companionAdsService = {
+ addEventListener: noopthisfn,
+ enableSyncLoading: noopfn,
+ setRefreshUnfilledSlots: noopfn,
+ };
+
+ const contentService = {
+ addEventListener: noopthisfn,
+ setContent: noopfn,
+ };
+
+ const pubadsService = {
+ addEventListener: noopthisfn,
+ clear: noopfn,
+ clearCategoryExclusions: noopthisfn,
+ clearTagForChildDirectedTreatment: noopthisfn,
+ clearTargeting: noopthisfn,
+ collapseEmptyDivs: noopfn,
+ defineOutOfPagePassback: () => newPassbackSlot(),
+ definePassback: () => newPassbackSlot(),
+ disableInitialLoad: noopfn,
+ display,
+ enableAsyncRendering: noopfn,
+ enableSingleRequest: noopfn,
+ enableSyncRendering: noopfn,
+ enableVideoAds: noopfn,
+ get: noopnullfn,
+ getAttributeKeys: nooparrayfn,
+ getTargeting: noopfn,
+ getTargetingKeys: nooparrayfn,
+ getSlots: nooparrayfn,
+ refresh: noopfn,
+ set: noopthisfn,
+ setCategoryExclusion: noopthisfn,
+ setCentering: noopfn,
+ setCookieOptions: noopthisfn,
+ setForceSafeFrame: noopthisfn,
+ setLocation: noopthisfn,
+ setPublisherProvidedId: noopthisfn,
+ setRequestNonPersonalizedAds: noopthisfn,
+ setSafeFrameConfig: noopthisfn,
+ setTagForChildDirectedTreatment: noopthisfn,
+ setTargeting: noopthisfn,
+ setVideoContent: noopthisfn,
+ updateCorrelator: noopfn,
+ };
+
+ function newSizeMappingBuilder() {
+ return {
+ addSize: noopthisfn,
+ build: noopnullfn,
+ };
+ }
+
+ function newSlot() {
+ return {
+ addService: noopthisfn,
+ clearCategoryExclusions: noopthisfn,
+ clearTargeting: noopthisfn,
+ defineSizeMapping: noopthisfn,
+ get: noopnullfn,
+ getAdUnitPath: nooparrayfn,
+ getAttributeKeys: nooparrayfn,
+ getCategoryExclusions: nooparrayfn,
+ getDomId: noopstrfn,
+ getSlotElementId: noopstrfn,
+ getSlotId: noopthisfn,
+ getTargeting: nooparrayfn,
+ getTargetingKeys: nooparrayfn,
+ set: noopthisfn,
+ setCategoryExclusion: noopthisfn,
+ setClickUrl: noopthisfn,
+ setCollapseEmptyDiv: noopthisfn,
+ setTargeting: noopthisfn,
+ };
+ }
+
+ let gt = window.googletag;
+ if (!gt) {
+ gt = window.googletag = {};
+ }
+
+ for (const [key, value] of Object.entries({
+ apiReady: true,
+ companionAds: () => companionAdsService,
+ content: () => contentService,
+ defineOutOfPageSlot: () => newSlot(),
+ defineSlot: () => newSlot(),
+ destroySlots: noopfn,
+ disablePublisherConsole: noopfn,
+ display,
+ enableServices: noopfn,
+ getVersion: noopstrfn,
+ pubads: () => pubadsService,
+ pubabsReady: true,
+ setAdIframeTitle: noopfn,
+ sizeMapping: () => newSizeMappingBuilder(),
+ })) {
+ gt[key] = value;
+ }
+
+ function runCmd(fn) {
+ try {
+ fn();
+ } catch (_) {}
+ return 1;
+ }
+
+ const cmds = gt.cmd;
+ const newCmd = [];
+ newCmd.push = runCmd;
+ gt.cmd = newCmd;
+
+ for (const cmd of cmds) {
+ runCmd(cmd);
+ }
+}
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..552020820f
--- /dev/null
+++ b/browser/extensions/webcompat/shims/live-test-shim.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";
+
+/* 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/mochitest-shim-1.js b/browser/extensions/webcompat/shims/mochitest-shim-1.js
new file mode 100644
index 0000000000..d18e965f3c
--- /dev/null
+++ b/browser/extensions/webcompat/shims/mochitest-shim-1.js
@@ -0,0 +1,89 @@
+/* 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..3b60038599
--- /dev/null
+++ b/browser/extensions/webcompat/shims/mochitest-shim-2.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.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/rambler-authenticator.js b/browser/extensions/webcompat/shims/rambler-authenticator.js
new file mode 100644
index 0000000000..0554eabc2c
--- /dev/null
+++ b/browser/extensions/webcompat/shims/rambler-authenticator.js
@@ -0,0 +1,86 @@
+/* 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..d7f2802ac9
--- /dev/null
+++ b/browser/extensions/webcompat/shims/rich-relevance.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 1449347 - Rich Relevance
+ */
+
+"use strict";
+
+if (!window.r3_common) {
+ const noopfn = () => {};
+
+ window.rr_flush_onload = noopfn;
+ window.r3 = noopfn;
+ window.r3_home = noopfn;
+ window.RR = noopfn;
+ window.r3_common = function() {};
+ window.r3_common.prototype = {
+ addContext: noopfn,
+ addPlacementType: noopfn,
+ setUserId: noopfn,
+ setSessionId: noopfn,
+ setClickthruServer: noopfn,
+ setBaseUrl: noopfn,
+ setApiKey: noopfn,
+ };
+}
diff --git a/browser/extensions/webcompat/tests/browser/browser.ini b/browser/extensions/webcompat/tests/browser/browser.ini
new file mode 100644
index 0000000000..2a7bc6800d
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser.ini
@@ -0,0 +1,13 @@
+[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_shims.js]
+skip-if = verify
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..cd07bb7390
--- /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_task(async function setup() {
+ 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..7bd1dde950
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/head.js
@@ -0,0 +1,139 @@
+"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.import(
+ "resource://testing-common/UrlClassifierTestUtils.jsm"
+);
+
+async function testShimRuns(
+ testPage,
+ frame,
+ trackersAllowed = true,
+ expectOptIn = true
+) {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: testPage,
+ waitForLoad: true,
+ });
+
+ const TrackingProtection = tab.ownerGlobal.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..bf86cc3b0b
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/iframe_test.html
@@ -0,0 +1,17 @@
+<!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..9c0204615c
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test.html
@@ -0,0 +1,18 @@
+<!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..38dfc26208
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_2.html
@@ -0,0 +1,18 @@
+<!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..7f8848d447
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/shims_test_3.html
@@ -0,0 +1,18 @@
+<!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");