diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/extensions/webcompat | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/extensions/webcompat')
164 files changed, 14951 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/about-compat/AboutCompat.jsm b/browser/extensions/webcompat/about-compat/AboutCompat.jsm new file mode 100644 index 0000000000..7c844726f8 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/AboutCompat.jsm @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["AboutCompat"]; + +const Services = + globalThis.Services || + ChromeUtils.import("resource://gre/modules/Services.jsm").Services; + +const addonID = "webcompat@mozilla.org"; +const addonPageRelativeURL = "/about-compat/aboutCompat.html"; + +function AboutCompat() { + this.chromeURL = + WebExtensionPolicy.getByID(addonID).getURL(addonPageRelativeURL); +} +AboutCompat.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIAboutModule"]), + getURIFlags() { + return ( + Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS | + Ci.nsIAboutModule.IS_SECURE_CHROME_UI + ); + }, + + newChannel(aURI, aLoadInfo) { + const uri = Services.io.newURI(this.chromeURL); + const channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo); + channel.originalURI = aURI; + + channel.owner = ( + Services.scriptSecurityManager.createContentPrincipal || + // Handles fallback to earlier versions. + // eslint-disable-next-line mozilla/valid-services-property + Services.scriptSecurityManager.createCodebasePrincipal + )(uri, aLoadInfo.originAttributes); + return channel; + }, +}; diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.css b/browser/extensions/webcompat/about-compat/aboutCompat.css new file mode 100644 index 0000000000..b51db7f9f5 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutCompat.css @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@media (any-pointer: fine) { + :root { + font-family: sans-serif; + margin: 40px auto; + min-width: 30em; + max-width: 60em; + } + + table { + width: 100%; + padding-bottom: 2em; + border-spacing: 0; + } + + td { + border-bottom: 1px solid var(--in-content-border-color); + } + + td:last-child > button { + float: inline-end; + } +} + +/* Mobile UI where common.css is not loaded */ + +@media (any-pointer: coarse), (any-pointer: none) { + * { + margin: 0; + padding: 0; + } + + :root { + --background-color: #fff; + --text-color: #0c0c0d; + --border-color: #e1e1e2; + --button-background-color: #f5f5f5; + --selected-tab-text-color: #0061e0; + } + + @media (prefers-color-scheme: dark) { + :root { + --background-color: #292833; + --text-color: #f9f9fa; + --border-color: rgba(255, 255, 255, 0.15); + --button-background-color: rgba(0, 0, 0, 0.15); + --selected-tab-text-color: #00ddff; + } + } + + body { + background-color: var(--background-color); + color: var(--text-color); + font: message-box; + font-size: 14px; + -moz-text-size-adjust: none; + display: grid; + grid-template-areas: "a b c" "d d d"; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: fit-content(100%) 1fr; + } + + .tab[data-l10n-id="label-overrides"] { + grid-area: a; + } + + .tab[data-l10n-id="label-interventions"] { + grid-area: b; + } + + .tab[data-l10n-id="label-smartblock"] { + grid-area: c; + } + + table { + grid-area: d; + } + + table, + tr, + p { + display: block; + } + + table { + border-top: 2px solid var(--border-color); + margin-top: -2px; + width: 100%; + z-index: 1; + display: none; + } + + tr { + border-bottom: 1px solid var(--border-color); + padding: 0; + } + + a { + color: inherit; + font-size: 94%; + } + + .tab { + cursor: pointer; + z-index: 2; + display: inline-block; + text-align: left; + border-block: 2px solid transparent; + font-size: 1em; + font-weight: bold; + padding: 1em; + } + + .tab.active { + color: var(--selected-tab-text-color); + border-bottom-color: currentColor; + margin-bottom: 0; + padding-bottom: calc(1em + 2px); + } + + .tab.active + table { + display: block; + } + + td { + grid-area: b; + padding-left: 1em; + } + + td:first-child { + grid-area: a; + padding-top: 1em; + } + + td:last-child { + grid-area: c; + padding-bottom: 1em; + } + + tr { + display: grid; + grid-template-areas: "a c" "b c"; + grid-template-columns: 1fr 6.5em; + } + + td[colspan="4"] { + padding: 1em; + font-style: italic; + text-align: center; + } + + td:not([colspan]):nth-child(1) { + font-weight: bold; + padding-bottom: 0.25em; + } + + td:nth-child(2) { + padding-bottom: 1em; + } + + td:nth-child(3) { + display: flex; + padding: 0; + } + + button { + cursor: pointer; + width: 100%; + height: 100%; + background: var(--button-background-color); + color: inherit; + inset-inline-end: 0; + margin: 0; + padding: 0; + border: 0; + border-inline-start: 1px solid var(--border-color); + font-weight: 600; + appearance: none; + } + + button::-moz-focus-inner { + border: 0; + } +} diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.html b/browser/extensions/webcompat/about-compat/aboutCompat.html new file mode 100644 index 0000000000..d820f20ee2 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutCompat.html @@ -0,0 +1,51 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE html> +<html> + <head> + <base /> + + <!-- If you change this script tag you must update the hash in the extension's + `content_security_policy` 'sha256-MmZkN2QaIHhfRWPZ8TVRjijTn5Ci1iEabtTEWrt9CCo=' --> + <script> + /* globals browser */ document.head.firstElementChild.href = + browser.runtime.getURL(""); + </script> + + <meta charset="utf-8" /> + <meta name="color-scheme" content="light dark" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="about-compat/aboutCompat.css" /> + <link + rel="stylesheet" + media="screen and (pointer:fine), projection" + type="text/css" + href="chrome://global/skin/in-content/common.css" + /> + <link rel="localization" href="toolkit/about/aboutCompat.ftl" /> + <title data-l10n-id="text-title"></title> + <script src="about-compat/aboutCompat.js"></script> + </head> + <body> + <h2 class="tab active" data-l10n-id="label-overrides"></h2> + <table id="overrides"> + <col /> + <col /> + <col /> + </table> + <h2 class="tab" data-l10n-id="label-interventions"></h2> + <table id="interventions"> + <col /> + <col /> + <col /> + </table> + <h2 class="tab" data-l10n-id="label-smartblock"></h2> + <table id="smartblock" class="shims"> + <col /> + <col /> + <col /> + </table> + </body> +</html> diff --git a/browser/extensions/webcompat/about-compat/aboutCompat.js b/browser/extensions/webcompat/about-compat/aboutCompat.js new file mode 100644 index 0000000000..e01b853877 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutCompat.js @@ -0,0 +1,283 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +let availablePatches; + +const portToAddon = (function () { + let port; + + function connect() { + port = browser.runtime.connect({ name: "AboutCompatTab" }); + port.onMessage.addListener(onMessageFromAddon); + port.onDisconnect.addListener(e => { + port = undefined; + }); + } + + connect(); + + async function send(message) { + if (port) { + return port.postMessage(message); + } + return Promise.reject("background script port disconnected"); + } + + return { send }; +})(); + +const $ = function (sel) { + return document.querySelector(sel); +}; + +const DOMContentLoadedPromise = new Promise(resolve => { + document.addEventListener( + "DOMContentLoaded", + () => { + resolve(); + }, + { once: true } + ); +}); + +Promise.all([ + browser.runtime.sendMessage("getAllInterventions"), + DOMContentLoadedPromise, +]).then(([info]) => { + document.body.addEventListener("click", async evt => { + const ele = evt.target; + if (ele.nodeName === "BUTTON") { + const row = ele.closest("[data-id]"); + if (row) { + evt.preventDefault(); + ele.disabled = true; + const id = row.getAttribute("data-id"); + try { + await browser.runtime.sendMessage({ command: "toggle", id }); + } catch (_) { + ele.disabled = false; + } + } + } else if (ele.classList.contains("tab")) { + document.querySelectorAll(".tab").forEach(tab => { + tab.classList.remove("active"); + }); + ele.classList.add("active"); + } + }); + + availablePatches = info; + redraw(); +}); + +function onMessageFromAddon(msg) { + const alsoShowHidden = location.hash === "#all"; + + if ("interventionsChanged" in msg) { + redrawTable($("#interventions"), msg.interventionsChanged, alsoShowHidden); + } + + if ("overridesChanged" in msg) { + redrawTable($("#overrides"), msg.overridesChanged, alsoShowHidden); + } + + if ("shimsChanged" in msg) { + updateShimTables(msg.shimsChanged, alsoShowHidden); + } + + const id = msg.toggling || msg.toggled; + const button = $(`[data-id="${id}"] button`); + if (!button) { + return; + } + const active = msg.active; + document.l10n.setAttributes( + button, + active ? "label-disable" : "label-enable" + ); + button.disabled = !!msg.toggling; +} + +function redraw() { + if (!availablePatches) { + return; + } + const { overrides, interventions, shims } = availablePatches; + const alsoShowHidden = location.hash === "#all"; + redrawTable($("#overrides"), overrides, alsoShowHidden); + redrawTable($("#interventions"), interventions, alsoShowHidden); + updateShimTables(shims, alsoShowHidden); +} + +function clearTableAndAddMessage(table, msgId) { + table.querySelectorAll("tr").forEach(tr => { + tr.remove(); + }); + + const tr = document.createElement("tr"); + tr.className = "message"; + tr.id = msgId; + + const td = document.createElement("td"); + td.setAttribute("colspan", "3"); + document.l10n.setAttributes(td, msgId); + tr.appendChild(td); + + table.appendChild(tr); +} + +function hideMessagesOnTable(table) { + table.querySelectorAll("tr.message").forEach(tr => { + tr.remove(); + }); +} + +function updateShimTables(shimsChanged, alsoShowHidden) { + const tables = document.querySelectorAll("table.shims"); + if (!tables.length) { + return; + } + + for (const { bug, disabledReason, hidden, id, name, type } of shimsChanged) { + // if any shim is disabled by global pref, all of them are. just show the + // "disabled in about:config" message on each shim table in that case. + if (disabledReason === "globalPref") { + for (const table of tables) { + clearTableAndAddMessage(table, "text-disabled-in-about-config"); + } + return; + } + + // otherwise, find which table the shim belongs in. if there is none, + // ignore the shim (we're not showing it on the UI for whatever reason). + const table = document.querySelector(`table.shims#${type}`); + if (!table) { + continue; + } + + // similarly, skip shims hidden from the UI (only for testing, etc). + if (!alsoShowHidden && hidden) { + continue; + } + + // also, hide the shim if it is disabled because it is not meant for this + // platform, release (etc) rather than being disabled by pref/about:compat + const notApplicable = + disabledReason && + disabledReason !== "pref" && + disabledReason !== "session"; + if (!alsoShowHidden && notApplicable) { + continue; + } + + // create an updated table-row for the shim + const tr = document.createElement("tr"); + tr.setAttribute("data-id", id); + + let td = document.createElement("td"); + td.innerText = name; + tr.appendChild(td); + + td = document.createElement("td"); + const a = document.createElement("a"); + a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`; + document.l10n.setAttributes(a, "label-more-information", { bug }); + a.target = "_blank"; + td.appendChild(a); + tr.appendChild(td); + + td = document.createElement("td"); + tr.appendChild(td); + const button = document.createElement("button"); + document.l10n.setAttributes( + button, + disabledReason ? "label-enable" : "label-disable" + ); + td.appendChild(button); + + // is it already in the table? + const row = table.querySelector(`tr[data-id="${id}"]`); + if (row) { + row.replaceWith(tr); + } else { + table.appendChild(tr); + } + } + + for (const table of tables) { + if (!table.querySelector("tr:not(.message)")) { + // no shims? then add a message that none are available for this platform/config + clearTableAndAddMessage(table, `text-no-${table.id}`); + } else { + // otherwise hide any such message, since we have shims on the list + hideMessagesOnTable(table); + } + } +} + +function redrawTable(table, data, alsoShowHidden) { + const df = document.createDocumentFragment(); + table.querySelectorAll("tr").forEach(tr => { + tr.remove(); + }); + + let noEntriesMessage; + if (data === false) { + noEntriesMessage = "text-disabled-in-about-config"; + } else if (data.length === 0) { + noEntriesMessage = `text-no-${table.id}`; + } + + if (noEntriesMessage) { + const tr = document.createElement("tr"); + df.appendChild(tr); + + const td = document.createElement("td"); + td.setAttribute("colspan", "3"); + document.l10n.setAttributes(td, noEntriesMessage); + tr.appendChild(td); + + table.appendChild(df); + return; + } + + for (const row of data) { + if (row.hidden && !alsoShowHidden) { + continue; + } + + const tr = document.createElement("tr"); + tr.setAttribute("data-id", row.id); + df.appendChild(tr); + + let td = document.createElement("td"); + td.innerText = row.domain; + tr.appendChild(td); + + td = document.createElement("td"); + const a = document.createElement("a"); + const bug = row.bug; + a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`; + document.l10n.setAttributes(a, "label-more-information", { bug }); + a.target = "_blank"; + td.appendChild(a); + tr.appendChild(td); + + td = document.createElement("td"); + tr.appendChild(td); + const button = document.createElement("button"); + document.l10n.setAttributes( + button, + row.active ? "label-disable" : "label-enable" + ); + td.appendChild(button); + } + table.appendChild(df); +} + +window.onhashchange = redraw; diff --git a/browser/extensions/webcompat/about-compat/aboutPage.js b/browser/extensions/webcompat/about-compat/aboutPage.js new file mode 100644 index 0000000000..0f2e7c4ad4 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutPage.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global ExtensionAPI, XPCOMUtils */ + +const Services = + globalThis.Services || + ChromeUtils.import("resource://gre/modules/Services.jsm").Services; + +XPCOMUtils.defineLazyServiceGetter( + this, + "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler" +); + +const ResourceSubstitution = "webcompat"; +const ProcessScriptURL = "resource://webcompat/aboutPageProcessScript.js"; +const ContractID = "@mozilla.org/network/protocol/about;1?what=compat"; + +this.aboutPage = class extends ExtensionAPI { + onStartup() { + const { rootURI } = this.extension; + + resProto.setSubstitution( + ResourceSubstitution, + Services.io.newURI("about-compat/", null, rootURI) + ); + + if (!(ContractID in Cc)) { + Services.ppmm.loadProcessScript(ProcessScriptURL, true); + this.processScriptRegistered = true; + } + } + + onShutdown() { + resProto.setSubstitution(ResourceSubstitution, null); + + if (this.processScriptRegistered) { + Services.ppmm.removeDelayedProcessScript(ProcessScriptURL); + } + } +}; diff --git a/browser/extensions/webcompat/about-compat/aboutPage.json b/browser/extensions/webcompat/about-compat/aboutPage.json new file mode 100644 index 0000000000..42e6114188 --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutPage.json @@ -0,0 +1,6 @@ +[ + { + "namespace": "aboutCompat", + "description": "Enables the about:compat page" + } +] diff --git a/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js b/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js new file mode 100644 index 0000000000..13cb4fd0bf --- /dev/null +++ b/browser/extensions/webcompat/about-compat/aboutPageProcessScript.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/process-script */ + +"use strict"; + +// Note: This script is used only when a static registration for our +// component is not already present in the libxul binary. + +const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + +const classID = Components.ID("{97bf9550-2a7b-11e9-b56e-0800200c9a66}"); + +if (!Cm.isCIDRegistered(classID)) { + const { ComponentUtils } = ChromeUtils.importESModule( + "resource://gre/modules/ComponentUtils.sys.mjs" + ); + + const factory = ComponentUtils.generateSingletonFactory(function () { + const { AboutCompat } = ChromeUtils.import( + "resource://webcompat/AboutCompat.jsm" + ); + return new AboutCompat(); + }); + + Cm.registerFactory( + classID, + "about:compat", + "@mozilla.org/network/protocol/about;1?what=compat", + factory + ); +} diff --git a/browser/extensions/webcompat/components.conf b/browser/extensions/webcompat/components.conf new file mode 100644 index 0000000000..ca5a6c3dbd --- /dev/null +++ b/browser/extensions/webcompat/components.conf @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Note: This file will add static component registration entries for our +# components to the libxul binary, though the actual component JSMs will be +# packaged with the extension. +Classes = [ + { + 'cid': '{97bf9550-2a7b-11e9-b56e-0800200c9a66}', + 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=compat'], + 'jsm': 'resource://webcompat/AboutCompat.jsm', + 'constructor': 'AboutCompat', + }, +] diff --git a/browser/extensions/webcompat/data/injections.js b/browser/extensions/webcompat/data/injections.js new file mode 100644 index 0000000000..3c0dc58bce --- /dev/null +++ b/browser/extensions/webcompat/data/injections.js @@ -0,0 +1,1059 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals module, require */ + +// This is a hack for the tests. +if (typeof InterventionHelpers === "undefined") { + var InterventionHelpers = require("../lib/intervention_helpers"); +} + +/** + * For detailed information on our policies, and a documention on this format + * and its possibilites, please check the Mozilla-Wiki at + * + * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides + */ +const AVAILABLE_INJECTIONS = [ + { + id: "testbed-injection", + platform: "all", + domain: "webcompat-addon-testbed.herokuapp.com", + bug: "0000000", + hidden: true, + contentScripts: { + matches: ["*://webcompat-addon-testbed.herokuapp.com/*"], + css: [ + { + file: "injections/css/bug0000000-testbed-css-injection.css", + }, + ], + js: [ + { + file: "injections/js/bug0000000-testbed-js-injection.js", + }, + ], + }, + }, + { + id: "bug1452707", + platform: "all", + domain: "ib.absa.co.za", + bug: "1452707", + contentScripts: { + matches: ["https://ib.absa.co.za/*"], + js: [ + { + file: "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js", + }, + ], + }, + }, + { + id: "bug1457335", + platform: "desktop", + domain: "histography.io", + bug: "1457335", + contentScripts: { + matches: ["*://histography.io/*"], + js: [ + { + file: "injections/js/bug1457335-histography.io-ua-change.js", + }, + ], + }, + }, + { + id: "bug1472075", + platform: "desktop", + domain: "bankofamerica.com", + bug: "1472075", + contentScripts: { + matches: [ + "*://*.bankofamerica.com/*", + "*://*.ml.com/*", // #120104 + ], + js: [ + { + file: "injections/js/bug1472075-bankofamerica.com-ua-change.js", + }, + ], + }, + }, + { + id: "bug1579159", + platform: "android", + domain: "m.tailieu.vn", + bug: "1579159", + contentScripts: { + matches: ["*://m.tailieu.vn/*", "*://m.elib.vn/*"], + js: [ + { + file: "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1583366", + platform: "desktop", + domain: "Download prompt for files with no content-type", + bug: "1583366", + data: { + urls: ["https://ads-us.rd.linksynergy.com/as.php*"], + contentType: { + name: "content-type", + value: "text/html; charset=utf-8", + }, + }, + customFunc: "noSniffFix", + }, + { + id: "bug1570328", + platform: "android", + domain: "developer.apple.com", + bug: "1570328", + contentScripts: { + matches: ["*://developer.apple.com/*"], + css: [ + { + file: "injections/css/bug1570328-developer-apple.com-transform-scale.css", + }, + ], + }, + }, + { + id: "bug1575000", + platform: "all", + domain: "apply.lloydsbank.co.uk", + bug: "1575000", + contentScripts: { + matches: ["*://apply.lloydsbank.co.uk/*"], + css: [ + { + file: "injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css", + }, + ], + }, + }, + { + id: "bug1605611", + platform: "android", + domain: "maps.google.com", + bug: "1605611", + contentScripts: { + matches: InterventionHelpers.matchPatternsForGoogle( + "*://www.google.", + "/maps*" + ), + css: [ + { + file: "injections/css/bug1605611-maps.google.com-directions-time.css", + }, + ], + js: [ + { + file: "injections/js/bug1605611-maps.google.com-directions-time.js", + }, + ], + }, + }, + { + id: "bug1610344", + platform: "all", + domain: "directv.com.co", + bug: "1610344", + contentScripts: { + matches: [ + "https://*.directv.com.co/*", + "https://*.directv.com.ec/*", // bug 1827706 + ], + css: [ + { + file: "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css", + }, + ], + }, + }, + { + id: "bug1644830", + platform: "desktop", + domain: "usps.com", + bug: "1644830", + contentScripts: { + matches: ["https://*.usps.com/*"], + css: [ + { + file: "injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css", + }, + ], + }, + }, + { + id: "bug1651917", + platform: "android", + domain: "teletrader.com", + bug: "1651917", + contentScripts: { + matches: ["*://*.teletrader.com/*"], + css: [ + { + file: "injections/css/bug1651917-teletrader.com.body-transform-origin.css", + }, + ], + }, + }, + { + id: "bug1653075", + platform: "desktop", + domain: "livescience.com", + bug: "1653075", + contentScripts: { + matches: ["*://*.livescience.com/*"], + css: [ + { + file: "injections/css/bug1653075-livescience.com-scrollbar-width.css", + }, + ], + }, + }, + { + id: "bug1654877", + platform: "android", + domain: "preev.com", + bug: "1654877", + contentScripts: { + matches: ["*://preev.com/*"], + css: [ + { + file: "injections/css/bug1654877-preev.com-moz-appearance-fix.css", + }, + ], + }, + }, + { + id: "bug1654907", + platform: "android", + domain: "reactine.ca", + bug: "1654907", + contentScripts: { + matches: ["*://*.reactine.ca/*"], + css: [ + { + file: "injections/css/bug1654907-reactine.ca-hide-unsupported.css", + }, + ], + }, + }, + { + id: "bug1631811", + platform: "all", + domain: "datastudio.google.com", + bug: "1631811", + contentScripts: { + matches: ["https://datastudio.google.com/embed/reporting/*"], + js: [ + { + file: "injections/js/bug1631811-datastudio.google.com-indexedDB.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1694470", + platform: "android", + domain: "m.myvidster.com", + bug: "1694470", + contentScripts: { + matches: ["https://m.myvidster.com/*"], + css: [ + { + file: "injections/css/bug1694470-myvidster.com-content-not-shown.css", + }, + ], + }, + }, + { + id: "bug1731825", + platform: "desktop", + domain: "Office 365 email handling prompt", + bug: "1731825", + contentScripts: { + matches: [ + "*://*.live.com/*", + "*://*.office.com/*", + "*://*.sharepoint.com/*", + "*://*.office365.com/*", + ], + js: [ + { + file: "injections/js/bug1731825-office365-email-handling-prompt-autohide.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1707795", + platform: "desktop", + domain: "Office Excel spreadsheets", + bug: "1707795", + contentScripts: { + matches: [ + "*://*.live.com/*", + "*://*.office.com/*", + "*://*.sharepoint.com/*", + ], + css: [ + { + file: "injections/css/bug1707795-office365-sheets-overscroll-disable.css", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1712833", + platform: "all", + domain: "buskocchi.desuca.co.jp", + bug: "1712833", + contentScripts: { + matches: ["*://buskocchi.desuca.co.jp/*"], + css: [ + { + file: "injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css", + }, + ], + }, + }, + { + id: "bug1722955", + platform: "android", + domain: "frontgate.com", + bug: "1722955", + contentScripts: { + matches: ["*://*.frontgate.com/*"], + js: [ + { + file: "lib/ua_helpers.js", + }, + { + file: "injections/js/bug1722955-frontgate.com-ua-override.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1724764", + platform: "android", + domain: "Issues related to missing window.print", + bug: "1724764", + contentScripts: { + matches: [ + "*://*.edupage.org/*", // 1804477 and 1800118 + ], + js: [ + { + file: "injections/js/bug1724764-window-print.js", + }, + ], + }, + }, + { + id: "bug1724868", + platform: "android", + domain: "news.yahoo.co.jp", + bug: "1724868", + contentScripts: { + matches: ["*://news.yahoo.co.jp/articles/*", "*://s.yimg.jp/*"], + js: [ + { + file: "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1741234", + platform: "all", + domain: "patient.alphalabs.ca", + bug: "1741234", + contentScripts: { + matches: ["*://patient.alphalabs.ca/*"], + css: [ + { + file: "injections/css/bug1741234-patient.alphalabs.ca-height-fix.css", + }, + ], + }, + }, + { + id: "bug1739489", + platform: "desktop", + domain: "Sites using draft.js", + bug: "1739489", + contentScripts: { + matches: [ + "*://draftjs.org/*", // Bug 1739489 + "*://www.facebook.com/*", // Bug 1739489 + "*://twitter.com/*", // Bug 1776229 + "*://mobile.twitter.com/*", // Bug 1776229 + "*://*.reddit.com/*", // Bug 1829755 + ], + js: [ + { + file: "injections/js/bug1739489-draftjs-beforeinput.js", + }, + ], + }, + }, + { + id: "bug1765947", + platform: "android", + domain: "veniceincoming.com", + bug: "1765947", + contentScripts: { + matches: ["*://veniceincoming.com/*"], + css: [ + { + file: "injections/css/bug1765947-veniceincoming.com-left-fix.css", + }, + ], + }, + }, + { + id: "bug11769762", + platform: "all", + domain: "tiktok.com", + bug: "1769762", + contentScripts: { + matches: ["https://www.tiktok.com/*"], + js: [ + { + file: "injections/js/bug1769762-tiktok.com-plugins-shim.js", + }, + ], + }, + }, + { + id: "bug1770962", + platform: "all", + domain: "coldwellbankerhomes.com", + bug: "1770962", + contentScripts: { + matches: ["*://*.coldwellbankerhomes.com/*"], + css: [ + { + file: "injections/css/bug1770962-coldwellbankerhomes.com-image-height.css", + }, + ], + }, + }, + { + id: "bug1774490", + platform: "all", + domain: "rainews.it", + bug: "1774490", + contentScripts: { + matches: ["*://www.rainews.it/*"], + css: [ + { + file: "injections/css/bug1774490-rainews.it-gallery-fix.css", + }, + ], + }, + }, + { + id: "bug1774005", + platform: "all", + domain: "Sites relying on window.InstallTrigger", + bug: "1774005", + contentScripts: { + matches: [ + "*://*.crunchyroll.com/*", // Bug 1777597 + "*://*.ersthelfer.tv/*", // Bug 1817520 + "*://*.webex.com/*", // Bug 1788934 + "*://ifcinema.institutfrancais.com/*", // Bug 1806423 + "*://islamionline.islamicbank.ps/*", // Bug 1821439 + "*://*.itv.com/*", // Bug 1830203 + "*://mobilevikings.be/*/registration/*", // Bug 1797400 + "*://www.schoolnutritionandfitness.com/*", // Bug 1793761 + ], + js: [ + { + file: "injections/js/bug1774005-installtrigger-shim.js", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1784302", + platform: "android", + domain: "open.toutiao.com", + bug: "1784302", + contentScripts: { + matches: ["*://open.toutiao.com/*"], + js: [ + { + file: "injections/js/bug1784302-effectiveType-shim.js", + }, + ], + }, + }, + { + id: "bug1784141", + platform: "android", + domain: "aveeno.com and acuvue.com", + bug: "1784141", + contentScripts: { + matches: [ + "*://*.aveeno.com/*", + "*://*.aveeno.ca/*", + "*://*.aveeno.com.au/*", + "*://*.aveeno.co.kr/*", + "*://*.aveeno.co.uk/*", + "*://*.aveeno.ie/*", + "*://*.acuvue.com/*", // 1804730 + "*://*.acuvue.com.ar/*", + "*://*.acuvue.com.br/*", + "*://*.acuvue.ca/*", + "*://*.acuvue-fr.ca/*", + "*://*.acuvue.cl/*", + "*://*.acuvue.co.cr/*", + "*://*.acuvue.com.co/*", + "*://*.acuvue.com.do/*", + "*://*.acuvue.com.pe/*", + "*://*.acuvue.com.sv/*", + "*://*.acuvue.com.gt/*", + "*://*.acuvue.hn/*", + "*://*.acuvue.com.mx/*", + "*://*.acuvue.com.pa/*", + "*://*.acuvue.com.py/*", + "*://*.acuvue.com.pr/*", + "*://*.acuvue.com.uy/*", + "*://*.acuvue.com.au/*", + "*://*.acuvue.com.cn/*", + "*://*.acuvue.com.hk/*", + "*://*.acuvue.co.in/*", + "*://*.acuvue.co.id/*", + "*://acuvuevision.jp/*", + "*://*.acuvue.co.kr/*", + "*://*.acuvue.com.my/*", + "*://*.acuvue.co.nz/*", + "*://*.acuvue.com.sg/*", + "*://*.acuvue.com.tw/*", + "*://*.acuvue.co.th/*", + "*://*.acuvue.com.vn/*", + "*://*.acuvue.at/*", + "*://*.acuvue.be/*", + "*://*.fr.acuvue.be/*", + "*://*.acuvue-croatia.com/*", + "*://*.acuvue.cz/*", + "*://*.acuvue.dk/*", + "*://*.acuvue.fi/*", + "*://*.acuvue.fr/*", + "*://*.acuvue.de/*", + "*://*.acuvue.gr/*", + "*://*.acuvue.hu/*", + "*://*.acuvue.ie/*", + "*://*.acuvue.co.il/*", + "*://*.acuvue.it/*", + "*://*.acuvuekz.com/*", + "*://*.acuvue.lu/*", + "*://*.en.acuvuearabia.com/*", + "*://*.acuvuearabia.com/*", + "*://*.acuvue.nl/*", + "*://*.acuvue.no/*", + "*://*.acuvue.pl/*", + "*://*.acuvue.pt/*", + "*://*.acuvue.ro/*", + "*://*.acuvue.ru/*", + "*://*.acuvue.sk/*", + "*://*.acuvue.si/*", + "*://*.acuvue.co.za/*", + "*://*.jnjvision.com.tr/*", + "*://*.acuvue.co.uk/*", + "*://*.acuvue.ua/*", + "*://*.acuvue.com.pe/*", + "*://*.acuvue.es/*", + "*://*.acuvue.se/*", + "*://*.acuvue.ch/*", + ], + css: [ + { + file: "injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css", + }, + ], + }, + }, + { + id: "bug1784199", + platform: "all", + domain: "Sites based on Entrata Platform", + bug: "1784199", + contentScripts: { + matches: [ + "*://*.aptsovation.com/*", + "*://*.avanabayview.com/*", // #118617 + "*://*.breakpointeandcoronado.com/*", // #117735 + "*://*.liveatlasathens.com/*", // #111189 + "*://*.liveobserverpark.com/*", // #105244 + "*://*.midwayurban.com/*", // #116523 + "*://*.nhcalaska.com/*", + "*://*.prospectportal.com/*", // #115206 + "*://*.securityproperties.com/*", + "*://*.theloftsorlando.com/*", + "*://*.vanallenapartments.com/*", // #120056 + ], + css: [ + { + file: "injections/css/bug1784199-entrata-platform-unsupported.css", + }, + ], + }, + }, + { + id: "bug1795490", + platform: "android", + domain: "www.china-airlines.com", + bug: "1795490", + contentScripts: { + matches: ["*://www.china-airlines.com/*"], + js: [ + { + file: "injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js", + }, + ], + }, + }, + { + id: "bug1799968", + platform: "linux", + domain: "www.samsung.com", + bug: "1799968", + contentScripts: { + matches: ["*://www.samsung.com/*/watches/*/*"], + js: [ + { + file: "injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js", + }, + ], + }, + }, + { + id: "bug1799980", + platform: "all", + domain: "healow.com", + bug: "1799980", + contentScripts: { + matches: ["*://healow.com/*"], + js: [ + { + file: "injections/js/bug1799980-healow.com-infinite-loop-fix.js", + }, + ], + }, + }, + { + id: "bug1799994", + platform: "desktop", + domain: "www.vivobarefoot.com", + bug: "1799994", + contentScripts: { + matches: ["*://www.vivobarefoot.com/*"], + css: [ + { + file: "injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css", + }, + ], + }, + }, + { + id: "bug1800000", + platform: "desktop", + domain: "www.honda.co.uk", + bug: "1800000", + contentScripts: { + matches: ["*://www.honda.co.uk/cars/book-a-service.html*"], + css: [ + { + file: "injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css", + }, + ], + }, + }, + { + id: "bug1448747", + platform: "android", + domain: "FastClick breakage", + bug: "1448747", + contentScripts: { + matches: [ + "*://*.co2meter.com/*", // 10959 + "*://*.franmar.com/*", // 27273 + "*://*.themusiclab.org/*", // 49667 + "*://*.oregonfoodbank.org/*", // 53203 + "*://*.fourbarrelcoffee.com/*", // 59427 + "*://bluetokaicoffee.com/*", // 99867 + "*://bathpublishing.com/*", // 100145 + "*://dylantalkstone.com/*", // 101356 + "*://renewd.com.au/*", // 104998 + "*://*.lamudi.co.id/*", // 106767 + "*://*.thehawksmoor.com/*", // 107549 + "*://weaversofireland.com/*", // 116816 + "*://*.iledefrance-mobilites.fr/*", // 117344 + "*://*.lawnmowerpartsworld.com/*", // 117577 + "*://*.discountcoffee.co.uk/*", // 118757 + "*://torguard.net/*", // 120113 + "*://*.arcsivr.com/*", // 120716 + ], + js: [ + { + file: "injections/js/bug1448747-fastclick-shim.js", + }, + ], + }, + }, + { + id: "bug1818818", + platform: "android", + domain: "FastClick breakage - legacy", + bug: "1818818", + contentScripts: { + matches: [ + "*://*.chatiw.com/*", // 5544 + "*://*.wellcare.com/*", // 116595 + ], + js: [ + { + file: "injections/js/bug1818818-fastclick-legacy-shim.js", + }, + ], + }, + }, + { + id: "bug1819476", + platform: "all", + domain: "axisbank.com", + bug: "1819476", + contentScripts: { + matches: ["*://*.axisbank.com/*"], + js: [ + { + file: "injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js", + }, + ], + }, + }, + { + id: "bug1819450", + platform: "android", + domain: "cmbchina.com", + bug: "1819450", + contentScripts: { + matches: ["*://www.cmbchina.com/*", "*://cmbchina.com/*"], + js: [ + { + file: "injections/js/bug1819450-cmbchina.com-ua-change.js", + }, + ], + }, + }, + { + id: "bug1819678", + platform: "android", + domain: "cnki.net", + bug: "1819678", + contentScripts: { + matches: ["*://*.cnki.net/*"], + js: [ + { + file: "injections/js/bug1819678-cnki.net-undisable-search-field.js", + }, + ], + }, + }, + { + id: "bug1827678-webc77727", + platform: "android", + domain: "free4talk.com", + bug: "1827678", + contentScripts: { + matches: ["*://www.free4talk.com/*"], + js: [ + { + file: "injections/js/bug1819678-free4talk.com-window-chrome-shim.js", + }, + ], + }, + }, + { + id: "bug1827678-webc119017", + platform: "desktop", + domain: "nppes.cms.hhs.gov", + bug: "1827678", + contentScripts: { + matches: ["*://nppes.cms.hhs.gov/*"], + css: [ + { + file: "injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css", + }, + ], + }, + }, + { + id: "bug1830776", + platform: "all", + domain: "blueshieldca.com", + bug: "1830776", + contentScripts: { + matches: ["*://*.blueshieldca.com/*"], + js: [ + { + file: "injections/js/bug1830776-blueshieldca.com-unsupported.js", + }, + ], + }, + }, + { + id: "bug1829949", + platform: "desktop", + domain: "tomshardware.com", + bug: "1829949", + contentScripts: { + matches: ["*://*.tomshardware.com/*"], + css: [ + { + file: "injections/css/bug1829949-tomshardware.com-scrollbar-width.css", + }, + ], + }, + }, + { + id: "bug1829952", + platform: "android", + domain: "eventer.co.il", + bug: "1829952", + contentScripts: { + matches: ["*://*.eventer.co.il/*"], + css: [ + { + file: "injections/css/bug1829952-eventer.co.il-button-height.css", + }, + ], + }, + }, + { + id: "bug1830747", + platform: "android", + domain: "my.babbel.com", + bug: "1830747", + contentScripts: { + matches: ["*://my.babbel.com/*"], + css: [ + { + file: "injections/css/bug1830747-babbel.com-page-height.css", + }, + ], + }, + }, + { + id: "bug1830752", + platform: "all", + domain: "afisha.ru", + bug: "1830752", + contentScripts: { + matches: ["*://*.afisha.ru/*"], + css: [ + { + file: "injections/css/bug1830752-afisha.ru-slider-pointer-events.css", + }, + ], + }, + }, + { + id: "bug1830761", + platform: "all", + domain: "91mobiles.com", + bug: "1830761", + contentScripts: { + matches: ["*://*.91mobiles.com/*"], + css: [ + { + file: "injections/css/bug1830761-91mobiles.com-content-height.css", + }, + ], + }, + }, + { + id: "bug1830796", + platform: "android", + domain: "copyleaks.com", + bug: "1830796", + contentScripts: { + matches: ["*://*.copyleaks.com/*"], + css: [ + { + file: "injections/css/bug1830796-copyleaks.com-hide-unsupported.css", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1830810", + platform: "all", + domain: "interceramic.com", + bug: "1830810", + contentScripts: { + matches: ["*://interceramic.com/*"], + css: [ + { + file: "injections/css/bug1830810-interceramic.com-hide-unsupported.css", + }, + ], + }, + }, + { + id: "bug1830813", + platform: "desktop", + domain: "onstove.com", + bug: "1830813", + contentScripts: { + matches: ["*://*.onstove.com/*"], + css: [ + { + file: "injections/css/bug1830813-page.onstove.com-hide-unsupported.css", + }, + ], + }, + }, + { + id: "bug1831007", + platform: "all", + domain: "All international Nintendo domains", + bug: "1831007", + contentScripts: { + matches: [ + "*://*.mojenintendo.cz/*", + "*://*.nintendo-europe.com/*", + "*://*.nintendo.at/*", + "*://*.nintendo.be/*", + "*://*.nintendo.ch/*", + "*://*.nintendo.co.il/*", + "*://*.nintendo.co.jp/*", + "*://*.nintendo.co.kr/*", + "*://*.nintendo.co.nz/*", + "*://*.nintendo.co.uk/*", + "*://*.nintendo.co.za/*", + "*://*.nintendo.com.au/*", + "*://*.nintendo.com.hk/*", + "*://*.nintendo.com/*", + "*://*.nintendo.de/*", + "*://*.nintendo.dk/*", + "*://*.nintendo.es/*", + "*://*.nintendo.fi/*", + "*://*.nintendo.fr/*", + "*://*.nintendo.gr/*", + "*://*.nintendo.hu/*", + "*://*.nintendo.it/*", + "*://*.nintendo.nl/*", + "*://*.nintendo.no/*", + "*://*.nintendo.pt/*", + "*://*.nintendo.ru/*", + "*://*.nintendo.se/*", + "*://*.nintendo.sk/*", + "*://*.nintendo.tw/*", + "*://*.nintendoswitch.com.cn/*", + ], + js: [ + { + file: "injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js", + }, + ], + }, + }, + { + id: "bug1836157", + platform: "android", + domain: "thai-masszazs.net", + bug: "1836157", + contentScripts: { + matches: ["*://www.thai-masszazs.net/en/*"], + js: [ + { + file: "injections/js/bug1836157-thai-masszazs-niceScroll-disable.js", + }, + ], + }, + }, + { + id: "bug1836103", + platform: "all", + domain: "autostar-novoross.ru", + bug: "1836103", + contentScripts: { + matches: ["*://autostar-novoross.ru/*"], + css: [ + { + file: "injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css", + }, + ], + }, + }, + { + id: "bug1836105", + platform: "all", + domain: "cnn.com", + bug: "1836105", + contentScripts: { + matches: ["*://*.cnn.com/*"], + css: [ + { + file: "injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css", + }, + ], + }, + }, + { + id: "bug1836177", + platform: "desktop", + domain: "clalit.co.il", + bug: "1836177", + contentScripts: { + matches: [ + "*://e-services.clalit.co.il/OnlineWeb/General/InfoFullLogin.aspx*", + ], + css: [ + { + file: "injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css", + }, + ], + allFrames: true, + }, + }, + { + id: "bug1842437", + platform: "desktop", + domain: "www.youtube.com", + bug: "1842437", + contentScripts: { + matches: ["*://www.youtube.com/*"], + js: [ + { + file: "injections/js/bug1842437-www.youtube.com-performance-now-precision.js", + }, + ], + }, + }, +]; + +module.exports = AVAILABLE_INJECTIONS; diff --git a/browser/extensions/webcompat/data/shims.js b/browser/extensions/webcompat/data/shims.js new file mode 100644 index 0000000000..8e08dd6c95 --- /dev/null +++ b/browser/extensions/webcompat/data/shims.js @@ -0,0 +1,874 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals module, require */ + +const AVAILABLE_SHIMS = [ + { + hiddenInAboutCompat: true, + id: "LiveTestShim", + platform: "all", + name: "Live test shim", + bug: "livetest", + file: "live-test-shim.js", + matches: ["*://webcompat-addon-testbed.herokuapp.com/shims_test.js"], + needsShimHelpers: ["getOptions", "optIn"], + }, + { + hiddenInAboutCompat: true, + id: "MochitestShim", + platform: "all", + branch: ["all:ignoredOtherPlatform"], + name: "Test shim for Mochitests", + bug: "mochitest", + file: "mochitest-shim-1.js", + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test.js", + ], + needsShimHelpers: ["getOptions", "optIn"], + options: { + simpleOption: true, + complexOption: { a: 1, b: "test" }, + branchValue: { value: true, branches: [] }, + platformValue: { value: true, platform: "neverUsed" }, + }, + unblocksOnOptIn: ["*://trackertest.org/*"], + }, + { + hiddenInAboutCompat: true, + disabled: true, + id: "MochitestShim2", + platform: "all", + name: "Test shim for Mochitests (disabled by default)", + bug: "mochitest", + file: "mochitest-shim-2.js", + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_2.js", + ], + needsShimHelpers: ["getOptions", "optIn"], + options: { + simpleOption: true, + complexOption: { a: 1, b: "test" }, + branchValue: { value: true, branches: [] }, + platformValue: { value: true, platform: "neverUsed" }, + }, + unblocksOnOptIn: ["*://trackertest.org/*"], + }, + { + hiddenInAboutCompat: true, + id: "MochitestShim3", + platform: "all", + name: "Test shim for Mochitests (host)", + bug: "mochitest", + file: "mochitest-shim-3.js", + notHosts: ["example.com"], + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js", + ], + }, + { + hiddenInAboutCompat: true, + id: "MochitestShim4", + platform: "all", + name: "Test shim for Mochitests (notHost)", + bug: "mochitest", + file: "mochitest-shim-3.js", + hosts: ["example.net"], + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js", + ], + }, + { + hiddenInAboutCompat: true, + id: "MochitestShim5", + platform: "all", + name: "Test shim for Mochitests (branch)", + bug: "mochitest", + file: "mochitest-shim-3.js", + branches: ["never matches"], + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js", + ], + }, + { + hiddenInAboutCompat: true, + id: "MochitestShim6", + platform: "never matches", + name: "Test shim for Mochitests (platform)", + bug: "mochitest", + file: "mochitest-shim-3.js", + matches: [ + "*://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test_3.js", + ], + }, + { + id: "AddThis", + platform: "all", + name: "AddThis", + bug: "1713694", + file: "addthis-angular.js", + matches: [ + "*://s7.addthis.com/icons/official-addthis-angularjs/current/dist/official-addthis-angularjs.min.js*", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Adform", + platform: "all", + name: "Adform", + bug: "1713695", + file: "adform.js", + matches: [ + "*://track.adform.net/serving/scripts/trackpoint/", + "*://track.adform.net/serving/scripts/trackpoint/async/", + { + patterns: ["*://track.adform.net/Serving/TrackPoint/*"], + target: "tracking-pixel.png", + types: ["image", "imageset", "xmlhttprequest"], + }, + ], + onlyIfBlockedByETP: true, + }, + { + id: "AdNexusAST", + platform: "all", + name: "AdNexus AST", + bug: "1734130", + file: "adnexus-ast.js", + matches: ["*://*.adnxs.com/*/ast.js*"], + onlyIfBlockedByETP: true, + }, + { + id: "AdNexusPrebid", + platform: "all", + name: "AdNexus Prebid", + bug: "1713696", + file: "adnexus-prebid.js", + matches: ["*://*.adnxs.com/*/pb.js*", "*://*.adnxs.com/*/prebid*"], + onlyIfBlockedByETP: true, + }, + { + id: "AdobeEverestJS", + platform: "all", + name: "Adobe EverestJS", + bug: "1728114", + file: "everest.js", + matches: ["*://www.everestjs.net/static/st.v3.js*"], + onlyIfBlockedByETP: true, + }, + { + // keep this above AdSafeProtectedTrackingPixels + id: "AdSafeProtectedGoogleIMAAdapter", + platform: "all", + name: "Ad Safe Protected Google IMA Adapter", + bug: "1508639", + file: "adsafeprotected-ima.js", + matches: ["*://static.adsafeprotected.com/vans-adapter-google-ima.js"], + onlyIfBlockedByETP: true, + }, + { + id: "AdsByGoogle", + platform: "all", + name: "Ads by Google", + bug: "1713726", + file: "google-ads.js", + matches: [ + "*://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js", + { + patterns: [ + "*://pagead2.googlesyndication.com/pagead/*.js*fcd=true", + "*://pagead2.googlesyndication.com/pagead/js/*.js*fcd=true", + ], + target: "empty-script.js", + types: ["xmlhttprequest"], + }, + ], + onlyIfBlockedByETP: true, + }, + { + id: "AdvertisingCom", + platform: "all", + name: "advertising.com", + bug: "1701685", + matches: [ + { + patterns: ["*://pixel.advertising.com/firefox-etp"], + target: "tracking-pixel.png", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + patterns: ["*://cdn.cmp.advertising.com/firefox-etp"], + target: "empty-script.js", + types: ["xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + patterns: ["*://*.advertising.com/*.js*"], + target: "https://cdn.cmp.advertising.com/firefox-etp", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + patterns: ["*://*.advertising.com/*"], + target: "https://pixel.advertising.com/firefox-etp", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + ], + }, + { + id: "Branch", + platform: "all", + name: "Branch Web SDK", + bug: "1716220", + file: "branch.js", + matches: ["*://cdn.branch.io/branch-latest.min.js*"], + onlyIfBlockedByETP: true, + }, + { + id: "DoubleVerify", + platform: "all", + name: "DoubleVerify", + bug: "1771557", + file: "doubleverify.js", + matches: ["*://pub.doubleverify.com/signals/pub.js*"], + onlyIfBlockedByETP: true, + }, + { + id: "AmazonTAM", + platform: "all", + name: "Amazon Transparent Ad Marketplace", + bug: "1713698", + file: "apstag.js", + matches: ["*://c.amazon-adsystem.com/aax2/apstag.js"], + onlyIfBlockedByETP: true, + }, + { + id: "BmAuth", + platform: "all", + name: "BmAuth by 9c9media", + bug: "1486337", + file: "bmauth.js", + matches: ["*://auth.9c9media.ca/auth/main.js"], + onlyIfBlockedByETP: true, + }, + { + id: "Chartbeat", + platform: "all", + name: "Chartbeat", + bug: "1713699", + file: "chartbeat.js", + matches: [ + "*://static.chartbeat.com/js/chartbeat.js", + "*://static.chartbeat.com/js/chartbeat_video.js", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Criteo", + platform: "all", + name: "Criteo", + bug: "1713720", + file: "criteo.js", + matches: ["*://static.criteo.net/js/ld/publishertag.js"], + onlyIfBlockedByETP: true, + }, + { + // keep this above AdSafeProtectedTrackingPixels + id: "Doubleclick", + platform: "all", + name: "Doubleclick", + bug: "1713693", + matches: [ + { + patterns: [ + "*://securepubads.g.doubleclick.net/gampad/*ad-blk*", + "*://pubads.g.doubleclick.net/gampad/*ad-blk*", + ], + target: "empty-shim.txt", + types: ["image", "imageset", "xmlhttprequest"], + }, + { + patterns: [ + "*://securepubads.g.doubleclick.net/gampad/*xml_vmap1*", + "*://pubads.g.doubleclick.net/gampad/*xml_vmap1*", + ], + target: "vmad.xml", + types: ["image", "imageset", "xmlhttprequest"], + }, + { + patterns: [ + "*://vast.adsafeprotected.com/vast*", + "*://securepubads.g.doubleclick.net/gampad/*xml_vmap2*", + "*://pubads.g.doubleclick.net/gampad/*xml_vmap2*", + ], + target: "vast2.xml", + types: ["image", "imageset", "xmlhttprequest"], + }, + { + patterns: [ + "*://securepubads.g.doubleclick.net/gampad/*ad*", + "*://pubads.g.doubleclick.net/gampad/*ad*", + ], + target: "vast3.xml", + types: ["image", "imageset", "xmlhttprequest"], + }, + ], + onlyIfBlockedByETP: true, + }, + { + id: "PBMWebAPIFixes", + platform: "all", + name: "Private Browsing Web APIs", + bug: "1773110", + runFirst: "private-browsing-web-api-fixes.js", + matches: [ + "*://*.imgur.com/js/vendor.*.bundle.js", + "*://*.imgur.io/js/vendor.*.bundle.js", + "*://www.rva311.com/static/js/main.*.chunk.js", + "*://web-assets.toggl.com/app/assets/scripts/*.js", // bug 1783919 + ], + onlyIfPrivateBrowsing: true, + }, + { + id: "Eluminate", + platform: "all", + name: "Eluminate", + bug: "1503211", + file: "eluminate.js", + matches: ["*://libs.coremetrics.com/eluminate.js"], + onlyIfBlockedByETP: true, + }, + { + id: "FacebookSDK", + platform: "all", + branches: ["nightly:android"], + name: "Facebook SDK", + bug: "1226498", + file: "facebook-sdk.js", + logos: ["facebook.svg", "play.svg"], + matches: [ + "*://connect.facebook.net/*/sdk.js*", + "*://connect.facebook.net/*/all.js*", + { + patterns: ["*://www.facebook.com/platform/impression.php*"], + target: "tracking-pixel.png", + types: ["image", "imageset", "xmlhttprequest"], + }, + ], + needsShimHelpers: ["optIn", "getOptions"], + onlyIfBlockedByETP: true, + unblocksOnOptIn: [ + "*://connect.facebook.net/*/sdk.js*", + "*://connect.facebook.net/*/all.js*", + "*://*.xx.fbcdn.net/*", // covers: + // "*://scontent-.*-\d.xx.fbcdn.net/*", + // "*://static.xx.fbcdn.net/rsrc.php/*", + "*://graph.facebook.com/v2*access_token*", + "*://graph.facebook.com/v*/me*", + "*://graph.facebook.com/*/picture*", + "*://www.facebook.com/*/plugins/login_button.php*", + "*://www.facebook.com/x/oauth/status*", + { + patterns: [ + "*://www.facebook.com/*/plugins/video.php*", + "*://www.facebook.com/rsrc.php/*", + ], + branches: ["nightly"], + }, + ], + }, + { + id: "Fastclick", + platform: "all", + name: "Fastclick", + bug: "1738220", + file: "fastclick.js", + matches: [ + "*://secure.cdn.fastclick.net/js/cnvr-launcher/*/launcher-stub.min.js*", + ], + onlyIfBlockedByETP: true, + }, + { + id: "GoogleAnalyticsAndTagManager", + platform: "all", + name: "Google Analytics and Tag Manager", + bug: "1713687", + file: "google-analytics-and-tag-manager.js", + matches: [ + "*://www.google-analytics.com/analytics.js*", + "*://www.google-analytics.com/gtm/js*", + "*://www.googletagmanager.com/gtm.js*", + ], + onlyIfBlockedByETP: true, + }, + { + id: "GoogleAnalyticsECommercePlugin", + platform: "all", + name: "Google Analytics E-Commerce Plugin", + bug: "1620533", + file: "google-analytics-ecommerce-plugin.js", + matches: ["*://www.google-analytics.com/plugins/ua/ec.js"], + onlyIfBlockedByETP: true, + }, + { + id: "GoogleAnalyticsLegacy", + platform: "all", + name: "Google Analytics (legacy version)", + bug: "1487072", + file: "google-analytics-legacy.js", + matches: ["*://ssl.google-analytics.com/ga.js"], + onlyIfBlockedByETP: true, + }, + { + id: "GoogleIMA", + platform: "all", + name: "Google Interactive Media Ads", + bug: "1713690", + file: "google-ima.js", + matches: [ + "*://s0.2mdn.net/instream/html5/ima3.js", + "*://imasdk.googleapis.com/js/sdkloader/ima3.js", + ], + onlyIfBlockedByETP: true, + }, + { + id: "GooglePageAd", + platform: "all", + name: "Google Page Ad", + bug: "1713692", + file: "google-page-ad.js", + matches: ["*://www.googleadservices.com/pagead/conversion_async.js"], + onlyIfBlockedByETP: true, + }, + { + id: "GooglePublisherTags", + platform: "all", + name: "Google Publisher Tags", + bug: "1713685", + file: "google-publisher-tags.js", + matches: [ + "*://www.googletagservices.com/tag/js/gpt.js*", + "*://pagead2.googlesyndication.com/tag/js/gpt.js*", + "*://pagead2.googlesyndication.com/gpt/pubads_impl_*.js*", + "*://securepubads.g.doubleclick.net/tag/js/gpt.js*", + "*://securepubads.g.doubleclick.net/gpt/pubads_impl_*.js*", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Google SafeFrame", + platform: "all", + name: "Google SafeFrame", + bug: "1713691", + matches: [ + { + patterns: [ + "*://tpc.googlesyndication.com/safeframe/*/html/container.html", + "*://*.safeframe.googlesyndication.com/safeframe/*/html/container.html", + ], + target: "google-safeframe.html", + types: ["sub_frame"], + }, + ], + onlyIfBlockedByETP: true, + }, + { + id: "GoogleTrends", + platform: "all", + name: "Google Trends", + bug: "1624914", + custom: "google-trends-dfpi-fix", + onlyIfDFPIActive: true, + matches: [ + { + patterns: ["*://trends.google.com/trends/embed*"], + types: ["sub_frame"], + }, + ], + }, + { + id: "IAM", + platform: "all", + name: "INFOnline IAM", + bug: "1761774", + file: "iam.js", + matches: ["*://script.ioam.de/iam.js"], + onlyIfBlockedByETP: true, + }, + { + // keep this above AdSafeProtectedTrackingPixels + id: "IASPET", + platform: "all", + name: "Integral Ad Science PET", + bug: "1713701", + file: "iaspet.js", + matches: [ + "*://cdn.adsafeprotected.com/iasPET.1.js", + "*://static.adsafeprotected.com/iasPET.1.js", + ], + onlyIfBlockedByETP: true, + }, + { + id: "MNet", + platform: "all", + name: "Media.net Ads", + bug: "1713703", + file: "empty-script.js", + matches: ["*://adservex.media.net/videoAds.js*"], + onlyIfBlockedByETP: true, + }, + { + id: "Moat", + platform: "all", + name: "Moat", + bug: "1713704", + file: "moat.js", + matches: [ + "*://*.moatads.com/*/moatad.js*", + "*://*.moatads.com/*/moatapi.js*", + "*://*.moatads.com/*/moatheader.js*", + "*://*.moatads.com/*/yi.js*", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Nielsen", + platform: "all", + name: "Nielsen", + bug: "1760754", + file: "nielsen.js", + matches: ["*://*.imrworldwide.com/v60.js"], + onlyIfBlockedByETP: true, + }, + { + id: "Optimizely", + platform: "all", + name: "Optimizely", + bug: "1714431", + file: "optimizely.js", + matches: [ + "*://cdn.optimizely.com/js/*.js", + "*://cdn.optimizely.com/public/*.js", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Rambler", + platform: "all", + name: "Rambler Authenticator", + bug: "1606428", + file: "rambler-authenticator.js", + matches: ["*://id.rambler.ru/rambler-id-helper/auth_events.js"], + needsShimHelpers: ["optIn"], + onlyIfBlockedByETP: true, + }, + { + id: "RichRelevance", + platform: "all", + name: "Rich Relevance", + bug: "1713725", + file: "rich-relevance.js", + matches: ["*://media.richrelevance.com/rrserver/js/1.2/p13n.js"], + onlyIfBlockedByETP: true, + }, + { + id: "Firebase", + platform: "all", + name: "Firebase", + bug: "1771783", + onlyIfPrivateBrowsing: true, + runFirst: "firebase.js", + matches: [ + // bugs 1750699, 1767407 + "*://www.gstatic.com/firebasejs/*/firebase-messaging.js*", + ], + contentScripts: [ + { + cookieStoreId: "firefox-private", + js: "firebase.js", + runAt: "document_start", + matches: [ + "*://www.homedepot.ca/*", // bug 1778993 + "*://orangerie.eu/*", // bug 1758442 + "*://web.whatsapp.com/*", // bug 1767407 + "*://www.tripadvisor.com/*", // bug 1779536 + "*://www.office.com/*", // bug 1783921 + ], + }, + ], + }, + { + id: "StickyAdsTV", + platform: "all", + name: "StickyAdsTV", + bug: "1717806", + matches: [ + { + patterns: ["https://ads.stickyadstv.com/firefox-etp"], + target: "tracking-pixel.png", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + patterns: [ + "*://ads.stickyadstv.com/auto-user-sync*", + "*://ads.stickyadstv.com/user-matching*", + ], + target: "https://ads.stickyadstv.com/firefox-etp", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + ], + }, + { + id: "Vidible", + branch: ["nightly"], + platform: "all", + name: "Vidible", + bug: "1713710", + file: "vidible.js", + logos: ["play.svg"], + matches: [ + "*://*.vidible.tv/*/vidible-min.js*", + "*://vdb-cdn-files.s3.amazonaws.com/*/vidible-min.js*", + ], + needsShimHelpers: ["optIn"], + onlyIfBlockedByETP: true, + unblocksOnOptIn: [ + "*://delivery.vidible.tv/jsonp/pid=*/vid=*/*.js*", + "*://delivery.vidible.tv/placement/*", + "*://img.vidible.tv/prod/*", + "*://cdn-ssl.vidible.tv/prod/player/js/*.js", + "*://hlsrv.vidible.tv/prod/*.m3u8*", + "*://videos.vidible.tv/prod/*.key*", + "*://videos.vidible.tv/prod/*.mp4*", + "*://videos.vidible.tv/prod/*.webm*", + "*://videos.vidible.tv/prod/*.ts*", + ], + }, + { + id: "Kinja", + platform: "all", + name: "Kinja", + bug: "1656171", + contentScripts: [ + { + js: "kinja.js", + matches: [ + "*://www.avclub.com/*", + "*://deadspin.com/*", + "*://gizmodo.com/*", + "*://jalopnik.com/*", + "*://jezebel.com/*", + "*://kotaku.com/*", + "*://lifehacker.com/*", + "*://www.theonion.com/*", + "*://www.theroot.com/*", + "*://thetakeout.com/*", + "*://theinventory.com/*", + ], + runAt: "document_start", + allFrames: true, + }, + ], + onlyIfDFPIActive: true, + }, + { + id: "MicrosoftLogin", + platform: "desktop", + name: "Microsoft Login", + bug: "1638383", + requestStorageAccessForRedirect: [ + ["*://web.powerva.microsoft.com/*", "*://login.microsoftonline.com/*"], + ["*://teams.microsoft.com/*", "*://login.microsoftonline.com/*"], + ["*://*.teams.microsoft.us/*", "*://login.microsoftonline.us/*"], + ], + contentScripts: [ + { + js: "microsoftLogin.js", + matches: [ + "*://web.powerva.microsoft.com/*", + "*://teams.microsoft.com/*", + "*://*.teams.microsoft.us/*", + ], + runAt: "document_start", + }, + ], + onlyIfDFPIActive: true, + }, + { + id: "MicrosoftVirtualAssistant", + platform: "all", + name: "Microsoft Virtual Assistant", + bug: "1801277", + contentScripts: [ + { + js: "microsoftVirtualAssistant.js", + matches: ["*://publisher.liveperson.net/*"], + runAt: "document_start", + allFrames: true, + }, + ], + }, + { + id: "History", + platform: "all", + name: "History.com", + bug: "1624853", + contentScripts: [ + { + js: "history.js", + matches: ["*://play.history.com/*"], + runAt: "document_start", + }, + ], + onlyIfDFPIActive: true, + }, + { + id: "Crave.ca", + platform: "all", + name: "Crave.ca", + bug: "1746439", + contentScripts: [ + { + js: "crave-ca.js", + matches: ["*://account.bellmedia.ca/login*"], + runAt: "document_start", + }, + ], + onlyIfDFPIActive: true, + }, + { + id: "Instagram.com", + platform: "android", + name: "Instagram.com", + bug: "1804445", + contentScripts: [ + { + js: "instagram.js", + matches: ["*://www.instagram.com/*"], + runAt: "document_start", + }, + ], + onlyIfDFPIActive: true, + }, + { + id: "MaxMindGeoIP", + platform: "all", + name: "MaxMind GeoIP", + bug: "1754389", + file: "maxmind-geoip.js", + matches: ["*://js.maxmind.com/js/apis/geoip2/*/geoip2.js"], + onlyIfBlockedByETP: true, + }, + { + id: "WebTrends", + platform: "all", + name: "WebTrends", + bug: "1766414", + file: "webtrends.js", + matches: [ + "*://s.webtrends.com/js/advancedLinkTracking.js", + "*://s.webtrends.com/js/webtrends.js", + "*://s.webtrends.com/js/webtrends.min.js", + ], + onlyIfBlockedByETP: true, + }, + { + id: "Blogger", + platform: "all", + name: "Blogger", + bug: "1776869", + contentScripts: [ + { + js: "blogger.js", + matches: ["*://www.blogger.com/comment/frame/*"], + runAt: "document_start", + allFrames: true, + }, + { + js: "bloggerAccount.js", + matches: ["*://www.blogger.com/blog/*"], + runAt: "document_end", + }, + ], + onlyIfDFPIActive: true, + }, + { + // keep this below any other shims checking adsafeprotected URLs + id: "AdSafeProtectedTrackingPixels", + platform: "all", + name: "Ad Safe Protected tracking pixels", + bug: "1717806", + matches: [ + { + patterns: ["https://static.adsafeprotected.com/firefox-etp-pixel"], + target: "tracking-pixel.png", + types: ["image", "imageset", "xmlhttprequest"], + }, + { + patterns: ["https://static.adsafeprotected.com/firefox-etp-js"], + target: "empty-script.js", + types: ["xmlhttprequest"], + }, + { + patterns: [ + "*://*.adsafeprotected.com/*.gif*", + "*://*.adsafeprotected.com/*.png*", + ], + target: "https://static.adsafeprotected.com/firefox-etp-pixel", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + patterns: [ + "*://*.adsafeprotected.com/*.js*", + "*://*.adsafeprotected.com/*/adj*", + "*://*.adsafeprotected.com/*/imp/*", + "*://*.adsafeprotected.com/*/Serving/*", + "*://*.adsafeprotected.com/*/unit/*", + "*://*.adsafeprotected.com/jload", + "*://*.adsafeprotected.com/jload?*", + "*://*.adsafeprotected.com/jsvid", + "*://*.adsafeprotected.com/jsvid?*", + "*://*.adsafeprotected.com/mon*", + "*://*.adsafeprotected.com/tpl", + "*://*.adsafeprotected.com/tpl?*", + "*://*.adsafeprotected.com/services/pub*", + ], + target: "https://static.adsafeprotected.com/firefox-etp-js", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + { + // note, fallback case seems to be an image + patterns: ["*://*.adsafeprotected.com/*"], + target: "https://static.adsafeprotected.com/firefox-etp-pixel", + types: ["image", "imageset", "xmlhttprequest"], + onlyIfBlockedByETP: true, + }, + ], + }, + { + id: "SpotifyEmbed", + platform: "all", + name: "SpotifyEmbed", + bug: "1792395", + contentScripts: [ + { + js: "spotify-embed.js", + matches: ["*://open.spotify.com/embed/*"], + runAt: "document_start", + allFrames: true, + }, + ], + onlyIfDFPIActive: true, + }, +]; + +module.exports = AVAILABLE_SHIMS; diff --git a/browser/extensions/webcompat/data/ua_overrides.js b/browser/extensions/webcompat/data/ua_overrides.js new file mode 100644 index 0000000000..150d13465d --- /dev/null +++ b/browser/extensions/webcompat/data/ua_overrides.js @@ -0,0 +1,1371 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser, module, require */ + +// This is a hack for the tests. +if (typeof InterventionHelpers === "undefined") { + var InterventionHelpers = require("../lib/intervention_helpers"); +} +if (typeof UAHelpers === "undefined") { + var UAHelpers = require("../lib/ua_helpers"); +} + +/** + * For detailed information on our policies, and a documention on this format + * and its possibilites, please check the Mozilla-Wiki at + * + * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides + */ +const AVAILABLE_UA_OVERRIDES = [ + { + id: "testbed-override", + platform: "all", + domain: "webcompat-addon-testbed.herokuapp.com", + bug: "0000000", + config: { + hidden: true, + matches: ["*://webcompat-addon-testbed.herokuapp.com/*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 for WebCompat" + ); + }, + }, + }, + { + /* + * Bug 1577519 - directv.com - Create a UA override for directv.com for playback on desktop + * WebCompat issue #3846 - https://webcompat.com/issues/3846 + * + * directv.com (attwatchtv.com) is blocking Firefox via UA sniffing. Spoofing as Chrome allows + * to access the site and playback works fine. This is former directvnow.com + */ + id: "bug1577519", + platform: "desktop", + domain: "directv.com", + bug: "1577519", + config: { + matches: [ + "*://*.attwatchtv.com/*", + "*://*.directv.com.ec/*", // bug 1827706 + "*://*.directv.com/*", + ], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1570108 - steamcommunity.com - UA override for steamcommunity.com + * WebCompat issue #34171 - https://webcompat.com/issues/34171 + * + * steamcommunity.com blocks chat feature for Firefox users showing unsupported browser message. + * When spoofing as Chrome the chat works fine + */ + id: "bug1570108", + platform: "desktop", + domain: "steamcommunity.com", + bug: "1570108", + config: { + matches: ["*://steamcommunity.com/chat*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1582582 - sling.com - UA override for sling.com + * WebCompat issue #17804 - https://webcompat.com/issues/17804 + * + * sling.com blocks Firefox users showing unsupported browser message. + * When spoofing as Chrome playing content works fine + */ + id: "bug1582582", + platform: "desktop", + domain: "sling.com", + bug: "1582582", + config: { + matches: ["https://watch.sling.com/*", "https://www.sling.com/*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1610026 - www.mobilesuica.com - UA override for www.mobilesuica.com + * WebCompat issue #4608 - https://webcompat.com/issues/4608 + * + * mobilesuica.com showing unsupported message for Firefox users + * Spoofing as Chrome allows to access the page + */ + id: "bug1610026", + platform: "all", + domain: "www.mobilesuica.com", + bug: "1610026", + config: { + matches: ["https://www.mobilesuica.com/*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1385206 - Create UA override for rakuten.co.jp on Firefox Android + * (Imported from ua-update.json.in) + * + * rakuten.co.jp serves a Desktop version if Firefox is included in the UA. + */ + id: "bug1385206", + platform: "android", + domain: "rakuten.co.jp", + bug: "1385206", + config: { + matches: ["*://*.rakuten.co.jp/*"], + uaTransformer: originalUA => { + return originalUA.replace(/Firefox.+$/, ""); + }, + }, + }, + { + /* + * Bug 969844 - mobile.de sends desktop site to Firefox on Android + * + * mobile.de sends the desktop site to Firefox Mobile. + * Spoofing as Chrome works fine. + */ + id: "bug969844", + platform: "android", + domain: "mobile.de", + bug: "969844", + config: { + matches: ["*://*.mobile.de/*"], + uaTransformer: _ => { + return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"; + }, + }, + }, + { + /* + * Bug 1509873 - zmags.com - Add UA override for secure.viewer.zmags.com + * WebCompat issue #21576 - https://webcompat.com/issues/21576 + * + * The zmags viewer locks out Firefox Mobile with a "Browser unsupported" + * message, but tests showed that it works just fine with a Chrome UA. + * Outreach attempts were unsuccessful, and as the site has a relatively + * high rank, we alter the UA. + */ + id: "bug1509873", + platform: "android", + domain: "zmags.com", + bug: "1509873", + config: { + matches: ["*://*.viewer.zmags.com/*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1574522 - UA override for enuri.com on Firefox for Android + * WebCompat issue #37139 - https://webcompat.com/issues/37139 + * + * enuri.com returns a different template for Firefox on Android + * based on server side UA detection. This results in page content cut offs. + * Spoofing as Chrome fixes the issue + */ + id: "bug1574522", + platform: "android", + domain: "enuri.com", + bug: "1574522", + config: { + matches: ["*://enuri.com/*"], + uaTransformer: _ => { + return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"; + }, + }, + }, + { + /* + * Bug 1574564 - UA override for ceskatelevize.cz on Firefox for Android + * WebCompat issue #15467 - https://webcompat.com/issues/15467 + * + * ceskatelevize sets streamingProtocol depending on the User-Agent it sees + * in the request headers, returning DASH for Chrome, HLS for iOS, + * and Flash for Firefox Mobile. Since Mobile has no Flash, the video + * doesn't work. Spoofing as Chrome makes the video play + */ + id: "bug1574564", + platform: "android", + domain: "ceskatelevize.cz", + bug: "1574564", + config: { + matches: ["*://*.ceskatelevize.cz/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1577267 - UA override for metfone.com.kh on Firefox for Android + * WebCompat issue #16363 - https://webcompat.com/issues/16363 + * + * metfone.com.kh has a server side UA detection which returns desktop site + * for Firefox for Android. Spoofing as Chrome allows to receive mobile version + */ + id: "bug1577267", + platform: "android", + domain: "metfone.com.kh", + bug: "1577267", + config: { + matches: ["*://*.metfone.com.kh/*"], + uaTransformer: originalUA => { + return ( + UAHelpers.getPrefix(originalUA) + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36" + ); + }, + }, + }, + { + /* + * Bug 1598198 - User Agent extension for Samsung's galaxy.store URLs + * + * Samsung's galaxy.store shortlinks are supposed to redirect to a Samsung + * intent:// URL on Samsung devices, but to an error page on other brands. + * As we do not provide device info in our user agent string, this check + * fails, and even Samsung users land on an error page if they use Firefox + * for Android. + * This intervention adds a simple "Samsung" identifier to the User Agent + * on only the Galaxy Store URLs if the device happens to be a Samsung. + */ + id: "bug1598198", + platform: "android", + domain: "galaxy.store", + bug: "1598198", + config: { + matches: [ + "*://galaxy.store/*", + "*://dev.galaxy.store/*", + "*://stg.galaxy.store/*", + ], + uaTransformer: originalUA => { + if (!browser.systemManufacturer) { + return originalUA; + } + + const manufacturer = browser.systemManufacturer.getManufacturer(); + if (manufacturer && manufacturer.toLowerCase() === "samsung") { + return originalUA.replace("Mobile;", "Mobile; Samsung;"); + } + + return originalUA; + }, + }, + }, + { + /* + * Bug 1595215 - UA overrides for Uniqlo sites + * Webcompat issue #38825 - https://webcompat.com/issues/38825 + * + * To receive the proper mobile version instead of the desktop version or + * avoid redirect loop, the UA is spoofed. + */ + id: "bug1595215", + platform: "android", + domain: "uniqlo.com", + bug: "1595215", + config: { + matches: ["*://*.uniqlo.com/*"], + uaTransformer: originalUA => { + return originalUA + " Mobile Safari"; + }, + }, + }, + { + /* + * Bug 1622063 - UA override for wp1-ext.usps.gov + * Webcompat issue #29867 - https://webcompat.com/issues/29867 + * + * The Job Search site for USPS does not work for Firefox Mobile + * browsers (a 500 is returned). + */ + id: "bug1622063", + platform: "android", + domain: "wp1-ext.usps.gov", + bug: "1622063", + config: { + matches: ["*://wp1-ext.usps.gov/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1697324 - Update the override for mobile2.bmo.com + * Previously Bug 1622081 - UA override for mobile2.bmo.com + * Webcompat issue #45019 - https://webcompat.com/issues/45019 + * + * Unless the UA string contains "Chrome", mobile2.bmo.com will + * display a modal saying the browser is out-of-date. + */ + id: "bug1697324", + platform: "android", + domain: "mobile2.bmo.com", + bug: "1697324", + config: { + matches: ["*://mobile2.bmo.com/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome"; + }, + }, + }, + { + /* + * Bug 1628455 - UA override for autotrader.ca + * Webcompat issue #50961 - https://webcompat.com/issues/50961 + * + * autotrader.ca is showing desktop site for Firefox on Android + * based on server side UA detection. Spoofing as Chrome allows to + * get mobile experience + */ + id: "bug1628455", + platform: "android", + domain: "autotrader.ca", + bug: "1628455", + config: { + matches: ["https://*.autotrader.ca/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1646791 - bancosantander.es - Re-add UA override. + * Bug 1665129 - *.gruposantander.es - Add wildcard domains. + * WebCompat issue #33462 - https://webcompat.com/issues/33462 + * SuMo request - https://support.mozilla.org/es/questions/1291085 + * + * santanderbank expects UA to have 'like Gecko', otherwise it runs + * xmlDoc.onload whose support has been dropped. It results in missing labels in forms + * and some other issues. Adding 'like Gecko' fixes those issues. + */ + id: "bug1646791", + platform: "all", + domain: "santanderbank.com", + bug: "1646791", + config: { + matches: [ + "*://*.bancosantander.es/*", + "*://*.gruposantander.es/*", + "*://*.santander.co.uk/*", + ], + uaTransformer: originalUA => { + // The first line related to Firefox 100 is for Bug 1743445. + // [TODO]: Remove when bug 1743429 gets backed out. + return UAHelpers.capVersionTo99(originalUA).replace( + "Gecko", + "like Gecko" + ); + }, + }, + }, + { + /* + * Bug 1651292 - UA override for www.jp.square-enix.com + * Webcompat issue #53018 - https://webcompat.com/issues/53018 + * + * Unless the UA string contains "Chrome 66+", a section of + * www.jp.square-enix.com will show a never ending LOADING + * page. + */ + id: "bug1651292", + platform: "android", + domain: "www.jp.square-enix.com", + bug: "1651292", + config: { + matches: ["*://www.jp.square-enix.com/music/sem/page/FF7R/ost/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome/83"; + }, + }, + }, + { + /* + * Bug 1666754 - Mobile UA override for lffl.org + * Bug 1665720 - lffl.org article page takes 2x as much time to load on Moto G + * + * This site returns desktop site based on server side UA detection. + * Spoofing as Chrome allows to get mobile experience + */ + id: "bug1666754", + platform: "android", + domain: "lffl.org", + bug: "1666754", + config: { + matches: ["*://*.lffl.org/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1704673 - Add UA override for app.xiaomi.com + * Webcompat issue #66163 - https://webcompat.com/issues/66163 + * + * The page isn’t redirecting properly error message received. + * Spoofing as Chrome makes the page load + */ + id: "bug1704673", + platform: "android", + domain: "app.xiaomi.com", + bug: "1704673", + config: { + matches: ["*://app.xiaomi.com/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1712807 - Add UA override for www.dealnews.com + * Webcompat issue #39341 - https://webcompat.com/issues/39341 + * + * The sites shows Firefox a different layout compared to Chrome. + * Spoofing as Chrome fixes this. + */ + id: "bug1712807", + platform: "android", + domain: "www.dealnews.com", + bug: "1712807", + config: { + matches: ["*://www.dealnews.com/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1719859 - Add UA override for saxoinvestor.fr + * Webcompat issue #74678 - https://webcompat.com/issues/74678 + * + * The site blocks Firefox with a server-side UA sniffer. Appending a + * Chrome version segment to the UA makes it work. + */ + id: "bug1719859", + platform: "all", + domain: "saxoinvestor.fr", + bug: "1719859", + config: { + matches: ["*://*.saxoinvestor.fr/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome/91.0.4472.114"; + }, + }, + }, + { + /* + * Bug 1722954 - Add UA override for game.granbluefantasy.jp + * Webcompat issue #34310 - https://github.com/webcompat/web-bugs/issues/34310 + * + * The website is sending a version of the site which is too small. Adding a partial + * safari iOS version of the UA sends us the right layout. + */ + id: "bug1722954", + platform: "android", + domain: "granbluefantasy.jp", + bug: "1722954", + config: { + matches: ["*://*.granbluefantasy.jp/*"], + uaTransformer: originalUA => { + return originalUA + " iPhone OS 12_0 like Mac OS X"; + }, + }, + }, + { + /* + * Bug 1738317 - Add UA override for vmos.cn + * Webcompat issue #90432 - https://github.com/webcompat/web-bugs/issues/90432 + * + * Firefox for Android receives a desktop-only layout based on server-side + * UA sniffing. Spoofing as Chrome works fine. + */ + id: "bug1738317", + platform: "android", + domain: "vmos.cn", + bug: "1738317", + config: { + matches: ["*://*.vmos.cn/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1743627 - Add UA override for renaud-bray.com + * Webcompat issue #55276 - https://github.com/webcompat/web-bugs/issues/55276 + * + * Firefox for Android depends on "Version/" being there in the UA string, + * or it'll throw a runtime error. + */ + id: "bug1743627", + platform: "android", + domain: "renaud-bray.com", + bug: "1743627", + config: { + matches: ["*://*.renaud-bray.com/*"], + uaTransformer: originalUA => { + return originalUA + " Version/0"; + }, + }, + }, + { + /* + * Bug 1743751 - Add UA override for slrclub.com + * Webcompat issue #91373 - https://github.com/webcompat/web-bugs/issues/91373 + * + * On Firefox Android, the browser is receiving the desktop layout. + * Spoofing as Chrome works fine. + */ + id: "bug1743751", + platform: "android", + domain: "slrclub.com", + bug: "1743751", + config: { + matches: ["*://*.slrclub.com/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1743754 - Add UA override for slrclub.com + * Webcompat issue #86839 - https://github.com/webcompat/web-bugs/issues/86839 + * + * On Firefox Android, the browser is failing a UA parsing on Firefox UA. + */ + id: "bug1743754", + platform: "android", + domain: "workflow.base.vn", + bug: "1743754", + config: { + matches: ["*://workflow.base.vn/*"], + uaTransformer: () => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1743429 - Add UA override for sites broken with the Version 100 User Agent + * + * Some sites have issues with a UA string with Firefox version 100 or higher, + * so present as version 99 for now. + */ + id: "bug1743429", + platform: "all", + domain: "Sites with known Version 100 User Agent breakage", + bug: "1743429", + config: { + matches: [ + "*://411.ca/", // #121332 + "*://*.commerzbank.de/*", // Bug 1767630 + "*://*.mms.telekom.de/*", // #1800241 + "*://ubank.com.au/*", // #104099 + "*://wifi.sncf/*", // #100194 + ], + uaTransformer: originalUA => { + return UAHelpers.capVersionTo99(originalUA); + }, + }, + }, + { + /* + * Bug 1753461 - UA override for serieson.naver.com + * Webcompat issue #99993 - https://webcompat.com/issues/97298 + * + * The site locks out Firefox users unless a Chrome UA is given, + * and locks out Linux users as well (so we use Windows+Chrome). + */ + id: "bug1753461", + platform: "desktop", + domain: "serieson.naver.com", + bug: "1753461", + config: { + matches: ["*://serieson.naver.com/*"], + uaTransformer: originalUA => { + return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"; + }, + }, + }, + { + /* + * Bug 1756872 - UA override for www.dolcegabbana.com + * Webcompat issue #99993 - https://webcompat.com/issues/99993 + * + * The site's layout is broken on Firefox for Android + * without a full Chrome user-agent string. + */ + id: "bug1756872", + platform: "android", + domain: "www.dolcegabbana.com", + bug: "1756872", + config: { + matches: ["*://www.dolcegabbana.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1771200 - UA override for animalplanet.com + * Webcompat issue #99993 - https://webcompat.com/issues/103727 + * + * The videos are not playing and an error message is displayed + * in Firefox for Android, but work with Chrome UA + */ + id: "bug1771200", + platform: "android", + domain: "animalplanet.com", + bug: "1771200", + config: { + matches: ["*://*.animalplanet.com/video/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1771200 - UA override for lazada.co.id + * Webcompat issue #106229 - https://webcompat.com/issues/106229 + * + * The map is not playing and an error message is displayed + * in Firefox for Android, but work with Chrome UA + */ + id: "bug1779059", + platform: "android", + domain: "lazada.co.id", + bug: "1779059", + config: { + matches: ["*://member-m.lazada.co.id/address/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1778168 - UA override for watch.antennaplus.gr + * Webcompat issue #106529 - https://webcompat.com/issues/106529 + * + * The site's content is not loaded unless a Chrome UA is used, + * and breaks on Linux (so we claim Windows instead in that case). + */ + id: "bug1778168", + platform: "desktop", + domain: "watch.antennaplus.gr", + bug: "1778168", + config: { + matches: ["*://watch.antennaplus.gr/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA({ + desktopOS: "nonLinux", + }); + }, + }, + }, + { + /* + * Bug 1776897 - UA override for www.edencast.fr + * Webcompat issue #106545 - https://webcompat.com/issues/106545 + * + * The site's podcast audio player does not load unless a Chrome UA is used. + */ + id: "bug1776897", + platform: "all", + domain: "www.edencast.fr", + bug: "1776897", + config: { + matches: ["*://www.edencast.fr/zoomcast*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1784361 - UA override for coldwellbankerhomes.com + * Webcompat issue #108535 - https://webcompat.com/issues/108535 + * + * An error is thrown due to missing element, unless Chrome UA is used + */ + id: "bug1784361", + platform: "android", + domain: "coldwellbankerhomes.com", + bug: "1784361", + config: { + matches: ["*://*.coldwellbankerhomes.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1786404 - UA override for business.help.royalmail.com + * Webcompat issue #109070 - https://webcompat.com/issues/109070 + * + * Replacing `Firefox` with `FireFox` to evade one of their UA tests... + */ + id: "bug1786404", + platform: "all", + domain: "business.help.royalmail.com", + bug: "1786404", + config: { + matches: ["*://business.help.royalmail.com/app/webforms/*"], + uaTransformer: originalUA => { + return originalUA.replace("Firefox", "FireFox"); + }, + }, + }, + { + /* + * Bug 1790698 - UA override for wolf777.com + * Webcompat issue #103981 - https://webcompat.com/issues/103981 + * + * Add 'Linux; ' next to the Android version or the site breaks + */ + id: "bug1790698", + platform: "android", + domain: "wolf777.com", + bug: "1790698", + config: { + matches: ["*://wolf777.com/*"], + uaTransformer: originalUA => { + return originalUA.replace("Android", "Linux; Android"); + }, + }, + }, + { + /* + * Bug 1800936 - UA override for cov19ent.kdca.go.kr + * Webcompat issue #110655 - https://webcompat.com/issues/110655 + * + * Add 'Chrome;' to the UA for the site to load styles + */ + id: "bug1800936", + platform: "all", + domain: "cov19ent.kdca.go.kr", + bug: "1800936", + config: { + matches: ["*://cov19ent.kdca.go.kr/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome"; + }, + }, + }, + { + /* + * Bug 1803131 - UA override for argaam.com + * Webcompat issue #113638 - https://webcompat.com/issues/113638 + * + * To receive the proper mobile version instead of the desktop version + * the UA is spoofed. + */ + id: "bug1803131", + platform: "android", + domain: "argaam.com", + bug: "1803131", + config: { + matches: ["*://*.argaam.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1819702 - UA override for feelgoodcontacts.com + * Webcompat issue #118030 - https://webcompat.com/issues/118030 + * + * Spoof the UA to receive the mobile version instead + * of the broken desktop version for Android. + */ + id: "bug1819702", + platform: "android", + domain: "feelgoodcontacts.com", + bug: "1819702", + config: { + matches: ["*://*.feelgoodcontacts.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1823966 - UA override for elearning.dmv.ca.gov + * Original report: https://bugzilla.mozilla.org/show_bug.cgi?id=1823785 + */ + id: "bug1823966", + platform: "all", + domain: "elearning.dmv.ca.gov", + bug: "1823966", + config: { + matches: ["*://*.elearning.dmv.ca.gov/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for admissions.nid.edu + * Webcompat issue #65753 - https://webcompat.com/issues/65753 + */ + id: "bug1827678-webc65753", + platform: "all", + domain: "admissions.nid.edu", + bug: "1827678", + config: { + matches: ["*://*.admissions.nid.edu/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for www.hepsiburada.com + * Webcompat issue #66888 - https://webcompat.com/issues/66888 + */ + id: "bug1827678-webc66888", + platform: "android", + domain: "www.hepsiburada.com", + bug: "1827678", + config: { + matches: ["*://www.hepsiburada.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for bankmandiri.co.id + * Webcompat issue #67924 - https://webcompat.com/issues/67924 + */ + id: "bug1827678-webc67924", + platform: "android", + domain: "bankmandiri.co.id", + bug: "1827678", + config: { + matches: ["*://*.bankmandiri.co.id/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for frankfred.com + * Webcompat issue #68007 - https://webcompat.com/issues/68007 + */ + id: "bug1827678-webc68007", + platform: "android", + domain: "frankfred.com", + bug: "1827678", + config: { + matches: ["*://*.frankfred.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for static.slots.lv + * Webcompat issue #68379 - https://webcompat.com/issues/68379 + */ + id: "bug1827678-webc68379", + platform: "android", + domain: "static.slots.lv", + bug: "1827678", + config: { + matches: ["*://static.slots.lv/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for mobile.onvue.com + * Webcompat issue #68520 - https://webcompat.com/issues/68520 + */ + id: "bug1827678-webc68520", + platform: "android", + domain: "mobile.onvue.com", + bug: "1827678", + config: { + matches: ["*://mobile.onvue.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for avizia.com + * Webcompat issue #68635 - https://webcompat.com/issues/68635 + */ + id: "bug1827678-webc68635", + platform: "all", + domain: "avizia.com", + bug: "1827678", + config: { + matches: ["*://*.avizia.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for www.yourtexasbenefits.com + * Webcompat issue #76785 - https://webcompat.com/issues/76785 + */ + id: "bug1827678-webc76785", + platform: "android", + domain: "www.yourtexasbenefits.com", + bug: "1827678", + config: { + matches: ["*://www.yourtexasbenefits.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for www.free4talk.com + * Webcompat issue #77727 - https://webcompat.com/issues/77727 + */ + id: "bug1827678-webc77727", + platform: "android", + domain: "www.free4talk.com", + bug: "1827678", + config: { + matches: ["*://www.free4talk.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for watch.indee.tv + * Webcompat issue #77912 - https://webcompat.com/issues/77912 + */ + id: "bug1827678-webc77912", + platform: "all", + domain: "watch.indee.tv", + bug: "1827678", + config: { + matches: ["*://watch.indee.tv/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for viewer-ebook.books.com.tw + * Webcompat issue #80180 - https://webcompat.com/issues/80180 + */ + id: "bug1827678-webc80180", + platform: "all", + domain: "viewer-ebook.books.com.tw", + bug: "1827678", + config: { + matches: ["*://viewer-ebook.books.com.tw/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for jelly.jd.com + * Webcompat issue #83269 - https://webcompat.com/issues/83269 + */ + id: "bug1827678-webc83269", + platform: "android", + domain: "jelly.jd.com", + bug: "1827678", + config: { + matches: ["*://jelly.jd.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for f2bbs.com + * Webcompat issue #84932 - https://webcompat.com/issues/84932 + */ + id: "bug1827678-webc84932", + platform: "android", + domain: "f2bbs.com", + bug: "1827678", + config: { + matches: ["*://f2bbs.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for kt.com + * Webcompat issue #119012 - https://webcompat.com/issues/119012 + */ + id: "bug1827678-webc119012", + platform: "all", + domain: "kt.com", + bug: "1827678", + config: { + matches: ["*://*.kt.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for oirsa.org + * Webcompat issue #119402 - https://webcompat.com/issues/119402 + */ + id: "bug1827678-webc119402", + platform: "all", + domain: "oirsa.org", + bug: "1827678", + config: { + matches: ["*://*.oirsa.org/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for sistema.ibglbrasil.com.br + * Webcompat issue #119785 - https://webcompat.com/issues/119785 + */ + id: "bug1827678-webc119785", + platform: "all", + domain: "sistema.ibglbrasil.com.br", + bug: "1827678", + config: { + matches: ["*://sistema.ibglbrasil.com.br/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1827678 - UA override for onp.cloud.waterloo.ca + * Webcompat issue #120450 - https://webcompat.com/issues/120450 + */ + id: "bug1827678-webc120450", + platform: "all", + domain: "onp.cloud.waterloo.ca", + bug: "1827678", + config: { + matches: ["*://onp.cloud.waterloo.ca/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1830739 - UA override for casino sites + * + * The sites are showing unsupported message with the same UI + */ + id: "bug1830739", + platform: "android", + domain: "casino sites", + bug: "1830739", + config: { + matches: [ + "*://*.captainjackcasino.com/*", // 79490 + "*://*.casinoextreme.eu/*", // 118175 + "*://*.cryptoloko.com/*", // 117911 + "*://*.heapsowins.com/*", // 120027 + "*://*.planet7casino.com/*", // 120609 + "*://*.yebocasino.co.za/*", // 88409 + ], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1830821 - UA override for m.tworld.co.kr + * Webcompat issue #118998 - https://webcompat.com/issues/118998 + */ + id: "bug1830821-webc118998", + platform: "android", + domain: "m.tworld.co.kr", + bug: "1830821", + config: { + matches: ["*://m.tworld.co.kr/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1830821 - UA override for webcartop.jp + * Webcompat issue #113663 - https://webcompat.com/issues/113663 + */ + id: "bug1830821-webc113663", + platform: "android", + domain: "webcartop.jp", + bug: "1830821", + config: { + matches: ["*://*.webcartop.jp/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1830821 - UA override for enjoy.point.auone.jp + * Webcompat issue #90981 - https://webcompat.com/issues/90981 + */ + id: "bug1830821-webc90981", + platform: "android", + domain: "enjoy.point.auone.jp", + bug: "1830821", + config: { + matches: ["*://enjoy.point.auone.jp/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1751604 - UA override for /www.otsuka.co.jp/fib/ + * + * The site's content is not loaded on mobile unless a Chrome UA is used. + */ + id: "bug1829126", + platform: "android", + domain: "www.otsuka.co.jp", + bug: "1829126", + config: { + matches: ["*://www.otsuka.co.jp/fib/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1831441 - UA override for luna.amazon.com + * + * Games are unplayable unless a Chrome UA is used. + */ + id: "bug1831441", + platform: "all", + domain: "luna.amazon.com", + bug: "1831441", + config: { + matches: ["*://luna.amazon.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1836109 - UA override for watch.tonton.com.my + * + * The site's content is not loaded unless a Chrome UA is used. + */ + id: "bug1836109", + platform: "all", + domain: "watch.tonton.com.my", + bug: "1836109", + config: { + matches: ["*://watch.tonton.com.my/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1836112 - UA override for www.capcut.cn + * + * The site's content is not loaded unless a Chrome UA is used. + */ + id: "bug1836112", + platform: "all", + domain: "www.capcut.cn", + bug: "1836112", + config: { + matches: ["*://www.capcut.cn/editor*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1836116 - UA override for www.slushy.com + * + * The site's content is not loaded without a Chrome UA spoof. + */ + id: "bug1836116", + platform: "all", + domain: "www.slushy.com", + bug: "1836116", + config: { + matches: ["*://www.slushy.com/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome/113.0.0.0"; + }, + }, + }, + { + /* + * Bug 1836135 - UA override for gts-pro.sdimedia.com + * + * The site's content is not loaded without a Chrome UA spoof. + */ + id: "bug1836135", + platform: "all", + domain: "gts-pro.sdimedia.com", + bug: "1836135", + config: { + matches: ["*://gts-pro.sdimedia.com/*"], + uaTransformer: originalUA => { + return originalUA.replace("Firefox/", "Fx/") + " Chrome/113.0.0.0"; + }, + }, + }, + { + /* + * Bug 1836140 - UA override for indices.iriworldwide.com + * + * The site's content is not loaded without a UA spoof. + */ + id: "bug1836140", + platform: "all", + domain: "indices.iriworldwide.com", + bug: "1836140", + config: { + matches: ["*://indices.iriworldwide.com/covid19/*"], + uaTransformer: originalUA => { + return originalUA.replace("Firefox/", "Fx/"); + }, + }, + }, + { + /* + * Bug 1836178 - UA override for atracker.pro + * + * The site's content is not loaded without a Chrome UA spoof. + */ + id: "bug1836178", + platform: "all", + domain: "atracker.pro", + bug: "1836178", + config: { + matches: ["*://atracker.pro/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome/113.0.0.0"; + }, + }, + }, + { + /* + * Bug 1836181 - UA override for conference.amwell.com + * + * The site's content is not loaded unless a Chrome UA is used. + */ + id: "bug1836181", + platform: "all", + domain: "conference.amwell.com", + bug: "1836181", + config: { + matches: ["*://conference.amwell.com/*"], + uaTransformer: originalUA => { + return UAHelpers.getDeviceAppropriateChromeUA(); + }, + }, + }, + { + /* + * Bug 1836182 - UA override for www.flatsatshadowglen.com + * + * The site's content is not loaded without a Chrome UA spoof. + */ + id: "bug1836182", + platform: "all", + domain: "www.flatsatshadowglen.com", + bug: "1836182", + config: { + matches: ["*://www.flatsatshadowglen.com/*"], + uaTransformer: originalUA => { + return originalUA + " Chrome/113.0.0.0"; + }, + }, + }, +]; + +module.exports = AVAILABLE_UA_OVERRIDES; diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js new file mode 100644 index 0000000000..21ad297dc1 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global ExtensionAPI, ExtensionCommon, Services, XPCOMUtils */ + +this.aboutConfigPrefs = class extends ExtensionAPI { + getAPI(context) { + const EventManager = ExtensionCommon.EventManager; + const extensionIDBase = context.extension.id.split("@")[0]; + const extensionPrefNameBase = `extensions.${extensionIDBase}.`; + + return { + aboutConfigPrefs: { + onPrefChange: new EventManager({ + context, + name: "aboutConfigPrefs.onUAOverridesPrefChange", + register: (fire, name) => { + const prefName = `${extensionPrefNameBase}${name}`; + const callback = () => { + fire.async(name).catch(() => {}); // ignore Message Manager disconnects + }; + Services.prefs.addObserver(prefName, callback); + return () => { + Services.prefs.removeObserver(prefName, callback); + }; + }, + }).api(), + async getBranch(branchName) { + const branch = `${extensionPrefNameBase}${branchName}.`; + return Services.prefs.getChildList(branch).map(pref => { + const name = pref.replace(branch, ""); + return { name, value: Services.prefs.getBoolPref(pref) }; + }); + }, + async getPref(name) { + try { + return Services.prefs.getBoolPref( + `${extensionPrefNameBase}${name}` + ); + } catch (_) { + return undefined; + } + }, + async setPref(name, value) { + Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value); + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json new file mode 100644 index 0000000000..44284f199c --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json @@ -0,0 +1,72 @@ +[ + { + "namespace": "aboutConfigPrefs", + "description": "experimental API extension to allow access to about:config preferences", + "events": [ + { + "name": "onPrefChange", + "type": "function", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference which changed" + } + ], + "extraParameters": [ + { + "name": "name", + "type": "string", + "description": "The preference to monitor" + } + ] + } + ], + "functions": [ + { + "name": "getBranch", + "type": "function", + "description": "Get all child prefs for a branch", + "parameters": [ + { + "name": "branchName", + "type": "string", + "description": "The branch name" + } + ], + "async": true + }, + { + "name": "getPref", + "type": "function", + "description": "Get a preference's value", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference name" + } + ], + "async": true + }, + { + "name": "setPref", + "type": "function", + "description": "Set a preference's value", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference name" + }, + { + "name": "value", + "type": "boolean", + "description": "The new value" + } + ], + "async": true + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.js b/browser/extensions/webcompat/experiment-apis/appConstants.js new file mode 100644 index 0000000000..2869f299a4 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/appConstants.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global AppConstants, ExtensionAPI, XPCOMUtils */ + +this.appConstants = class extends ExtensionAPI { + getAPI(context) { + return { + appConstants: { + getReleaseBranch: () => { + if (AppConstants.NIGHTLY_BUILD) { + return "nightly"; + } else if (AppConstants.MOZ_DEV_EDITION) { + return "dev_edition"; + } else if (AppConstants.EARLY_BETA_OR_EARLIER) { + return "early_beta_or_earlier"; + } else if (AppConstants.RELEASE_OR_BETA) { + return "release_or_beta"; + } + return "unknown"; + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.json b/browser/extensions/webcompat/experiment-apis/appConstants.json new file mode 100644 index 0000000000..cf04915eca --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/appConstants.json @@ -0,0 +1,15 @@ +[ + { + "namespace": "appConstants", + "description": "experimental API to expose some app constants", + "functions": [ + { + "name": "getReleaseBranch", + "type": "function", + "description": "", + "async": true, + "parameters": [] + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.js b/browser/extensions/webcompat/experiment-apis/matchPatterns.js new file mode 100644 index 0000000000..422cba5fc4 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global ExtensionAPI */ + +this.matchPatterns = class extends ExtensionAPI { + getAPI(context) { + return { + matchPatterns: { + getMatcher(patterns) { + const set = new MatchPatternSet(patterns); + return Cu.cloneInto( + { + matches: url => { + return set.matches(url); + }, + }, + context.cloneScope, + { + cloneFunctions: true, + } + ); + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.json b/browser/extensions/webcompat/experiment-apis/matchPatterns.json new file mode 100644 index 0000000000..6fb4dc10fc --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.json @@ -0,0 +1,29 @@ +[ + { + "namespace": "matchPatterns", + "description": "experimental API extension to expose MatchPattern functionality", + "functions": [ + { + "name": "getMatcher", + "type": "function", + "description": "get a MatchPatternSet", + "parameters": [ + { + "name": "patterns", + "description": "Array of string URL patterns to match", + "type": "array", + "items": { + "type": "string" + } + } + ], + "returns": { + "type": "object", + "properties": { + "matches": { "type": "function" } + } + } + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.js b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js new file mode 100644 index 0000000000..b7dc68415c --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global ExtensionAPI, Services, XPCOMUtils */ + +this.systemManufacturer = class extends ExtensionAPI { + getAPI(context) { + return { + systemManufacturer: { + getManufacturer() { + try { + return Services.sysinfo.getProperty("manufacturer"); + } catch (_) { + return undefined; + } + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.json b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json new file mode 100644 index 0000000000..c64fccc46d --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json @@ -0,0 +1,20 @@ +[ + { + "namespace": "systemManufacturer", + "description": "experimental API extension to allow reading the device's manufacturer", + "functions": [ + { + "name": "getManufacturer", + "type": "function", + "description": "Get the device's manufacturer", + "parameters": [], + "returns": { + "type": "string", + "properties": {}, + "additionalProperties": { "type": "any" }, + "description": "The manufacturer's name." + } + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.js b/browser/extensions/webcompat/experiment-apis/trackingProtection.js new file mode 100644 index 0000000000..0f5d9a4233 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.js @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global ExtensionAPI, ExtensionCommon, ExtensionParent, Services, XPCOMUtils */ + +// eslint-disable-next-line mozilla/reject-importGlobalProperties +XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]); + +class AllowList { + constructor(id) { + this._id = id; + } + + setShims(patterns, notHosts) { + this._shimPatterns = patterns; + this._shimMatcher = new MatchPatternSet(patterns || []); + this._shimNotHosts = notHosts || []; + return this; + } + + setAllows(patterns, hosts) { + this._allowPatterns = patterns; + this._allowMatcher = new MatchPatternSet(patterns || []); + this._allowHosts = hosts || []; + return this; + } + + shims(url, topHost) { + return ( + this._shimMatcher?.matches(url) && !this._shimNotHosts?.includes(topHost) + ); + } + + allows(url, topHost) { + return ( + this._allowMatcher?.matches(url) && this._allowHosts?.includes(topHost) + ); + } +} + +class Manager { + constructor() { + this._allowLists = new Map(); + } + + _getAllowList(id) { + if (!this._allowLists.has(id)) { + this._allowLists.set(id, new AllowList(id)); + } + return this._allowLists.get(id); + } + + _ensureStarted() { + if (this._classifierObserver) { + return; + } + + this._unblockedChannelIds = new Set(); + this._channelClassifier = Cc[ + "@mozilla.org/url-classifier/channel-classifier-service;1" + ].getService(Ci.nsIChannelClassifierService); + this._classifierObserver = {}; + this._classifierObserver.observe = (subject, topic, data) => { + switch (topic) { + case "http-on-stop-request": { + const { channelId } = subject.QueryInterface(Ci.nsIIdentChannel); + this._unblockedChannelIds.delete(channelId); + break; + } + case "urlclassifier-before-block-channel": { + const channel = subject.QueryInterface( + Ci.nsIUrlClassifierBlockedChannel + ); + const { channelId, url } = channel; + let topHost; + try { + topHost = new URL(channel.topLevelUrl).hostname; + } catch (_) { + return; + } + // If anti-tracking webcompat is disabled, we only permit replacing + // channels, not fully unblocking them. + if (Manager.ENABLE_WEBCOMPAT) { + // if any allowlist unblocks the request entirely, we allow it + for (const allowList of this._allowLists.values()) { + if (allowList.allows(url, topHost)) { + this._unblockedChannelIds.add(channelId); + channel.allow(); + return; + } + } + } + // otherwise, if any allowlist shims the request we say it's replaced + for (const allowList of this._allowLists.values()) { + if (allowList.shims(url, topHost)) { + this._unblockedChannelIds.add(channelId); + channel.replace(); + return; + } + } + break; + } + } + }; + Services.obs.addObserver(this._classifierObserver, "http-on-stop-request"); + this._channelClassifier.addListener(this._classifierObserver); + } + + stop() { + if (!this._classifierObserver) { + return; + } + + Services.obs.removeObserver( + this._classifierObserver, + "http-on-stop-request" + ); + this._channelClassifier.removeListener(this._classifierObserver); + delete this._channelClassifier; + delete this._classifierObserver; + } + + wasChannelIdUnblocked(channelId) { + return this._unblockedChannelIds?.has(channelId); + } + + allow(allowListId, patterns, hosts) { + this._ensureStarted(); + this._getAllowList(allowListId).setAllows(patterns, hosts); + } + + shim(allowListId, patterns, notHosts) { + this._ensureStarted(); + this._getAllowList(allowListId).setShims(patterns, notHosts); + } + + revoke(allowListId) { + this._allowLists.delete(allowListId); + } +} +var manager = new Manager(); + +function getChannelId(context, requestId) { + const wrapper = ChannelWrapper.getRegisteredChannel( + requestId, + context.extension.policy, + context.xulBrowser.frameLoader.remoteTab + ); + return wrapper?.channel?.QueryInterface(Ci.nsIIdentChannel)?.channelId; +} + +var dFPIPrefName = "network.cookie.cookieBehavior"; +var dFPIPbPrefName = "network.cookie.cookieBehavior.pbmode"; +var dFPIStatus; +function updateDFPIStatus() { + dFPIStatus = { + nonPbMode: 5 == Services.prefs.getIntPref(dFPIPrefName), + pbMode: 5 == Services.prefs.getIntPref(dFPIPbPrefName), + }; +} + +this.trackingProtection = class extends ExtensionAPI { + onShutdown(isAppShutdown) { + if (manager) { + manager.stop(); + } + Services.prefs.removeObserver(dFPIPrefName, updateDFPIStatus); + Services.prefs.removeObserver(dFPIPbPrefName, updateDFPIStatus); + } + + getAPI(context) { + Services.prefs.addObserver(dFPIPrefName, updateDFPIStatus); + Services.prefs.addObserver(dFPIPbPrefName, updateDFPIStatus); + updateDFPIStatus(); + + return { + trackingProtection: { + async shim(allowListId, patterns, notHosts) { + manager.shim(allowListId, patterns, notHosts); + }, + async allow(allowListId, patterns, hosts) { + manager.allow(allowListId, patterns, hosts); + }, + async revoke(allowListId) { + manager.revoke(allowListId); + }, + async wasRequestUnblocked(requestId) { + if (!manager) { + return false; + } + const channelId = getChannelId(context, requestId); + if (!channelId) { + return false; + } + return manager.wasChannelIdUnblocked(channelId); + }, + async isDFPIActive(isPrivate) { + if (isPrivate) { + return dFPIStatus.pbMode; + } + return dFPIStatus.nonPbMode; + }, + }, + }; + } +}; + +XPCOMUtils.defineLazyPreferenceGetter( + Manager, + "ENABLE_WEBCOMPAT", + "privacy.antitracking.enableWebcompat", + false +); diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.json b/browser/extensions/webcompat/experiment-apis/trackingProtection.json new file mode 100644 index 0000000000..c495f39add --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.json @@ -0,0 +1,102 @@ +[ + { + "namespace": "trackingProtection", + "description": "experimental API allow requests through ETP", + "functions": [ + { + "name": "isDFPIActive", + "type": "function", + "description": "Returns whether dFPI is active for private/non-private browsing tabs", + "parameters": [ + { + "type": "boolean", + "name": "isPrivate" + } + ], + "async": true + }, + { + "name": "shim", + "type": "function", + "description": "Set specified URL patterns as intended to be shimmed", + "parameters": [ + { + "name": "allowlistId", + "description": "Identfier for the allow-list, so it may be added-to or revoked", + "type": "string" + }, + { + "name": "patterns", + "description": "Array of match patterns", + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "notHosts", + "description": "Hosts on which to not shim these patterns", + "type": "array", + "optional": true, + "items": { + "type": "string" + } + } + ] + }, + { + "name": "allow", + "type": "function", + "description": "Set specified URL patterns as intended to be allowed through the content blocker for the specified top hosts", + "parameters": [ + { + "name": "allowlistId", + "description": "Identfier for the allow-list, so it may be added-to or revoked", + "type": "string" + }, + { + "name": "patterns", + "description": "Array of match patterns", + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "hosts", + "description": "Hosts to allow the patterns on", + "type": "array", + "items": { + "type": "string" + } + } + ], + "async": true + }, + { + "name": "revoke", + "type": "function", + "description": "Revokes the given allow-list entirely (both shims and allows)", + "parameters": [ + { + "name": "allowListId", + "type": "string" + } + ], + "async": true + }, + { + "name": "wasRequestUnblocked", + "type": "function", + "description": "Whether the given requestId was unblocked by any allowList", + "parameters": [ + { + "name": "requestId", + "type": "string" + } + ], + "async": true + } + ] + } +] diff --git a/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css b/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css new file mode 100644 index 0000000000..566685c2da --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug0000000-testbed-css-injection.css @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#css-injection.red { + background-color: #0f0; +} diff --git a/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css b/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css new file mode 100644 index 0000000000..05f7e685b9 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1570328-developer-apple.com-transform-scale.css @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * developer.apple.com - content of the page is shifted to the left + * Bug #1570328 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570328 + * WebCompat issue #4070 - https://webcompat.com/issues/4070 + * + * The site is relying on zoom property which is not supported by Mozilla, + * see https://bugzilla.mozilla.org/show_bug.cgi?id=390936. Adding a combination + * of transform: scale(1.4), transform-origin and width fixes the issue + */ +@media only screen and (min-device-width: 320px) and (max-device-width: 980px), + (min-device-width: 1024px) and (max-device-width: 1024px) and (min-device-height: 1366px) and (max-device-height: 1366px) and (min-width: 320px) and (max-width: 980px) { + #tocContainer { + transform-origin: 0 0; + transform: scale(1.4); + width: 71.4%; + } +} diff --git a/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css b/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css new file mode 100644 index 0000000000..e157dc6920 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * apply.lloydsbank.co.uk - radio buttons are misplaced + * Bug #1575000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575000 + * WebCompat issue #34969 - https://webcompat.com/issues/34969 + * + * Radio buttons are displaced to the left due to positioning issue of ::before + * pseudo element, adding position relative to it's parent fixes the issue. + */ +.radio-content-field .radio.inline label span.text { + position: relative; +} diff --git a/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css b/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css new file mode 100644 index 0000000000..75ca4a8723 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1605611-maps.google.com-directions-time.css @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bug 1605611 - Cannot change Departure/arrival dates in Google Maps on Android + * + * Google Maps hides a datetime-local in its directions picker by giving it + * z-index:-50000, which causes it to be unclickable in Firefox. Here we + * use opacity:0 instead to hide it, while letting it remain clickable. + */ + +.ml-route-options-picker-container input[type="datetime-local"] { + z-index: unset; + opacity: 0; +} diff --git a/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css b/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css new file mode 100644 index 0000000000..9f673bee95 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1610344-directv.com.co-hide-unsupported-message.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * directv.com.co - Browser is not supported message + * Bug #1610344 - https://bugzilla.mozilla.org/show_bug.cgi?id=1610344 + * WebCompat issue #41822 - https://webcompat.com/issues/41822 + * + * directv.com.co is showing a "This browser is not supported" message in + * Firefox. Our tests indicated that everything is working just fine, and our + * previous contact attempts have not been successful. This intervention + * hides the large red unsupported banner. + */ +.browser-compatible.compatible.incompatible { + display: none; +} diff --git a/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css b/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css new file mode 100644 index 0000000000..fc1cb7489a --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * missingmail.usps.com - Unable to mark the check-boxes from "Disclaimer and + * Terms and Conditions" section + * Bug #1644830 - https://bugzilla.mozilla.org/show_bug.cgi?id=1644830 + * WebCompat issue #53950 - https://webcompat.com/issues/53950 + * + * missingmail.usps.com runs into a case of bug 997189, where an absolutely + * positioned inline-block element with floating siblings is shifter to the + * right, and thus invisible. + */ +.mrc-custom-checkbox-container input { + margin-left: -3rem; +} diff --git a/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css b/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css new file mode 100644 index 0000000000..2c4429a301 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1651917-teletrader.com.body-transform-origin.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * teletrader.com - content is shifted down and right + * Bug #1651917 - https://bugzilla.mozilla.org/show_bug.cgi?id=1651917 + * WebCompat issue #55217 - https://webcompat.com/issues/55217 + * + * The content is shifted down and right, because they use webkit prefixes + * for scaling and redefining the origin. Firefox doesn't support + * -webkit-transform-origin-x/y + * This is the object of https://bugzilla.mozilla.org/show_bug.cgi?id=1584881 + * Adding transform-origin: 0 0; to body fixes the issue + */ +body { + transform-origin: 0 0; +} diff --git a/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css b/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css new file mode 100644 index 0000000000..ae14d1ec13 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1653075-livescience.com-scrollbar-width.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * livescience.com - a scrollbar covering navigation menu + * Bug #1653075 - https://bugzilla.mozilla.org/show_bug.cgi?id=1653075 + * + * The scrollbar is covering navigation items and that makes them half hidden. + * There are some ::-webkit-scrollbar css rules applied to the scrollbar, + * making it thinner. Adding similar rules for Firefox fixes the issue. + */ + +.trending__list { + scrollbar-width: thin; + scrollbar-color: #f9ae3b #f5f5f5; +} diff --git a/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css b/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css new file mode 100644 index 0000000000..b13c3052f3 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1654877-preev.com-moz-appearance-fix.css @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * preev.com - typed numbers are not fully visible + * Bug #1654877 - https://bugzilla.mozilla.org/show_bug.cgi?id=1654877 + * WebCompat issue #55099 - https://webcompat.com/issues/55099 + * + * It's hard to see the entered number because the spin button is + * taking too much space. While there is -moz-appearance: textfield, + * -webkit-appearance: none; underneath supersedes it, + * leaving the spin button visible. Adding -moz-appearance: textfield; + * as a separate rule fixes the issue + */ +input[type="number"], +input[type="text"] { + -moz-appearance: textfield; +} diff --git a/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css new file mode 100644 index 0000000000..6cecb6658a --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1654907-reactine.ca-hide-unsupported.css @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * reactine.ca - Unsupported browser message + * Bug #1654907 - https://bugzilla.mozilla.org/show_bug.cgi?id=1654907 + * WebCompat issue #55481 - https://webcompat.com/issues/55481 + * + * reactine.ca is showing "Sorry this browser is not supported." + * message if Firefox for Android based on UA detection. Site seems + * to be working fine, so this intervention is to hide this message + */ +#browser-alert { + display: none !important; +} diff --git a/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css b/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css new file mode 100644 index 0000000000..adec7101ba --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1694470-myvidster.com-content-not-shown.css @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * m.myvidster.com - Content is not shown + * Bug #1694470 - https://bugzilla.mozilla.org/show_bug.cgi?id=1694470 + * WebCompat issue #67308 - https://webcompat.com/issues/67308 + * + * The site depends on Sencha Touch and should receive some specific + * -webkit-box-flex be working. + */ +#home_refresh_var { + -webkit-box-flex: 1; +} diff --git a/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css b/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css new file mode 100644 index 0000000000..7165ceb70f --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1707795-office365-sheets-overscroll-disable.css @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * www.office.com - There is an overscroll effect on Excel sheets which is + * not a very pleasant user experience. This invention disables it. + */ + +.ewr-sheetcontainer { + overscroll-behavior: none; +} diff --git a/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css b/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css new file mode 100644 index 0000000000..b4b8ca4a34 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * buskocchi.desuca.co.jp - Ensure that the map has a height so it is visible. + * Bug #1712833 - https://bugzilla.mozilla.org/show_bug.cgi?id=1712833 + * WebCompat issue #50837 - https://webcompat.com/issues/50837 + */ + +form[name="main"] { + height: 100%; +} diff --git a/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css b/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css new file mode 100644 index 0000000000..3765d1de1e --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1741234-patient.alphalabs.ca-height-fix.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * patient.alphalabs.ca - "Continue" button is overlapped by displaced page footer + * Bug #1741234 - https://bugzilla.mozilla.org/show_bug.cgi?id=1741234 + * WebCompat issue #93156 - https://webcompat.com/issues/93156 + */ + +body { + height: 100%; +} diff --git a/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css b/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css new file mode 100644 index 0000000000..8c1ab7b2ae --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1765947-veniceincoming.com-left-fix.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * veniceincoming.com - site is not usable + * Bug #1765947 - https://bugzilla.mozilla.org/show_bug.cgi?id=1765947 + * WebCompat issue #102133 - https://webcompat.com/issues/102133 + */ + +.tour-list .single-tour .mobile-link { + left: 0; +} diff --git a/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css b/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css new file mode 100644 index 0000000000..f9460951af --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1770962-coldwellbankerhomes.com-image-height.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * coldwellbankerhomes.com - Property images are displayed squeezed + * Bug #1770962 - https://bugzilla.mozilla.org/show_bug.cgi?id=1770962 + * WebCompat issue #102872 - https://webcompat.com/issues/102872 + */ + +.property-snapshot-psr-panel + .prop-pix + .photo-carousel.owl + .owl-stage-outer + .owl-item + img { + height: -moz-available; +} diff --git a/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css b/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css new file mode 100644 index 0000000000..840e4ad7fb --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1774490-rainews.it-gallery-fix.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * rainews.it - Image slideshow is not shown + * Bug #1774490 - https://bugzilla.mozilla.org/show_bug.cgi?id=1774490 + * WebCompat issue #105402 - https://webcompat.com/issues/105402 + */ + +.photogallery-swiper .swiper-slide { + height: auto; +} diff --git a/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css b/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css new file mode 100644 index 0000000000..50b5bb2e90 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * aveeno.com and acuvue.com - Unsupported message is displayed + * + * Bug #1784141 - aveeno.com - https://bugzilla.mozilla.org/show_bug.cgi?id=1784141 + * Bug #1804730 - acuvue.com - https://bugzilla.mozilla.org/show_bug.cgi?id=1804730 + * + * WebCompat issue #103557 - https://webcompat.com/issues/103557 + * WebCompat issue #103557 - https://webcompat.com/issues/110797 + */ + +#browser-alert { + display: none !important; +} diff --git a/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css b/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css new file mode 100644 index 0000000000..d20b84a99b --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1784199-entrata-platform-unsupported.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * aptsovation.com - Unsupported message is displayed on sites based on Entrata platform + * Bug #1784199 - https://bugzilla.mozilla.org/show_bug.cgi?id=1784199 + * WebCompat issue #100131 - https://webcompat.com/issues/100131 + */ + +* { + color: unset; +} + +#propertyProduct, +.banner_overlay { + display: none; +} diff --git a/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css b/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css new file mode 100644 index 0000000000..fd42fd7648 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * www.vivobarefoot.com - product filters cannot be interacted-with + * Bug #1799994 - https://bugzilla.mozilla.org/show_bug.cgi?id=1799994 + * WebCompat issue #108752 - https://webcompat.com/issues/108752 + * + * The breakage is actually correct behavior, but because of Chrome + * bug https://bugs.chromium.org/p/chromium/issues/detail?id=606208 + * it is currently not breaking on Chrome. We can work around it by + * bumping the z-index of the filter options. + */ +.page-products .filter-options { + z-index: 2; +} diff --git a/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css b/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css new file mode 100644 index 0000000000..5ce3d9f15c --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * www.honda.co.uk- "choose dealer" buttons cannot be interacted-with + * Bug #1800000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1800000 + * WebCompat issue #109242 - https://webcompat.com/issues/109242 + * + * The breakage is actually correct behavior, but because of Chrome + * bug https://bugs.chromium.org/p/chromium/issues/detail?id=606208 + * it is currently not breaking on Chrome. We can work around it by + * bumping the z-index of the filter options. + */ +.cta-button { + z-index: 2; +} diff --git a/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css b/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css new file mode 100644 index 0000000000..ab9788ddc1 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * nppes.cms.hhs.gov - Firefox is an unsupported browser + * WebCompat issue #119017 - https://github.com/webcompat/web-bugs/issues/119017 + * + * As everything seems to work just fine, this intervention simply hides the + * banner. + */ + +#unsupportedDiv { + display: none !important; +} diff --git a/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css b/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css new file mode 100644 index 0000000000..f6bee8c878 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1829949-tomshardware.com-scrollbar-width.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * tomshardware.com - a scrollbar covering navigation menu + * Bug #1829949 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829949 + * WebCompat issue #121170 - https://github.com/webcompat/web-bugs/issues/121170 + * + * The scrollbar is covering navigation items and that makes them half hidden. + * There are some ::-webkit-scrollbar css rules applied to the scrollbar, + * making it thinner. Adding similar rules for Firefox fixes the issue. + */ + +.trending__list { + scrollbar-width: thin; + scrollbar-color: #000 #f5f5f5; +} diff --git a/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css b/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css new file mode 100644 index 0000000000..54de51589a --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1829952-eventer.co.il-button-height.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * eventer.co.il - a button is covering entire page + * Bug #1829952 - https://bugzilla.mozilla.org/show_bug.cgi?id=1829952 + * WebCompat issue #121296 - https://github.com/webcompat/web-bugs/issues/121296 + * + * The button is covering the page only in Firefox on mobile + * because of additional styles applied via @-moz-document url-prefix. + * Resetting the height makes the button normal size + */ + +#purchasePageRedesignContainer .mobileStripButton { + height: auto; + min-height: auto; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css b/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css new file mode 100644 index 0000000000..1f7585e637 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830747-babbel.com-page-height.css @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * my.babbel.com - "Next" button is not visible + * Bug #1830747 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830747 + * WebCompat issue #119212 - https://github.com/webcompat/web-bugs/issues/119212 + * + * The next button on the bottom of the page is not visible in Firefox, + * but visible in Chrome since the site is using -webkit-fill-available rule. + * Adding height: 100% to the page wrapper allows to see the button. + */ + +[data-main] { + height: 100%; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css b/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css new file mode 100644 index 0000000000..64974c46e4 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830752-afisha.ru-slider-pointer-events.css @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * afisha.ru - Slider not working + * Bug #1830752 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830752 + * WebCompat issue #120455 - https://github.com/webcompat/web-bugs/issues/120455 + * + * The range slider for price filtering is not working because of pointer-events:none applied + * on the slider element. It's working in Chrome because of webkit specific rules + * set with -moz-range-thumb that override the pointer events on the slider thumb to auto. + * Setting the same rule with -moz-range-thumb makes the slider to work. + */ + +.gNPvK::-moz-range-thumb, +.y5iHc::-moz-range-thumb { + background-color: #0050ff; + border-color: #0050ff; + border-radius: 50%; + cursor: pointer; + pointer-events: auto; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css b/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css new file mode 100644 index 0000000000..f2e24346e1 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830761-91mobiles.com-content-height.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * 91mobiles.com - Text overlapping + * Bug #1830761 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830761 + * WebCompat issue #117029 - https://github.com/webcompat/web-bugs/issues/117029 + * + * The content overlaps dedicated space since Firefox honors small heights on <td> + * due to https://bugzilla.mozilla.org/show_bug.cgi?id=1461852. Setting the height to + * fit-content makes it work as expected. + */ + +#fixed-table tr td .cmp-summary-box, +.cmpr-table .textpanel { + height: fit-content; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css new file mode 100644 index 0000000000..753835de6a --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830796-copyleaks.com-hide-unsupported.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * copyleaks.com - Unsupported message + * Bug #1830796 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830796 + * WebCompat issue #121395 - https://github.com/webcompat/web-bugs/issues/121395 + */ + +#outdated { + display: none !important; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css new file mode 100644 index 0000000000..7726140d0e --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830810-interceramic.com-hide-unsupported.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * interceramic.com - Unsupported message + * Bug #1830810 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830810 + * WebCompat issue #117807 - https://github.com/webcompat/web-bugs/issues/117807 + */ + +#ff-modal { + display: none !important; +} diff --git a/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css b/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css new file mode 100644 index 0000000000..707e75765e --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1830813-page.onstove.com-hide-unsupported.css @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * onstove.com - Unsupported message + * Bug #1830813 - https://bugzilla.mozilla.org/show_bug.cgi?id=1830813 + * WebCompat issue #116760 - https://github.com/webcompat/web-bugs/issues/116760 + */ + +.gnb-alerts.gnb-old-browser { + height: 0; +} + +.isCampaign .gnb-stove.gnb-default-fixed, +.isCampaign .layout.layout-base .layout-header { + height: 68px; +} diff --git a/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css b/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css new file mode 100644 index 0000000000..70fc01b86f --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * autostar-novoross.ru - Map is not as tall as expected + * Bug #1836103 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836103 + * WebCompat issue #80763 - https://github.com/webcompat/web-bugs/issues/80763 + */ + +.t396 .tn-atom { + height: 100%; +} diff --git a/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css b/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css new file mode 100644 index 0000000000..db6f018619 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * cnn.com - Printing in portrait mode results in blank pages + * Bug #1836105 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836105 + * + * Disable some of CNN's giant styles for height, top and margin-bottom, + * since they break print layout in Firefox (as per bug 1830307) + */ + +@media print { + .header__wrapper-outer { + height: initial !important; + top: initial !important; + margin-bottom: initial !important; + } +} diff --git a/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css b/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css new file mode 100644 index 0000000000..ef255c7b89 --- /dev/null +++ b/browser/extensions/webcompat/injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * clalit.co.il - Hide number input spinners as page intends + * Bug #1836177 - https://bugzilla.mozilla.org/show_bug.cgi?id=1836177 + * WebCompat issue #109468 - https://github.com/webcompat/web-bugs/issues/109468 + */ + +input[type="number"] { + -moz-appearance: textfield; +} diff --git a/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js b/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js new file mode 100644 index 0000000000..7a192d6c41 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug0000000-testbed-js-injection.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals exportFunction */ + +Object.defineProperty(window.wrappedJSObject, "isTestFeatureSupported", { + get: exportFunction(function () { + return true; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js b/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js new file mode 100644 index 0000000000..7a8e85f538 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1448747-fastclick-shim.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1448747 - Neutralize FastClick + * + * The patch is applied on sites using FastClick library + * to make sure `FastClick.notNeeded` returns `true`. + * This allows to disable FastClick and fix various breakage caused + * by the library (mainly non-functioning drop-down lists). + */ + +/* globals exportFunction */ + +(function () { + const proto = CSS2Properties.prototype.wrappedJSObject; + const descriptor = Object.getOwnPropertyDescriptor(proto, "touchAction"); + const { get } = descriptor; + + descriptor.get = exportFunction(function () { + try { + throw Error(); + } catch (e) { + if (e.stack?.includes("notNeeded")) { + return "none"; + } + } + return get.call(this); + }, window); + + Object.defineProperty(proto, "touchAction", descriptor); +})(); diff --git a/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js b/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js new file mode 100644 index 0000000000..40e17b4a36 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1452707 - Build site patch for ib.absa.co.za + * WebCompat issue #16401 - https://webcompat.com/issues/16401 + * + * The online banking at ib.absa.co.za detect if window.controllers is a + * non-falsy value to detect if the current browser is Firefox or something + * else. In bug 1448045, this shim has been disabled for Firefox Nightly 61+, + * which breaks the UA detection on this site and results in a "Browser + * unsuppored" error message. + * + * This site patch simply sets window.controllers to a string, resulting in + * their check to work again. + */ + +/* globals exportFunction */ + +console.info( + "window.controllers has been shimmed for compatibility reasons. See https://webcompat.com/issues/16401 for details." +); + +Object.defineProperty(window.wrappedJSObject, "controllers", { + get: exportFunction(function () { + return true; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js b/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js new file mode 100644 index 0000000000..06085acc5a --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1457335 - histography.io - Override UA & navigator.vendor + * WebCompat issue #1804 - https://webcompat.com/issues/1804 + * + * This site is using a strict matching of navigator.userAgent and + * navigator.vendor to allow access for Safari or Chrome. Here, we set the + * values appropriately so we get recognized as Chrome. + */ + +/* globals exportFunction */ + +console.info( + "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/1804 for details." +); + +const CHROME_UA = navigator.userAgent + " Chrome for WebCompat"; + +Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { + get: exportFunction(function () { + return CHROME_UA; + }, window), + + set: exportFunction(function () {}, window), +}); + +Object.defineProperty(window.navigator.wrappedJSObject, "vendor", { + get: exportFunction(function () { + return "Google Inc."; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js b/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js new file mode 100644 index 0000000000..5aa72e75ae --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1472075 - Build UA override for Bank of America for OSX & Linux + * WebCompat issue #2787 - https://webcompat.com/issues/2787 + * + * BoA is showing a red warning to Linux and macOS users, while accepting + * Windows users without warning. From our side, there is no difference here + * and we receive a lot of user complains about the warnings, so we spoof + * as Firefox on Windows in those cases. + */ + +/* globals exportFunction */ + +if (!navigator.platform.includes("Win")) { + console.info( + "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/2787 for details." + ); + + const WINDOWS_UA = navigator.userAgent.replace( + /\(.*; rv:/i, + "(Windows NT 10.0; Win64; x64; rv:" + ); + + Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { + get: exportFunction(function () { + return WINDOWS_UA; + }, window), + + set: exportFunction(function () {}, window), + }); + + Object.defineProperty(window.navigator.wrappedJSObject, "appVersion", { + get: exportFunction(function () { + return "appVersion"; + }, window), + + set: exportFunction(function () {}, window), + }); + + Object.defineProperty(window.navigator.wrappedJSObject, "platform", { + get: exportFunction(function () { + return "Win64"; + }, window), + + set: exportFunction(function () {}, window), + }); +} diff --git a/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js b/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js new file mode 100644 index 0000000000..5c757466c6 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * m.tailieu.vn - Override PDFJS.disableWorker to be true + * WebCompat issue #39057 - https://webcompat.com/issues/39057 + * + * Custom viewer built with PDF.js is not working in Firefox for Android + * Disabling worker to match Chrome behavior fixes the issue + */ + +/* globals exportFunction */ + +console.info( + "window.PDFJS.disableWorker has been set to true for compatibility reasons. See https://webcompat.com/issues/39057 for details." +); + +let globals = {}; + +Object.defineProperty(window.wrappedJSObject, "PDFJS", { + get: exportFunction(function () { + return globals; + }, window), + + set: exportFunction(function (value = {}) { + globals = value; + globals.disableWorker = true; + }, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js b/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js new file mode 100644 index 0000000000..a068e8dcd5 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1605611-maps.google.com-directions-time.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1605611 - Cannot change Departure/arrival dates in Google Maps on Android + * + * This patch re-enables the disabled "Leave now" button. + * + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1800498 and + * https://bugzilla.mozilla.org/show_bug.cgi?id=1605611 for details. + */ + +const selector = + ".ml-directions-searchbox-parent [aria-haspopup=dialog][disabled=true]"; + +document.addEventListener("DOMContentLoaded", () => { + // In case the element appeared before the MutationObserver was activated. + for (const elem of document.querySelectorAll(selector)) { + elem.disabled = false; + } + // Start watching for the insertion of the "Leave now" button. + const moOptions = { + attributeFilter: ["disabled"], + attributes: true, + subtree: true, + }; + const mo = new MutationObserver(function (records) { + for (const { target } of records) { + if (target.matches(selector)) { + target.disabled = false; + } + } + }); + mo.observe(document.body, moOptions); +}); diff --git a/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js b/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js new file mode 100644 index 0000000000..fb9be74039 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1631811-datastudio.google.com-indexedDB.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1631811 - disable indexedDB for datastudio.google.com iframes + * + * Indexed DB is disabled already for these iframes due to cookie blocking. + * This intervention changes the functionality from throwing a SecurityError + * when indexedDB is accessed to removing it from the window object + */ + +console.info( + "window.indexedDB has been overwritten for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1631811 for details." +); + +Object.defineProperty(window.wrappedJSObject, "indexedDB", { + get: undefined, + set: undefined, +}); diff --git a/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js b/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js new file mode 100644 index 0000000000..577a55450a --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1722955-frontgate.com-ua-override.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Bug 1722955 - Add UA override for frontgate.com + * Webcompat issue #36277 - https://github.com/webcompat/web-bugs/issues/36277 + * + * The website is sending the desktop version to Firefox on mobile devices + * based on UA sniffing. Spoofing as Chrome fixes this. + */ + +/* globals exportFunction, UAHelpers */ + +console.info( + "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/36277 for details." +); + +UAHelpers.overrideWithDeviceAppropriateChromeUA(); diff --git a/browser/extensions/webcompat/injections/js/bug1724764-window-print.js b/browser/extensions/webcompat/injections/js/bug1724764-window-print.js new file mode 100644 index 0000000000..20e9ecf22d --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1724764-window-print.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Generic window.print shim + * + * Issues related to an error caused by missing window.print() method on Android. + * Adding print to the window object allows to unbreak the sites. + */ + +/* globals exportFunction */ + +if (typeof window.print === "undefined") { + console.info( + "window.print has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1659818 for details." + ); + + Object.defineProperty(window.wrappedJSObject, "print", { + get: exportFunction(function () { + return true; + }, window), + + set: exportFunction(function () {}, window), + }); +} diff --git a/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js b/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js new file mode 100644 index 0000000000..ab7b76c799 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1724868-news.yahoo.co.jp-ua-override.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1724868 - news.yahoo.co.jp - Override UA + * WebCompat issue #82605 - https://webcompat.com/issues/82605 + * + * Yahoo Japan news doesn't allow playing video in Firefox on Android + * as they don't have it in their support matrix. They check UA override twice + * and display different ui with the same error. Changing UA to Chrome via + * content script allows playing the videos. + */ + +/* globals exportFunction */ + +console.info( + "The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/82605 for details." +); + +Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { + get: exportFunction(function () { + return "Mozilla/5.0 (Linux; Android 11; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36"; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js b/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js new file mode 100644 index 0000000000..d1823aec74 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1731825 - Office 365 email handling prompt autohide + * + * This site patch prevents the notification bar on Office 365 + * apps from popping up on each page-load, offering to handle + * email with Outlook. + */ + +/* globals exportFunction */ + +const warning = + "Office 365 Outlook email handling prompt has been hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=1731825 for details."; + +const localStorageKey = "mailProtocolHandlerAlreadyOffered"; + +const nav = navigator.wrappedJSObject; +const { registerProtocolHandler } = nav; +const { localStorage } = window.wrappedJSObject; + +Object.defineProperty(navigator.wrappedJSObject, "registerProtocolHandler", { + value: exportFunction(function (scheme, url, title) { + if (localStorage.getItem(localStorageKey)) { + console.info(warning); + return undefined; + } + registerProtocolHandler.call(nav, scheme, url, title); + localStorage.setItem(localStorageKey, true); + return undefined; + }, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js new file mode 100644 index 0000000000..5ae55ec6f3 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1739489 - Entering an emoji using the MacOS IME "crashes" Draft.js editors. + */ + +/* globals exportFunction */ + +console.info( + "textInput event has been remapped to beforeinput for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1739489 for details." +); + +window.wrappedJSObject.TextEvent = window.wrappedJSObject.InputEvent; + +const { CustomEvent, Event, EventTarget } = window.wrappedJSObject; +var Remapped = [ + [CustomEvent, "constructor"], + [Event, "constructor"], + [Event, "initEvent"], + [EventTarget, "addEventListener"], + [EventTarget, "removeEventListener"], +]; + +for (const [obj, name] of Remapped) { + const { prototype } = obj; + const orig = prototype[name]; + Object.defineProperty(prototype, name, { + value: exportFunction(function (type, b, c, d) { + if (type?.toLowerCase() === "textinput") { + type = "beforeinput"; + } + return orig.call(this, type, b, c, d); + }, window), + }); +} + +if (location.host === "www.reddit.com") { + (function () { + const EditorCSS = ".public-DraftEditor-content[contenteditable=true]"; + let obsEditor, obsStart, obsText, obsKey, observer; + const obsConfig = { characterData: true, childList: true, subtree: true }; + const obsHandler = () => { + observer.disconnect(); + const finalTextNode = obsEditor.querySelector( + `[data-offset-key="${obsKey}"] [data-text='true']` + ).firstChild; + const end = obsStart + obsText.length; + window + .getSelection() + .setBaseAndExtent(finalTextNode, end, finalTextNode, end); + }; + observer = new MutationObserver(obsHandler); + + document.documentElement.addEventListener( + "beforeinput", + e => { + if (e.inputType != "insertFromPaste") { + return; + } + const { target } = e; + obsEditor = target.closest(EditorCSS); + if (!obsEditor) { + return; + } + const items = e?.dataTransfer.items; + for (let item of items) { + if (item.type === "text/plain") { + e.preventDefault(); + item.getAsString(text => { + obsText = text; + + // find the editor-managed <span> which contains the text node the + // cursor starts on, and the cursor's location (or the selection start) + const sel = window.getSelection(); + obsStart = sel.anchorOffset; + let anchor = sel.anchorNode; + if (!anchor.closest) { + anchor = anchor.parentElement; + } + anchor = anchor.closest("[data-offset-key]"); + obsKey = anchor.getAttribute("data-offset-key"); + + // set us up to wait for the editor to either update or replace the + // <span> with that key (the one containing the text to be changed). + // we will then make sure the cursor is after the pasted text, as if + // the editor recreates the node, the cursor position is lost + observer.observe(obsEditor, obsConfig); + + // force the editor to "paste". sending paste or other events will not + // work, nor using execCommand (adding HTML will screw up the DOM that + // the editor expects, and adding plain text will make it ignore newlines). + target.dispatchEvent( + new InputEvent("beforeinput", { + inputType: "insertText", + data: text, + bubbles: true, + cancelable: true, + }) + ); + + // blur the editor to force it to update/flush its state, because otherwise + // the paste works, but the editor doesn't show it (until it is re-focused). + obsEditor.blur(); + }); + break; + } + } + }, + true + ); + })(); +} diff --git a/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js b/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js new file mode 100644 index 0000000000..7383a4e567 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1769762-tiktok.com-plugins-shim.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1769762 - Empty out navigator.plugins + * WebCompat issue #103612 - https://webcompat.com/issues/103612 + * + * Certain features of the site are breaking if navigator.plugins array is not empty: + * + * 1. "Likes" on the comments are not saved + * 2. Can't reply to other people's comments + * 3. "Likes" on the videos are not saved + * 4. Can't follow an account (after refreshing "Follow" button is visible again) + * + * (note that the first 2 are still broken if you open devtools even with this intervention) + */ + +/* globals exportFunction */ + +console.info( + "The PluginArray has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1753874 for details." +); + +const pluginsArray = new window.wrappedJSObject.Array(); +Object.setPrototypeOf(pluginsArray, PluginArray.prototype); + +Object.defineProperty(navigator.wrappedJSObject, "plugins", { + get: exportFunction(function () { + return pluginsArray; + }, window), + set: exportFunction(function (val) {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js b/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js new file mode 100644 index 0000000000..ca7ef5b6c5 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1774005-installtrigger-shim.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1774005 - Generic window.InstallTrigger shim + * + * This interventions shims window.InstallTrigger to a string, which evaluates + * as `true` in web developers browser sniffing code. This intervention will + * be applied to multiple domains, see bug 1774005 for more information. + */ + +/* globals exportFunction */ + +console.info( + "The InstallTrigger has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1774005 for details." +); + +Object.defineProperty(window.wrappedJSObject, "InstallTrigger", { + get: exportFunction(function () { + return "This property has been shimed for Web Compatibility reasons."; + }, window), + set: exportFunction(function (_) {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js b/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js new file mode 100644 index 0000000000..7bde4a7d82 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1784302-effectiveType-shim.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1784302 - Issues due to missing navigator.connection after + * https://bugzilla.mozilla.org/show_bug.cgi?id=1637922 landed. + * Webcompat issue #104838 - https://github.com/webcompat/web-bugs/issues/104838 + */ + +/* globals cloneInto, exportFunction */ + +console.info( + "navigator.connection has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1756692 for details." +); + +var connection = { + addEventListener: () => {}, + removeEventListener: () => {}, + effectiveType: "4g", +}; + +window.navigator.wrappedJSObject.connection = cloneInto(connection, window, { + cloneFunctions: true, +}); diff --git a/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js b/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js new file mode 100644 index 0000000000..f3c0e03513 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1795490 - Cannot use date fields on China Airlines mobile page + * + * This patch ensures that the search input never has the [disabled] + * attribute, so that users may tap/click on it to search. + * + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1795490 for details. + */ + +const SELECTOR = `#departureDateMobile[disabled], #returnDateMobile[disabled]`; + +function check(target) { + if (target.nodeName === "INPUT" && target.matches(SELECTOR)) { + target.removeAttribute("disabled"); + return true; + } + return false; +} + +new MutationObserver(mutations => { + for (const { addedNodes, target, attributeName } of mutations) { + if (attributeName === "disabled") { + check(target); + } else { + addedNodes?.forEach(node => { + if (!check(node)) { + node + .querySelectorAll?.(SELECTOR) + ?.forEach(n => n.removeAttribute("disabled")); + } + }); + } + } +}).observe(document, { attributes: true, childList: true, subtree: true }); diff --git a/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js b/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js new file mode 100644 index 0000000000..941f071e2c --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1799968 - Build site patch for www.samsung.com + * WebCompat issue #108993 - https://webcompat.com/issues/108993 + * + * Samsung's Watch pages try to detect the OS via navigator.appVersion, + * but fail with Linux because they expect it to contain the literal + * string "linux", and their JS breaks. + * + * As such this site patch sets appVersion to "5.0 (Linux)", and is + * only meant to be applied on Linux. + */ + +/* globals exportFunction */ + +console.info( + "navigator.appVersion has been shimmed for compatibility reasons. See https://webcompat.com/issues/108993 for details." +); + +Object.defineProperty(navigator.wrappedJSObject, "appVersion", { + get: exportFunction(function () { + return "5.0 (Linux)"; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js b/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js new file mode 100644 index 0000000000..191e97dec1 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1799980-healow.com-infinite-loop-fix.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1799980 - Healow gets stuck in an infinite loop while pages load + * + * This patch keeps Healow's localization scripts from getting stuck in + * an infinite loop while their pages are loading. + * + * This happens because they use synchronous XMLHttpRequests to fetch a + * JSON file with their localized text on the first call to their i18n + * function, and then force subsequent calls to wait for it by waiting + * in an infinite loop. + * + * But since they're in an infinite loop, the code after the syncXHR will + * never be able to run, so this ultimately triggers a slow script warning. + * + * We can improve this by just preventing the infinite loop from happening, + * though since they disable caching on their JSON files it means that more + * XHRs may happen. But since those files are small, this seems like a + * reasonable compromise until they migrate to a better i18n solution. + * + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1799980 for details. + */ + +/* globals exportFunction */ + +Object.defineProperty(window.wrappedJSObject, "ajaxRequestProcessing", { + get: exportFunction(function () { + return false; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js b/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js new file mode 100644 index 0000000000..91f1c1a19a --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1818818-fastclick-legacy-shim.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1818818 - Neutralize FastClick + * + * The patch is applied on sites using older version of FastClick library. + * This allows to disable FastClick and fix various breakage caused + * by the library. + */ + +/* globals exportFunction */ + +const proto = CSS2Properties.prototype.wrappedJSObject; +Object.defineProperty(proto, "msTouchAction", { + get: exportFunction(function () { + return "none"; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js b/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js new file mode 100644 index 0000000000..bbe76c465f --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1819450-cmbchina.com-ua-change.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1819450 - cmbchina.com - Override UA + * + * The site is using UA detection to redirect to + * m.cmbchina.com (mobile version of the site). Adding `SAMSUNG` allows + * to bypass the detection of mobile browser. + */ + +/* globals exportFunction */ + +console.info( + "The user agent has been overridden for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1081239 for details." +); + +const MODIFIED_UA = navigator.userAgent + " SAMSUNG"; + +Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { + get: exportFunction(function () { + return MODIFIED_UA; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js b/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js new file mode 100644 index 0000000000..a72e938e4f --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * axisbank.com - Shim webkitSpeechRecognition + * WebCompat issue #117770 - https://webcompat.com/issues/117770 + * + * The page with bank offerings is not loading options due to the + * site relying on webkitSpeechRecognition, which is undefined in Firefox. + * Shimming it to `class {}` makes the pages work. + */ + +/* globals exportFunction */ + +console.info( + "webkitSpeechRecognition was shimmed for compatibility reasons. See https://webcompat.com/issues/117770 for details." +); + +Object.defineProperty(window.wrappedJSObject, "webkitSpeechRecognition", { + value: exportFunction(function () { + return class {}; + }, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js b/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js new file mode 100644 index 0000000000..c230feb43d --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1819678-cnki.net-undisable-search-field.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1819678 - cnki.net - Cannot use search field + * WebCompat issue #115777 - https://webcompat.com/issues/115777 + * + * This patch ensures that the search input never has the [disabled] + * attribute, so that users may tap/click on it to search. + * + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1819678 for details. + */ + +console.info( + "search input disabled attribute was removed for compatibility reasons. See https://webcompat.com/issues/115777 for details." +); + +const SELECTOR = `.searchimg[disabled]`; + +function check(target) { + if (target.nodeName === "INPUT" && target.matches(SELECTOR)) { + target.removeAttribute("disabled"); + return true; + } + return false; +} + +new MutationObserver(mutations => { + for (const { addedNodes, target, attributeName } of mutations) { + if (attributeName === "disabled") { + check(target); + } else { + addedNodes?.forEach(node => { + if (!check(node)) { + node + .querySelectorAll?.(SELECTOR) + ?.forEach(n => n.removeAttribute("disabled")); + } + }); + } + } +}).observe(document, { attributes: true, childList: true, subtree: true }); diff --git a/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js b/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js new file mode 100644 index 0000000000..6e6b5823cb --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1819678-free4talk.com-window-chrome-shim.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1827678 - UA spoof for www.free4talk.com + * + * This site is checking for window.chrome, so let's spoof that. + */ + +/* globals exportFunction */ + +console.info( + "window.chrome has been shimmed for compatibility reasons. See https://github.com/webcompat/web-bugs/issues/77727 for details." +); + +Object.defineProperty(window.wrappedJSObject, "chrome", { + get: exportFunction(function () { + return true; + }, window), + + set: exportFunction(function () {}, window), +}); diff --git a/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js b/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js new file mode 100644 index 0000000000..2b1eb11baf --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1830776-blueshieldca.com-unsupported.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1830776 - blueshieldca.com + * WebCompat issue #112630 - https://webcompat.com/issues/112630 + * + * The site is showing unsupported message in Firefox. + * They're also checking for "browserCollapsed" item in sessionStorage + * before showing the message, to only show it once. Adding this + * item to sessionStorage will make sure the message is not shown + * on the initial load. + */ + +console.info( + "browserCollapsed in sessionStorage has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1830776 for details." +); + +if (!sessionStorage.getItem("browserCollapsed")) { + sessionStorage.setItem("browserCollapsed", "true"); +} diff --git a/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js b/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js new file mode 100644 index 0000000000..433c416770 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1831007 - Shim window.OnetrustActiveGroups for Nintendo sites + * + * Nintendo relies on `window.OnetrustActiveGroups` being defined. If it's not, + * users may have intermittent issues signing into their account, as they're + * then trying to call `.split()` on `undefined`. + * + * This intervention sets a default value (an empty string), but still allows + * the value to be overwritten at any time. + */ + +/* globals exportFunction */ + +console.info( + "The window.OnetrustActiveGroups property has been shimmed for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1831007 for details." +); + +Object.defineProperty(window.wrappedJSObject, "OnetrustActiveGroups", { + value: "", + writable: true, +}); diff --git a/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js b/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js new file mode 100644 index 0000000000..719267748b --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1836157-thai-masszazs-niceScroll-disable.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bug 1836157 - Shim navigator.platform on www.thai-massaszs.net/en/ + * + * This page adds niceScroll on Android, which breaks scrolling and + * zooming on Firefox. Adding ` Mac` to `navigator.platform` makes + * the page avoid adding niceScroll entirely, unbreaking the page. + */ + +var plat = navigator.platform; +if (!plat.includes("Mac")) { + console.info( + "The navigator.platform property has been shimmed to include 'Mac' for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1836157 for details." + ); + + Object.defineProperty(navigator.__proto__.wrappedJSObject, "platform", { + value: plat + " Mac", + writable: true, + }); +} diff --git a/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js b/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js new file mode 100644 index 0000000000..2d328de108 --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1842437-www.youtube.com-performance-now-precision.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1842437 - When attempting to go back on youtube.com, the content remains the same + * + * If consecutive session history entries had history.state.entryTime set to same value, + * back button doesn't work as expected. The entryTime value is coming from performance.now() + * and modifying its return value slightly to make sure two close consecutive calls don't + * get the same result helped with resolving the issue. + */ + +/* globals exportFunction */ + +console.info( + "performance.now precision has been modified for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1756970 for details." +); + +const origPerf = performance.wrappedJSObject; +const origNow = origPerf.now; + +let counter = 0; +let previousVal = 0; + +Object.defineProperty(window.performance.wrappedJSObject, "now", { + value: exportFunction(function () { + let originalVal = origNow.call(origPerf); + if (originalVal === previousVal) { + originalVal += 0.00000003 * ++counter; + } else { + previousVal = originalVal; + counter = 0; + } + return originalVal; + }, window), +}); diff --git a/browser/extensions/webcompat/lib/about_compat_broker.js b/browser/extensions/webcompat/lib/about_compat_broker.js new file mode 100644 index 0000000000..faaa56a38e --- /dev/null +++ b/browser/extensions/webcompat/lib/about_compat_broker.js @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global browser, module, onMessageFromTab */ + +class AboutCompatBroker { + constructor(bindings) { + this._injections = bindings.injections; + this._uaOverrides = bindings.uaOverrides; + this._shims = bindings.shims; + + if (!this._injections && !this._uaOverrides && !this._shims) { + throw new Error("No interventions; about:compat broker is not needed"); + } + + this.portsToAboutCompatTabs = this.buildPorts(); + this._injections?.bindAboutCompatBroker(this); + this._uaOverrides?.bindAboutCompatBroker(this); + this._shims?.bindAboutCompatBroker(this); + } + + buildPorts() { + const ports = new Set(); + + browser.runtime.onConnect.addListener(port => { + ports.add(port); + port.onDisconnect.addListener(function () { + ports.delete(port); + }); + }); + + async function broadcast(message) { + for (const port of ports) { + port.postMessage(message); + } + } + + return { broadcast }; + } + + filterOverrides(overrides) { + return overrides + .filter(override => override.availableOnPlatform) + .map(override => { + const { id, active, bug, domain, hidden } = override; + return { id, active, bug, domain, hidden }; + }); + } + + getInterventionById(id) { + for (const [type, things] of Object.entries({ + overrides: this._uaOverrides?.getAvailableOverrides() || [], + interventions: this._injections?.getAvailableInjections() || [], + shims: this._shims?.getAvailableShims() || [], + })) { + for (const what of things) { + if (what.id === id) { + return { type, what }; + } + } + } + return {}; + } + + bootup() { + onMessageFromTab(msg => { + switch (msg.command || msg) { + case "toggle": { + const id = msg.id; + const { type, what } = this.getInterventionById(id); + if (!what) { + return Promise.reject( + `No such override or intervention to toggle: ${id}` + ); + } + const active = type === "shims" ? !what.disabledReason : what.active; + this.portsToAboutCompatTabs + .broadcast({ toggling: id, active }) + .then(async () => { + switch (type) { + case "interventions": { + if (active) { + await this._injections?.disableInjection(what); + } else { + await this._injections?.enableInjection(what); + } + break; + } + case "overrides": { + if (active) { + await this._uaOverrides?.disableOverride(what); + } else { + await this._uaOverrides?.enableOverride(what); + } + break; + } + case "shims": { + if (active) { + await this._shims?.disableShimForSession(id); + } else { + await this._shims?.enableShimForSession(id); + } + // no need to broadcast the "toggled" signal for shims, as + // they send a shimsUpdated message themselves instead + return; + } + } + this.portsToAboutCompatTabs.broadcast({ + toggled: id, + active: !active, + }); + }); + break; + } + case "getAllInterventions": { + return Promise.resolve({ + overrides: + (this._uaOverrides?.isEnabled() && + this.filterOverrides( + this._uaOverrides?.getAvailableOverrides() + )) || + false, + interventions: + (this._injections?.isEnabled() && + this.filterOverrides( + this._injections?.getAvailableInjections() + )) || + false, + shims: this._shims?.getAvailableShims() || false, + }); + } + } + return undefined; + }); + } +} + +module.exports = AboutCompatBroker; diff --git a/browser/extensions/webcompat/lib/custom_functions.js b/browser/extensions/webcompat/lib/custom_functions.js new file mode 100644 index 0000000000..97603e0424 --- /dev/null +++ b/browser/extensions/webcompat/lib/custom_functions.js @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser, module */ + +const replaceStringInRequest = ( + requestId, + inString, + outString, + inEncoding = "utf-8" +) => { + const filter = browser.webRequest.filterResponseData(requestId); + const decoder = new TextDecoder(inEncoding); + const encoder = new TextEncoder(); + const RE = new RegExp(inString, "g"); + const carryoverLength = inString.length; + let carryover = ""; + + filter.ondata = event => { + const replaced = ( + carryover + decoder.decode(event.data, { stream: true }) + ).replace(RE, outString); + filter.write(encoder.encode(replaced.slice(0, -carryoverLength))); + carryover = replaced.slice(-carryoverLength); + }; + + filter.onstop = event => { + if (carryover.length) { + filter.write(encoder.encode(carryover)); + } + filter.close(); + }; +}; + +const CUSTOM_FUNCTIONS = { + detectSwipeFix: injection => { + const { urls, types } = injection.data; + const listener = (injection.data.listener = ({ requestId }) => { + replaceStringInRequest( + requestId, + "preventDefault:true", + "preventDefault:false" + ); + return {}; + }); + browser.webRequest.onBeforeRequest.addListener(listener, { urls, types }, [ + "blocking", + ]); + }, + detectSwipeFixDisable: injection => { + const { listener } = injection.data; + browser.webRequest.onBeforeRequest.removeListener(listener); + delete injection.data.listener; + }, + noSniffFix: injection => { + const { urls, contentType } = injection.data; + const listener = (injection.data.listener = e => { + e.responseHeaders.push(contentType); + return { responseHeaders: e.responseHeaders }; + }); + + browser.webRequest.onHeadersReceived.addListener(listener, { urls }, [ + "blocking", + "responseHeaders", + ]); + }, + noSniffFixDisable: injection => { + const { listener } = injection.data; + browser.webRequest.onHeadersReceived.removeListener(listener); + delete injection.data.listener; + }, + runScriptBeforeRequest: injection => { + const { bug, message, request, script, types } = injection; + const warning = `${message} See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; + + const listener = (injection.listener = e => { + const { tabId, frameId } = e; + return browser.tabs + .executeScript(tabId, { + file: script, + frameId, + runAt: "document_start", + }) + .then(() => { + browser.tabs.executeScript(tabId, { + code: `console.warn(${JSON.stringify(warning)})`, + runAt: "document_start", + }); + }) + .catch(_ => {}); + }); + + browser.webRequest.onBeforeRequest.addListener( + listener, + { urls: request, types: types || ["script"] }, + ["blocking"] + ); + }, + runScriptBeforeRequestDisable: injection => { + const { listener } = injection; + browser.webRequest.onBeforeRequest.removeListener(listener); + delete injection.data.listener; + }, +}; + +module.exports = CUSTOM_FUNCTIONS; diff --git a/browser/extensions/webcompat/lib/injections.js b/browser/extensions/webcompat/lib/injections.js new file mode 100644 index 0000000000..8760f551c7 --- /dev/null +++ b/browser/extensions/webcompat/lib/injections.js @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser, module */ + +class Injections { + constructor(availableInjections, customFunctions) { + this.INJECTION_PREF = "perform_injections"; + + this._injectionsEnabled = true; + + this._availableInjections = availableInjections; + this._activeInjections = new Map(); + this._customFunctions = customFunctions; + } + + bindAboutCompatBroker(broker) { + this._aboutCompatBroker = broker; + } + + bootup() { + browser.aboutConfigPrefs.onPrefChange.addListener(() => { + this.checkInjectionPref(); + }, this.INJECTION_PREF); + this.checkInjectionPref(); + } + + checkInjectionPref() { + browser.aboutConfigPrefs.getPref(this.INJECTION_PREF).then(value => { + if (value === undefined) { + browser.aboutConfigPrefs.setPref(this.INJECTION_PREF, true); + } else if (value === false) { + this.unregisterContentScripts(); + } else { + this.registerContentScripts(); + } + }); + } + + getAvailableInjections() { + return this._availableInjections; + } + + isEnabled() { + return this._injectionsEnabled; + } + + async registerContentScripts() { + const platformInfo = await browser.runtime.getPlatformInfo(); + const platformMatches = [ + "all", + platformInfo.os, + platformInfo.os == "android" ? "android" : "desktop", + ]; + for (const injection of this._availableInjections) { + if (platformMatches.includes(injection.platform)) { + injection.availableOnPlatform = true; + await this.enableInjection(injection); + } + } + + this._injectionsEnabled = true; + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ + interventionsChanged: this._aboutCompatBroker.filterOverrides( + this._availableInjections + ), + }); + } + + assignContentScriptDefaults(contentScripts) { + let finalConfig = Object.assign({}, contentScripts); + + if (!finalConfig.runAt) { + finalConfig.runAt = "document_start"; + } + + return finalConfig; + } + + async enableInjection(injection) { + if (injection.active) { + return undefined; + } + + if (injection.customFunc) { + return this.enableCustomInjection(injection); + } + + return this.enableContentScripts(injection); + } + + enableCustomInjection(injection) { + if (injection.customFunc in this._customFunctions) { + this._customFunctions[injection.customFunc](injection); + injection.active = true; + } else { + console.error( + `Provided function ${injection.customFunc} wasn't found in functions list` + ); + } + } + + async enableContentScripts(injection) { + try { + const handle = await browser.contentScripts.register( + this.assignContentScriptDefaults(injection.contentScripts) + ); + this._activeInjections.set(injection, handle); + injection.active = true; + } catch (ex) { + console.error( + "Registering WebCompat GoFaster content scripts failed: ", + ex + ); + } + } + + unregisterContentScripts() { + for (const injection of this._availableInjections) { + this.disableInjection(injection); + } + + this._injectionsEnabled = false; + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ + interventionsChanged: false, + }); + } + + async disableInjection(injection) { + if (!injection.active) { + return undefined; + } + + if (injection.customFunc) { + return this.disableCustomInjections(injection); + } + + return this.disableContentScripts(injection); + } + + disableCustomInjections(injection) { + const disableFunc = injection.customFunc + "Disable"; + + if (disableFunc in this._customFunctions) { + this._customFunctions[disableFunc](injection); + injection.active = false; + } else { + console.error( + `Provided function ${disableFunc} for disabling injection wasn't found in functions list` + ); + } + } + + async disableContentScripts(injection) { + const contentScript = this._activeInjections.get(injection); + await contentScript.unregister(); + this._activeInjections.delete(injection); + injection.active = false; + } +} + +module.exports = Injections; diff --git a/browser/extensions/webcompat/lib/intervention_helpers.js b/browser/extensions/webcompat/lib/intervention_helpers.js new file mode 100644 index 0000000000..16ea6572f2 --- /dev/null +++ b/browser/extensions/webcompat/lib/intervention_helpers.js @@ -0,0 +1,233 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals module */ + +const GOOGLE_TLDS = [ + "com", + "ac", + "ad", + "ae", + "com.af", + "com.ag", + "com.ai", + "al", + "am", + "co.ao", + "com.ar", + "as", + "at", + "com.au", + "az", + "ba", + "com.bd", + "be", + "bf", + "bg", + "com.bh", + "bi", + "bj", + "com.bn", + "com.bo", + "com.br", + "bs", + "bt", + "co.bw", + "by", + "com.bz", + "ca", + "com.kh", + "cc", + "cd", + "cf", + "cat", + "cg", + "ch", + "ci", + "co.ck", + "cl", + "cm", + "cn", + "com.co", + "co.cr", + "com.cu", + "cv", + "com.cy", + "cz", + "de", + "dj", + "dk", + "dm", + "com.do", + "dz", + "com.ec", + "ee", + "com.eg", + "es", + "com.et", + "fi", + "com.fj", + "fm", + "fr", + "ga", + "ge", + "gf", + "gg", + "com.gh", + "com.gi", + "gl", + "gm", + "gp", + "gr", + "com.gt", + "gy", + "com.hk", + "hn", + "hr", + "ht", + "hu", + "co.id", + "iq", + "ie", + "co.il", + "im", + "co.in", + "io", + "is", + "it", + "je", + "com.jm", + "jo", + "co.jp", + "co.ke", + "ki", + "kg", + "co.kr", + "com.kw", + "kz", + "la", + "com.lb", + "com.lc", + "li", + "lk", + "co.ls", + "lt", + "lu", + "lv", + "com.ly", + "co.ma", + "md", + "me", + "mg", + "mk", + "ml", + "com.mm", + "mn", + "ms", + "com.mt", + "mu", + "mv", + "mw", + "com.mx", + "com.my", + "co.mz", + "com.na", + "ne", + "com.nf", + "com.ng", + "com.ni", + "nl", + "no", + "com.np", + "nr", + "nu", + "co.nz", + "com.om", + "com.pk", + "com.pa", + "com.pe", + "com.ph", + "pl", + "com.pg", + "pn", + "com.pr", + "ps", + "pt", + "com.py", + "com.qa", + "ro", + "rs", + "ru", + "rw", + "com.sa", + "com.sb", + "sc", + "se", + "com.sg", + "sh", + "si", + "sk", + "com.sl", + "sn", + "sm", + "so", + "st", + "sr", + "com.sv", + "td", + "tg", + "co.th", + "com.tj", + "tk", + "tl", + "tm", + "to", + "tn", + "com.tr", + "tt", + "com.tw", + "co.tz", + "com.ua", + "co.ug", + "co.uk", + "com", + "com.uy", + "co.uz", + "com.vc", + "co.ve", + "vg", + "co.vi", + "com.vn", + "vu", + "ws", + "co.za", + "co.zm", + "co.zw", +]; + +var InterventionHelpers = { + /** + * Useful helper to generate a list of domains with a fixed base domain and + * multiple country-TLDs or other cases with various TLDs. + * + * Example: + * matchPatternsForTLDs("*://mozilla.", "/*", ["com", "org"]) + * => ["*://mozilla.com/*", "*://mozilla.org/*"] + */ + matchPatternsForTLDs(base, suffix, tlds) { + return tlds.map(tld => base + tld + suffix); + }, + + /** + * A modified version of matchPatternsForTLDs that always returns the match + * list for all known Google country TLDs. + */ + matchPatternsForGoogle(base, suffix = "/*") { + return InterventionHelpers.matchPatternsForTLDs(base, suffix, GOOGLE_TLDS); + }, +}; + +module.exports = InterventionHelpers; diff --git a/browser/extensions/webcompat/lib/messaging_helper.js b/browser/extensions/webcompat/lib/messaging_helper.js new file mode 100644 index 0000000000..d978ed384f --- /dev/null +++ b/browser/extensions/webcompat/lib/messaging_helper.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +// By default, only the first handler for browser.runtime.onMessage which +// returns a value will get to return one. As such, we need to let them all +// receive the message, and all have a chance to return a response (with the +// first non-undefined result being the one that is ultimately returned). +// This way, about:compat and the shims library can both get a chance to +// process a message, and just return undefined if they wish to ignore it. + +const onMessageFromTab = (function () { + const handlers = new Set(); + + browser.runtime.onMessage.addListener((msg, sender) => { + const promises = [...handlers.values()].map(fn => fn(msg, sender)); + return Promise.allSettled(promises).then(results => { + for (const { reason, value } of results) { + if (reason) { + console.error(reason); + } else if (value !== undefined) { + return value; + } + } + return undefined; + }); + }); + + return function (handler) { + handlers.add(handler); + }; +})(); diff --git a/browser/extensions/webcompat/lib/module_shim.js b/browser/extensions/webcompat/lib/module_shim.js new file mode 100644 index 0000000000..2fd39fdbbd --- /dev/null +++ b/browser/extensions/webcompat/lib/module_shim.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * We cannot yet use proper JS modules within webextensions, as support for them + * is highly experimental and highly instable. So we end up just including all + * the JS files we need as separate background scripts, and since they all are + * executed within the same context, this works for our in-browser deployment. + * + * However, this code is tracked outside of mozilla-central, and we work on + * shipping this code in other products, like android-components as well. + * Because of that, we have automated tests running within that repository. To + * make our lives easier, we add `module.exports` statements to the JS source + * files, so we can easily import their contents into our NodeJS-based test + * suite. + * + * This works fine, but obviously, `module` is not defined when running + * in-browser. So let's use this empty object as a shim, so we don't run into + * runtime exceptions because of that. + */ +var module = {}; diff --git a/browser/extensions/webcompat/lib/requestStorageAccess_helper.js b/browser/extensions/webcompat/lib/requestStorageAccess_helper.js new file mode 100644 index 0000000000..032225bb78 --- /dev/null +++ b/browser/extensions/webcompat/lib/requestStorageAccess_helper.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals browser */ + +// Helper for calling the internal requestStorageAccessForOrigin method. The +// method is called on the first-party document for the third-party which needs +// first-party storage access. +browser.runtime.onMessage.addListener(request => { + let { requestStorageAccessOrigin, warning } = request; + if (!requestStorageAccessOrigin) { + return false; + } + + // Log a warning to the web console, informing about the shim. + console.warn(warning); + + // Call the internal storage access API. Passing false means we don't require + // user activation, but will always show the storage access prompt. The user + // has to explicitly allow storage access. + return document + .requestStorageAccessForOrigin(requestStorageAccessOrigin, false) + .then(() => { + return { success: true }; + }) + .catch(() => { + return { success: false }; + }); +}); diff --git a/browser/extensions/webcompat/lib/shim_messaging_helper.js b/browser/extensions/webcompat/lib/shim_messaging_helper.js new file mode 100644 index 0000000000..ee109713a5 --- /dev/null +++ b/browser/extensions/webcompat/lib/shim_messaging_helper.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +if (!window.Shims) { + window.Shims = new Map(); +} + +if (!window.ShimsHelperReady) { + window.ShimsHelperReady = true; + + browser.runtime.onMessage.addListener(details => { + const { shimId, warning } = details; + if (!shimId) { + return; + } + window.Shims.set(shimId, details); + if (warning) { + console.warn(warning); + } + }); + + async function handleMessage(port, shimId, messageId, message) { + let response; + const shim = window.Shims.get(shimId); + if (shim) { + const { needsShimHelpers, origin } = shim; + if (origin === location.origin) { + if (needsShimHelpers?.includes(message)) { + const msg = { shimId, message }; + try { + response = await browser.runtime.sendMessage(msg); + } catch (_) {} + } + } + } + port.postMessage({ messageId, response }); + } + + window.addEventListener( + "ShimConnects", + e => { + e.stopPropagation(); + e.preventDefault(); + const { port, pendingMessages, shimId } = e.detail; + const shim = window.Shims.get(shimId); + if (!shim) { + return; + } + port.onmessage = ({ data }) => { + handleMessage(port, shimId, data.messageId, data.message); + }; + for (const [messageId, message] of pendingMessages) { + handleMessage(port, shimId, messageId, message); + } + }, + true + ); + + window.dispatchEvent(new CustomEvent("ShimHelperReady")); +} diff --git a/browser/extensions/webcompat/lib/shims.js b/browser/extensions/webcompat/lib/shims.js new file mode 100644 index 0000000000..ee33627c57 --- /dev/null +++ b/browser/extensions/webcompat/lib/shims.js @@ -0,0 +1,1044 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser, module, onMessageFromTab */ + +// To grant shims access to bundled logo images without risking +// exposing our moz-extension URL, we have the shim request them via +// nonsense URLs which we then redirect to the actual files (but only +// on tabs where a shim using a given logo happens to be active). +const LogosBaseURL = "https://smartblock.firefox.etp/"; + +const releaseBranchPromise = browser.appConstants.getReleaseBranch(); + +const platformPromise = browser.runtime.getPlatformInfo().then(info => { + return info.os === "android" ? "android" : "desktop"; +}); + +let debug = async function () { + if ((await releaseBranchPromise) !== "release_or_beta") { + console.debug.apply(this, arguments); + } +}; +let error = async function () { + if ((await releaseBranchPromise) !== "release_or_beta") { + console.error.apply(this, arguments); + } +}; +let warn = async function () { + if ((await releaseBranchPromise) !== "release_or_beta") { + console.warn.apply(this, arguments); + } +}; + +class Shim { + constructor(opts, manager) { + this.manager = manager; + + const { contentScripts, matches, unblocksOnOptIn } = opts; + + this.branches = opts.branches; + this.bug = opts.bug; + this.isGoogleTrendsDFPIFix = opts.custom == "google-trends-dfpi-fix"; + this.file = opts.file; + this.hiddenInAboutCompat = opts.hiddenInAboutCompat; + this.hosts = opts.hosts; + this.id = opts.id; + this.logos = opts.logos || []; + this.matches = []; + this.name = opts.name; + this.notHosts = opts.notHosts; + this.onlyIfBlockedByETP = opts.onlyIfBlockedByETP; + this.onlyIfDFPIActive = opts.onlyIfDFPIActive; + this.onlyIfPrivateBrowsing = opts.onlyIfPrivateBrowsing; + this._options = opts.options || {}; + this.needsShimHelpers = opts.needsShimHelpers; + this.platform = opts.platform || "all"; + this.runFirst = opts.runFirst; + this.unblocksOnOptIn = unblocksOnOptIn; + this.requestStorageAccessForRedirect = opts.requestStorageAccessForRedirect; + + this._hostOptIns = new Set(); + + this._disabledByConfig = opts.disabled; + this._disabledGlobally = false; + this._disabledForSession = false; + this._disabledByPlatform = false; + this._disabledByReleaseBranch = false; + + this._activeOnTabs = new Set(); + this._showedOptInOnTabs = new Set(); + + const pref = `disabled_shims.${this.id}`; + + this.redirectsRequests = !!this.file && matches?.length; + + this._contentScriptRegistrations = []; + this.contentScripts = contentScripts || []; + for (const script of this.contentScripts) { + if (typeof script.css === "string") { + script.css = [{ file: `/shims/${script.css}` }]; + } + if (typeof script.js === "string") { + script.js = [{ file: `/shims/${script.js}` }]; + } + } + + for (const match of matches || []) { + if (!match.types) { + this.matches.push({ patterns: [match], types: ["script"] }); + } else { + this.matches.push(match); + } + if (match.target) { + this.redirectsRequests = true; + } + } + + browser.aboutConfigPrefs.onPrefChange.addListener(async () => { + const value = await browser.aboutConfigPrefs.getPref(pref); + this._disabledPrefValue = value; + this._onEnabledStateChanged(); + }, pref); + + this.ready = Promise.all([ + browser.aboutConfigPrefs.getPref(pref), + platformPromise, + releaseBranchPromise, + ]).then(([disabledPrefValue, platform, branch]) => { + this._disabledPrefValue = disabledPrefValue; + + this._disabledByPlatform = + this.platform !== "all" && this.platform !== platform; + + this._disabledByReleaseBranch = false; + for (const supportedBranchAndPlatform of this.branches || []) { + const [supportedBranch, supportedPlatform] = + supportedBranchAndPlatform.split(":"); + if ( + (!supportedPlatform || supportedPlatform == platform) && + supportedBranch != branch + ) { + this._disabledByReleaseBranch = true; + } + } + + this._preprocessOptions(platform, branch); + this._onEnabledStateChanged(); + }); + } + + _preprocessOptions(platform, branch) { + // options may be any value, but can optionally be gated for specified + // platform/branches, if in the format `{value, branches, platform}` + this.options = {}; + for (const [k, v] of Object.entries(this._options)) { + if (v?.value) { + if ( + (!v.platform || v.platform === platform) && + (!v.branches || v.branches.includes(branch)) + ) { + this.options[k] = v.value; + } + } else { + this.options[k] = v; + } + } + } + + get enabled() { + if (this._disabledGlobally || this._disabledForSession) { + return false; + } + + if (this._disabledPrefValue !== undefined) { + return !this._disabledPrefValue; + } + + return ( + !this._disabledByConfig && + !this._disabledByPlatform && + !this._disabledByReleaseBranch + ); + } + + get disabledReason() { + if (this._disabledGlobally) { + return "globalPref"; + } + + if (this._disabledForSession) { + return "session"; + } + + if (this._disabledPrefValue !== undefined) { + if (this._disabledPrefValue === true) { + return "pref"; + } + return false; + } + + if (this._disabledByConfig) { + return "config"; + } + + if (this._disabledByPlatform) { + return "platform"; + } + + if (this._disabledByReleaseBranch) { + return "releaseBranch"; + } + + return false; + } + + onAllShimsEnabled() { + const wasEnabled = this.enabled; + this._disabledGlobally = false; + if (!wasEnabled) { + this._onEnabledStateChanged(); + } + } + + onAllShimsDisabled() { + const wasEnabled = this.enabled; + this._disabledGlobally = true; + if (wasEnabled) { + this._onEnabledStateChanged(); + } + } + + enableForSession() { + const wasEnabled = this.enabled; + this._disabledForSession = false; + if (!wasEnabled) { + this._onEnabledStateChanged(); + } + } + + disableForSession() { + const wasEnabled = this.enabled; + this._disabledForSession = true; + if (wasEnabled) { + this._onEnabledStateChanged(); + } + } + + async _onEnabledStateChanged() { + this.manager?.onShimStateChanged(this.id); + if (!this.enabled) { + await this._unregisterContentScripts(); + return this._revokeRequestsInETP(); + } + await this._registerContentScripts(); + return this._allowRequestsInETP(); + } + + async _registerContentScripts() { + if ( + this.contentScripts.length && + !this._contentScriptRegistrations.length + ) { + const matches = []; + for (const options of this.contentScripts) { + matches.push(options.matches); + const reg = await browser.contentScripts.register(options); + this._contentScriptRegistrations.push(reg); + } + const urls = Array.from(new Set(matches.flat())); + debug("Enabling content scripts for these URLs:", urls); + } + } + + async _unregisterContentScripts() { + for (const registration of this._contentScriptRegistrations) { + registration.unregister(); + } + this._contentScriptRegistrations = []; + } + + async _allowRequestsInETP() { + const matches = this.matches.map(m => m.patterns).flat(); + if (matches.length) { + await browser.trackingProtection.shim(this.id, matches); + } + + if (this._hostOptIns.size) { + const optIns = this.getApplicableOptIns(); + if (optIns.length) { + await browser.trackingProtection.allow( + this.id, + this._optInPatterns, + Array.from(this._hostOptIns) + ); + } + } + } + + _revokeRequestsInETP() { + return browser.trackingProtection.revoke(this.id); + } + + setActiveOnTab(tabId, active = true) { + if (active) { + this._activeOnTabs.add(tabId); + } else { + this._activeOnTabs.delete(tabId); + this._showedOptInOnTabs.delete(tabId); + } + } + + isActiveOnTab(tabId) { + return this._activeOnTabs.has(tabId); + } + + meantForHost(host) { + const { hosts, notHosts } = this; + if (hosts || notHosts) { + if ( + (notHosts && notHosts.includes(host)) || + (hosts && !hosts.includes(host)) + ) { + return false; + } + } + return true; + } + + async unblocksURLOnOptIn(url) { + if (!this._optInPatterns) { + this._optInPatterns = await this.getApplicableOptIns(); + } + + if (!this._optInMatcher) { + this._optInMatcher = browser.matchPatterns.getMatcher( + Array.from(this._optInPatterns) + ); + } + + return this._optInMatcher.matches(url); + } + + isTriggeredByURLAndType(url, type) { + for (const entry of this.matches || []) { + if (!entry.types.includes(type)) { + continue; + } + if (!entry.matcher) { + entry.matcher = browser.matchPatterns.getMatcher( + Array.from(entry.patterns) + ); + } + if (entry.matcher.matches(url)) { + return entry; + } + } + + return undefined; + } + + async getApplicableOptIns() { + if (this._applicableOptIns) { + return this._applicableOptIns; + } + const optins = []; + for (const unblock of this.unblocksOnOptIn || []) { + if (typeof unblock === "string") { + optins.push(unblock); + continue; + } + const { branches, patterns, platforms } = unblock; + if (platforms?.length) { + const platform = await platformPromise; + if (platform !== "all" && !platforms.includes(platform)) { + continue; + } + } + if (branches?.length) { + const branch = await releaseBranchPromise; + if (!branches.includes(branch)) { + continue; + } + } + optins.push.apply(optins, patterns); + } + this._applicableOptIns = optins; + return optins; + } + + async onUserOptIn(host) { + const optins = await this.getApplicableOptIns(); + if (optins.length) { + this.userHasOptedIn = true; + this._hostOptIns.add(host); + await browser.trackingProtection.allow( + this.id, + optins, + Array.from(this._hostOptIns) + ); + } + } + + hasUserOptedInAlready(host) { + return this._hostOptIns.has(host); + } + + showOptInWarningOnce(tabId, origin) { + if (this._showedOptInOnTabs.has(tabId)) { + return Promise.resolve(); + } + this._showedOptInOnTabs.add(tabId); + + const { bug, name } = this; + const warning = `${name} is allowed on ${origin} for this browsing session due to user opt-in. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; + return browser.tabs + .executeScript(tabId, { + code: `console.warn(${JSON.stringify(warning)})`, + runAt: "document_start", + }) + .catch(() => {}); + } +} + +class Shims { + constructor(availableShims) { + if (!browser.trackingProtection) { + console.error("Required experimental add-on APIs for shims unavailable"); + return; + } + + this._registerShims(availableShims); + + onMessageFromTab(this._onMessageFromShim.bind(this)); + + this.ENABLED_PREF = "enable_shims"; + browser.aboutConfigPrefs.onPrefChange.addListener(() => { + this._checkEnabledPref(); + }, this.ENABLED_PREF); + this._haveCheckedEnabledPref = this._checkEnabledPref(); + } + + bindAboutCompatBroker(broker) { + this._aboutCompatBroker = broker; + } + + getShimInfoForAboutCompat(shim) { + const { bug, disabledReason, hiddenInAboutCompat, id, name } = shim; + const type = "smartblock"; + return { bug, disabledReason, hidden: hiddenInAboutCompat, id, name, type }; + } + + disableShimForSession(id) { + const shim = this.shims.get(id); + shim?.disableForSession(); + } + + enableShimForSession(id) { + const shim = this.shims.get(id); + shim?.enableForSession(); + } + + onShimStateChanged(id) { + if (!this._aboutCompatBroker) { + return; + } + + const shim = this.shims.get(id); + if (!shim) { + return; + } + + const shimsChanged = [this.getShimInfoForAboutCompat(shim)]; + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ shimsChanged }); + } + + getAvailableShims() { + const shims = Array.from(this.shims.values()).map( + this.getShimInfoForAboutCompat + ); + shims.sort((a, b) => a.name.localeCompare(b.name)); + return shims; + } + + _registerShims(shims) { + if (this.shims) { + throw new Error("_registerShims has already been called"); + } + + this.shims = new Map(); + for (const shimOpts of shims) { + const { id } = shimOpts; + if (!this.shims.has(id)) { + this.shims.set(shimOpts.id, new Shim(shimOpts, this)); + } + } + + // Register onBeforeRequest listener which handles storage access requests + // on matching redirects. + let redirectTargetUrls = Array.from(shims.values()) + .filter(shim => shim.requestStorageAccessForRedirect) + .flatMap(shim => shim.requestStorageAccessForRedirect) + .map(([, dstUrl]) => dstUrl); + + // Unique target urls. + redirectTargetUrls = Array.from(new Set(redirectTargetUrls)); + + if (redirectTargetUrls.length) { + debug("Registering redirect listener for requestStorageAccess helper", { + redirectTargetUrls, + }); + browser.webRequest.onBeforeRequest.addListener( + this._onRequestStorageAccessRedirect.bind(this), + { urls: redirectTargetUrls, types: ["main_frame"] }, + ["blocking"] + ); + } + + function addTypePatterns(type, patterns, set) { + if (!set.has(type)) { + set.set(type, { patterns: new Set() }); + } + const allSet = set.get(type).patterns; + for (const pattern of patterns) { + allSet.add(pattern); + } + } + + const allMatchTypePatterns = new Map(); + const allHeaderChangingMatchTypePatterns = new Map(); + const allLogos = []; + for (const shim of this.shims.values()) { + const { logos, matches } = shim; + allLogos.push(...logos); + for (const { patterns, target, types } of matches || []) { + for (const type of types) { + if (shim.isGoogleTrendsDFPIFix) { + addTypePatterns(type, patterns, allHeaderChangingMatchTypePatterns); + } + if (target || shim.file || shim.runFirst) { + addTypePatterns(type, patterns, allMatchTypePatterns); + } + } + } + } + + if (allLogos.length) { + const urls = Array.from(new Set(allLogos)).map(l => { + return `${LogosBaseURL}${l}`; + }); + debug("Allowing access to these logos:", urls); + const unmarkShimsActive = tabId => { + for (const shim of this.shims.values()) { + shim.setActiveOnTab(tabId, false); + } + }; + browser.tabs.onRemoved.addListener(unmarkShimsActive); + browser.tabs.onUpdated.addListener((tabId, changeInfo) => { + if (changeInfo.discarded || changeInfo.url) { + unmarkShimsActive(tabId); + } + }); + browser.webRequest.onBeforeRequest.addListener( + this._redirectLogos.bind(this), + { urls, types: ["image"] }, + ["blocking"] + ); + } + + if (allHeaderChangingMatchTypePatterns) { + for (const [ + type, + { patterns }, + ] of allHeaderChangingMatchTypePatterns.entries()) { + const urls = Array.from(patterns); + debug("Shimming these", type, "URLs:", urls); + browser.webRequest.onBeforeSendHeaders.addListener( + this._onBeforeSendHeaders.bind(this), + { urls, types: [type] }, + ["blocking", "requestHeaders"] + ); + browser.webRequest.onHeadersReceived.addListener( + this._onHeadersReceived.bind(this), + { urls, types: [type] }, + ["blocking", "responseHeaders"] + ); + } + } + + if (!allMatchTypePatterns.size) { + debug("Skipping shims; none enabled"); + return; + } + + for (const [type, { patterns }] of allMatchTypePatterns.entries()) { + const urls = Array.from(patterns); + debug("Shimming these", type, "URLs:", urls); + + browser.webRequest.onBeforeRequest.addListener( + this._ensureShimForRequestOnTab.bind(this), + { urls, types: [type] }, + ["blocking"] + ); + } + } + + async _checkEnabledPref() { + await browser.aboutConfigPrefs.getPref(this.ENABLED_PREF).then(value => { + if (value === undefined) { + browser.aboutConfigPrefs.setPref(this.ENABLED_PREF, true); + } else if (value === false) { + this.enabled = false; + } else { + this.enabled = true; + } + }); + } + + get enabled() { + return this._enabled; + } + + set enabled(enabled) { + if (enabled === this._enabled) { + return; + } + + this._enabled = enabled; + + for (const shim of this.shims.values()) { + if (enabled) { + shim.onAllShimsEnabled(); + } else { + shim.onAllShimsDisabled(); + } + } + } + + async _onRequestStorageAccessRedirect({ + originUrl: srcUrl, + url: dstUrl, + tabId, + }) { + debug("Detected redirect", { srcUrl, dstUrl, tabId }); + + // Check if a shim needs to request storage access for this redirect. This + // handler is called when the *source url* matches a shims redirect pattern, + // but we still need to check if the *destination url* matches. + const matchingShims = Array.from(this.shims.values()).filter(shim => { + const { enabled, requestStorageAccessForRedirect } = shim; + + if (!enabled || !requestStorageAccessForRedirect) { + return false; + } + + return requestStorageAccessForRedirect.some( + ([srcPattern, dstPattern]) => + browser.matchPatterns.getMatcher([srcPattern]).matches(srcUrl) && + browser.matchPatterns.getMatcher([dstPattern]).matches(dstUrl) + ); + }); + + // For each matching shim, find out if its enabled in regard to dFPI state. + const bugNumbers = new Set(); + let isDFPIActive = null; + await Promise.all( + matchingShims.map(async shim => { + if (shim.onlyIfDFPIActive) { + // Only get the dFPI state for the first shim which requires it. + if (isDFPIActive === null) { + const tabIsPB = (await browser.tabs.get(tabId)).incognito; + isDFPIActive = await browser.trackingProtection.isDFPIActive( + tabIsPB + ); + } + if (!isDFPIActive) { + return; + } + } + bugNumbers.add(shim.bug); + }) + ); + + // If there is no shim which needs storage access for this redirect src/dst + // pair, resume it. + if (!bugNumbers.size) { + return; + } + + // Inject the helper to call requestStorageAccessForOrigin on the document. + await browser.tabs.executeScript(tabId, { + file: "/lib/requestStorageAccess_helper.js", + runAt: "document_start", + }); + + const bugUrls = Array.from(bugNumbers) + .map(bugNo => `https://bugzilla.mozilla.org/show_bug.cgi?id=${bugNo}`) + .join(", "); + const warning = `Firefox calls the Storage Access API for ${dstUrl} on behalf of ${srcUrl}. See the following bugs for details: ${bugUrls}`; + + // Request storage access for the origin of the destination url of the + // redirect. + const { origin: requestStorageAccessOrigin } = new URL(dstUrl); + + // Wait for the requestStorageAccess request to finish before resuming the + // redirect. + const { success } = await browser.tabs.sendMessage(tabId, { + requestStorageAccessOrigin, + warning, + }); + debug("requestStorageAccess callback", { + success, + requestStorageAccessOrigin, + srcUrl, + dstUrl, + bugNumbers, + }); + } + + async _onMessageFromShim(payload, sender, sendResponse) { + const { tab, frameId } = sender; + const { id, url } = tab; + const { shimId, message } = payload; + + // Ignore unknown messages (for instance, from about:compat). + if (message !== "getOptions" && message !== "optIn") { + return undefined; + } + + if (sender.id !== browser.runtime.id || id === -1) { + throw new Error("not allowed"); + } + + // Important! It is entirely possible for sites to spoof + // these messages, due to shims allowing web pages to + // communicate with the extension. + + const shim = this.shims.get(shimId); + if (!shim?.needsShimHelpers?.includes(message)) { + throw new Error("not allowed"); + } + + if (message === "getOptions") { + return Object.assign( + { + platform: await platformPromise, + releaseBranch: await releaseBranchPromise, + }, + shim.options + ); + } else if (message === "optIn") { + try { + await shim.onUserOptIn(new URL(url).hostname); + const origin = new URL(tab.url).origin; + warn( + "** User opted in for", + shim.name, + "shim on", + origin, + "on tab", + id, + "frame", + frameId + ); + await shim.showOptInWarningOnce(id, origin); + } catch (err) { + console.error(err); + throw new Error("error"); + } + } + + return undefined; + } + + async _redirectLogos(details) { + await this._haveCheckedEnabledPref; + + if (!this.enabled) { + return { cancel: true }; + } + + const { tabId, url } = details; + const logo = new URL(url).pathname.slice(1); + + for (const shim of this.shims.values()) { + await shim.ready; + + if (!shim.enabled) { + continue; + } + + if (shim.onlyIfDFPIActive) { + const isPB = (await browser.tabs.get(details.tabId)).incognito; + if (!(await browser.trackingProtection.isDFPIActive(isPB))) { + continue; + } + } + + if (!shim.logos.includes(logo)) { + continue; + } + + if (shim.isActiveOnTab(tabId)) { + return { redirectUrl: browser.runtime.getURL(`shims/${logo}`) }; + } + } + + return { cancel: true }; + } + + async _onHeadersReceived(details) { + await this._haveCheckedEnabledPref; + + for (const shim of this.shims.values()) { + await shim.ready; + + if (!shim.enabled) { + continue; + } + + if (shim.onlyIfDFPIActive) { + const isPB = (await browser.tabs.get(details.tabId)).incognito; + if (!(await browser.trackingProtection.isDFPIActive(isPB))) { + continue; + } + } + + if (shim.isGoogleTrendsDFPIFix) { + if (shim.GoogleNidCookieToUse) { + continue; + } + + for (const header of details.responseHeaders) { + if (header.name == "set-cookie") { + shim.GoogleNidCookieToUse = header.value; + return { redirectUrl: details.url }; + } + } + } + } + + return undefined; + } + + async _onBeforeSendHeaders(details) { + await this._haveCheckedEnabledPref; + + const { frameId, requestHeaders, tabId } = details; + + if (!this.enabled) { + return { requestHeaders }; + } + + for (const shim of this.shims.values()) { + await shim.ready; + + if (!shim.enabled) { + continue; + } + + if (shim.isGoogleTrendsDFPIFix) { + const value = shim.GoogleNidCookieToUse; + + if (!value) { + continue; + } + + let found; + for (let header of requestHeaders) { + if (header.name.toLowerCase() === "cookie") { + header.value = value; + found = true; + } + } + if (!found) { + requestHeaders.push({ name: "Cookie", value }); + } + + browser.tabs + .get(tabId) + .then(({ url }) => { + debug( + `Google Trends dFPI fix used on tab ${tabId} frame ${frameId} (${url})` + ); + }) + .catch(() => {}); + + const warning = `Working around Google Trends tracking protection breakage. See https://bugzilla.mozilla.org/show_bug.cgi?id=${shim.bug} for details.`; + browser.tabs + .executeScript(tabId, { + code: `console.warn(${JSON.stringify(warning)})`, + runAt: "document_start", + }) + .catch(() => {}); + } + } + + return { requestHeaders }; + } + + async _ensureShimForRequestOnTab(details) { + await this._haveCheckedEnabledPref; + + if (!this.enabled) { + return undefined; + } + + // We only ever reach this point if a request is for a URL which ought to + // be shimmed. We never get here if a request is blocked, and we only + // unblock requests if at least one shim matches it. + + const { frameId, originUrl, requestId, tabId, type, url } = details; + + // Ignore requests unrelated to tabs + if (tabId < 0) { + return undefined; + } + + // We need to base our checks not on the frame's host, but the tab's. + const topHost = new URL((await browser.tabs.get(tabId)).url).hostname; + const unblocked = await browser.trackingProtection.wasRequestUnblocked( + requestId + ); + + let match; + let shimToApply; + for (const shim of this.shims.values()) { + await shim.ready; + + if (!shim.enabled || (!shim.redirectsRequests && !shim.runFirst)) { + continue; + } + + if (shim.onlyIfDFPIActive || shim.onlyIfPrivateBrowsing) { + const isPB = (await browser.tabs.get(details.tabId)).incognito; + if (!isPB && shim.onlyIfPrivateBrowsing) { + continue; + } + if ( + shim.onlyIfDFPIActive && + !(await browser.trackingProtection.isDFPIActive(isPB)) + ) { + continue; + } + } + + // Do not apply the shim if it is only meant to apply when strict mode ETP + // (content blocking) was going to block the request. + if (!unblocked && shim.onlyIfBlockedByETP) { + continue; + } + + if (!shim.meantForHost(topHost)) { + continue; + } + + // If this URL and content type isn't meant for this shim, don't apply it. + match = shim.isTriggeredByURLAndType(url, type); + if (match) { + if (!unblocked && match.onlyIfBlockedByETP) { + continue; + } + + // If the user has already opted in for this shim, all requests it covers + // should be allowed; no need for a shim anymore. + if (shim.hasUserOptedInAlready(topHost)) { + warn( + `Allowing tracking ${type} ${url} on tab ${tabId} frame ${frameId} due to opt-in` + ); + shim.showOptInWarningOnce(tabId, new URL(originUrl).origin); + return undefined; + } + shimToApply = shim; + break; + } + } + + let runFirst = false; + + if (shimToApply) { + // Note that sites may request the same shim twice, but because the requests + // may differ enough for some to fail (CSP/CORS/etc), we always let the request + // complete via local redirect. Shims should gracefully handle this as well. + + const { target } = match; + const { bug, file, id, name, needsShimHelpers } = shimToApply; + runFirst = shimToApply.runFirst; + + const redirect = target || file; + + warn( + `Shimming tracking ${type} ${url} on tab ${tabId} frame ${frameId} with ${ + redirect || runFirst + }` + ); + + const warning = `${name} is being shimmed by Firefox. See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`; + + let needConsoleMessage = true; + + if (runFirst) { + try { + await browser.tabs.executeScript(tabId, { + file: `/shims/${runFirst}`, + frameId, + runAt: "document_start", + }); + } catch (_) {} + } + + // For scripts, we also set up any needed shim helpers. + if (type === "script" && needsShimHelpers?.length) { + try { + await browser.tabs.executeScript(tabId, { + file: "/lib/shim_messaging_helper.js", + frameId, + runAt: "document_start", + }); + const origin = new URL(originUrl).origin; + await browser.tabs.sendMessage( + tabId, + { origin, shimId: id, needsShimHelpers, warning }, + { frameId } + ); + needConsoleMessage = false; + shimToApply.setActiveOnTab(tabId); + } catch (_) {} + } + + if (needConsoleMessage) { + try { + await browser.tabs.executeScript(tabId, { + code: `console.warn(${JSON.stringify(warning)})`, + runAt: "document_start", + }); + } catch (_) {} + } + + if (!redirect.indexOf("http://") || !redirect.indexOf("https://")) { + return { redirectUrl: redirect }; + } + + // If any shims matched the request to replace it, then redirect to the local + // file bundled with SmartBlock, so the request never hits the network. + return { redirectUrl: browser.runtime.getURL(`shims/${redirect}`) }; + } + + // Sanity check: if no shims end up handling this request, + // yet it was meant to be blocked by ETP, then block it now. + if (unblocked) { + error(`unexpected: ${url} not shimmed on tab ${tabId} frame ${frameId}`); + return { cancel: true }; + } + + if (!runFirst) { + debug(`ignoring ${url} on tab ${tabId} frame ${frameId}`); + } + return undefined; + } +} + +module.exports = Shims; diff --git a/browser/extensions/webcompat/lib/ua_helpers.js b/browser/extensions/webcompat/lib/ua_helpers.js new file mode 100644 index 0000000000..e2ab29c628 --- /dev/null +++ b/browser/extensions/webcompat/lib/ua_helpers.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals exportFunction, module */ + +var UAHelpers = { + _deviceAppropriateChromeUAs: {}, + getDeviceAppropriateChromeUA(config = {}) { + const { version = "103.0.5060.71", androidDevice, desktopOS } = config; + const key = `${version}:${androidDevice}:${desktopOS}`; + if (!UAHelpers._deviceAppropriateChromeUAs[key]) { + const userAgent = + typeof navigator !== "undefined" ? navigator.userAgent : ""; + const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [ + "", + "58.0", + ])[1]; + + if (userAgent.includes("Android")) { + const RunningAndroidVersion = + userAgent.match(/Android [0-9.]+/) || "Android 6.0"; + if (androidDevice) { + UAHelpers._deviceAppropriateChromeUAs[ + key + ] = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; ${androidDevice}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`; + } else { + const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`; + const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; + const IsPhone = userAgent.includes("Mobile"); + UAHelpers._deviceAppropriateChromeUAs[key] = IsPhone + ? ChromePhoneUA + : ChromeTabletUA; + } + } else { + let osSegment = "Windows NT 10.0; Win64; x64"; + if (desktopOS === "macOS" || userAgent.includes("Macintosh")) { + osSegment = "Macintosh; Intel Mac OS X 10_15_7"; + } + if ( + desktopOS !== "nonLinux" && + (desktopOS === "linux" || userAgent.includes("Linux")) + ) { + osSegment = "X11; Ubuntu; Linux x86_64"; + } + + UAHelpers._deviceAppropriateChromeUAs[ + key + ] = `Mozilla/5.0 (${osSegment}) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; + } + } + return UAHelpers._deviceAppropriateChromeUAs[key]; + }, + getPrefix(originalUA) { + return originalUA.substr(0, originalUA.indexOf(")") + 1); + }, + overrideWithDeviceAppropriateChromeUA(config) { + const chromeUA = UAHelpers.getDeviceAppropriateChromeUA(config); + Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", { + get: exportFunction(() => chromeUA, window), + set: exportFunction(function () {}, window), + }); + }, + capVersionTo99(originalUA) { + const ver = originalUA.match(/Firefox\/(\d+\.\d+)/); + if (!ver || parseFloat(ver[1]) < 100) { + return originalUA; + } + return originalUA + .replace(`Firefox/${ver[1]}`, "Firefox/99.0") + .replace(`rv:${ver[1]}`, "rv:99.0"); + }, +}; + +if (typeof module !== "undefined") { + module.exports = UAHelpers; +} diff --git a/browser/extensions/webcompat/lib/ua_overrides.js b/browser/extensions/webcompat/lib/ua_overrides.js new file mode 100644 index 0000000000..2426293f3f --- /dev/null +++ b/browser/extensions/webcompat/lib/ua_overrides.js @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser, module */ + +class UAOverrides { + constructor(availableOverrides) { + this.OVERRIDE_PREF = "perform_ua_overrides"; + + this._overridesEnabled = true; + + this._availableOverrides = availableOverrides; + this._activeListeners = new Map(); + } + + bindAboutCompatBroker(broker) { + this._aboutCompatBroker = broker; + } + + bootup() { + browser.aboutConfigPrefs.onPrefChange.addListener(() => { + this.checkOverridePref(); + }, this.OVERRIDE_PREF); + this.checkOverridePref(); + } + + checkOverridePref() { + browser.aboutConfigPrefs.getPref(this.OVERRIDE_PREF).then(value => { + if (value === undefined) { + browser.aboutConfigPrefs.setPref(this.OVERRIDE_PREF, true); + } else if (value === false) { + this.unregisterUAOverrides(); + } else { + this.registerUAOverrides(); + } + }); + } + + getAvailableOverrides() { + return this._availableOverrides; + } + + isEnabled() { + return this._overridesEnabled; + } + + enableOverride(override) { + if (override.active) { + return; + } + + const { blocks, matches, uaTransformer } = override.config; + const listener = details => { + // Don't actually override the UA for an experiment if the user is not + // part of the experiment (unless they force-enabed the override). + if ( + !override.config.experiment || + override.permanentPrefEnabled === true + ) { + for (const header of details.requestHeaders) { + if (header.name.toLowerCase() === "user-agent") { + // Don't override the UA if we're on a mobile device that has the + // "Request Desktop Site" mode enabled. The UA for the desktop mode + // is set inside Gecko with a simple string replace, so we can use + // that as a check, see https://searchfox.org/mozilla-central/rev/89d33e1c3b0a57a9377b4815c2f4b58d933b7c32/mobile/android/chrome/geckoview/GeckoViewSettingsChild.js#23-28 + let isMobileWithDesktopMode = + override.currentPlatform == "android" && + header.value.includes("X11; Linux x86_64"); + + if (!isMobileWithDesktopMode) { + header.value = uaTransformer(header.value); + } + } + } + } + return { requestHeaders: details.requestHeaders }; + }; + + browser.webRequest.onBeforeSendHeaders.addListener( + listener, + { urls: matches }, + ["blocking", "requestHeaders"] + ); + + const listeners = { onBeforeSendHeaders: listener }; + if (blocks) { + const blistener = details => { + return { cancel: true }; + }; + + browser.webRequest.onBeforeRequest.addListener( + blistener, + { urls: blocks }, + ["blocking"] + ); + + listeners.onBeforeRequest = blistener; + } + this._activeListeners.set(override, listeners); + override.active = true; + } + + onOverrideConfigChanged(override) { + // Check whether the override should be hidden from about:compat. + override.hidden = override.config.hidden; + + // Setting the override's permanent pref overrules whether it is hidden. + if (override.permanentPrefEnabled !== undefined) { + override.hidden = !override.permanentPrefEnabled; + } + + // Also check whether the override should be active. + let shouldBeActive = true; + + // Overrides can be force-deactivated by their permanent preference. + if (override.permanentPrefEnabled === false) { + shouldBeActive = false; + } + + // Overrides gated behind an experiment the user is not part of do not + // have to be activated, unless they are gathering telemetry, or the + // user has force-enabled them with their permanent pref. + if (override.config.experiment && override.permanentPrefEnabled !== true) { + shouldBeActive = false; + } + + if (shouldBeActive) { + this.enableOverride(override); + } else { + this.disableOverride(override); + } + + if (this._overridesEnabled) { + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ + overridesChanged: this._aboutCompatBroker.filterOverrides( + this._availableOverrides + ), + }); + } + } + + async registerUAOverrides() { + const platformMatches = ["all"]; + let platformInfo = await browser.runtime.getPlatformInfo(); + platformMatches.push(platformInfo.os == "android" ? "android" : "desktop"); + + for (const override of this._availableOverrides) { + if (platformMatches.includes(override.platform)) { + override.availableOnPlatform = true; + override.currentPlatform = platformInfo.os; + + // If there is a specific about:config preference governing + // this override, monitor its state. + const pref = override.config.permanentPref; + override.permanentPrefEnabled = + pref && (await browser.aboutConfigPrefs.getPref(pref)); + if (pref) { + const checkOverridePref = () => { + browser.aboutConfigPrefs.getPref(pref).then(value => { + override.permanentPrefEnabled = value; + this.onOverrideConfigChanged(override); + }); + }; + browser.aboutConfigPrefs.onPrefChange.addListener( + checkOverridePref, + pref + ); + } + + this.onOverrideConfigChanged(override); + } + } + + this._overridesEnabled = true; + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ + overridesChanged: this._aboutCompatBroker.filterOverrides( + this._availableOverrides + ), + }); + } + + unregisterUAOverrides() { + for (const override of this._availableOverrides) { + this.disableOverride(override); + } + + this._overridesEnabled = false; + this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({ + overridesChanged: false, + }); + } + + disableOverride(override) { + if (!override.active) { + return; + } + + const listeners = this._activeListeners.get(override); + for (const [name, listener] of Object.entries(listeners)) { + browser.webRequest[name].removeListener(listener); + } + override.active = false; + this._activeListeners.delete(override); + } +} + +module.exports = UAOverrides; diff --git a/browser/extensions/webcompat/manifest.json b/browser/extensions/webcompat/manifest.json new file mode 100644 index 0000000000..c4a2592f3d --- /dev/null +++ b/browser/extensions/webcompat/manifest.json @@ -0,0 +1,153 @@ +{ + "manifest_version": 2, + "name": "Web Compatibility Interventions", + "description": "Urgent post-release fixes for web compatibility.", + "version": "115.1.0", + "browser_specific_settings": { + "gecko": { + "id": "webcompat@mozilla.org", + "strict_min_version": "102.0" + } + }, + + "experiment_apis": { + "aboutConfigPrefs": { + "schema": "experiment-apis/aboutConfigPrefs.json", + "parent": { + "scopes": ["addon_parent"], + "script": "experiment-apis/aboutConfigPrefs.js", + "paths": [["aboutConfigPrefs"]] + } + }, + "appConstants": { + "schema": "experiment-apis/appConstants.json", + "parent": { + "scopes": ["addon_parent"], + "script": "experiment-apis/appConstants.js", + "paths": [["appConstants"]] + } + }, + "aboutPage": { + "schema": "about-compat/aboutPage.json", + "parent": { + "scopes": ["addon_parent"], + "script": "about-compat/aboutPage.js", + "events": ["startup"] + } + }, + "matchPatterns": { + "schema": "experiment-apis/matchPatterns.json", + "child": { + "scopes": ["addon_child"], + "script": "experiment-apis/matchPatterns.js", + "paths": [["matchPatterns"]] + } + }, + "systemManufacturer": { + "schema": "experiment-apis/systemManufacturer.json", + "child": { + "scopes": ["addon_child"], + "script": "experiment-apis/systemManufacturer.js", + "paths": [["systemManufacturer"]] + } + }, + "trackingProtection": { + "schema": "experiment-apis/trackingProtection.json", + "parent": { + "scopes": ["addon_parent"], + "script": "experiment-apis/trackingProtection.js", + "paths": [["trackingProtection"]] + } + } + }, + + "content_security_policy": "script-src 'self' 'sha256-PeZc2H1vv7M8NXqlFyNbN4y4oM6wXmYEbf73m+Aqpak='; default-src 'self'; base-uri moz-extension://*; object-src 'none'", + + "permissions": [ + "mozillaAddons", + "tabs", + "webNavigation", + "webRequest", + "webRequestBlocking", + "<all_urls>" + ], + + "background": { + "scripts": [ + "lib/module_shim.js", + "lib/messaging_helper.js", + "lib/intervention_helpers.js", + "lib/requestStorageAccess_helper.js", + "lib/ua_helpers.js", + "data/injections.js", + "data/shims.js", + "data/ua_overrides.js", + "lib/about_compat_broker.js", + "lib/custom_functions.js", + "lib/injections.js", + "lib/shims.js", + "lib/ua_overrides.js", + "run.js" + ] + }, + + "web_accessible_resources": [ + "shims/addthis-angular.js", + "shims/adform.js", + "shims/adnexus-ast.js", + "shims/adnexus-prebid.js", + "shims/adsafeprotected-ima.js", + "shims/apstag.js", + "shims/blogger.js", + "shims/bloggerAccount.js", + "shims/bmauth.js", + "shims/branch.js", + "shims/chartbeat.js", + "shims/crave-ca.js", + "shims/criteo.js", + "shims/cxense.js", + "shims/doubleverify.js", + "shims/eluminate.js", + "shims/empty-script.js", + "shims/empty-shim.txt", + "shims/everest.js", + "shims/facebook-sdk.js", + "shims/facebook.svg", + "shims/fastclick.js", + "shims/firebase.js", + "shims/google-ads.js", + "shims/google-analytics-and-tag-manager.js", + "shims/google-analytics-ecommerce-plugin.js", + "shims/google-analytics-legacy.js", + "shims/google-ima.js", + "shims/google-page-ad.js", + "shims/google-publisher-tags.js", + "shims/google-safeframe.html", + "shims/history.js", + "shims/iam.js", + "shims/iaspet.js", + "shims/instagram.js", + "shims/kinja.js", + "shims/live-test-shim.js", + "shims/maxmind-geoip.js", + "shims/microsoftLogin.js", + "shims/microsoftVirtualAssistant.js", + "shims/moat.js", + "shims/mochitest-shim-1.js", + "shims/mochitest-shim-2.js", + "shims/mochitest-shim-3.js", + "shims/nielsen.js", + "shims/optimizely.js", + "shims/play.svg", + "shims/private-browsing-web-api-fixes.js", + "shims/rambler-authenticator.js", + "shims/rich-relevance.js", + "shims/spotify-embed.js", + "shims/tracking-pixel.png", + "shims/vast2.xml", + "shims/vast3.xml", + "shims/vidible.js", + "shims/vmad.xml", + "shims/webtrends.js" + ] +} diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build new file mode 100644 index 0000000000..0a75a15c90 --- /dev/null +++ b/browser/extensions/webcompat/moz.build @@ -0,0 +1,192 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"] +DEFINES["MOZ_APP_MAXVERSION"] = CONFIG["MOZ_APP_MAXVERSION"] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"] += [ + "manifest.json", + "run.js", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["about-compat"] += [ + "about-compat/aboutCompat.css", + "about-compat/aboutCompat.html", + "about-compat/aboutCompat.js", + "about-compat/AboutCompat.jsm", + "about-compat/aboutPage.js", + "about-compat/aboutPage.json", + "about-compat/aboutPageProcessScript.js", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["data"] += [ + "data/injections.js", + "data/shims.js", + "data/ua_overrides.js", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["experiment-apis"] += [ + "experiment-apis/aboutConfigPrefs.js", + "experiment-apis/aboutConfigPrefs.json", + "experiment-apis/appConstants.js", + "experiment-apis/appConstants.json", + "experiment-apis/matchPatterns.js", + "experiment-apis/matchPatterns.json", + "experiment-apis/systemManufacturer.js", + "experiment-apis/systemManufacturer.json", + "experiment-apis/trackingProtection.js", + "experiment-apis/trackingProtection.json", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["css"] += [ + "injections/css/bug0000000-testbed-css-injection.css", + "injections/css/bug1570328-developer-apple.com-transform-scale.css", + "injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css", + "injections/css/bug1605611-maps.google.com-directions-time.css", + "injections/css/bug1610344-directv.com.co-hide-unsupported-message.css", + "injections/css/bug1644830-missingmail.usps.com-checkboxes-not-visible.css", + "injections/css/bug1651917-teletrader.com.body-transform-origin.css", + "injections/css/bug1653075-livescience.com-scrollbar-width.css", + "injections/css/bug1654877-preev.com-moz-appearance-fix.css", + "injections/css/bug1654907-reactine.ca-hide-unsupported.css", + "injections/css/bug1694470-myvidster.com-content-not-shown.css", + "injections/css/bug1707795-office365-sheets-overscroll-disable.css", + "injections/css/bug1712833-buskocchi.desuca.co.jp-fix-map-height.css", + "injections/css/bug1741234-patient.alphalabs.ca-height-fix.css", + "injections/css/bug1765947-veniceincoming.com-left-fix.css", + "injections/css/bug1770962-coldwellbankerhomes.com-image-height.css", + "injections/css/bug1774490-rainews.it-gallery-fix.css", + "injections/css/bug1784141-aveeno.com-acuvue.com-unsupported.css", + "injections/css/bug1784199-entrata-platform-unsupported.css", + "injections/css/bug1799994-www.vivobarefoot.com-product-filters-fix.css", + "injections/css/bug1800000-www.honda.co.uk-choose-dealer-button-fix.css", + "injections/css/bug1819678-nppes.cms.hhs.gov-unsupported-banner.css", + "injections/css/bug1829949-tomshardware.com-scrollbar-width.css", + "injections/css/bug1829952-eventer.co.il-button-height.css", + "injections/css/bug1830747-babbel.com-page-height.css", + "injections/css/bug1830752-afisha.ru-slider-pointer-events.css", + "injections/css/bug1830761-91mobiles.com-content-height.css", + "injections/css/bug1830796-copyleaks.com-hide-unsupported.css", + "injections/css/bug1830810-interceramic.com-hide-unsupported.css", + "injections/css/bug1830813-page.onstove.com-hide-unsupported.css", + "injections/css/bug1836103-autostar-novoross.ru-make-map-taller.css", + "injections/css/bug1836105-cnn.com-fix-blank-pages-when-printing.css", + "injections/css/bug1836177-clalit.co.il-hide-number-input-spinners.css", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["js"] += [ + "injections/js/bug0000000-testbed-js-injection.js", + "injections/js/bug1448747-fastclick-shim.js", + "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js", + "injections/js/bug1457335-histography.io-ua-change.js", + "injections/js/bug1472075-bankofamerica.com-ua-change.js", + "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js", + "injections/js/bug1605611-maps.google.com-directions-time.js", + "injections/js/bug1631811-datastudio.google.com-indexedDB.js", + "injections/js/bug1722955-frontgate.com-ua-override.js", + "injections/js/bug1724764-window-print.js", + "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js", + "injections/js/bug1731825-office365-email-handling-prompt-autohide.js", + "injections/js/bug1739489-draftjs-beforeinput.js", + "injections/js/bug1769762-tiktok.com-plugins-shim.js", + "injections/js/bug1774005-installtrigger-shim.js", + "injections/js/bug1784302-effectiveType-shim.js", + "injections/js/bug1795490-www.china-airlines.com-undisable-date-fields-on-mobile.js", + "injections/js/bug1799968-www.samsung.com-appVersion-linux-fix.js", + "injections/js/bug1799980-healow.com-infinite-loop-fix.js", + "injections/js/bug1818818-fastclick-legacy-shim.js", + "injections/js/bug1819450-cmbchina.com-ua-change.js", + "injections/js/bug1819476-axisbank.com-webkitSpeechRecognition-shim.js", + "injections/js/bug1819678-cnki.net-undisable-search-field.js", + "injections/js/bug1819678-free4talk.com-window-chrome-shim.js", + "injections/js/bug1830776-blueshieldca.com-unsupported.js", + "injections/js/bug1831007-nintendo-window-OnetrustActiveGroups.js", + "injections/js/bug1836157-thai-masszazs-niceScroll-disable.js", + "injections/js/bug1842437-www.youtube.com-performance-now-precision.js", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [ + "shims/addthis-angular.js", + "shims/adform.js", + "shims/adnexus-ast.js", + "shims/adnexus-prebid.js", + "shims/adsafeprotected-ima.js", + "shims/apstag.js", + "shims/blogger.js", + "shims/bloggerAccount.js", + "shims/bmauth.js", + "shims/branch.js", + "shims/chartbeat.js", + "shims/crave-ca.js", + "shims/criteo.js", + "shims/cxense.js", + "shims/doubleverify.js", + "shims/eluminate.js", + "shims/empty-script.js", + "shims/empty-shim.txt", + "shims/everest.js", + "shims/facebook-sdk.js", + "shims/facebook.svg", + "shims/fastclick.js", + "shims/firebase.js", + "shims/google-ads.js", + "shims/google-analytics-and-tag-manager.js", + "shims/google-analytics-ecommerce-plugin.js", + "shims/google-analytics-legacy.js", + "shims/google-ima.js", + "shims/google-page-ad.js", + "shims/google-publisher-tags.js", + "shims/google-safeframe.html", + "shims/history.js", + "shims/iam.js", + "shims/iaspet.js", + "shims/instagram.js", + "shims/kinja.js", + "shims/live-test-shim.js", + "shims/maxmind-geoip.js", + "shims/microsoftLogin.js", + "shims/microsoftVirtualAssistant.js", + "shims/moat.js", + "shims/mochitest-shim-1.js", + "shims/mochitest-shim-2.js", + "shims/mochitest-shim-3.js", + "shims/nielsen.js", + "shims/optimizely.js", + "shims/play.svg", + "shims/private-browsing-web-api-fixes.js", + "shims/rambler-authenticator.js", + "shims/rich-relevance.js", + "shims/spotify-embed.js", + "shims/tracking-pixel.png", + "shims/vast2.xml", + "shims/vast3.xml", + "shims/vidible.js", + "shims/vmad.xml", + "shims/webtrends.js", +] + +FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["lib"] += [ + "lib/about_compat_broker.js", + "lib/custom_functions.js", + "lib/injections.js", + "lib/intervention_helpers.js", + "lib/messaging_helper.js", + "lib/module_shim.js", + "lib/requestStorageAccess_helper.js", + "lib/shim_messaging_helper.js", + "lib/shims.js", + "lib/ua_helpers.js", + "lib/ua_overrides.js", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"] + +with Files("**"): + BUG_COMPONENT = ("Web Compatibility", "Tooling & Investigations") diff --git a/browser/extensions/webcompat/run.js b/browser/extensions/webcompat/run.js new file mode 100644 index 0000000000..5822dbb2ca --- /dev/null +++ b/browser/extensions/webcompat/run.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals AboutCompatBroker, AVAILABLE_INJECTIONS, AVAILABLE_SHIMS, + AVAILABLE_PIP_OVERRIDES, AVAILABLE_UA_OVERRIDES, CUSTOM_FUNCTIONS, + Injections, Shims, UAOverrides */ + +let injections, shims, uaOverrides; + +try { + injections = new Injections(AVAILABLE_INJECTIONS, CUSTOM_FUNCTIONS); + injections.bootup(); +} catch (e) { + console.error("Injections failed to start", e); + injections = undefined; +} + +try { + uaOverrides = new UAOverrides(AVAILABLE_UA_OVERRIDES); + uaOverrides.bootup(); +} catch (e) { + console.error("UA overrides failed to start", e); + uaOverrides = undefined; +} + +try { + shims = new Shims(AVAILABLE_SHIMS); +} catch (e) { + console.error("Shims failed to start", e); + shims = undefined; +} + +try { + const aboutCompatBroker = new AboutCompatBroker({ + injections, + shims, + uaOverrides, + }); + aboutCompatBroker.bootup(); +} catch (e) { + console.error("about:compat broker failed to start", e); +} diff --git a/browser/extensions/webcompat/shims/addthis-angular.js b/browser/extensions/webcompat/shims/addthis-angular.js new file mode 100644 index 0000000000..0f0cdd5029 --- /dev/null +++ b/browser/extensions/webcompat/shims/addthis-angular.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713694 - Shim AddThis Angular module + * + * Sites using Angular with AddThis can break entirely if the module is + * blocked. This shim mitigates that breakage by loading an empty module. + */ + +if (!window.addthisModule) { + window.addthisModule = window?.angular?.module("addthis", ["ng"]); +} diff --git a/browser/extensions/webcompat/shims/adform.js b/browser/extensions/webcompat/shims/adform.js new file mode 100644 index 0000000000..d6727d500e --- /dev/null +++ b/browser/extensions/webcompat/shims/adform.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713695 - Shim Adform tracking + * + * Sites such as m.tim.it may gate content behind AdForm's trackpoint, + * breaking download links and such if blocked. This shim stubs out the + * script and its related tracking pixel, so the content still works. + */ + +if (!window.Adform) { + window.Adform = { + Opt: { + disableRedirect() {}, + getStatus(clientID, callback) { + callback({ + clientID, + errorMessage: undefined, + optIn() {}, + optOut() {}, + status: "nocookie", + }); + }, + }, + }; +} diff --git a/browser/extensions/webcompat/shims/adnexus-ast.js b/browser/extensions/webcompat/shims/adnexus-ast.js new file mode 100644 index 0000000000..ae07fa6a03 --- /dev/null +++ b/browser/extensions/webcompat/shims/adnexus-ast.js @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1734130 - Shim AdNexus AST + * + * Some sites expect AST to successfully load, or they break. + * This shim mitigates that breakage. + */ + +if (!window.apntag?.loaded) { + const anq = window.apntag?.anq || []; + + const gTags = new Map(); + const gAds = new Map(); + const gEventHandlers = {}; + + const Ad = class { + adType = "banner"; + auctionId = "-"; + banner = { + width: 1, + height: 1, + content: "", + trackers: { + impression_urls: [], + video_events: {}, + }, + }; + brandCategoryId = 0; + buyerMemberId = 0; + cpm = 0.1; + cpm_publisher_currency = 0.1; + creativeId = 0; + dealId = undefined; + height = 1; + mediaSubtypeId = 1; + mediaTypeId = 1; + publisher_currency_code = "US"; + source = "-"; + tagId = -1; + targetId = ""; + width = 1; + + constructor(tagId, targetId) { + this.tagId = tagId; + this.targetId = targetId; + } + }; + + const fireAdEvent = (type, adObj) => { + const { targetId } = adObj; + const handlers = gEventHandlers[type]?.[targetId]; + if (!handlers) { + return Promise.resolve(); + } + const evt = { adObj, type }; + return new Promise(done => { + setTimeout(() => { + for (const cb of handlers) { + try { + cb(evt); + } catch (e) { + console.error(e); + } + } + done(); + }, 1); + }); + }; + + const refreshTag = targetId => { + const tag = gTags.get(targetId); + if (!tag) { + return; + } + if (!gAds.has(targetId)) { + gAds.set(targetId, new Ad(tag.tagId, targetId)); + } + const adObj = gAds.get(targetId); + fireAdEvent("adRequested", adObj).then(() => { + // TODO: do some sites expect adAvailable+adLoaded instead of adNoBid? + fireAdEvent("adNoBid", adObj); + }); + }; + + const off = (type, targetId, cb) => { + gEventHandlers[type]?.[targetId]?.delete(cb); + }; + + const on = (type, targetId, cb) => { + gEventHandlers[type] = gEventHandlers[type] || {}; + gEventHandlers[type][targetId] = + gEventHandlers[type][targetId] || new Set(); + gEventHandlers[type][targetId].add(cb); + }; + + const Tag = class { + static #nextId = 0; + debug = undefined; + displayed = false; + initialHeight = 1; + initialWidth = 1; + keywords = {}; + member = 0; + showTagCalled = false; + sizes = []; + targetId = ""; + utCalled = true; + utDivId = ""; + utiframeId = ""; + uuid = ""; + + constructor(raw) { + const { keywords, sizes, targetId } = raw; + this.tagId = Tag.#nextId++; + this.keywords = keywords || {}; + this.sizes = sizes || []; + this.targetId = targetId || ""; + } + modifyTag() {} + off(type, cb) { + off(type, this.targetId, cb); + } + on(type, cb) { + on(type, this.targetId, cb); + } + setKeywords(kw) { + this.keywords = kw; + } + }; + + window.apntag = { + anq, + attachClickTrackers() {}, + checkAdAvailable() {}, + clearPageTargeting() {}, + clearRequest() {}, + collapseAd() {}, + debug: false, + defineTag(dfn) { + const { targetId } = dfn; + if (!targetId) { + return; + } + gTags.set(targetId, new Tag(dfn)); + }, + disableDebug() {}, + dongle: undefined, + emitEvent(adObj, type) { + fireAdEvent(type, adObj); + }, + enableCookieSet() {}, + enableDebug() {}, + fireImpressionTrackers() {}, + getAdMarkup: () => "", + getAdWrap() {}, + getAstVersion: () => "0.49.0", + getPageTargeting() {}, + getTag(targetId) { + return gTags.get(targetId); + }, + handleCb() {}, + handleMediationBid() {}, + highlightAd() {}, + loaded: true, + loadTags() { + for (const tagName of gTags.keys()) { + refreshTag(tagName); + } + }, + modifyTag() {}, + notify() {}, + offEvent(type, target, cb) { + off(type, target, cb); + }, + onEvent(type, target, cb) { + on(type, target, cb); + }, + recordErrorEvent() {}, + refresh() {}, + registerRenderer() {}, + requests: {}, + resizeAd() {}, + setEndpoint() {}, + setKeywords() {}, + setPageOpts() {}, + setPageTargeting() {}, + setSafeFrameConfig() {}, + setSizes() {}, + showTag() {}, + }; + + const push = function (fn) { + if (typeof fn === "function") { + try { + fn(); + } catch (e) { + console.trace(e); + } + } + }; + + anq.push = push; + + anq.forEach(push); +} diff --git a/browser/extensions/webcompat/shims/adnexus-prebid.js b/browser/extensions/webcompat/shims/adnexus-prebid.js new file mode 100644 index 0000000000..f0f810f0e9 --- /dev/null +++ b/browser/extensions/webcompat/shims/adnexus-prebid.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1694401 - Shim Prebid.js + * + * Some sites rely on prebid.js to place content, perhaps in conjunction with + * other services like Google Publisher Tags and Amazon TAM. This shim prevents + * site breakage like image galleries breaking as the user browsers them, by + * allowing the content placement to succeed. + */ + +if (!window.pbjs?.requestBids) { + const que = window.pbjs?.que || []; + const cmd = window.pbjs?.cmd || []; + const adUnits = window.pbjs?.adUnits || []; + + window.pbjs = { + adUnits, + addAdUnits(arr) { + if (!Array.isArray(arr)) { + arr = [arr]; + } + adUnits.push(arr); + }, + cmd, + offEvent() {}, + que, + refreshAds() {}, + removeAdUnit(codes) { + if (!Array.isArray(codes)) { + codes = [codes]; + } + for (const code of codes) { + for (let i = adUnits.length - 1; i >= 0; i--) { + if (adUnits[i].code === code) { + adUnits.splice(i, 1); + } + } + } + }, + renderAd() {}, + requestBids(params) { + params?.bidsBackHandler?.(); + }, + setConfig() {}, + setTargetingForGPTAsync() {}, + }; + + const push = function (fn) { + if (typeof fn === "function") { + try { + fn(); + } catch (e) { + console.trace(e); + } + } + }; + + que.push = push; + cmd.push = push; + + que.forEach(push); + cmd.forEach(push); +} diff --git a/browser/extensions/webcompat/shims/adsafeprotected-ima.js b/browser/extensions/webcompat/shims/adsafeprotected-ima.js new file mode 100644 index 0000000000..93cd8e1eab --- /dev/null +++ b/browser/extensions/webcompat/shims/adsafeprotected-ima.js @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * + * Sites relying on Ad Safe Protected's adapter for Google IMA may + * have broken videos when the script is blocked. This shim stubs + * out the API to help mitigate major breakage. + */ + +if (!window.googleImaVansAdapter) { + window.googleImaVansAdapter = { + init() {}, + dispose() {}, + }; +} diff --git a/browser/extensions/webcompat/shims/apstag.js b/browser/extensions/webcompat/shims/apstag.js new file mode 100644 index 0000000000..55be05916b --- /dev/null +++ b/browser/extensions/webcompat/shims/apstag.js @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713698 - Shim Amazon Transparent Ad Marketplace's apstag.js + * + * Some sites such as politico.com rely on Amazon TAM tracker to serve ads, + * breaking functionality like galleries if it is blocked. This shim helps + * mitigate major breakage in that case. + */ + +if (!window.apstag?._getSlotIdToNameMapping) { + const _Q = window.apstag?._Q || []; + + const newBid = config => { + return { + amznbid: "", + amzniid: "", + amznp: "", + amznsz: "0x0", + size: "0x0", + slotID: config.slotID, + }; + }; + + window.apstag = { + _Q, + _getSlotIdToNameMapping() {}, + bids() {}, + debug() {}, + deleteId() {}, + fetchBids(cfg, cb) { + if (!Array.isArray(cfg?.slots)) { + return; + } + setTimeout(() => { + cb(cfg.slots.map(s => newBid(s))); + }, 1); + }, + init() {}, + punt() {}, + renderImp() {}, + renewId() {}, + setDisplayBids() {}, + targetingKeys: () => [], + thirdPartyData: {}, + updateId() {}, + }; + + window.apstagLOADED = true; + + _Q.push = function (prefix, args) { + try { + switch (prefix) { + case "f": + window.apstag.fetchBids(...args); + break; + case "i": + window.apstag.init(...args); + break; + } + } catch (e) { + console.trace(e); + } + }; + + for (const cmd of _Q) { + _Q.push(cmd); + } +} diff --git a/browser/extensions/webcompat/shims/blogger.js b/browser/extensions/webcompat/shims/blogger.js new file mode 100644 index 0000000000..a474b3c5e9 --- /dev/null +++ b/browser/extensions/webcompat/shims/blogger.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals exportFunction */ + +"use strict"; + +/** + * Blogger powered blogs rely on storage access to https://blogger.com to enable + * oauth with Google. For dFPI, sites need to use the Storage Access API to gain + * first party storage access. This shim calls requestStorageAccess on behalf of + * the site when a user wants to log in via oauth. + */ + +console.warn( + `When using oauth, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1776869 for details.` +); + +const GOOGLE_OAUTH_PATH_PREFIX = "https://accounts.google.com/ServiceLogin"; + +// Overwrite the window.open method so we can detect oauth related popups. +const origOpen = window.wrappedJSObject.open; +Object.defineProperty(window.wrappedJSObject, "open", { + value: exportFunction((url, ...args) => { + // Filter oauth popups. + if (!url.startsWith(GOOGLE_OAUTH_PATH_PREFIX)) { + return origOpen(url, ...args); + } + // Request storage access for the Blogger iframe. + document.requestStorageAccess().then(() => { + origOpen(url, ...args); + }); + // We don't have the window object yet which window.open returns, since the + // sign-in flow is dependent on the async storage access request. This isn't + // a problem as long as the website does not consume it. + return null; + }, window), +}); diff --git a/browser/extensions/webcompat/shims/bloggerAccount.js b/browser/extensions/webcompat/shims/bloggerAccount.js new file mode 100644 index 0000000000..19e80dbfbe --- /dev/null +++ b/browser/extensions/webcompat/shims/bloggerAccount.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals exportFunction */ + +"use strict"; + +/** + * Blogger uses Google as the auth provider. The account panel uses a + * third-party iframe of https://ogs.google.com, which requires first-party + * storage access to authenticate. This shim calls requestStorageAccess on + * behalf of the site when the user opens the account panel. + */ + +console.warn( + `When logging in with Google, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1777690 for details.` +); + +const STORAGE_ACCESS_ORIGIN = "https://ogs.google.com"; + +document.documentElement.addEventListener( + "click", + e => { + const { target, isTrusted } = e; + if (!isTrusted) { + return; + } + + const anchorEl = target.closest("a"); + if (!anchorEl) { + return; + } + + if ( + !anchorEl.href.startsWith("https://accounts.google.com/SignOutOptions") + ) { + return; + } + + // The storage access request below runs async so the panel won't open + // immediately. Mitigate this UX issue by updating the clicked element's + // style so the user gets some immediate feedback. + anchorEl.style.opacity = 0.5; + e.stopPropagation(); + e.preventDefault(); + + document + .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) + .then(() => { + // Reload all iframes of ogs.google.com so the first-party cookies are + // sent to the server. + // The reload mechanism here is a bit of a hack, since we don't have + // access to the content window of a cross-origin iframe. + document + .querySelectorAll("iframe[src^='https://ogs.google.com/']") + .forEach(frame => (frame.src += "")); + }) + // Show the panel in both success and error state. When the user denies + // the storage access prompt they will see an error message in the account + // panel. + .finally(() => { + anchorEl.style.opacity = 1.0; + target.click(); + }); + }, + true +); diff --git a/browser/extensions/webcompat/shims/bmauth.js b/browser/extensions/webcompat/shims/bmauth.js new file mode 100644 index 0000000000..944f2100d6 --- /dev/null +++ b/browser/extensions/webcompat/shims/bmauth.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +if (!window.BmAuth) { + window.BmAuth = { + init: () => new Promise(() => {}), + handleSignIn: () => { + // TODO: handle this properly! + }, + isAuthenticated: () => Promise.resolve(false), + addListener: () => {}, + api: { + event: { + addListener: () => {}, + }, + }, + }; +} diff --git a/browser/extensions/webcompat/shims/branch.js b/browser/extensions/webcompat/shims/branch.js new file mode 100644 index 0000000000..31e8f4eeec --- /dev/null +++ b/browser/extensions/webcompat/shims/branch.js @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1716220 - Shim Branch Web SDK + * + * Sites such as TataPlay may not load properly if Branch Web SDK is + * blocked. This shim stubs out its script so the page still loads. + */ + +if (!window?.branch?.b) { + const queue = window?.branch?._q || []; + window.branch = new (class { + V = {}; + g = 0; + X = "web2.62.0"; + b = { + A: {}, + clear() {}, + get() {}, + getAll() {}, + isEnabled: () => true, + remove() {}, + set() {}, + ca() {}, + g: [], + l: 0, + o: 0, + s: null, + }; + addListener() {} + applyCode() {} + autoAppIndex() {} + banner() {} + c() {} + closeBanner() {} + closeJourney() {} + constructor() {} + creditHistory() {} + credits() {} + crossPlatformIds() {} + data() {} + deepview() {} + deepviewCta() {} + disableTracking() {} + first() {} + getBrowserFingerprintId() {} + getCode() {} + init(key, ...args) { + const cb = args.pop(); + if (typeof cb === "function") { + cb(undefined, {}); + } + } + lastAttributedTouchData() {} + link() {} + logEvent() {} + logout() {} + qrCode() {} + redeem() {} + referrals() {} + removeListener() {} + renderFinalize() {} + renderQueue() {} + sendSMS() {} + setAPIResponseCallback() {} + setBranchViewData() {} + setIdentity() {} + track() {} + trackCommerceEvent() {} + validateCode() {} + })(); + const push = ([fn, ...args]) => { + try { + window.branch[fn].apply(window.branch, args); + } catch (e) { + console.error(e); + } + }; + queue.forEach(push); +} diff --git a/browser/extensions/webcompat/shims/chartbeat.js b/browser/extensions/webcompat/shims/chartbeat.js new file mode 100644 index 0000000000..0e57fc6da1 --- /dev/null +++ b/browser/extensions/webcompat/shims/chartbeat.js @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713699 - Shim ChartBeat tracking + * + * Sites may rely on chartbeat's tracking as they might with Google Analytics, + * expecting it to be present for interactive site content to function. This + * shim mitigates related breakage. + */ + +window.pSUPERFLY = { + activity() {}, + virtualPage() {}, +}; diff --git a/browser/extensions/webcompat/shims/crave-ca.js b/browser/extensions/webcompat/shims/crave-ca.js new file mode 100644 index 0000000000..b4d93ccdfa --- /dev/null +++ b/browser/extensions/webcompat/shims/crave-ca.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Bug 1746439 - crave.ca login broken with dFPI enabled + * + * Crave.ca relies upon a login page that is out-of-origin. That login page + * sets a cookie for https://www.crave.ca, which is then used as an proof of + * authentication on redirect back to the main site. This shim adds a request + * for storage access for https://www.crave.ca when the user tries to log in. + */ + +console.warn( + `When logging in, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1746439 for details.` +); + +// Third-party origin we need to request storage access for. +const STORAGE_ACCESS_ORIGIN = "https://www.crave.ca"; + +document.documentElement.addEventListener( + "click", + e => { + const { target, isTrusted } = e; + if (!isTrusted) { + return; + } + const button = target.closest("button"); + if (!button) { + return; + } + const form = target.closest(".login-form"); + if (!form) { + return; + } + + console.warn( + "Calling the Storage Access API on behalf of " + STORAGE_ACCESS_ORIGIN + ); + button.disabled = true; + e.stopPropagation(); + e.preventDefault(); + document + .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) + .then(() => { + button.disabled = false; + target.click(); + }) + .catch(() => { + button.disabled = false; + }); + }, + true +); diff --git a/browser/extensions/webcompat/shims/criteo.js b/browser/extensions/webcompat/shims/criteo.js new file mode 100644 index 0000000000..afdc00b888 --- /dev/null +++ b/browser/extensions/webcompat/shims/criteo.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713720 - Shim Criteo + * + * Sites relying on window.Criteo to be loaded can experience + * breakage if it is blocked. Stubbing out the API in a shim can + * mitigate this breakage. + */ + +if (window.Criteo?.CallRTA === undefined) { + window.Criteo = { + CallRTA() {}, + ComputeStandaloneDFPTargeting() {}, + DisplayAcceptableAdIfAdblocked() {}, + DisplayAd() {}, + GetBids() {}, + GetBidsForAdUnit() {}, + Passback: { + RequestBids() {}, + RenderAd() {}, + }, + PubTag: { + Adapters: { + AMP() {}, + Prebid() {}, + }, + Context: { + GetIdfs() {}, + SetIdfs() {}, + }, + DirectBidding: { + DirectBiddingEvent() {}, + DirectBiddingSlot() {}, + DirectBiddingUrlBuilder() {}, + Size() {}, + }, + RTA: { + DefaultCrtgContentName: "crtg_content", + DefaultCrtgRtaCookieName: "crtg_rta", + }, + }, + RenderAd() {}, + RequestBids() {}, + RequestBidsOnGoogleTagSlots() {}, + SetCCPAExplicitOptOut() {}, + SetCeh() {}, + SetDFPKeyValueTargeting() {}, + SetLineItemRanges() {}, + SetPublisherExt() {}, + SetSlotsExt() {}, + SetTargeting() {}, + SetUserExt() {}, + events: { + push() {}, + }, + passbackEvents: [], + usePrebidEvents: true, + }; +} diff --git a/browser/extensions/webcompat/shims/cxense.js b/browser/extensions/webcompat/shims/cxense.js new file mode 100644 index 0000000000..55862f4fb5 --- /dev/null +++ b/browser/extensions/webcompat/shims/cxense.js @@ -0,0 +1,593 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713721 - Shim Cxense + * + * Sites relying on window.cX can experience breakage if it is blocked. + * Stubbing out the API in a shim can mitigate this breakage. There are + * two versions of the API, one including window.cX.CCE, but both appear + * to be very similar so we use one shim for both. + */ + +if (window.cX?.getUserSegmentIds === undefined) { + const callQueue = window.cX?.callQueue || []; + const callQueueCCE = window.cX?.CCE?.callQueue || []; + + function getRandomString(l = 16) { + const v = crypto.getRandomValues(new Uint8Array(l)); + const s = Array.from(v, c => c.toString(16)).join(""); + return s.slice(0, l); + } + + const call = (cb, ...args) => { + if (typeof cb !== "function") { + return; + } + try { + cb(...args); + } catch (e) { + console.error(e); + } + }; + + const invokeOn = lib => { + return (fn, ...args) => { + try { + lib[fn](...args); + } catch (e) { + console.error(e); + } + }; + }; + + const userId = getRandomString(); + const cxUserId = `cx:${getRandomString(25)}:${getRandomString(12)}`; + const topLeft = { left: 0, top: 0 }; + const margins = { left: 0, top: 0, right: 0, bottom: 0 }; + const ccePushUrl = + "https://comcluster.cxense.com/cce/push?callback={{callback}}"; + const displayWidget = (divId, a, ctx, callback) => call(callback, ctx, divId); + const getUserSegmentIds = a => call(a?.callback, a?.defaultValue || []); + const init = (a, b, c, d, callback) => call(callback); + const render = (a, data, ctx, callback) => call(callback, data, ctx); + const run = (params, ctx, callback) => call(callback, params, ctx); + const runCtrlVersion = (a, b, callback) => call(callback); + const runCxVersion = (a, data, b, ctx, callback) => call(callback, data, ctx); + const runTest = (a, divId, b, c, ctx, callback) => call(callback, divId, ctx); + const sendConversionEvent = (a, options) => call(options?.callback, {}); + const sendEvent = (a, b, args) => call(args?.callback, {}); + + const getDivId = className => { + const e = document.querySelector(`.${className}`); + if (e) { + return `${className}-01`; + } + return null; + }; + + const getDocumentSize = () => { + const width = document.body.clientWidth; + const height = document.body.clientHeight; + return { width, height }; + }; + + const getNowSeconds = () => { + return Math.round(new Date().getTime() / 1000); + }; + + const getPageContext = () => { + return { + location: location.href, + pageViewRandom: "", + userId, + }; + }; + + const getWindowSize = () => { + const width = window.innerWidth; + const height = window.innerHeight; + return { width, height }; + }; + + const isObject = i => { + return typeof i === "object" && i !== null && !Array.isArray(i); + }; + + const runMulti = widgets => { + widgets?.forEach(({ widgetParams, widgetContext, widgetCallback }) => { + call(widgetCallback, widgetParams, widgetContext); + }); + }; + + let testGroup = -1; + let snapPoints = []; + const startTime = new Date(); + + const library = { + addCustomerScript() {}, + addEventListener() {}, + addExternalId() {}, + afterInitializePage() {}, + allUserConsents() {}, + backends: { + production: { + baseAdDeliveryUrl: "http://adserver.cxad.cxense.com/adserver/search", + secureBaseAdDeliveryUrl: + "https://s-adserver.cxad.cxense.com/adserver/search", + }, + sandbox: { + baseAdDeliveryUrl: + "http://adserver.sandbox.cxad.cxense.com/adserver/search", + secureBaseAdDeliveryUrl: + "https://s-adserver.sandbox.cxad.cxense.com/adserver/search", + }, + }, + calculateAdSpaceSize(adCount, adUnitSize, marginA, marginB) { + return adCount * (adUnitSize + marginA + marginB); + }, + cdn: { + template: { + direct: { + http: "http://cdn.cxpublic.com/", + https: "https://cdn.cxpublic.com/", + }, + mapped: { + http: "http://cdn-templates.cxpublic.com/", + https: "https://cdn-templates.cxpublic.com/", + }, + }, + }, + cint() {}, + cleanUpGlobalIds: [], + clearBaseUrl: "https://scdn.cxense.com/sclear.html", + clearCustomParameters() {}, + clearIdUrl: "https://scomcluster.cxense.com/public/clearid", + clearIds() {}, + clickTracker: (a, b, callback) => call(callback), + clientStorageUrl: "https://clientstorage.cxense.com", + combineArgs: () => Object.create(), + combineKeywordsIntoArray: () => [], + consentClasses: ["pv", "segment", "ad", "recs"], + consentClassesV2: ["geo", "device"], + cookieSyncRUrl: "csyn-r.cxense.com", + createDelegate() {}, + csdUrls: { + domainScriptUrl: "//csd.cxpublic.com/d/", + customerScriptUrl: "//csd.cxpublic.com/t/", + }, + cxenseGlobalIdIframeUrl: "https://scdn.cxense.com/sglobal.html", + cxenseUserIdUrl: "https://id.cxense.com/public/user/id", + decodeUrlEncodedNameValuePairs: () => Object.create(), + defaultAdRenderer: () => "", + deleteCookie() {}, + denyWithoutConsent: { + addExternalId: "pv", + getUserSegmentIds: "segment", + insertAdSpace: "ad", + insertMultipleAdSpaces: "ad", + sendEvent: "pv", + sendPageViewEvent: "pv", + sync: "ad", + }, + dmpPushUrl: "https://comcluster.cxense.com/dmp/push?callback={{callback}}", + emptyWidgetUrl: "https://scdn.cxense.com/empty.html", + eventReceiverBaseUrl: "https://scomcluster.cxense.com/Repo/rep.html", + eventReceiverBaseUrlGif: "https://scomcluster.cxense.com/Repo/rep.gif", + getAllText: () => "", + getClientStorageVariable() {}, + getCookie: () => null, + getCxenseUserId: () => cxUserId, + getDocumentSize, + getElementPosition: () => topLeft, + getHashFragment: () => location.hash.substr(1), + getLocalStats: () => Object.create(), + getNodeValue: n => n.nodeValue, + getNowSeconds, + getPageContext, + getRandomString, + getScrollPos: () => topLeft, + getSessionId: () => "", + getSiteId: () => "", + getTimezoneOffset: () => new Date().getTimezoneOffset(), + getTopLevelDomain: () => location.hostname, + getUserId: () => userId, + getUserSegmentIds, + getWindowSize, + hasConsent: () => true, + hasHistory: () => true, + hasLocalStorage: () => true, + hasPassiveEventListeners: () => true, + hasPostMessage: () => true, + hasSessionStorage() {}, + initializePage() {}, + insertAdSpace() {}, + insertMultipleAdSpaces() {}, + insertWidget() {}, + invoke: invokeOn(library), + isAmpIFrame() {}, + isArray() {}, + isCompatModeActive() {}, + isConsentRequired() {}, + isEdge: () => false, + isFirefox: () => true, + isIE6Or7: () => false, + isObject, + isRecsDestination: () => false, + isSafari: () => false, + isTextNode: n => n?.nodeType === 3, + isTopWindow: () => window === top, + jsonpRequest: () => false, + loadScript() {}, + m_accountId: "0", + m_activityEvents: false, + m_activityState: { + activeTime: startTime, + currScrollLeft: 0, + currScrollTop: 0, + exitLink: "", + hadHIDActivity: false, + maxViewLeft: 1, + maxViewTop: 1, + parentMetrics: undefined, + prevActivityTime: startTime + 2, + prevScreenX: 0, + prevScreenY: 0, + prevScrollLeft: 0, + prevScrollTop: 0, + prevTime: startTime + 1, + prevWindowHeight: 1, + prevWindowWidth: 1, + scrollDepthPercentage: 0, + scrollDepthPixels: 0, + }, + m_atfr: null, + m_c1xTpWait: 0, + m_clientStorage: { + iframeEl: null, + iframeIsLoaded: false, + iframeOrigin: "https://clientstorage.cxense.com", + iframePath: "/clientstorage_v2.html", + messageContexts: {}, + messageQueue: [], + }, + m_compatMode: {}, + m_compatModeActive: false, + m_compatPvSent: false, + m_consentVersion: 1, + m_customParameters: [], + m_documentSizeRequestedFromChild: false, + m_externalUserIds: [], + m_globalIdLoading: { + globalIdIFrameEl: null, + globalIdIFrameElLoaded: false, + }, + m_isSpaRecsDestination: false, + m_knownMessageSources: [], + m_p1Complete: false, + m_prevLocationHash: "", + m_previousPageViewReport: null, + m_rawCustomParameters: {}, + m_rnd: getRandomString(), + m_scriptStartTime: startTime, + m_siteId: "0", + m_spaRecsClickUrl: null, + m_thirdPartyIds: true, + m_usesConsent: false, + m_usesIabConsent: false, + m_usesSecureCookies: true, + m_usesTcf20Consent: false, + m_widgetSpecs: {}, + Object, + onClearIds() {}, + onFFP1() {}, + onP1() {}, + p1BaseUrl: "https://scdn.cxense.com/sp1.html", + p1JsUrl: "https://p1cluster.cxense.com/p1.js", + parseHashArgs: () => Object.create(), + parseMargins: () => margins, + parseUrlArgs: () => Object.create(), + postMessageToParent() {}, + publicWidgetDataUrl: "https://api.cxense.com/public/widget/data", + removeClientStorageVariable() {}, + removeEventListener() {}, + renderContainedImage: () => "<div/>", + renderTemplate: () => "<div/>", + reportActivity() {}, + requireActivityEvents() {}, + requireConsent() {}, + requireOnlyFirstPartyIds() {}, + requireSecureCookies() {}, + requireTcf20() {}, + sendEvent, + sendSpaRecsClick: (a, callback) => call(callback), + setAccountId() {}, + setAllConsentsTo() {}, + setClientStorageVariable() {}, + setCompatMode() {}, + setConsent() {}, + setCookie() {}, + setCustomParameters() {}, + setEventAttributes() {}, + setGeoPosition() {}, + setNodeValue() {}, + setRandomId() {}, + setRestrictionsToConsentClasses() {}, + setRetargetingParameters() {}, + setSiteId() {}, + setUserProfileParameters() {}, + setupIabCmp() {}, + setupTcfApi() {}, + shouldPollActivity() {}, + startLocalStats() {}, + startSessionAnnotation() {}, + stopAllSessionAnnotations() {}, + stopSessionAnnotation() {}, + sync() {}, + trackAmpIFrame() {}, + trackElement() {}, + trim: s => s.trim(), + tsridUrl: "https://tsrid.cxense.com/lookup?callback={{callback}}", + userSegmentUrl: + "https://api.cxense.com/profile/user/segment?callback={{callback}}", + }; + + const libraryCCE = { + "__cx-toolkit__": { + isShown: true, + data: [], + }, + activeSnapPoint: null, + activeWidgets: [], + ccePushUrl, + clickTracker: () => "", + displayResult() {}, + displayWidget, + getDivId, + getTestGroup: () => testGroup, + init, + insertMaster() {}, + instrumentClickLinks() {}, + invoke: invokeOn(libraryCCE), + noCache: false, + offerProductId: null, + persistedQueryId: null, + prefix: null, + previewCampaign: null, + previewDiv: null, + previewId: null, + previewTestId: null, + processCxResult() {}, + render, + reportTestImpression() {}, + run, + runCtrlVersion, + runCxVersion, + runMulti, + runTest, + sendConversionEvent, + sendPageViewEvent: (a, b, c, callback) => call(callback), + setSnapPoints(x) { + snapPoints = x; + }, + setTestGroup(x) { + testGroup = x; + }, + setVisibilityField() {}, + get snapPoints() { + return snapPoints; + }, + startTime, + get testGroup() { + return testGroup; + }, + testVariant: null, + trackTime: 0.5, + trackVisibility() {}, + updateRecsClickUrls() {}, + utmParams: [], + version: "2.42", + visibilityField: "timeHalf", + }; + + const CCE = { + activeSnapPoint: null, + activeWidgets: [], + callQueue: callQueueCCE, + ccePushUrl, + clickTracker: () => "", + displayResult() {}, + displayWidget, + getDivId, + getTestGroup: () => testGroup, + init, + insertMaster() {}, + instrumentClickLinks() {}, + invoke: invokeOn(libraryCCE), + library: libraryCCE, + noCache: false, + offerProductId: null, + persistedQueryId: null, + prefix: null, + previewCampaign: null, + previewDiv: null, + previewId: null, + previewTestId: null, + processCxResult() {}, + render, + reportTestImpression() {}, + run, + runCtrlVersion, + runCxVersion, + runMulti, + runTest, + sendConversionEvent, + sendPageViewEvent: (a, b, c, callback) => call(callback), + setSnapPoints(x) { + snapPoints = x; + }, + setTestGroup(x) { + testGroup = x; + }, + setVisibilityField() {}, + get snapPoints() { + return snapPoints; + }, + startTime, + get testGroup() { + return testGroup; + }, + testVariant: null, + trackTime: 0.5, + trackVisibility() {}, + updateRecsClickUrls() {}, + utmParams: [], + version: "2.42", + visibilityField: "timeHalf", + }; + + window.cX = { + addCustomerScript() {}, + addEventListener() {}, + addExternalId() {}, + afterInitializePage() {}, + allUserConsents: () => undefined, + Array, + calculateAdSpaceSize: () => 0, + callQueue, + CCE, + cint: () => undefined, + clearCustomParameters() {}, + clearIds() {}, + clickTracker: () => "", + combineArgs: () => Object.create(), + combineKeywordsIntoArray: () => [], + createDelegate() {}, + decodeUrlEncodedNameValuePairs: () => Object.create(), + defaultAdRenderer: () => "", + deleteCookie() {}, + getAllText: () => "", + getClientStorageVariable() {}, + getCookie: () => null, + getCxenseUserId: () => cxUserId, + getDocumentSize, + getElementPosition: () => topLeft, + getHashFragment: () => location.hash.substr(1), + getLocalStats: () => Object.create(), + getNodeValue: n => n.nodeValue, + getNowSeconds, + getPageContext, + getRandomString, + getScrollPos: () => topLeft, + getSessionId: () => "", + getSiteId: () => "", + getTimezoneOffset: () => new Date().getTimezoneOffset(), + getTopLevelDomain: () => location.hostname, + getUserId: () => userId, + getUserSegmentIds, + getWindowSize, + hasConsent: () => true, + hasHistory: () => true, + hasLocalStorage: () => true, + hasPassiveEventListeners: () => true, + hasPostMessage: () => true, + hasSessionStorage() {}, + initializePage() {}, + insertAdSpace() {}, + insertMultipleAdSpaces() {}, + insertWidget() {}, + invoke: invokeOn(library), + isAmpIFrame() {}, + isArray() {}, + isCompatModeActive() {}, + isConsentRequired() {}, + isEdge: () => false, + isFirefox: () => true, + isIE6Or7: () => false, + isObject, + isRecsDestination: () => false, + isSafari: () => false, + isTextNode: n => n?.nodeType === 3, + isTopWindow: () => window === top, + JSON, + jsonpRequest: () => false, + library, + loadScript() {}, + Object, + onClearIds() {}, + onFFP1() {}, + onP1() {}, + parseHashArgs: () => Object.create(), + parseMargins: () => margins, + parseUrlArgs: () => Object.create(), + postMessageToParent() {}, + removeClientStorageVariable() {}, + removeEventListener() {}, + renderContainedImage: () => "<div/>", + renderTemplate: () => "<div/>", + reportActivity() {}, + requireActivityEvents() {}, + requireConsent() {}, + requireOnlyFirstPartyIds() {}, + requireSecureCookies() {}, + requireTcf20() {}, + sendEvent, + sendPageViewEvent: (a, callback) => call(callback, {}), + sendSpaRecsClick() {}, + setAccountId() {}, + setAllConsentsTo() {}, + setClientStorageVariable() {}, + setCompatMode() {}, + setConsent() {}, + setCookie() {}, + setCustomParameters() {}, + setEventAttributes() {}, + setGeoPosition() {}, + setNodeValue() {}, + setRandomId() {}, + setRestrictionsToConsentClasses() {}, + setRetargetingParameters() {}, + setSiteId() {}, + setUserProfileParameters() {}, + setupIabCmp() {}, + setupTcfApi() {}, + shouldPollActivity() {}, + startLocalStats() {}, + startSessionAnnotation() {}, + stopAllSessionAnnotations() {}, + stopSessionAnnotation() {}, + sync() {}, + trackAmpIFrame() {}, + trackElement() {}, + trim: s => s.trim(), + }; + + window.cxTest = window.cX; + + window.cx_pollActiveTime = () => undefined; + window.cx_pollActivity = () => undefined; + window.cx_pollFragmentMessage = () => undefined; + + const execQueue = (lib, queue) => { + return () => { + const invoke = invokeOn(lib); + setTimeout(() => { + queue.push = cmd => { + setTimeout(() => invoke(...cmd), 1); + }; + for (const cmd of queue) { + invoke(...cmd); + } + }, 25); + }; + }; + + window.cx_callQueueExecute = execQueue(library, callQueue); + window.cxCCE_callQueueExecute = execQueue(libraryCCE, callQueueCCE); + + window.cx_callQueueExecute(); + window.cxCCE_callQueueExecute(); +} diff --git a/browser/extensions/webcompat/shims/doubleverify.js b/browser/extensions/webcompat/shims/doubleverify.js new file mode 100644 index 0000000000..7eaf945d77 --- /dev/null +++ b/browser/extensions/webcompat/shims/doubleverify.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1771557 - Shim DoubleVerify analytics + * + * Some sites such as Sports Illustrated expect DoubleVerify's + * analytics script to load, otherwise odd breakage may occur. + * This shim helps mitigate such breakage. + */ + +if (!window?.PQ?.loaded) { + const cmd = []; + cmd.push = function (c) { + try { + c?.(); + } catch (_) {} + }; + + window.apntag = { + anq: [], + }; + + window.PQ = { + cmd, + loaded: true, + getTargeting: (_, cb) => cb?.([]), + init: () => {}, + loadSignals: (_, cb) => cb?.(), + loadSignalsForSlots: (_, cb) => cb?.(), + PTS: {}, + }; +} diff --git a/browser/extensions/webcompat/shims/eluminate.js b/browser/extensions/webcompat/shims/eluminate.js new file mode 100644 index 0000000000..3fa65c048c --- /dev/null +++ b/browser/extensions/webcompat/shims/eluminate.js @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1606448 - Shim CoreMetrics Eluminate analytics + * + * Sites may rely on eluminate.js tracking in ways which cause breakage, + * which has been seen on shopping sites such as Vans.com, where the + * search filtering UX is broken. This shim mitigates such breakage. + */ + +if (!window.CM_DDX) { + window.CM_DDX = { + domReadyFired: false, + headScripts: true, + dispatcherLoadRequested: false, + firstPassFunctionBinding: false, + BAD_PAGE_ID_ELAPSED_TIMEOUT: 5000, + version: -1, + standalone: false, + test: { + syndicate: true, + testCounter: "", + doTest: false, + newWin: false, + process: () => {}, + }, + partner: {}, + invokeFunctionWhenAvailable: a => { + a(); + }, + gup: d => "", + privacy: { + isDoNotTrackEnabled: () => false, + setDoNotTrack: () => {}, + getDoNotTrack: () => false, + }, + setSubCookie: () => {}, + }; + const noopfn = () => {}; + const w = window; + w.cmAddShared = noopfn; + w.cmCalcSKUString = noopfn; + w.cmCreateManualImpressionTag = noopfn; + w.cmCreateManualLinkClickTag = noopfn; + w.cmCreateManualPageviewTag = noopfn; + w.cmCreateOrderTag = noopfn; + w.cmCreatePageviewTag = noopfn; + w.cmExecuteTagQueue = noopfn; + w.cmRetrieveUserID = noopfn; + w.cmSetClientID = noopfn; + w.cmSetCurrencyCode = noopfn; + w.cmSetFirstPartyIDs = noopfn; + w.cmSetSubCookie = noopfn; + w.cmSetupCookieMigration = noopfn; + w.cmSetupNormalization = noopfn; + w.cmSetupOther = noopfn; + w.cmStartTagSet = noopfn; + w.cmCreateConversionEventTag = noopfn; + w.cmCreateDefaultPageviewTag = noopfn; + w.cmCreateElementTag = noopfn; + w.cmCreateManualImpressionTag = noopfn; + w.cmCreateManualLinkClickTag = noopfn; + w.cmCreateManualPageviewTag = noopfn; + w.cmCreatePageElementTag = noopfn; + w.cmCreatePageviewTag = noopfn; + w.cmCreateProductElementTag = noopfn; + w.cmCreateProductviewTag = noopfn; + w.cmCreateTechPropsTag = noopfn; + w.cmLoadIOConfig = noopfn; + w.cmSetClientID = noopfn; + w.cmSetCurrencyCode = noopfn; + w.cmSetFirstPartyIDs = noopfn; + w.cmSetupCookieMigration = noopfn; + w.cmSetupNormalization = noopfn; + + w.cmSetupOther = b => { + for (const a in b) { + window[a] = b[a]; + } + }; + + const techProps = {}; + + w.coremetrics = { + cmLastReferencedPageID: "", + cmLoad: noopfn, + cmUpdateConfig: noopfn, + getTechProps: () => techProps, + isDef: c => typeof c !== "undefined" && c, + }; +} diff --git a/browser/extensions/webcompat/shims/empty-script.js b/browser/extensions/webcompat/shims/empty-script.js new file mode 100644 index 0000000000..d01f2ab537 --- /dev/null +++ b/browser/extensions/webcompat/shims/empty-script.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This script is intentionally empty */ diff --git a/browser/extensions/webcompat/shims/empty-shim.txt b/browser/extensions/webcompat/shims/empty-shim.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/browser/extensions/webcompat/shims/empty-shim.txt diff --git a/browser/extensions/webcompat/shims/everest.js b/browser/extensions/webcompat/shims/everest.js new file mode 100644 index 0000000000..259ab9033e --- /dev/null +++ b/browser/extensions/webcompat/shims/everest.js @@ -0,0 +1,171 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1728114 - Shim Adobe EverestJS + * + * Sites assuming EverestJS will load can break if it is blocked. + * This shim mitigates that breakage. + */ + +if (!window.__ql) { + window.__ql = {}; +} + +if (!window.EF) { + const AdCloudLocalStorage = { + get: (_, cb) => cb(), + isInitDone: true, + isInitSuccess: true, + }; + + const emptyObj = {}; + + const nullSrc = { + getHosts: () => [undefined], + getProtocols: () => [undefined], + hash: {}, + hashParamsOrder: [], + host: undefined, + path: [], + port: undefined, + query: {}, + queryDelimiter: "&", + queryParamsOrder: [], + queryPrefix: "?", + queryWithoutEncode: {}, + respectEmptyQueryParamValue: undefined, + scheme: undefined, + text: "//", + userInfo: undefined, + }; + + const pixelDetailsEvent = { + addToDom() {}, + canAddToDom: () => false, + fire() {}, + getDomElement() {}, + initializeUri() {}, + pixelDetailsReceiver() {}, + scheme: "https:", + uri: nullSrc, + userid: 0, + }; + + window.EF = { + AdCloudLocalStorage, + accessTopUrl: 0, + acquireCookieMatchingSlot() {}, + addListener() {}, + addPixelDetailsReadyListener() {}, + addToDom() {}, + allow3rdPartyPixels: 1, + appData: "", + appendDictionary() {}, + checkGlobalSid() {}, + checkUrlParams() {}, + cmHost: "cm.everesttech.net", + context: { + isFbApp: () => 0, + isPageview: () => false, + isSegmentation: () => false, + isTransaction: () => false, + }, + conversionData: "", + cookieMatchingSlots: 1, + debug: 0, + deserializeUrlParams: () => emptyObj, + doCookieMatching() {}, + ef_itp_ls: false, + eventType: "", + executeAfterLoad() {}, + executeOnloadCallbacks() {}, + expectedTrackingParams: ["ev_cl", "ev_sid"], + fbIsApp: 0, + fbsCM: 0, + fbsPixelId: 0, + filterList: () => [], + getArrayIndex: -1, + getConversionData: () => "", + getConversionDataFromLocalStorage: cb => cb(), + getDisplayClickUri: () => "", + getEpochFromEfUniq: () => 0, + getFirstLevelObjectCopy: () => emptyObj, + getInvisibleIframeElement() {}, + getInvisibleImageElement() {}, + getMacroSubstitutedText: () => "", + getPixelDetails: cb => cb({}), + getScriptElement() {}, + getScriptSrc: () => "", + getServerParams: () => emptyObj, + getSortedAttributes: () => [], + getTrackingParams: () => emptyObj, + getTransactionParams: () => emptyObj, + handleConversionData() {}, + impressionProperties: "", + impressionTypes: ["impression", "impression_served"], + inFloodlight: 0, + init(config) { + try { + const { userId } = config; + window.EF.userId = userId; + pixelDetailsEvent.userId = userId; + } catch (_) {} + }, + initializeEFVariables() {}, + isArray: a => Array.isArray(a), + isEmptyDictionary: () => true, + isITPEnabled: () => false, + isPermanentCookieSet: () => false, + isSearchClick: () => 0, + isXSSReady() {}, + jsHost: "www.everestjs.net", + jsTagAdded: 0, + location: nullSrc, + locationHref: nullSrc, + locationSkipBang: nullSrc, + log() {}, + main() {}, + main2() {}, + newCookieMatchingEvent: () => emptyObj, + newFbsCookieMatching: () => emptyObj, + newImpression: () => emptyObj, + newPageview: () => emptyObj, + newPixelDetails: () => emptyObj, + newPixelEvent: () => emptyObj, + newPixelServerDisplayClickRedirectUri: () => emptyObj, + newPixelServerGenericRedirectUri: () => emptyObj, + newPixelServerUri: () => emptyObj, + newProductSegment: () => emptyObj, + newSegmentJavascript: () => emptyObj, + newTransaction: () => emptyObj, + newUri: () => emptyObj, + onloadCallbacks: [], + pageViewProperties: "", + pageviewProperties: "", + pixelDetails: {}, + pixelDetailsAdded: 1, + pixelDetailsEvent, + pixelDetailsParams: [], + pixelDetailsReadyCallbackFns: [], + pixelDetailsRecieverCalled: 1, + pixelHost: "pixel.everesttech.net", + protocol: document?.location?.protocol || "", + referrer: nullSrc, + removeListener() {}, + searchSegment: "", + segment: "", + serverParamsListener() {}, + sid: 0, + sku: "", + throttleCookie: "", + trackingJavascriptSrc: nullSrc, + transactionObjectList: [], + transactionProperties: "", + userServerParams: {}, + userid: 0, + }; +} diff --git a/browser/extensions/webcompat/shims/facebook-sdk.js b/browser/extensions/webcompat/shims/facebook-sdk.js new file mode 100644 index 0000000000..1e995ff047 --- /dev/null +++ b/browser/extensions/webcompat/shims/facebook-sdk.js @@ -0,0 +1,554 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1226498 - Shim Facebook SDK + * + * This shim provides functionality to enable Facebook's authenticator on third + * party sites ("continue/log in with Facebook" buttons). This includes rendering + * the button as the SDK would, if sites require it. This way, if users wish to + * opt into the Facebook login process regardless of the tracking consequences, + * they only need to click the button as usual. + * + * In addition, the shim also attempts to provide placeholders for Facebook + * videos, which users may click to opt into seeing the video (also despite + * the increased tracking risks). This is an experimental feature enabled + * that is only currently enabled on nightly builds. + * + * Finally, this shim also stubs out as much of the SDK as possible to prevent + * breaking on sites which expect that it will always successfully load. + */ + +if (!window.FB) { + const FacebookLogoURL = "https://smartblock.firefox.etp/facebook.svg"; + const PlayIconURL = "https://smartblock.firefox.etp/play.svg"; + + const originalUrl = document.currentScript.src; + + let haveUnshimmed; + let initInfo; + let activeOnloginAttribute; + const placeholdersToRemoveOnUnshim = new Set(); + const loggedGraphApiCalls = []; + const eventHandlers = new Map(); + + function getGUID() { + const v = crypto.getRandomValues(new Uint8Array(20)); + return Array.from(v, c => c.toString(16)).join(""); + } + + const sendMessageToAddon = (function () { + const shimId = "FacebookSDK"; + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = getGUID(); + return new Promise(resolve => { + const payload = { message, messageId, shimId }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + const isNightly = sendMessageToAddon("getOptions").then(opts => { + return opts.releaseBranch === "nightly"; + }); + + function makeLoginPlaceholder(target) { + // Sites may provide their own login buttons, or rely on the Facebook SDK + // to render one for them. For the latter case, we provide placeholders + // which try to match the examples and documentation here: + // https://developers.facebook.com/docs/facebook-login/web/login-button/ + + if (target.textContent || target.hasAttribute("fb-xfbml-state")) { + return; + } + target.setAttribute("fb-xfbml-state", ""); + + const size = target.getAttribute("data-size") || "large"; + + let font, margin, minWidth, maxWidth, height, iconHeight; + if (size === "small") { + font = 11; + margin = 8; + minWidth = maxWidth = 200; + height = 20; + iconHeight = 12; + } else if (size === "medium") { + font = 13; + margin = 8; + minWidth = 200; + maxWidth = 320; + height = 28; + iconHeight = 16; + } else { + font = 16; + minWidth = 240; + maxWidth = 400; + margin = 12; + height = 40; + iconHeight = 24; + } + + const wattr = target.getAttribute("data-width") || ""; + const width = + wattr === "100%" ? wattr : `${parseFloat(wattr) || minWidth}px`; + + const round = target.getAttribute("data-layout") === "rounded" ? 20 : 4; + + const text = + target.getAttribute("data-button-type") === "continue_with" + ? "Continue with Facebook" + : "Log in with Facebook"; + + const button = document.createElement("div"); + button.style = ` + display: flex; + align-items: center; + justify-content: center; + padding-left: ${margin + iconHeight}px; + ${width}; + min-width: ${minWidth}px; + max-width: ${maxWidth}px; + height: ${height}px; + border-radius: ${round}px; + -moz-text-size-adjust: none; + -moz-user-select: none; + color: #fff; + font-size: ${font}px; + font-weight: bold; + font-family: Helvetica, Arial, sans-serif; + letter-spacing: .25px; + background-color: #1877f2; + background-repeat: no-repeat; + background-position: ${margin}px 50%; + background-size: ${iconHeight}px ${iconHeight}px; + background-image: url(${FacebookLogoURL}); + `; + button.textContent = text; + target.appendChild(button); + target.addEventListener("click", () => { + activeOnloginAttribute = target.getAttribute("onlogin"); + }); + } + + async function makeVideoPlaceholder(target) { + // For videos, we provide a more generic placeholder of roughly the + // expected size with a play button, as well as a Facebook logo. + if (!(await isNightly) || target.hasAttribute("fb-xfbml-state")) { + return; + } + target.setAttribute("fb-xfbml-state", ""); + + let width = parseInt(target.getAttribute("data-width")); + let height = parseInt(target.getAttribute("data-height")); + if (height) { + height = `${width * 0.6}px`; + } else { + height = `100%; min-height:${width * 0.75}px`; + } + if (width) { + width = `${width}px`; + } else { + width = `100%; min-width:200px`; + } + + const placeholder = document.createElement("div"); + placeholdersToRemoveOnUnshim.add(placeholder); + placeholder.style = ` + width: ${width}; + height: ${height}; + top: 0px; + left: 0px; + background: #000; + color: #fff; + text-align: center; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + background-image: url(${FacebookLogoURL}), url(${PlayIconURL}); + background-position: calc(100% - 24px) 24px, 50% 47.5%; + background-repeat: no-repeat, no-repeat; + background-size: 43px 42px, 25% 25%; + -moz-text-size-adjust: none; + -moz-user-select: none; + color: #fff; + align-items: center; + padding-top: 200px; + font-size: 14pt; + `; + placeholder.textContent = "Click to allow blocked Facebook content"; + placeholder.addEventListener("click", evt => { + if (!evt.isTrusted) { + return; + } + allowFacebookSDK(() => { + placeholdersToRemoveOnUnshim.forEach(p => p.remove()); + }); + }); + + target.innerHTML = ""; + target.appendChild(placeholder); + } + + // We monitor for XFBML objects as Facebook SDK does, so we + // can provide placeholders for dynamically-added ones. + const xfbmlObserver = new MutationObserver(mutations => { + for (let { addedNodes, target, type } of mutations) { + const nodes = type === "attributes" ? [target] : addedNodes; + for (const node of nodes) { + if (node?.classList?.contains("fb-login-button")) { + makeLoginPlaceholder(node); + } + if (node?.classList?.contains("fb-video")) { + makeVideoPlaceholder(node); + } + } + } + }); + + xfbmlObserver.observe(document.documentElement, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["class"], + }); + + const needPopup = + !/app_runner/.test(window.name) && !/iframe_canvas/.test(window.name); + const popupName = getGUID(); + let activePopup; + + if (needPopup) { + const oldWindowOpen = window.open; + window.open = function (href, name, params) { + try { + const url = new URL(href, window.location.href); + if ( + url.protocol === "https:" && + (url.hostname === "m.facebook.com" || + url.hostname === "www.facebook.com") && + url.pathname.endsWith("/oauth") + ) { + name = popupName; + } + } catch (e) { + console.error(e); + } + return oldWindowOpen.call(window, href, name, params); + }; + } + + let allowingFacebookPromise; + + async function allowFacebookSDK(postInitCallback) { + if (allowingFacebookPromise) { + return allowingFacebookPromise; + } + + let resolve, reject; + allowingFacebookPromise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + await sendMessageToAddon("optIn"); + + xfbmlObserver.disconnect(); + + const shim = window.FB; + window.FB = undefined; + + // We need to pass the site's initialization info to the real + // SDK as it loads, so we use the fbAsyncInit mechanism to + // do so, also ensuring our own post-init callbacks are called. + const oldInit = window.fbAsyncInit; + window.fbAsyncInit = () => { + try { + if (typeof initInfo !== "undefined") { + window.FB.init(initInfo); + } else if (oldInit) { + oldInit(); + } + } catch (e) { + console.error(e); + } + + // Also re-subscribe any SDK event listeners as early as possible. + for (const [name, fns] of eventHandlers.entries()) { + for (const fn of fns) { + window.FB.Event.subscribe(name, fn); + } + } + + // Allow the shim to do any post-init work early as well, while the + // SDK script finishes loading and we ask it to re-parse XFBML etc. + postInitCallback?.(); + }; + + const script = document.createElement("script"); + script.src = originalUrl; + + script.addEventListener("error", () => { + allowingFacebookPromise = null; + script.remove(); + activePopup?.close(); + window.FB = shim; + reject(); + alert("Failed to load Facebook SDK; please try again"); + }); + + script.addEventListener("load", () => { + haveUnshimmed = true; + + // After the real SDK has fully loaded we re-issue any Graph API + // calls the page is waiting on, as well as requesting for it to + // re-parse any XBFML elements (including ones with placeholders). + + for (const args of loggedGraphApiCalls) { + try { + window.FB.api.apply(window.FB, args); + } catch (e) { + console.error(e); + } + } + + window.FB.XFBML.parse(document.body, resolve); + }); + + document.head.appendChild(script); + + return allowingFacebookPromise; + } + + function buildPopupParams() { + // We try to match Facebook's popup size reasonably closely. + const { outerWidth, outerHeight, screenX, screenY } = window; + const { width, height } = window.screen; + const w = Math.min(width, 400); + const h = Math.min(height, 400); + const ua = navigator.userAgent; + const isMobile = ua.includes("Mobile") || ua.includes("Tablet"); + const left = screenX + (screenX < 0 ? width : 0) + (outerWidth - w) / 2; + const top = screenY + (screenY < 0 ? height : 0) + (outerHeight - h) / 2.5; + let params = `left=${left},top=${top},width=${w},height=${h},scrollbars=1,toolbar=0,location=1`; + if (!isMobile) { + params = `${params},width=${w},height=${h}`; + } + return params; + } + + // If a page stores the window.FB reference of the shim, then we + // want to have it proxy calls to the real SDK once we've unshimmed. + function ensureProxiedToUnshimmed(obj) { + const shim = {}; + for (const key in obj) { + const value = obj[key]; + if (typeof value === "function") { + shim[key] = function () { + if (haveUnshimmed) { + return window.FB[key].apply(window.FB, arguments); + } + return value.apply(this, arguments); + }; + } else if (typeof value !== "object" || value === null) { + shim[key] = value; + } else { + shim[key] = ensureProxiedToUnshimmed(value); + } + } + return new Proxy(shim, { + get: (shimmed, key) => (haveUnshimmed ? window.FB : shimmed)[key], + }); + } + + window.FB = ensureProxiedToUnshimmed({ + api() { + loggedGraphApiCalls.push(arguments); + }, + AppEvents: { + activateApp() {}, + clearAppVersion() {}, + clearUserID() {}, + EventNames: { + ACHIEVED_LEVEL: "fb_mobile_level_achieved", + ADDED_PAYMENT_INFO: "fb_mobile_add_payment_info", + ADDED_TO_CART: "fb_mobile_add_to_cart", + ADDED_TO_WISHLIST: "fb_mobile_add_to_wishlist", + COMPLETED_REGISTRATION: "fb_mobile_complete_registration", + COMPLETED_TUTORIAL: "fb_mobile_tutorial_completion", + INITIATED_CHECKOUT: "fb_mobile_initiated_checkout", + PAGE_VIEW: "fb_page_view", + RATED: "fb_mobile_rate", + SEARCHED: "fb_mobile_search", + SPENT_CREDITS: "fb_mobile_spent_credits", + UNLOCKED_ACHIEVEMENT: "fb_mobile_achievement_unlocked", + VIEWED_CONTENT: "fb_mobile_content_view", + }, + getAppVersion: () => "", + getUserID: () => "", + logEvent() {}, + logPageView() {}, + logPurchase() {}, + ParameterNames: { + APP_USER_ID: "_app_user_id", + APP_VERSION: "_appVersion", + CONTENT_ID: "fb_content_id", + CONTENT_TYPE: "fb_content_type", + CURRENCY: "fb_currency", + DESCRIPTION: "fb_description", + LEVEL: "fb_level", + MAX_RATING_VALUE: "fb_max_rating_value", + NUM_ITEMS: "fb_num_items", + PAYMENT_INFO_AVAILABLE: "fb_payment_info_available", + REGISTRATION_METHOD: "fb_registration_method", + SEARCH_STRING: "fb_search_string", + SUCCESS: "fb_success", + }, + setAppVersion() {}, + setUserID() {}, + updateUserProperties() {}, + }, + Canvas: { + getHash: () => "", + getPageInfo(cb) { + cb?.call(this, { + clientHeight: 1, + clientWidth: 1, + offsetLeft: 0, + offsetTop: 0, + scrollLeft: 0, + scrollTop: 0, + }); + }, + Plugin: { + hidePluginElement() {}, + showPluginElement() {}, + }, + Prefetcher: { + COLLECT_AUTOMATIC: 0, + COLLECT_MANUAL: 1, + addStaticResource() {}, + setCollectionMode() {}, + }, + scrollTo() {}, + setAutoGrow() {}, + setDoneLoading() {}, + setHash() {}, + setSize() {}, + setUrlHandler() {}, + startTimer() {}, + stopTimer() {}, + }, + Event: { + subscribe(e, f) { + if (!eventHandlers.has(e)) { + eventHandlers.set(e, new Set()); + } + eventHandlers.get(e).add(f); + }, + unsubscribe(e, f) { + eventHandlers.get(e)?.delete(f); + }, + }, + frictionless: { + init() {}, + isAllowed: () => false, + }, + gamingservices: { + friendFinder() {}, + uploadImageToMediaLibrary() {}, + }, + getAccessToken: () => null, + getAuthResponse() { + return { status: "" }; + }, + getLoginStatus(cb) { + cb?.call(this, { status: "unknown" }); + }, + getUserID() {}, + init(_initInfo) { + initInfo = _initInfo; // in case the site is not using fbAsyncInit + }, + login(cb, opts) { + // We have to load Facebook's script, and then wait for it to call + // window.open. By that time, the popup blocker will likely trigger. + // So we open a popup now with about:blank, and then make sure FB + // will re-use that same popup later. + if (needPopup) { + activePopup = window.open("about:blank", popupName, buildPopupParams()); + } + allowFacebookSDK(() => { + activePopup = undefined; + function runPostLoginCallbacks() { + try { + cb?.apply(this, arguments); + } catch (e) { + console.error(e); + } + if (activeOnloginAttribute) { + setTimeout(activeOnloginAttribute, 1); + activeOnloginAttribute = undefined; + } + } + window.FB.login(runPostLoginCallbacks, opts); + }).catch(() => { + activePopup = undefined; + activeOnloginAttribute = undefined; + try { + cb?.({}); + } catch (e) { + console.error(e); + } + }); + }, + logout(cb) { + cb?.call(this); + }, + ui(params, fn) { + if (params.method === "permissions.oauth") { + window.FB.login(fn, params); + } + }, + XFBML: { + parse(node, cb) { + node = node || document; + node.querySelectorAll(".fb-login-button").forEach(makeLoginPlaceholder); + node.querySelectorAll(".fb-video").forEach(makeVideoPlaceholder); + try { + cb?.call(this); + } catch (e) { + console.error(e); + } + }, + }, + }); + + window.FB.XFBML.parse(); + + window?.fbAsyncInit?.(); +} diff --git a/browser/extensions/webcompat/shims/facebook.svg b/browser/extensions/webcompat/shims/facebook.svg new file mode 100644 index 0000000000..df63700a9e --- /dev/null +++ b/browser/extensions/webcompat/shims/facebook.svg @@ -0,0 +1,3 @@ +<!-- copyright is dedicated to the Public Domain. + https://en.wikipedia.org/wiki/File:Facebook_f_logo_(2019).svg --> +<svg xmlns="http://www.w3.org/2000/svg" width="1365.12" height="1365.12" viewBox="0 0 14222 14222"><circle cx="7111" cy="7112" r="7111" fill="#fff"/><path d="M9879 9168l315-2056H8222V5778c0-562 275-1111 1159-1111h897V2917s-814-139-1592-139c-1624 0-2686 984-2686 2767v1567H4194v2056h1806v4969c362 57 733 86 1111 86s749-30 1111-86V9168z" fill="#1977f3"/></svg> diff --git a/browser/extensions/webcompat/shims/fastclick.js b/browser/extensions/webcompat/shims/fastclick.js new file mode 100644 index 0000000000..ad6814c995 --- /dev/null +++ b/browser/extensions/webcompat/shims/fastclick.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1738220 - Shim Conversant FastClick + * + * Sites assuming FastClick will load can break if it is blocked. + * This shim mitigates that breakage. + */ + +// FastClick bundles nodeJS packages/core-js/internals/dom-iterables.js +// which is known to be needed by at least one site. +if (!HTMLCollection.prototype.forEach) { + const DOMIterables = [ + "CSSRuleList", + "CSSStyleDeclaration", + "CSSValueList", + "ClientRectList", + "DOMRectList", + "DOMStringList", + "DOMTokenList", + "DataTransferItemList", + "FileList", + "HTMLAllCollection", + "HTMLCollection", + "HTMLFormElement", + "HTMLSelectElement", + "MediaList", + "MimeTypeArray", + "NamedNodeMap", + "NodeList", + "PaintRequestList", + "Plugin", + "PluginArray", + "SVGLengthList", + "SVGNumberList", + "SVGPathSegList", + "SVGPointList", + "SVGStringList", + "SVGTransformList", + "SourceBufferList", + "StyleSheetList", + "TextTrackCueList", + "TextTrackList", + "TouchList", + ]; + + const forEach = Array.prototype.forEach; + + const handlePrototype = proto => { + if (!proto || proto.forEach === forEach) { + return; + } + try { + Object.defineProperty(proto, "forEach", { + enumerable: false, + get: () => forEach, + }); + } catch (_) { + proto.forEach = forEach; + } + }; + + for (const name of DOMIterables) { + handlePrototype(window[name]?.prototype); + } +} + +if (!window.conversant?.launch) { + const c = (window.conversant = window.conversant || {}); + c.launch = () => {}; +} diff --git a/browser/extensions/webcompat/shims/firebase.js b/browser/extensions/webcompat/shims/firebase.js new file mode 100644 index 0000000000..8ac049c5e4 --- /dev/null +++ b/browser/extensions/webcompat/shims/firebase.js @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1767407 - Shim Firebase + * + * Sites relying on firebase-messaging.js will break in Private + * browsing mode because it assumes that they require service + * workers and indexedDB, when they generally do not. + */ + +/* globals cloneInto */ + +(function () { + const win = window.wrappedJSObject; + const emptyObj = new win.Object(); + const emptyArr = new win.Array(); + const emptyMsg = cloneInto({ message: "" }, window); + const noOpFn = cloneInto(function () {}, window, { cloneFunctions: true }); + + if (!win.indexedDB) { + const idb = { + open: () => win.Promise.reject(emptyMsg), + }; + + Object.defineProperty(win, "indexedDB", { + value: cloneInto(idb, window, { cloneFunctions: true }), + }); + } + + // bug 1778993 + for (const name of [ + "IDBCursor", + "IDBDatabase", + "IDBIndex", + "IDBOpenDBRequest", + "IDBRequest", + "IDBTransaction", + ]) { + if (!win[name]) { + Object.defineProperty(win, name, { value: emptyObj }); + } + } + + if (!win.serviceWorker) { + const sw = { + addEventListener() {}, + getRegistrations: () => win.Promise.resolve(emptyArr), + register: () => win.Promise.reject(emptyMsg), + }; + + Object.defineProperty(navigator.wrappedJSObject, "serviceWorker", { + value: cloneInto(sw, window, { cloneFunctions: true }), + }); + + // bug 1779536 + Object.defineProperty(navigator.wrappedJSObject.serviceWorker, "ready", { + value: new win.Promise(noOpFn), + }); + } + + // bug 1750699 + if (!win.PushManager) { + Object.defineProperty(win, "PushManager", { value: emptyObj }); + } + + // bug 1750699 + if (!win.PushSubscription) { + const ps = { + prototype: { + getKey() {}, + }, + }; + + Object.defineProperty(win, "PushSubscription", { + value: cloneInto(ps, window, { cloneFunctions: true }), + }); + } + + // bug 1750699 + if (!win.ServiceWorkerRegistration) { + const swr = { + prototype: { + showNotification() {}, + }, + }; + + Object.defineProperty(win, "ServiceWorkerRegistration", { + value: cloneInto(swr, window, { cloneFunctions: true }), + }); + } +})(); diff --git a/browser/extensions/webcompat/shims/google-ads.js b/browser/extensions/webcompat/shims/google-ads.js new file mode 100644 index 0000000000..a432186f43 --- /dev/null +++ b/browser/extensions/webcompat/shims/google-ads.js @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713726 - Shim Ads by Google + * + * Sites relying on window.adsbygoogle may encounter breakage if it is blocked. + * This shim provides a stub for that API to mitigate that breakage. + */ + +if (window.adsbygoogle?.loaded === undefined) { + window.adsbygoogle = { + loaded: true, + push() {}, + }; +} + +if (window.gapi?._pl === undefined) { + const stub = { + go() {}, + render: () => "", + }; + window.gapi = { + _pl: true, + additnow: stub, + autocomplete: stub, + backdrop: stub, + blogger: stub, + commentcount: stub, + comments: stub, + community: stub, + donation: stub, + family_creation: stub, + follow: stub, + hangout: stub, + health: stub, + interactivepost: stub, + load() {}, + logutil: { + enableDebugLogging() {}, + }, + page: stub, + partnersbadge: stub, + person: stub, + platform: { + go() {}, + }, + playemm: stub, + playreview: stub, + plus: stub, + plusone: stub, + post: stub, + profile: stub, + ratingbadge: stub, + recobar: stub, + savetoandroidpay: stub, + savetodrive: stub, + savetowallet: stub, + share: stub, + sharetoclassroom: stub, + shortlists: stub, + signin: stub, + signin2: stub, + surveyoptin: stub, + visibility: stub, + youtube: stub, + ytsubscribe: stub, + zoomableimage: stub, + }; +} + +for (const e of document.querySelectorAll("ins.adsbygoogle")) { + e.style.maxWidth = "0px"; +} diff --git a/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js b/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js new file mode 100644 index 0000000000..8809fca8ec --- /dev/null +++ b/browser/extensions/webcompat/shims/google-analytics-and-tag-manager.js @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713687 - Shim Google Analytics and Tag Manager + * + * Sites often rely on the Google Analytics window object and will + * break if it fails to load or is blocked. This shim works around + * such breakage. + * + * Sites also often use the Google Optimizer (asynchide) code snippet, + * only for it to cause multi-second delays if Google Analytics does + * not load. This shim also avoids such delays. + * + * They also rely on Google Tag Manager, which often goes hand-in- + * hand with Analytics, but is not always blocked by anti-tracking + * lists. Handling both in the same shim handles both cases. + */ + +if (window[window.GoogleAnalyticsObject || "ga"]?.loaded === undefined) { + const DEFAULT_TRACKER_NAME = "t0"; + + const trackers = new Map(); + + const run = function (fn, ...args) { + if (typeof fn === "function") { + try { + fn(...args); + } catch (e) { + console.error(e); + } + } + }; + + const create = (id, cookie, name, opts) => { + id = id || opts?.trackerId; + if (!id) { + return undefined; + } + cookie = cookie || opts?.cookieDomain || "_ga"; + name = name || opts?.name || DEFAULT_TRACKER_NAME; + if (!trackers.has(name)) { + let props; + try { + props = new Map(Object.entries(opts)); + } catch (_) { + props = new Map(); + } + trackers.set(name, { + get(p) { + if (p === "name") { + return name; + } else if (p === "trackingId") { + return id; + } else if (p === "cookieDomain") { + return cookie; + } + return props.get(p); + }, + ma() {}, + requireSync() {}, + send() {}, + set(p, v) { + if (typeof p !== "object") { + p = Object.fromEntries([[p, v]]); + } + for (const k in p) { + props.set(k, p[k]); + if (k === "hitCallback") { + run(p[k]); + } + } + }, + }); + } + return trackers.get(name); + }; + + const cmdRE = /((?<name>.*?)\.)?((?<plugin>.*?):)?(?<method>.*)/; + + function ga(cmd, ...args) { + if (arguments.length === 1 && typeof cmd === "function") { + run(cmd, trackers.get(DEFAULT_TRACKER_NAME)); + return undefined; + } + + if (typeof cmd !== "string") { + return undefined; + } + + const groups = cmdRE.exec(cmd)?.groups; + if (!groups) { + console.error("Could not parse GA command", cmd); + return undefined; + } + + let { name, plugin, method } = groups; + + if (plugin) { + return undefined; + } + + if (cmd === "set") { + trackers.get(name)?.set(args[0], args[1]); + } + + if (method === "remove") { + trackers.delete(name); + return undefined; + } + + if (cmd === "send") { + run(args.at(-1)?.hitCallback); + return undefined; + } + + if (method === "create") { + let id, cookie, fields; + for (const param of args.slice(0, 4)) { + if (typeof param === "object") { + fields = param; + break; + } + if (id === undefined) { + id = param; + } else if (cookie === undefined) { + cookie = param; + } else { + name = param; + } + } + return create(id, cookie, name, fields); + } + + return undefined; + } + + Object.assign(ga, { + create: (a, b, c, d) => ga("create", a, b, c, d), + getAll: () => Array.from(trackers.values()), + getByName: name => trackers.get(name), + loaded: true, + remove: t => ga("remove", t), + }); + + // Process any GA command queue the site pre-declares (bug 1736850) + const q = window[window.GoogleAnalyticsObject || "ga"]?.q; + window[window.GoogleAnalyticsObject || "ga"] = ga; + + if (Array.isArray(q)) { + const push = o => { + ga(...o); + return true; + }; + q.push = push; + q.forEach(o => push(o)); + } + + // Also process the Google Tag Manager dataLayer (bug 1713688) + const dl = window.dataLayer; + + if (Array.isArray(dl) && !dl.find(e => e["gtm.start"])) { + const push = function (o) { + setTimeout(() => run(o?.eventCallback), 1); + return true; + }; + dl.push = push; + dl.forEach(o => push(o)); + } + + // Run dataLayer.hide.end to handle asynchide (bug 1628151) + run(window.dataLayer?.hide?.end); +} + +if (!window?.gaplugins?.Linker) { + window.gaplugins = window.gaplugins || {}; + window.gaplugins.Linker = class { + autoLink() {} + decorate(url) { + return url; + } + passthrough() {} + }; +} diff --git a/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js b/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js new file mode 100644 index 0000000000..60b49df120 --- /dev/null +++ b/browser/extensions/webcompat/shims/google-analytics-ecommerce-plugin.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +if (!window.gaplugins) { + window.gaplugins = {}; +} + +if (!window.gaplugins.EC) { + window.gaplugins.EC = () => {}; +} diff --git a/browser/extensions/webcompat/shims/google-analytics-legacy.js b/browser/extensions/webcompat/shims/google-analytics-legacy.js new file mode 100644 index 0000000000..da1a638e12 --- /dev/null +++ b/browser/extensions/webcompat/shims/google-analytics-legacy.js @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// based on https://github.com/gorhill/uBlock/blob/6f49e079db0262e669b70f4169924f796ac8db7c/src/web_accessible_resources/google-analytics_ga.js + +"use strict"; + +if (!window._gaq) { + function noopfn() {} + + const gaq = { + Na: noopfn, + O: noopfn, + Sa: noopfn, + Ta: noopfn, + Va: noopfn, + _createAsyncTracker: noopfn, + _getAsyncTracker: noopfn, + _getPlugin: noopfn, + push: a => { + if (typeof a === "function") { + a(); + return; + } + if (!Array.isArray(a)) { + return; + } + if ( + typeof a[0] === "string" && + /(^|\.)_link$/.test(a[0]) && + typeof a[1] === "string" + ) { + window.location.assign(a[1]); + } + if ( + a[0] === "_set" && + a[1] === "hitCallback" && + typeof a[2] === "function" + ) { + a[2](); + } + }, + }; + + const tracker = { + _addIgnoredOrganic: noopfn, + _addIgnoredRef: noopfn, + _addItem: noopfn, + _addOrganic: noopfn, + _addTrans: noopfn, + _clearIgnoredOrganic: noopfn, + _clearIgnoredRef: noopfn, + _clearOrganic: noopfn, + _cookiePathCopy: noopfn, + _deleteCustomVar: noopfn, + _getName: noopfn, + _setAccount: noopfn, + _getAccount: noopfn, + _getClientInfo: noopfn, + _getDetectFlash: noopfn, + _getDetectTitle: noopfn, + _getLinkerUrl: a => a, + _getLocalGifPath: noopfn, + _getServiceMode: noopfn, + _getVersion: noopfn, + _getVisitorCustomVar: noopfn, + _initData: noopfn, + _link: noopfn, + _linkByPost: noopfn, + _setAllowAnchor: noopfn, + _setAllowHash: noopfn, + _setAllowLinker: noopfn, + _setCampContentKey: noopfn, + _setCampMediumKey: noopfn, + _setCampNameKey: noopfn, + _setCampNOKey: noopfn, + _setCampSourceKey: noopfn, + _setCampTermKey: noopfn, + _setCampaignCookieTimeout: noopfn, + _setCampaignTrack: noopfn, + _setClientInfo: noopfn, + _setCookiePath: noopfn, + _setCookiePersistence: noopfn, + _setCookieTimeout: noopfn, + _setCustomVar: noopfn, + _setDetectFlash: noopfn, + _setDetectTitle: noopfn, + _setDomainName: noopfn, + _setLocalGifPath: noopfn, + _setLocalRemoteServerMode: noopfn, + _setLocalServerMode: noopfn, + _setReferrerOverride: noopfn, + _setRemoteServerMode: noopfn, + _setSampleRate: noopfn, + _setSessionTimeout: noopfn, + _setSiteSpeedSampleRate: noopfn, + _setSessionCookieTimeout: noopfn, + _setVar: noopfn, + _setVisitorCookieTimeout: noopfn, + _trackEvent: noopfn, + _trackPageLoadTime: noopfn, + _trackPageview: noopfn, + _trackSocial: noopfn, + _trackTiming: noopfn, + _trackTrans: noopfn, + _visitCode: noopfn, + }; + + const gat = { + _anonymizeIP: noopfn, + _createTracker: noopfn, + _forceSSL: noopfn, + _getPlugin: noopfn, + _getTracker: () => tracker, + _getTrackerByName: () => tracker, + _getTrackers: noopfn, + aa: noopfn, + ab: noopfn, + hb: noopfn, + la: noopfn, + oa: noopfn, + pa: noopfn, + u: noopfn, + }; + + window._gat = gat; + + const aa = window._gaq || []; + if (Array.isArray(aa)) { + while (aa[0]) { + gaq.push(aa.shift()); + } + } + + window._gaq = gaq.qf = gaq; +} diff --git a/browser/extensions/webcompat/shims/google-ima.js b/browser/extensions/webcompat/shims/google-ima.js new file mode 100644 index 0000000000..1f5e56239d --- /dev/null +++ b/browser/extensions/webcompat/shims/google-ima.js @@ -0,0 +1,620 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bug 1713690 - Shim Google Interactive Media Ads ima3.js + * + * Many sites use ima3.js for ad bidding and placement, often in conjunction + * with Google Publisher Tags, Prebid.js and/or other scripts. This shim + * provides a stubbed-out version of the API which helps work around related + * site breakage, such as black bxoes where videos ought to be placed. + */ + +if (!window.google?.ima?.VERSION) { + const VERSION = "3.517.2"; + + const CheckCanAutoplay = (function () { + // Sourced from: https://searchfox.org/mozilla-central/source/dom/media/gtest/negative_duration.mp4 + const TEST_VIDEO = new Blob( + [ + new Uint32Array([ + 469762048, 1887007846, 1752392036, 0, 913273705, 1717987696, + 828601953, -1878917120, 1987014509, 1811939328, 1684567661, 0, 0, 0, + -402456576, 0, 256, 1, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 33554432, -201261056, 1801548404, 1744830464, 1684564852, + 251658241, 0, 0, 0, 0, 16777216, 0, -1, -1, 0, 0, 0, 0, 256, 0, 0, 0, + 256, 0, 0, 0, 64, 5, 53250, -2080309248, 1634296941, 738197504, + 1684563053, 1, 0, 0, 0, 0, -2137614336, -1, -1, 50261, 754974720, + 1919706216, 0, 0, 1701079414, 0, 0, 0, 1701079382, 1851869295, + 1919249508, 16777216, 1852402979, 102, 1752004116, 100, 1, 0, 0, + 1852400676, 102, 1701995548, 102, 0, 1, 1819440396, 32, 1, 1651799011, + 108, 1937011607, 100, 0, 1, 1668702599, 49, 0, 1, 0, 0, 0, 33555712, + 4718800, 4718592, 0, 65536, 0, 0, 0, 0, 0, 0, 0, 0, 16776984, + 1630601216, 21193590, -14745500, 1729626337, -1407254428, 89161945, + 1049019, 9453056, -251611125, 27269507, -379058688, -1329024392, + 268435456, 1937011827, 0, 0, 268435456, 1668510835, 0, 0, 335544320, + 2054386803, 0, 0, 0, 268435456, 1868788851, 0, 0, 671088640, + 2019915373, 536870912, 2019914356, 0, 16777216, 16777216, 0, 0, 0, + ]), + ], + { type: "video/mp4" } + ); + + let testVideo = undefined; + + return function () { + if (!testVideo) { + testVideo = document.createElement("video"); + testVideo.style = + "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0"; + testVideo.setAttribute("muted", "muted"); + testVideo.setAttribute("playsinline", "playsinline"); + testVideo.src = URL.createObjectURL(TEST_VIDEO); + document.body.appendChild(testVideo); + } + return testVideo.play(); + }; + })(); + + let ima = {}; + + class AdDisplayContainer { + destroy() {} + initialize() {} + } + + class ImaSdkSettings { + #c = true; + #f = {}; + #i = false; + #l = ""; + #p = ""; + #r = 0; + #t = ""; + #v = ""; + getCompanionBackfill() {} + getDisableCustomPlaybackForIOS10Plus() { + return this.#i; + } + getFeatureFlags() { + return this.#f; + } + getLocale() { + return this.#l; + } + getNumRedirects() { + return this.#r; + } + getPlayerType() { + return this.#t; + } + getPlayerVersion() { + return this.#v; + } + getPpid() { + return this.#p; + } + isCookiesEnabled() { + return this.#c; + } + setAutoPlayAdBreaks() {} + setCompanionBackfill() {} + setCookiesEnabled(c) { + this.#c = !!c; + } + setDisableCustomPlaybackForIOS10Plus(i) { + this.#i = !!i; + } + setFeatureFlags(f) { + this.#f = f; + } + setLocale(l) { + this.#l = l; + } + setNumRedirects(r) { + this.#r = r; + } + setPlayerType(t) { + this.#t = t; + } + setPlayerVersion(v) { + this.#v = v; + } + setPpid(p) { + this.#p = p; + } + setSessionId(s) {} + setVpaidAllowed(a) {} + setVpaidMode(m) {} + } + ImaSdkSettings.CompanionBackfillMode = { + ALWAYS: "always", + ON_MASTER_AD: "on_master_ad", + }; + ImaSdkSettings.VpaidMode = { + DISABLED: 0, + ENABLED: 1, + INSECURE: 2, + }; + + class EventHandler { + #listeners = new Map(); + + _dispatch(e) { + const listeners = this.#listeners.get(e.type) || []; + for (const listener of Array.from(listeners)) { + try { + listener(e); + } catch (r) { + console.error(r); + } + } + } + + addEventListener(t, c) { + if (!this.#listeners.has(t)) { + this.#listeners.set(t, new Set()); + } + this.#listeners.get(t).add(c); + } + + removeEventListener(t, c) { + this.#listeners.get(t)?.delete(c); + } + } + + class AdsLoader extends EventHandler { + #settings = new ImaSdkSettings(); + contentComplete() {} + destroy() {} + getSettings() { + return this.#settings; + } + getVersion() { + return VERSION; + } + requestAds(r, c) { + // If autoplay is disabled and the page is trying to autoplay a tracking + // ad, then IMA fails with an error, and the page is expected to request + // ads again later when the user clicks to play. + CheckCanAutoplay().then( + () => { + const { ADS_MANAGER_LOADED } = AdsManagerLoadedEvent.Type; + this._dispatch(new ima.AdsManagerLoadedEvent(ADS_MANAGER_LOADED)); + }, + () => { + const e = new ima.AdError( + "adPlayError", + 1205, + 1205, + "The browser prevented playback initiated without user interaction." + ); + this._dispatch(new ima.AdErrorEvent(e)); + } + ); + } + } + + class AdsManager extends EventHandler { + #volume = 1; + collapse() {} + configureAdsManager() {} + destroy() {} + discardAdBreak() {} + expand() {} + focus() {} + getAdSkippableState() { + return false; + } + getCuePoints() { + return [0]; + } + getCurrentAd() { + return currentAd; + } + getCurrentAdCuePoints() { + return []; + } + getRemainingTime() { + return 0; + } + getVolume() { + return this.#volume; + } + init(w, h, m, e) {} + isCustomClickTrackingUsed() { + return false; + } + isCustomPlaybackUsed() { + return false; + } + pause() {} + requestNextAdBreak() {} + resize(w, h, m) {} + resume() {} + setVolume(v) { + this.#volume = v; + } + skip() {} + start() { + requestAnimationFrame(() => { + for (const type of [ + AdEvent.Type.LOADED, + AdEvent.Type.STARTED, + AdEvent.Type.CONTENT_RESUME_REQUESTED, + AdEvent.Type.AD_BUFFERING, + AdEvent.Type.FIRST_QUARTILE, + AdEvent.Type.MIDPOINT, + AdEvent.Type.THIRD_QUARTILE, + AdEvent.Type.COMPLETE, + AdEvent.Type.ALL_ADS_COMPLETED, + ]) { + try { + this._dispatch(new ima.AdEvent(type)); + } catch (e) { + console.error(e); + } + } + }); + } + stop() {} + updateAdsRenderingSettings(s) {} + } + + class AdsRenderingSettings {} + + class AdsRequest { + setAdWillAutoPlay() {} + setAdWillPlayMuted() {} + setContinuousPlayback() {} + } + + class AdPodInfo { + getAdPosition() { + return 1; + } + getIsBumper() { + return false; + } + getMaxDuration() { + return -1; + } + getPodIndex() { + return 1; + } + getTimeOffset() { + return 0; + } + getTotalAds() { + return 1; + } + } + + class Ad { + _pi = new AdPodInfo(); + getAdId() { + return ""; + } + getAdPodInfo() { + return this._pi; + } + getAdSystem() { + return ""; + } + getAdvertiserName() { + return ""; + } + getApiFramework() { + return null; + } + getCompanionAds() { + return []; + } + getContentType() { + return ""; + } + getCreativeAdId() { + return ""; + } + getCreativeId() { + return ""; + } + getDealId() { + return ""; + } + getDescription() { + return ""; + } + getDuration() { + return 8.5; + } + getHeight() { + return 0; + } + getMediaUrl() { + return null; + } + getMinSuggestedDuration() { + return -2; + } + getSkipTimeOffset() { + return -1; + } + getSurveyUrl() { + return null; + } + getTitle() { + return ""; + } + getTraffickingParameters() { + return {}; + } + getTraffickingParametersString() { + return ""; + } + getUiElements() { + return [""]; + } + getUniversalAdIdRegistry() { + return "unknown"; + } + getUniversalAdIds() { + return [""]; + } + getUniversalAdIdValue() { + return "unknown"; + } + getVastMediaBitrate() { + return 0; + } + getVastMediaHeight() { + return 0; + } + getVastMediaWidth() { + return 0; + } + getWidth() { + return 0; + } + getWrapperAdIds() { + return [""]; + } + getWrapperAdSystems() { + return [""]; + } + getWrapperCreativeIds() { + return [""]; + } + isLinear() { + return true; + } + isSkippable() { + return true; + } + } + + class CompanionAd { + getAdSlotId() { + return ""; + } + getContent() { + return ""; + } + getContentType() { + return ""; + } + getHeight() { + return 1; + } + getWidth() { + return 1; + } + } + + class AdError { + #errorCode = -1; + #message = ""; + #type = ""; + #vastErrorCode = -1; + constructor(type, code, vast, message) { + this.#errorCode = code; + this.#message = message; + this.#type = type; + this.#vastErrorCode = vast; + } + getErrorCode() { + return this.#errorCode; + } + getInnerError() {} + getMessage() { + return this.#message; + } + getType() { + return this.#type; + } + getVastErrorCode() { + return this.#vastErrorCode; + } + toString() { + return `AdError ${this.#errorCode}: ${this.#message}`; + } + } + AdError.ErrorCode = {}; + AdError.Type = {}; + + const isEngadget = () => { + try { + for (const ctx of Object.values(window.vidible._getContexts())) { + if (ctx.getPlayer()?.div?.innerHTML.includes("www.engadget.com")) { + return true; + } + } + } catch (_) {} + return false; + }; + + const currentAd = isEngadget() ? undefined : new Ad(); + + class AdEvent { + constructor(type) { + this.type = type; + } + getAd() { + return currentAd; + } + getAdData() { + return {}; + } + } + AdEvent.Type = { + AD_BREAK_READY: "adBreakReady", + AD_BUFFERING: "adBuffering", + AD_CAN_PLAY: "adCanPlay", + AD_METADATA: "adMetadata", + AD_PROGRESS: "adProgress", + ALL_ADS_COMPLETED: "allAdsCompleted", + CLICK: "click", + COMPLETE: "complete", + CONTENT_PAUSE_REQUESTED: "contentPauseRequested", + CONTENT_RESUME_REQUESTED: "contentResumeRequested", + DURATION_CHANGE: "durationChange", + EXPANDED_CHANGED: "expandedChanged", + FIRST_QUARTILE: "firstQuartile", + IMPRESSION: "impression", + INTERACTION: "interaction", + LINEAR_CHANGE: "linearChange", + LINEAR_CHANGED: "linearChanged", + LOADED: "loaded", + LOG: "log", + MIDPOINT: "midpoint", + PAUSED: "pause", + RESUMED: "resume", + SKIPPABLE_STATE_CHANGED: "skippableStateChanged", + SKIPPED: "skip", + STARTED: "start", + THIRD_QUARTILE: "thirdQuartile", + USER_CLOSE: "userClose", + VIDEO_CLICKED: "videoClicked", + VIDEO_ICON_CLICKED: "videoIconClicked", + VIEWABLE_IMPRESSION: "viewable_impression", + VOLUME_CHANGED: "volumeChange", + VOLUME_MUTED: "mute", + }; + + class AdErrorEvent { + type = "adError"; + #error = ""; + constructor(error) { + this.#error = error; + } + getError() { + return this.#error; + } + getUserRequestContext() { + return {}; + } + } + AdErrorEvent.Type = { + AD_ERROR: "adError", + }; + + const manager = new AdsManager(); + + class AdsManagerLoadedEvent { + constructor(type) { + this.type = type; + } + getAdsManager() { + return manager; + } + getUserRequestContext() { + return {}; + } + } + AdsManagerLoadedEvent.Type = { + ADS_MANAGER_LOADED: "adsManagerLoaded", + }; + + class CustomContentLoadedEvent {} + CustomContentLoadedEvent.Type = { + CUSTOM_CONTENT_LOADED: "deprecated-event", + }; + + class CompanionAdSelectionSettings {} + CompanionAdSelectionSettings.CreativeType = { + ALL: "All", + FLASH: "Flash", + IMAGE: "Image", + }; + CompanionAdSelectionSettings.ResourceType = { + ALL: "All", + HTML: "Html", + IFRAME: "IFrame", + STATIC: "Static", + }; + CompanionAdSelectionSettings.SizeCriteria = { + IGNORE: "IgnoreSize", + SELECT_EXACT_MATCH: "SelectExactMatch", + SELECT_NEAR_MATCH: "SelectNearMatch", + }; + + class AdCuePoints { + getCuePoints() { + return []; + } + } + + class AdProgressData {} + + class UniversalAdIdInfo { + getAdIdRegistry() { + return ""; + } + getAdIsValue() { + return ""; + } + } + + Object.assign(ima, { + AdCuePoints, + AdDisplayContainer, + AdError, + AdErrorEvent, + AdEvent, + AdPodInfo, + AdProgressData, + AdsLoader, + AdsManager: manager, + AdsManagerLoadedEvent, + AdsRenderingSettings, + AdsRequest, + CompanionAd, + CompanionAdSelectionSettings, + CustomContentLoadedEvent, + gptProxyInstance: {}, + ImaSdkSettings, + OmidAccessMode: { + DOMAIN: "domain", + FULL: "full", + LIMITED: "limited", + }, + settings: new ImaSdkSettings(), + UiElements: { + AD_ATTRIBUTION: "adAttribution", + COUNTDOWN: "countdown", + }, + UniversalAdIdInfo, + VERSION, + ViewMode: { + FULLSCREEN: "fullscreen", + NORMAL: "normal", + }, + }); + + if (!window.google) { + window.google = {}; + } + + window.google.ima = ima; +} diff --git a/browser/extensions/webcompat/shims/google-page-ad.js b/browser/extensions/webcompat/shims/google-page-ad.js new file mode 100644 index 0000000000..42f3a0fca5 --- /dev/null +++ b/browser/extensions/webcompat/shims/google-page-ad.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713692 - Shim Google Page Ad conversion tracker + * + * This shim stubs out the simple API for converstion tracking with + * Google Page Ad, mitigating major breakage on pages which presume + * the API will always successfully load. + */ + +if (!window.google_trackConversion) { + window.google_trackConversion = () => {}; +} diff --git a/browser/extensions/webcompat/shims/google-publisher-tags.js b/browser/extensions/webcompat/shims/google-publisher-tags.js new file mode 100644 index 0000000000..e5174d3244 --- /dev/null +++ b/browser/extensions/webcompat/shims/google-publisher-tags.js @@ -0,0 +1,509 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713685 - Shim Google Publisher Tags + * + * Many sites rely on googletag to place content or drive ad bidding, + * and will experience major breakage if it is blocked. This shim provides + * enough of the API's frame To mitigate much of that breakage. + */ + +if (window.googletag?.apiReady === undefined) { + const version = "2021050601"; + + const noopthisfn = function () { + return this; + }; + + const slots = new Map(); + const slotsById = new Map(); + const slotsPerPath = new Map(); + const slotCreatives = new Map(); + const usedCreatives = new Map(); + const fetchedSlots = new Set(); + const eventCallbacks = new Map(); + + const fireSlotEvent = (name, slot) => { + return new Promise(resolve => { + requestAnimationFrame(() => { + const size = [0, 0]; + for (const cb of eventCallbacks.get(name) || []) { + cb({ isEmpty: true, size, slot }); + } + resolve(); + }); + }); + }; + + const recreateIframeForSlot = slot => { + const eid = `google_ads_iframe_${slot.getId()}`; + document.getElementById(eid)?.remove(); + const node = document.getElementById(slot.getSlotElementId()); + if (node) { + const f = document.createElement("iframe"); + f.id = eid; + f.srcdoc = "<body></body>"; + f.style = + "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0"; + f.setAttribute("width", 0); + f.setAttribute("height", 0); + node.appendChild(f); + } + }; + + const emptySlotElement = slot => { + const node = document.getElementById(slot.getSlotElementId()); + while (node?.lastChild) { + node.lastChild.remove(); + } + }; + + const SizeMapping = class extends Array { + getCreatives() { + const { clientWidth, clientHeight } = document.documentElement; + for (const [size, creatives] of this) { + if (clientWidth >= size[0] && clientHeight >= size[1]) { + return creatives; + } + } + return []; + } + }; + + const fetchSlot = slot => { + if (!slot) { + return; + } + + const id = slot.getSlotElementId(); + + const node = document.getElementById(id); + if (!node) { + return; + } + + let creatives = slotCreatives.get(id); + if (creatives instanceof SizeMapping) { + creatives = creatives.getCreatives(); + } + + if (!creatives?.length) { + return; + } + + for (const creative of creatives) { + if (usedCreatives.has(creative)) { + return; + } + } + + const creative = creatives[0]; + usedCreatives.set(creative, slot); + fetchedSlots.add(id); + }; + + const displaySlot = async slot => { + if (!slot) { + return; + } + + const id = slot.getSlotElementId(); + if (!document.getElementById(id)) { + return; + } + + if (!fetchedSlots.has(id)) { + fetchSlot(slot); + } + + const parent = document.getElementById(id); + if (parent) { + parent.appendChild(document.createElement("div")); + } + + emptySlotElement(slot); + recreateIframeForSlot(slot); + await fireSlotEvent("slotRenderEnded", slot); + await fireSlotEvent("slotRequested", slot); + await fireSlotEvent("slotResponseReceived", slot); + await fireSlotEvent("slotOnload", slot); + await fireSlotEvent("impressionViewable", slot); + }; + + const addEventListener = function (name, listener) { + if (!eventCallbacks.has(name)) { + eventCallbacks.set(name, new Set()); + } + eventCallbacks.get(name).add(listener); + return this; + }; + + const removeEventListener = function (name, listener) { + if (eventCallbacks.has(name)) { + return eventCallbacks.get(name).delete(listener); + } + return false; + }; + + const companionAdsService = { + addEventListener, + enable() {}, + fillSlot() {}, + getAttributeKeys: () => [], + getDisplayAdsCorrelator: () => "", + getName: () => "companion_ads", + getSlotIdMap: () => { + return {}; + }, + getSlots: () => [], + getVideoStreamCorrelator() {}, + isRoadblockingSupported: () => false, + isSlotAPersistentRoadblock: () => false, + notifyUnfilledSlots() {}, + onImplementationLoaded() {}, + refreshAllSlots() { + for (const slot of slotsById.values()) { + fetchSlot(slot); + displaySlot(slot); + } + }, + removeEventListener, + set() {}, + setRefreshUnfilledSlots() {}, + setVideoSession() {}, + slotRenderEnded() {}, + }; + + const contentService = { + addEventListener, + setContent() {}, + removeEventListener, + }; + + const getTargetingValue = v => { + if (typeof v === "string") { + return [v]; + } + try { + return [Array.prototype.flat.call(v)[0]]; + } catch (_) {} + return []; + }; + + const updateTargeting = (targeting, map) => { + if (typeof map === "object") { + const entries = Object.entries(map || {}); + for (const [k, v] of entries) { + targeting.set(k, getTargetingValue(v)); + } + } + }; + + const defineSlot = (adUnitPath, creatives, opt_div) => { + if (slotsById.has(opt_div)) { + document.getElementById(opt_div)?.remove(); + return slotsById.get(opt_div); + } + const attributes = new Map(); + const targeting = new Map(); + const exclusions = new Set(); + const response = { + advertiserId: undefined, + campaignId: undefined, + creativeId: undefined, + creativeTemplateId: undefined, + lineItemId: undefined, + }; + const sizes = [ + { + getHeight: () => 2, + getWidth: () => 2, + }, + ]; + const num = (slotsPerPath.get(adUnitPath) || 0) + 1; + slotsPerPath.set(adUnitPath, num); + const id = `${adUnitPath}_${num}`; + let clickUrl = ""; + let collapseEmptyDiv = null; + let services = new Set(); + const slot = { + addService(e) { + services.add(e); + return slot; + }, + clearCategoryExclusions: noopthisfn, + clearTargeting(k) { + if (k === undefined) { + targeting.clear(); + } else { + targeting.delete(k); + } + }, + defineSizeMapping(mapping) { + slotCreatives.set(opt_div, mapping); + return this; + }, + get: k => attributes.get(k), + getAdUnitPath: () => adUnitPath, + getAttributeKeys: () => Array.from(attributes.keys()), + getCategoryExclusions: () => Array.from(exclusions), + getClickUrl: () => clickUrl, + getCollapseEmptyDiv: () => collapseEmptyDiv, + getContentUrl: () => "", + getDivStartsCollapsed: () => null, + getDomId: () => opt_div, + getEscapedQemQueryId: () => "", + getFirstLook: () => 0, + getId: () => id, + getHtml: () => "", + getName: () => id, + getOutOfPage: () => false, + getResponseInformation: () => response, + getServices: () => Array.from(services), + getSizes: () => sizes, + getSlotElementId: () => opt_div, + getSlotId: () => slot, + getTargeting: k => targeting.get(k) || gTargeting.get(k) || [], + getTargetingKeys: () => + Array.from( + new Set(Array.of(...gTargeting.keys(), ...targeting.keys())) + ), + getTargetingMap: () => + Object.assign( + Object.fromEntries(gTargeting.entries()), + Object.fromEntries(targeting.entries()) + ), + set(k, v) { + attributes.set(k, v); + return slot; + }, + setCategoryExclusion(e) { + exclusions.add(e); + return slot; + }, + setClickUrl(u) { + clickUrl = u; + return slot; + }, + setCollapseEmptyDiv(v) { + collapseEmptyDiv = !!v; + return slot; + }, + setSafeFrameConfig: noopthisfn, + setTagForChildDirectedTreatment: noopthisfn, + setTargeting(k, v) { + targeting.set(k, getTargetingValue(v)); + return slot; + }, + toString: () => id, + updateTargetingFromMap(map) { + updateTargeting(targeting, map); + return slot; + }, + }; + slots.set(adUnitPath, slot); + slotsById.set(opt_div, slot); + slotCreatives.set(opt_div, creatives); + return slot; + }; + + let initialLoadDisabled = false; + + const gTargeting = new Map(); + const gAttributes = new Map(); + + let imaContent = { vid: "", cmsid: "" }; + let videoContent = { vid: "", cmsid: "" }; + + const pubadsService = { + addEventListener, + clear() {}, + clearCategoryExclusions: noopthisfn, + clearTagForChildDirectedTreatment: noopthisfn, + clearTargeting(k) { + if (k === undefined) { + gTargeting.clear(); + } else { + gTargeting.delete(k); + } + }, + collapseEmptyDivs() {}, + defineOutOfPagePassback: (a, o) => defineSlot(a, 0, o), + definePassback: (a, s, o) => defineSlot(a, s, o), + disableInitialLoad() { + initialLoadDisabled = true; + return this; + }, + display(adUnitPath, sizes, opt_div) { + const slot = defineSlot(adUnitPath, sizes, opt_div); + displaySlot(slot); + }, + enable() {}, + enableAsyncRendering() {}, + enableLazyLoad() {}, + enableSingleRequest() {}, + enableSyncRendering() {}, + enableVideoAds() {}, + forceExperiment() {}, + get: k => gAttributes.get(k), + getAttributeKeys: () => Array.from(gAttributes.keys()), + getCorrelator() {}, + getImaContent: () => imaContent, + getName: () => "publisher_ads", + getSlots: () => Array.from(slots.values()), + getSlotIdMap() { + const map = {}; + slots.values().forEach(s => { + map[s.getId()] = s; + }); + return map; + }, + getTagSessionCorrelator() {}, + getTargeting: k => gTargeting.get(k) || [], + getTargetingKeys: () => Array.from(gTargeting.keys()), + getTargetingMap: () => Object.fromEntries(gTargeting.entries()), + getVersion: () => version, + getVideoContent: () => videoContent, + isInitialLoadDisabled: () => initialLoadDisabled, + isSRA: () => false, + markAsAmp() {}, + refresh(slts) { + if (!slts) { + slts = slots.values(); + } else if (!Array.isArray(slts)) { + slts = [slts]; + } + for (const slot of slts) { + if (slot) { + try { + fetchSlot(slot); + displaySlot(slot); + } catch (e) { + console.error(e); + } + } + } + }, + removeEventListener, + set(k, v) { + gAttributes[k] = v; + return this; + }, + setCategoryExclusion: noopthisfn, + setCentering() {}, + setCookieOptions: noopthisfn, + setCorrelator: noopthisfn, + setForceSafeFrame: noopthisfn, + setImaContent(vid, cmsid) { + imaContent = { vid, cmsid }; + return this; + }, + setLocation: noopthisfn, + setPrivacySettings: noopthisfn, + setPublisherProvidedId: noopthisfn, + setRequestNonPersonalizedAds: noopthisfn, + setSafeFrameConfig: noopthisfn, + setTagForChildDirectedTreatment: noopthisfn, + setTagForUnderAgeOfConsent: noopthisfn, + setTargeting(k, v) { + gTargeting.set(k, getTargetingValue(v)); + return this; + }, + setVideoContent(vid, cmsid) { + videoContent = { vid, cmsid }; + return this; + }, + updateCorrelator() {}, + updateTargetingFromMap(map) { + updateTargeting(gTargeting, map); + return this; + }, + }; + + const SizeMappingBuilder = class { + #mapping; + constructor() { + this.#mapping = new SizeMapping(); + } + addSize(size, creatives) { + if ( + size !== "fluid" && + (!Array.isArray(size) || isNaN(size[0]) || isNaN(size[1])) + ) { + this.#mapping = null; + } else { + this.#mapping?.push([size, creatives]); + } + return this; + } + build() { + return this.#mapping; + } + }; + + let gt = window.googletag; + if (!gt) { + gt = window.googletag = {}; + } + + Object.assign(gt, { + apiReady: true, + companionAds: () => companionAdsService, + content: () => contentService, + defineOutOfPageSlot: (a, o) => defineSlot(a, 0, o), + defineSlot: (a, s, o) => defineSlot(a, s, o), + destroySlots() { + slots.clear(); + slotsById.clear(); + }, + disablePublisherConsole() {}, + display(arg) { + let id; + if (arg?.getSlotElementId) { + id = arg.getSlotElementId(); + } else if (arg?.nodeType) { + id = arg.id; + } else { + id = String(arg); + } + displaySlot(slotsById.get(id)); + }, + enableServices() {}, + enums: { + OutOfPageFormat: { + BOTTOM_ANCHOR: 3, + INTERSTITIAL: 5, + REWARDED: 4, + TOP_ANCHOR: 2, + }, + }, + getVersion: () => version, + pubads: () => pubadsService, + pubadsReady: true, + setAdIframeTitle() {}, + sizeMapping: () => new SizeMappingBuilder(), + }); + + const run = function (fn) { + if (typeof fn === "function") { + try { + fn.call(window); + } catch (e) { + console.error(e); + } + } + }; + + const cmds = gt.cmd || []; + const newCmd = []; + newCmd.push = run; + gt.cmd = newCmd; + + for (const cmd of cmds) { + run(cmd); + } +} diff --git a/browser/extensions/webcompat/shims/google-safeframe.html b/browser/extensions/webcompat/shims/google-safeframe.html new file mode 100644 index 0000000000..34bc1d242f --- /dev/null +++ b/browser/extensions/webcompat/shims/google-safeframe.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. + + Bug 1713691 - Shim Google SafeFrame + + Some sites will break if they cannot load a Google SafeFrame. This + shim provides a stand-in for the frame to mitigate that breakage. +--> +<html> + <head> + <meta charset="UTF-8" /> + <title>SafeFrame Container</title> + <script> + try { + const F = /^([^;]+);(\d+);([\s\S]*)$/.exec(window.name); + window.name = ""; + const P = window.document; + P.open("text/html", "replace"); + P.write(F[3].substr(0, +F[2])); + P.close(); + } catch (e) { + console.error(e); + } + </script> + </head> + <body></body> +</html> diff --git a/browser/extensions/webcompat/shims/history.js b/browser/extensions/webcompat/shims/history.js new file mode 100644 index 0000000000..6fbd1fdedb --- /dev/null +++ b/browser/extensions/webcompat/shims/history.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Bug 1624853 - Shim Storage Access API on history.com + * + * history.com uses Adobe as a necessary third party to authenticating + * with a TV provider. In order to accomodate this, we grant storage access + * to the Adobe domain via the Storage Access API when the login or logout + * buttons are clicked, then forward the click to continue as normal. + */ + +console.warn( + `When using oauth, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1624853 for details.` +); + +// Third-party origin we need to request storage access for. +const STORAGE_ACCESS_ORIGIN = "https://sp.auth.adobe.com"; + +document.documentElement.addEventListener( + "click", + e => { + const { target, isTrusted } = e; + if (!isTrusted) { + return; + } + + const button = target.closest("a"); + if (!button) { + return; + } + + const buttonLink = button.href; + if (buttonLink?.startsWith("https://www.history.com/mvpd-auth")) { + button.disabled = true; + button.style.opacity = 0.5; + e.stopPropagation(); + e.preventDefault(); + document + .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) + .then(() => { + target.click(); + }) + .catch(() => { + button.disabled = false; + button.style.opacity = 1.0; + }); + } + }, + true +); diff --git a/browser/extensions/webcompat/shims/iam.js b/browser/extensions/webcompat/shims/iam.js new file mode 100644 index 0000000000..84dee0e484 --- /dev/null +++ b/browser/extensions/webcompat/shims/iam.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1761774 - Shim INFOnline IAM tracker + * + * Sites using IAM can break if it is blocked. This shim mitigates that + * breakage by loading a stand-in module. + */ + +if (!window.iom?.c) { + window.iom = { + c: () => {}, + consent: () => {}, + count: () => {}, + ct: () => {}, + deloptout: () => {}, + doo: () => {}, + e: () => {}, + event: () => {}, + getInvitation: () => {}, + getPlus: () => {}, + gi: () => {}, + gp: () => {}, + h: () => {}, + hybrid: () => {}, + i: () => {}, + init: () => {}, + oi: () => {}, + optin: () => {}, + setMultiIdentifier: () => {}, + setoptout: () => {}, + smi: () => {}, + soo: () => {}, + }; +} diff --git a/browser/extensions/webcompat/shims/iaspet.js b/browser/extensions/webcompat/shims/iaspet.js new file mode 100644 index 0000000000..7e19dd52ad --- /dev/null +++ b/browser/extensions/webcompat/shims/iaspet.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713701 - Shim Integral Ad Science iaspet.js + * + * Some sites use iaspet to place content, often together with Google Publisher + * Tags. This shim prevents breakage when the script is blocked. + */ + +if (!window.__iasPET?.VERSION) { + let queue = window?.__iasPET?.queue; + if (!Array.isArray(queue)) { + queue = []; + } + + const response = JSON.stringify({ + brandSafety: {}, + slots: {}, + }); + + function run(cmd) { + try { + cmd?.dataHandler?.(response); + } catch (_) {} + } + + queue.push = run; + + window.__iasPET = { + VERSION: "1.16.18", + queue, + sessionId: "", + setTargetingForAppNexus() {}, + setTargetingForGPT() {}, + start() {}, + }; + + while (queue.length) { + run(queue.shift()); + } +} diff --git a/browser/extensions/webcompat/shims/instagram.js b/browser/extensions/webcompat/shims/instagram.js new file mode 100644 index 0000000000..5bf5014fdc --- /dev/null +++ b/browser/extensions/webcompat/shims/instagram.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Bug 1804445 - instagram login broken with dFPI enabled + * + * Instagram login with Facebook account requires Facebook to have the storage + * access under Instagram. This shim adds a request for storage access for + * Facebook when the user tries to log in with a Facebook account. + */ + +console.warn( + `When logging in, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1804445 for details.` +); + +// Third-party origin we need to request storage access for. +const STORAGE_ACCESS_ORIGIN = "https://www.facebook.com"; + +document.documentElement.addEventListener( + "click", + e => { + const { target, isTrusted } = e; + if (!isTrusted) { + return; + } + const button = target.closest("button[type=button]"); + if (!button) { + return; + } + const form = target.closest("#loginForm"); + if (!form) { + return; + } + + console.warn( + "Calling the Storage Access API on behalf of " + STORAGE_ACCESS_ORIGIN + ); + button.disabled = true; + e.stopPropagation(); + e.preventDefault(); + document + .requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN) + .then(() => { + button.disabled = false; + target.click(); + }) + .catch(() => { + button.disabled = false; + }); + }, + true +); diff --git a/browser/extensions/webcompat/shims/kinja.js b/browser/extensions/webcompat/shims/kinja.js new file mode 100644 index 0000000000..d30425b42d --- /dev/null +++ b/browser/extensions/webcompat/shims/kinja.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals exportFunction */ + +"use strict"; + +/** + * Kinja powered blogs rely on storage access to https://kinja.com to enable + * oauth with external providers. For dFPI, sites need to use the Storage Access + * API to gain first party storage access. This shim calls requestStorageAccess + * on behalf of the site when a user wants to log in via oauth. + */ + +// Third-party origin we need to request storage access for. +const STORAGE_ACCESS_ORIGIN = "https://kinja.com"; + +// Prefix of the path opened in a new window when users click the oauth login +// buttons. +const OAUTH_PATH_PREFIX = "/oauthlogin?provider="; + +console.warn( + `When using oauth, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1656171 for details.` +); + +// Overwrite the window.open method so we can detect oauth related popups. +const origOpen = window.wrappedJSObject.open; +Object.defineProperty(window.wrappedJSObject, "open", { + value: exportFunction((url, ...args) => { + // Filter oauth popups. + if (!url.startsWith(OAUTH_PATH_PREFIX)) { + return origOpen(url, ...args); + } + // Request storage access for Kinja. + document.requestStorageAccessForOrigin(STORAGE_ACCESS_ORIGIN).then(() => { + origOpen(url, ...args); + }); + // We don't have the window object yet which window.open returns, since the + // sign-in flow is dependent on the async storage access request. This isn't + // a problem as long as the website does not consume it. + return null; + }, window), +}); diff --git a/browser/extensions/webcompat/shims/live-test-shim.js b/browser/extensions/webcompat/shims/live-test-shim.js new file mode 100644 index 0000000000..45ab9ba48b --- /dev/null +++ b/browser/extensions/webcompat/shims/live-test-shim.js @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +if (!window.LiveTestShimPromise) { + const originalUrl = document.currentScript.src; + + const shimId = "LiveTestShim"; + + const sendMessageToAddon = (function () { + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = + Math.random().toString(36).substring(2) + Date.now().toString(36); + return new Promise(resolve => { + const payload = { + message, + messageId, + shimId, + }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + async function go(options) { + try { + const o = document.getElementById("shims"); + const cl = o.classList; + cl.remove("red"); + cl.add("green"); + o.innerText = JSON.stringify(options || ""); + } catch (_) {} + + if (window !== top) { + return; + } + + await sendMessageToAddon("optIn"); + + const s = document.createElement("script"); + s.src = originalUrl; + document.head.appendChild(s); + } + + window[`${shimId}Promise`] = sendMessageToAddon("getOptions").then( + options => { + if (document.readyState !== "loading") { + go(options); + } else { + window.addEventListener("DOMContentLoaded", () => { + go(options); + }); + } + } + ); +} diff --git a/browser/extensions/webcompat/shims/maxmind-geoip.js b/browser/extensions/webcompat/shims/maxmind-geoip.js new file mode 100644 index 0000000000..e5eb1e45a3 --- /dev/null +++ b/browser/extensions/webcompat/shims/maxmind-geoip.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1754389 - Shim Maxmind GeoIP library + * + * Some sites rely on Maxmind's GeoIP library which gets blocked by ETP's + * fingerprinter blocking. With the library window global not being defined + * functionality may break or the site does not render at all. This shim + * has it return the United States as the location for all users. + */ + +if (!window.geoip2) { + const continent = { + code: "NA", + geoname_id: 6255149, + names: { + de: "Nordamerika", + en: "North America", + es: "Norteamérica", + fr: "Amérique du Nord", + ja: "北アメリカ", + "pt-BR": "América do Norte", + ru: "Северная Америка", + "zh-CN": "北美洲", + }, + }; + + const country = { + geoname_id: 6252001, + iso_code: "US", + names: { + de: "USA", + en: "United States", + es: "Estados Unidos", + fr: "États-Unis", + ja: "アメリカ合衆国", + "pt-BR": "Estados Unidos", + ru: "США", + "zh-CN": "美国", + }, + }; + + const city = { + names: { + en: "", + }, + }; + + const callback = onSuccess => { + requestAnimationFrame(() => { + onSuccess({ + city, + continent, + country, + registered_country: country, + }); + }); + }; + + window.geoip2 = { + country: callback, + city: callback, + insights: callback, + }; +} diff --git a/browser/extensions/webcompat/shims/microsoftLogin.js b/browser/extensions/webcompat/shims/microsoftLogin.js new file mode 100644 index 0000000000..ebbfb2fbff --- /dev/null +++ b/browser/extensions/webcompat/shims/microsoftLogin.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const SANDBOX_ATTR = "allow-storage-access-by-user-activation"; + +console.warn( + "Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1638383 for details." +); + +// Watches for MS auth iframes and adds missing sandbox attribute. The attribute +// is required so the third-party iframe can gain access to its first party +// storage via the Storage Access API. +function init() { + const observer = new MutationObserver(() => { + document.body + .querySelectorAll("iframe[id^='msalRenewFrame'][sandbox]") + .forEach(frame => { + frame.sandbox.add(SANDBOX_ATTR); + }); + }); + + observer.observe(document.body, { + attributes: true, + subtree: false, + childList: true, + }); +} +window.addEventListener("DOMContentLoaded", init); diff --git a/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js b/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js new file mode 100644 index 0000000000..4b4493750c --- /dev/null +++ b/browser/extensions/webcompat/shims/microsoftVirtualAssistant.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1801277 - Shim Microsoft virtual assistant. + * + * The microsoft virtual assistant will break when accessing the indexedDB that + * will throw a security error because the virtual assistant is under a + * third-party tracking domain 'liveperson.net'. The shim replaces the indexedDB + * with a fake interface that won't throw an error. + */ + +/* globals cloneInto */ + +(function () { + const win = window.wrappedJSObject; + + try { + // We only replace the indexedDB when liveperson.net is loaded in a + // third-party context. Note that this is not strictly correct because + // this is a cross-origin check but not a third-party check. + if (win.parent == win || win.location.origin == win.top.location.origin) { + return; + } + } catch (e) { + // If we get a security error when accessing the top-level origin, this + // shows that the window is in a cross-origin context. In this case, we can + // proceed to apply the shim. + if (e.name != "SecurityError") { + throw e; + } + } + + const emptyMsg = cloneInto({ message: "" }, window); + + const idb = { + open: () => win.Promise.reject(emptyMsg), + }; + + Object.defineProperty(win, "indexedDB", { + value: cloneInto(idb, window, { cloneFunctions: true }), + }); +})(); diff --git a/browser/extensions/webcompat/shims/moat.js b/browser/extensions/webcompat/shims/moat.js new file mode 100644 index 0000000000..9957492684 --- /dev/null +++ b/browser/extensions/webcompat/shims/moat.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713704 - Shim Moat ad tracker + * + * Sites such as Forbes may gate content behind Moat ads, resulting in + * breakage like black boxes where videos should be placed. This shim + * helps mitigate that breakage by allowing the placement to succeed. + */ + +if (!window.moatPrebidAPI?.__A) { + const targeting = new Map(); + + const slotConfig = { + m_categories: ["moat_safe"], + m_data: "0", + m_safety: "safe", + }; + + window.moatPrebidApi = { + __A() {}, + disableLogging() {}, + enableLogging() {}, + getMoatTargetingForPage: () => slotConfig, + getMoatTargetingForSlot(slot) { + return targeting.get(slot?.getSlotElementId()); + }, + pageDataAvailable: () => true, + safetyDataAvailable: () => true, + setMoatTargetingForAllSlots() { + for (const slot of window.googletag.pubads().getSlots() || []) { + targeting.set(slot.getSlotElementId(), slot.getTargeting()); + } + }, + setMoatTargetingForSlot(slot) { + targeting.set(slot?.getSlotElementId(), slotConfig); + }, + slotDataAvailable() { + return window.googletag?.pubads().getSlots().length > 0; + }, + }; +} diff --git a/browser/extensions/webcompat/shims/mochitest-shim-1.js b/browser/extensions/webcompat/shims/mochitest-shim-1.js new file mode 100644 index 0000000000..d95059cf7a --- /dev/null +++ b/browser/extensions/webcompat/shims/mochitest-shim-1.js @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +if (!window.MochitestShimPromise) { + const originalUrl = document.currentScript.src; + + const shimId = "MochitestShim"; + + const sendMessageToAddon = (function () { + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = + Math.random().toString(36).substring(2) + Date.now().toString(36); + return new Promise(resolve => { + const payload = { + message, + messageId, + shimId, + }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + async function go(options) { + try { + const o = document.getElementById("shims"); + const cl = o.classList; + cl.remove("red"); + cl.add("green"); + o.innerText = JSON.stringify(options || ""); + } catch (_) {} + + window.shimPromiseResolve("shimmed"); + + if (window !== top) { + window.optInPromiseResolve(false); + return; + } + + await sendMessageToAddon("optIn"); + + window.doingOptIn = true; + const s = document.createElement("script"); + s.src = originalUrl; + s.onerror = () => window.optInPromiseResolve("error"); + document.head.appendChild(s); + } + + window[`${shimId}Promise`] = new Promise(resolve => { + sendMessageToAddon("getOptions").then(options => { + if (document.readyState !== "loading") { + resolve(go(options)); + } else { + window.addEventListener("DOMContentLoaded", () => { + resolve(go(options)); + }); + } + }); + }); +} diff --git a/browser/extensions/webcompat/shims/mochitest-shim-2.js b/browser/extensions/webcompat/shims/mochitest-shim-2.js new file mode 100644 index 0000000000..bc5536405e --- /dev/null +++ b/browser/extensions/webcompat/shims/mochitest-shim-2.js @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals browser */ + +if (!window.testPromise) { + const originalUrl = document.currentScript.src; + + const shimId = "MochitestShim2"; + + const sendMessageToAddon = (function () { + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = + Math.random().toString(36).substring(2) + Date.now().toString(36); + return new Promise(resolve => { + const payload = { + message, + messageId, + shimId, + }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + async function go(options) { + try { + const o = document.getElementById("shims"); + const cl = o.classList; + cl.remove("red"); + cl.add("green"); + o.innerText = JSON.stringify(options || ""); + } catch (_) {} + + window.shimPromiseResolve("shimmed"); + + if (window !== top) { + window.optInPromiseResolve(false); + return; + } + + await sendMessageToAddon("optIn"); + + window.doingOptIn = true; + const s = document.createElement("script"); + s.src = originalUrl; + s.onerror = () => window.optInPromiseResolve("error"); + document.head.appendChild(s); + } + + sendMessageToAddon("getOptions").then(options => { + if (document.readyState !== "loading") { + go(options); + } else { + window.addEventListener("DOMContentLoaded", () => { + go(options); + }); + } + }); +} diff --git a/browser/extensions/webcompat/shims/mochitest-shim-3.js b/browser/extensions/webcompat/shims/mochitest-shim-3.js new file mode 100644 index 0000000000..dc0a8005f5 --- /dev/null +++ b/browser/extensions/webcompat/shims/mochitest-shim-3.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +window.shimPromiseResolve("shimmed"); diff --git a/browser/extensions/webcompat/shims/nielsen.js b/browser/extensions/webcompat/shims/nielsen.js new file mode 100644 index 0000000000..d34528a7c1 --- /dev/null +++ b/browser/extensions/webcompat/shims/nielsen.js @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1760754 - Shim Nielsen tracker + * + * Sites expecting the Nielsen tracker to load properly can break if it + * is blocked. This shim mitigates that breakage by loading a stand-in. + */ + +if (!window.nol_t) { + const cid = ""; + + let domain = ""; + let schemeHost = ""; + let scriptName = ""; + try { + const url = document?.currentScript?.src; + const { pathname, protocol, host } = new URL(url); + domain = host.split(".").slice(0, -2).join("."); + schemeHost = `${protocol}//${host}/`; + scriptName = pathname.split("/").pop(); + } catch (_) {} + + const NolTracker = class { + CONST = { + max_tags: 20, + }; + feat = {}; + globals = { + cid, + content: "0", + defaultApidFile: "config250", + defaultErrorParams: { + nol_vcid: "c00", + nol_clientid: "", + }, + domain, + fpidSfCodeList: [""], + init() {}, + tagCurrRetry: -1, + tagMaxRetry: 3, + wlCurrRetry: -1, + wlMaxRetry: 3, + }; + pmap = []; + pvar = { + cid, + content: "0", + cookies_enabled: "n", + server: domain, + }; + scriptName = [scriptName]; + version = "6.0.107"; + + addScript() {} + catchLinkOverlay() {} + clickEvent() {} + clickTrack() {} + do_sample() {} + downloadEvent() {} + eventTrack() {} + filter() {} + fireToUrl() {} + getSchemeHost() { + return schemeHost; + } + getVersion() {} + iframe() {} + in_sample() { + return true; + } + injectBsdk() {} + invite() {} + linkTrack() {} + mergeFeatures() {} + pageEvent() {} + pause() {} + populateWhitelist() {} + post() {} + postClickTrack() {} + postData() {} + postEvent() {} + postEventTrack() {} + postLinkTrack() {} + prefix() { + return ""; + } + processDdrsSvc() {} + random() {} + record() { + return this; + } + regLinkOverlay() {} + regListen() {} + retrieveCiFileViaCors() {} + sectionEvent() {} + sendALink() {} + sendForm() {} + sendIt() {} + slideEvent() {} + whitelistAssigned() {} + }; + + window.nol_t = () => { + return new NolTracker(); + }; +} diff --git a/browser/extensions/webcompat/shims/optimizely.js b/browser/extensions/webcompat/shims/optimizely.js new file mode 100644 index 0000000000..dcda87421d --- /dev/null +++ b/browser/extensions/webcompat/shims/optimizely.js @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bug 1714431 - Shim Optimizely + * + * This shim stubs out window.optimizely for those sites which + * break when it is not successfully loaded. + */ + +if (!window.optimizely?.state) { + const behavior = { + query: () => [], + }; + + const dcp = { + getAttributeValue() {}, + waitForAttributeValue: () => Promise.resolve(), + }; + + const data = { + accountId: "", + audiences: {}, + campaigns: {}, + clientName: "js", + clientVersion: "0.166.0", + dcpServiceId: null, + events: {}, + experiments: {}, + groups: {}, + pages: {}, + projectId: "", + revision: "", + snippetId: null, + variations: {}, + }; + + const activationId = String(Date.now()); + + const state = { + getActivationId() { + return activationId; + }, + getActiveExperimentIds() { + return []; + }, + getCampaignStateLists() { + return {}; + }, + getCampaignStates() { + return {}; + }, + getDecisionObject() { + return null; + }, + getDecisionString() { + return null; + }, + getExperimentStates() { + return {}; + }, + getPageStates() { + return {}; + }, + getRedirectInfo() { + return null; + }, + getVariationMap() { + return {}; + }, + isGlobalHoldback() { + return false; + }, + }; + + const poll = (fn, to) => { + setInterval(() => { + try { + fn(); + } catch (_) {} + }, to); + }; + + const waitUntil = test => { + let interval, resolve; + function check() { + try { + if (test()) { + clearInterval(interval); + resolve?.(); + return true; + } + } catch (_) {} + return false; + } + return new Promise(r => { + resolve = r; + if (check()) { + resolve(); + return; + } + interval = setInterval(check, 250); + }); + }; + + const waitForElement = sel => { + return waitUntil(() => { + document.querySelector(sel); + }); + }; + + const observeSelector = (sel, fn, opts) => { + let interval; + const observed = new Set(); + function check() { + try { + for (const e of document.querySelectorAll(sel)) { + if (observed.has(e)) { + continue; + } + observed.add(e); + try { + fn(e); + } catch (_) {} + if (opts.once) { + clearInterval(interval); + } + } + } catch (_) {} + } + interval = setInterval(check, 250); + const timeout = { opts }; + if (timeout) { + setTimeout(() => { + clearInterval(interval); + }, timeout); + } + }; + + const utils = { + Promise: window.Promise, + observeSelector, + poll, + waitForElement, + waitUntil, + }; + + const visitorId = { + randomId: "", + }; + + let browserVersion = ""; + try { + browserVersion = navigator.userAgent.match(/rv:(.*)\)/)[1]; + } catch (_) {} + + const visitor = { + browserId: "ff", + browserVersion, + currentTimestamp: Date.now(), + custom: {}, + customBehavior: {}, + device: "desktop", + device_type: "desktop_laptop", + events: [], + first_session: true, + offset: 240, + referrer: null, + source_type: "direct", + visitorId, + }; + + window.optimizely = { + data: { + note: "Obsolete, use optimizely.get('data') instead", + }, + get(e) { + switch (e) { + case "behavior": + return behavior; + case "data": + return data; + case "dcp": + return dcp; + case "jquery": + throw new Error("jQuery not implemented"); + case "session": + return undefined; + case "state": + return state; + case "utils": + return utils; + case "visitor": + return visitor; + case "visitor_id": + return visitorId; + } + return undefined; + }, + initialized: true, + push() {}, + state: {}, + }; +} diff --git a/browser/extensions/webcompat/shims/play.svg b/browser/extensions/webcompat/shims/play.svg new file mode 100644 index 0000000000..df5bbcb4f1 --- /dev/null +++ b/browser/extensions/webcompat/shims/play.svg @@ -0,0 +1,7 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. + source: https://searchfox.org/mozilla-central/source/devtools/client/themes/images/play.svg --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <path fill="#fff" d="M20.436 11.37L5.904 2.116c-.23-.147-.523-.158-.762-.024-.24.132-.39.384-.39.657v18.5c0 .273.15.525.39.657.112.063.236.093.36.093.14 0 .28-.04.402-.117l14.53-9.248c.218-.138.35-.376.35-.633 0-.256-.132-.495-.348-.633z"/> +</svg> diff --git a/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js b/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js new file mode 100644 index 0000000000..bc45aeda26 --- /dev/null +++ b/browser/extensions/webcompat/shims/private-browsing-web-api-fixes.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1714354 - Fix for site issues with web APIs in private browsing + * + * Some sites expect specific DOM APIs to work in specific ways, which + * is not always true, such as in private browsing mode. We work around + * related breakage by undefining those APIs entirely in private + * browsing mode for those sites. + */ + +delete window.wrappedJSObject.caches; +delete window.wrappedJSObject.indexedDB; diff --git a/browser/extensions/webcompat/shims/rambler-authenticator.js b/browser/extensions/webcompat/shims/rambler-authenticator.js new file mode 100644 index 0000000000..1fe074b660 --- /dev/null +++ b/browser/extensions/webcompat/shims/rambler-authenticator.js @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +if (!window.ramblerIdHelper) { + const originalScript = document.currentScript.src; + + const sendMessageToAddon = (function () { + const shimId = "Rambler"; + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = + Math.random().toString(36).substring(2) + Date.now().toString(36); + return new Promise(resolve => { + const payload = { + message, + messageId, + shimId, + }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + const ramblerIdHelper = { + getProfileInfo: (successCallback, errorCallback) => { + successCallback({}); + }, + openAuth: () => { + sendMessageToAddon("optIn").then(function () { + const openAuthArgs = arguments; + window.ramblerIdHelper = undefined; + const s = document.createElement("script"); + s.src = originalScript; + document.head.appendChild(s); + s.addEventListener("load", () => { + const helper = window.ramblerIdHelper; + for (const { fn, args } of callLog) { + helper[fn].apply(helper, args); + } + helper.openAuth.apply(helper, openAuthArgs); + }); + }); + }, + }; + + const callLog = []; + function addLoggedCall(fn) { + ramblerIdHelper[fn] = () => { + callLog.push({ fn, args: arguments }); + }; + } + + addLoggedCall("registerOnFrameCloseCallback"); + addLoggedCall("registerOnFrameRedirect"); + addLoggedCall("registerOnPossibleLoginCallback"); + addLoggedCall("registerOnPossibleLogoutCallback"); + addLoggedCall("registerOnPossibleOauthLoginCallback"); + + window.ramblerIdHelper = ramblerIdHelper; +} diff --git a/browser/extensions/webcompat/shims/rich-relevance.js b/browser/extensions/webcompat/shims/rich-relevance.js new file mode 100644 index 0000000000..aea85c030a --- /dev/null +++ b/browser/extensions/webcompat/shims/rich-relevance.js @@ -0,0 +1,288 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713725 - Shim Rich Relevance personalized shopping + * + * Sites may expect the Rich Relevance personalized shopping API to load, + * breaking if it is blocked. This shim attempts to limit breakage on those + * site to just the personalized shopping aspects, by stubbing out the APIs. + */ + +if (!window.r3_common) { + const jsonCallback = window.RR?.jsonCallback; + const defaultCallback = window.RR?.defaultCallback; + + const getRandomString = (l = 66) => { + const v = crypto.getRandomValues(new Uint8Array(l)); + const s = Array.from(v, c => c.toString(16)).join(""); + return s.slice(0, l); + }; + + const call = (fn, ...args) => { + if (typeof fn === "function") { + try { + fn(...args); + } catch (e) { + console.error(e); + } + } + }; + + class r3_generic { + type = "GENERIC"; + createScript() {} + destroy() {} + } + + class r3_addtocart extends r3_generic { + type = "ADDTOCART"; + addItemIdToCart() {} + } + + class r3_addtoregistry extends r3_generic { + type = "ADDTOREGISTRY"; + addItemIdCentsQuantity() {} + } + + class r3_brand extends r3_generic { + type = "BRAND"; + } + + class r3_cart extends r3_generic { + type = "CART"; + addItemId() {} + addItemIdCentsQuantity() {} + addItemIdDollarsAndCentsQuantity() {} + addItemIdPriceQuantity() {} + } + + class r3_category extends r3_generic { + type = "CATEGORY"; + addItemId() {} + setId() {} + setName() {} + setParentId() {} + setTopName() {} + } + + class r3_common extends r3_generic { + type = "COMMON"; + baseUrl = "https://recs.richrelevance.com/rrserver/"; + devFlags = {}; + jsFileName = "p13n_generated.js"; + RICHSORT = { + paginate() {}, + filterPrice() {}, + filterAttribute() {}, + }; + addCategoryHintId() {} + addClickthruParams() {} + addContext() {} + addFilter() {} + addFilterBrand() {} + addFilterCategory() {} + addItemId() {} + addItemIdToCart() {} + addPlacementType() {} + addRefinement() {} + addSearchTerm() {} + addSegment() {} + blockItemId() {} + enableCfrad() {} + enableRad() {} + forceDebugMode() {} + forceDevMode() {} + forceDisplayMode() {} + forceLocale() {} + initFromParams() {} + setApiKey() {} + setBaseUrl() {} + setCartValue() {} + setChannel() {} + setClickthruServer() {} + setCurrency() {} + setDeviceId() {} + setFilterBrandsIncludeMatchingElements() {} + setForcedTreatment() {} + setImageServer() {} + setLanguage() {} + setMVTForcedTreatment() {} + setNoCookieMode() {} + setPageBrand() {} + setPrivateMode() {} + setRefinementFallback() {} + setRegionId() {} + setRegistryId() {} + setRegistryType() {} + setSessionId() {} + setUserId() {} + useDummyData() {} + } + + class r3_error extends r3_generic { + type = "ERROR"; + } + + class r3_home extends r3_generic { + type = "HOME"; + } + + class r3_item extends r3_generic { + type = "ITEM"; + addAttribute() {} + addCategory() {} + addCategoryId() {} + setBrand() {} + setEndDate() {} + setId() {} + setImageId() {} + setLinkId() {} + setName() {} + setPrice() {} + setRating() {} + setRecommendable() {} + setReleaseDate() {} + setSalePrice() {} + } + + class r3_personal extends r3_generic { + type = "PERSONAL"; + } + + class r3_purchased extends r3_generic { + type = "PURCHASED"; + addItemId() {} + addItemIdCentsQuantity() {} + addItemIdDollarsAndCentsQuantity() {} + addItemIdPriceQuantity() {} + setOrderNumber() {} + setPromotionCode() {} + setShippingCost() {} + setTaxes() {} + setTotalPrice() {} + } + + class r3_search extends r3_generic { + type = "SEARCH"; + addItemId() {} + setTerms() {} + } + + class r3_wishlist extends r3_generic { + type = "WISHLIST"; + addItemId() {} + } + + const RR = { + add() {}, + addItemId() {}, + addItemIdCentsQuantity() {}, + addItemIdDollarsAndCentsQuantity() {}, + addItemIdPriceQuantity() {}, + addItemIdToCart() {}, + addObject() {}, + addSearchTerm() {}, + c() {}, + charset: "UTF-8", + checkParamCookieValue: () => null, + d: document, + data: { + JSON: { + placements: [], + }, + }, + debugWindow() {}, + set defaultCallback(fn) { + call(fn); + }, + fixName: n => n, + genericAddItemPriceQuantity() {}, + get() {}, + getDomElement(a) { + return typeof a === "string" && a ? document.querySelector(a) : null; + }, + id() {}, + insert() {}, + insertDynamicPlacement() {}, + isArray: a => Array.isArray(a), + js() {}, + set jsonCallback(fn) { + call(fn, {}); + }, + l: document.location.href, + lc() {}, + noCookieMode: false, + ol() {}, + onloadCalled: true, + pq() {}, + rcsCookieDefaultDuration: 364, + registerPageType() {}, + registeredPageTypes: { + ADDTOCART: r3_addtocart, + ADDTOREGISTRY: r3_addtoregistry, + BRAND: r3_brand, + CART: r3_cart, + CATEGORY: r3_category, + COMMON: r3_common, + ERROR: r3_error, + GENERIC: r3_generic, + HOME: r3_home, + ITEM: r3_item, + PERSONAL: r3_personal, + PURCHASED: r3_purchased, + SEARCH: r3_search, + WISHLIST: r3_wishlist, + }, + renderDynamicPlacements() {}, + set() {}, + setCharset() {}, + U: "undefined", + unregisterAllPageType() {}, + unregisterPageType() {}, + }; + + Object.assign(window, { + r3() {}, + r3_addtocart, + r3_addtoregistry, + r3_brand, + r3_cart, + r3_category, + r3_common, + r3_error, + r3_generic, + r3_home, + r3_item, + r3_personal, + r3_placement() {}, + r3_purchased, + r3_search, + r3_wishlist, + RR, + rr_addLoadEvent() {}, + rr_annotations_array: [undefined], + rr_call_after_flush() {}, + rr_create_script() {}, + rr_dynamic: { + placements: [], + }, + rr_flush() {}, + rr_flush_onload() {}, + rr_insert_placement() {}, + rr_onload_called: true, + rr_placement_place_holders: [], + rr_placements: [], + rr_recs: { + placements: [], + }, + rr_remote_data: getRandomString(), + rr_v: "1.2.6.20210212", + }); + + call(jsonCallback); + call(defaultCallback, {}); +} diff --git a/browser/extensions/webcompat/shims/spotify-embed.js b/browser/extensions/webcompat/shims/spotify-embed.js new file mode 100644 index 0000000000..62ad05b725 --- /dev/null +++ b/browser/extensions/webcompat/shims/spotify-embed.js @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals exportFunction */ + +"use strict"; + +/** + * Spotify embeds default to "track preview mode". They require first-party + * storage access in order to detect the login status and allow the user to play + * the whole song or add it to their library. + * Upon clicking the "play" button in the preview view this shim attempts to get + * storage access and on success, reloads the frame and plays the full track. + * This only works if the user is already logged in to Spotify in the + * first-party context. + */ + +const AUTOPLAY_FLAG = "shimPlayAfterStorageAccess"; +const SELECTOR_PREVIEW_PLAY = 'div[data-testid="preview-play-pause"] > button'; +const SELECTOR_FULL_PLAY = 'button[data-testid="play-pause-button"]'; + +/** + * Promise-wrapper around DOMContentLoaded event. + */ +function waitForDOMContentLoaded() { + return new Promise(resolve => { + window.addEventListener("DOMContentLoaded", resolve, { once: true }); + }); +} + +/** + * Listener for the preview playback button which requests storage access and + * reloads the page. + */ +function previewPlayButtonListener(event) { + const { target, isTrusted } = event; + if (!isTrusted) { + return; + } + + const button = target.closest("button"); + if (!button) { + return; + } + + // Filter for the preview playback button. This won't match the full + // playback button that is shown when the user is logged in. + if (!button.matches(SELECTOR_PREVIEW_PLAY)) { + return; + } + + // The storage access request below runs async so playback won't start + // immediately. Mitigate this UX issue by updating the clicked element's + // style so the user gets some immediate feedback. + button.style.opacity = 0.5; + event.stopPropagation(); + event.preventDefault(); + + console.debug("Requesting storage access.", location.origin); + document + .requestStorageAccess() + // When storage access is granted, reload the frame for the embedded + // player to detect the login state and give us full playback + // capabilities. + .then(() => { + // Use a flag to indicate that we want to click play after reload. + // This is so the user does not have to click play twice. + sessionStorage.setItem(AUTOPLAY_FLAG, "true"); + console.debug("Reloading after storage access grant."); + location.reload(); + }) + // If the user denies the storage access prompt we can't use the login + // state. Attempt start preview playback instead. + .catch(() => { + button.click(); + }) + // Reset button style for both success and error case. + .finally(() => { + button.style.opacity = 1.0; + }); +} + +/** + * Attempt to start (full) playback. Waits for the play button to appear and + * become ready. + */ +async function startFullPlayback() { + // Wait for DOMContentLoaded before looking for the playback button. + await waitForDOMContentLoaded(); + + let numTries = 0; + let intervalId = setInterval(() => { + try { + document.querySelector(SELECTOR_FULL_PLAY).click(); + clearInterval(intervalId); + console.debug("Clicked play after storage access grant."); + } catch (e) {} + numTries++; + + if (numTries >= 50) { + console.debug("Can not start playback. Giving up."); + clearInterval(intervalId); + } + }, 200); +} + +(async () => { + // Only run the shim for embedded iframes. + if (window.top == window) { + return; + } + + console.warn( + `When using the Spotify embedded player, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1792395 for details.` + ); + + // Already requested storage access before the reload, trigger playback. + if (sessionStorage.getItem(AUTOPLAY_FLAG) == "true") { + sessionStorage.removeItem(AUTOPLAY_FLAG); + + await startFullPlayback(); + return; + } + + // Wait for the user to click the preview play button. If the player has + // already loaded the full version, this method will do nothing. + document.documentElement.addEventListener( + "click", + previewPlayButtonListener, + { capture: true } + ); +})(); diff --git a/browser/extensions/webcompat/shims/tracking-pixel.png b/browser/extensions/webcompat/shims/tracking-pixel.png Binary files differnew file mode 100644 index 0000000000..52c591798e --- /dev/null +++ b/browser/extensions/webcompat/shims/tracking-pixel.png diff --git a/browser/extensions/webcompat/shims/vast2.xml b/browser/extensions/webcompat/shims/vast2.xml new file mode 100644 index 0000000000..3536ccfc0f --- /dev/null +++ b/browser/extensions/webcompat/shims/vast2.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. + + Bug 1713693 - Shim Doubleclick + + Some sites rely on an XML VAST Ad response from Doubleclick, or will + break (showing black boxes instead of videos, etc). This shim mitigates + such breakage. +--> +<VAST version="2.0"></VAST> diff --git a/browser/extensions/webcompat/shims/vast3.xml b/browser/extensions/webcompat/shims/vast3.xml new file mode 100644 index 0000000000..ae03f0dc14 --- /dev/null +++ b/browser/extensions/webcompat/shims/vast3.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. + + Bug 1713693 - Shim Doubleclick + + Some sites rely on an XML VAST Ad response from Doubleclick, or will + break (showing black boxes instead of videos, etc). This shim mitigates + such breakage. +--> +<VAST version="3.0"></VAST> diff --git a/browser/extensions/webcompat/shims/vidible.js b/browser/extensions/webcompat/shims/vidible.js new file mode 100644 index 0000000000..1d45bc0f7e --- /dev/null +++ b/browser/extensions/webcompat/shims/vidible.js @@ -0,0 +1,424 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1713710 - Shim Vidible video player + * + * Sites relying on Vidible's video player may experience broken videos if that + * script is blocked. This shim allows users to opt into viewing those videos + * regardless of any tracking consequences, by providing placeholders for each. + */ + +if (!window.vidible?.version) { + const PlayIconURL = "https://smartblock.firefox.etp/play.svg"; + + const originalScript = document.currentScript.src; + + const getGUID = () => { + const v = crypto.getRandomValues(new Uint8Array(20)); + return Array.from(v, c => c.toString(16)).join(""); + }; + + const sendMessageToAddon = (function () { + const shimId = "Vidible"; + const pendingMessages = new Map(); + const channel = new MessageChannel(); + channel.port1.onerror = console.error; + channel.port1.onmessage = event => { + const { messageId, response } = event.data; + const resolve = pendingMessages.get(messageId); + if (resolve) { + pendingMessages.delete(messageId); + resolve(response); + } + }; + function reconnect() { + const detail = { + pendingMessages: [...pendingMessages.values()], + port: channel.port2, + shimId, + }; + window.dispatchEvent(new CustomEvent("ShimConnects", { detail })); + } + window.addEventListener("ShimHelperReady", reconnect); + reconnect(); + return function (message) { + const messageId = getGUID(); + return new Promise(resolve => { + const payload = { message, messageId, shimId }; + pendingMessages.set(messageId, resolve); + channel.port1.postMessage(payload); + }); + }; + })(); + + const Shimmer = (function () { + // If a page might store references to an object before we replace it, + // ensure that it only receives proxies to that object created by + // `Shimmer.proxy(obj)`. Later when the unshimmed object is created, + // call `Shimmer.unshim(proxy, unshimmed)`. This way the references + // will automatically "become" the unshimmed object when appropriate. + + const shimmedObjects = new WeakMap(); + const unshimmedObjects = new Map(); + + function proxy(shim) { + if (shimmedObjects.has(shim)) { + return shimmedObjects.get(shim); + } + + const prox = new Proxy(shim, { + get: (target, k) => { + if (unshimmedObjects.has(prox)) { + return unshimmedObjects.get(prox)[k]; + } + return target[k]; + }, + apply: (target, thisArg, args) => { + if (unshimmedObjects.has(prox)) { + return unshimmedObjects.get(prox)(...args); + } + return target.apply(thisArg, args); + }, + construct: (target, args) => { + if (unshimmedObjects.has(prox)) { + return new unshimmedObjects.get(prox)(...args); + } + return new target(...args); + }, + }); + shimmedObjects.set(shim, prox); + shimmedObjects.set(prox, prox); + + for (const key in shim) { + const value = shim[key]; + if (typeof value === "function") { + shim[key] = function () { + const unshimmed = unshimmedObjects.get(prox); + if (unshimmed) { + return unshimmed[key].apply(unshimmed, arguments); + } + return value.apply(this, arguments); + }; + } else if (typeof value !== "object" || value === null) { + shim[key] = value; + } else { + shim[key] = Shimmer.proxy(value); + } + } + + return prox; + } + + function unshim(shim, unshimmed) { + unshimmedObjects.set(shim, unshimmed); + + for (const prop in shim) { + if (prop in unshimmed) { + const un = unshimmed[prop]; + if (typeof un === "object" && un !== null) { + unshim(shim[prop], un); + } + } else { + unshimmedObjects.set(shim[prop], undefined); + } + } + } + + return { proxy, unshim }; + })(); + + const extras = []; + const playersByNode = new WeakMap(); + const playerData = new Map(); + + const getJSONPVideoPlacements = () => { + return document.querySelectorAll( + `script[src*="delivery.vidible.tv/jsonp"]` + ); + }; + + const allowVidible = () => { + if (allowVidible.promise) { + return allowVidible.promise; + } + + const shim = window.vidible; + window.vidible = undefined; + + allowVidible.promise = sendMessageToAddon("optIn") + .then(() => { + return new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = originalScript; + script.addEventListener("load", () => { + Shimmer.unshim(shim, window.vidible); + + for (const args of extras) { + window.visible.registerExtra(...args); + } + + for (const jsonp of getJSONPVideoPlacements()) { + const { src } = jsonp; + const jscript = document.createElement("script"); + jscript.onload = resolve; + jscript.src = src; + jsonp.replaceWith(jscript); + } + + for (const [playerShim, data] of playerData.entries()) { + const { loadCalled, on, parent, placeholder, setup } = data; + + placeholder?.remove(); + + const player = window.vidible.player(parent); + Shimmer.unshim(playerShim, player); + + for (const [type, fns] of on.entries()) { + for (const fn of fns) { + try { + player.on(type, fn); + } catch (e) { + console.error(e); + } + } + } + + if (setup) { + player.setup(setup); + } + + if (loadCalled) { + player.load(); + } + } + + resolve(); + }); + + script.addEventListener("error", () => { + script.remove(); + reject(); + }); + + document.head.appendChild(script); + }); + }) + .catch(() => { + window.vidible = shim; + delete allowVidible.promise; + }); + + return allowVidible.promise; + }; + + const createVideoPlaceholder = (service, callback) => { + const placeholder = document.createElement("div"); + placeholder.style = ` + position: absolute; + width: 100%; + height: 100%; + min-width: 160px; + min-height: 100px; + top: 0px; + left: 0px; + background: #000; + color: #fff; + text-align: center; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + background-image: url(${PlayIconURL}); + background-position: 50% 47.5%; + background-repeat: no-repeat; + background-size: 25% 25%; + -moz-text-size-adjust: none; + -moz-user-select: none; + color: #fff; + align-items: center; + padding-top: 200px; + font-size: 14pt; + `; + placeholder.textContent = `Click to allow blocked ${service} content`; + placeholder.addEventListener("click", evt => { + evt.isTrusted && callback(); + }); + return placeholder; + }; + + const Player = function (parent) { + const existing = playersByNode.get(parent); + if (existing) { + return existing; + } + + const player = Shimmer.proxy(this); + playersByNode.set(parent, player); + + const placeholder = createVideoPlaceholder("Vidible", allowVidible); + parent.parentNode.insertBefore(placeholder, parent); + + playerData.set(player, { + on: new Map(), + parent, + placeholder, + }); + return player; + }; + + const changeData = function (fn) { + const data = playerData.get(this); + if (data) { + fn(data); + playerData.set(this, data); + } + }; + + Player.prototype = { + addEventListener() {}, + destroy() { + const { placeholder } = playerData.get(this); + placeholder?.remove(); + playerData.delete(this); + }, + dispatchEvent() {}, + getAdsPassedTime() {}, + getAllMacros() {}, + getCurrentTime() {}, + getDuration() {}, + getHeight() {}, + getPixelsLog() {}, + getPlayerContainer() {}, + getPlayerInfo() {}, + getPlayerStatus() {}, + getRequestsLog() {}, + getStripUrl() {}, + getVolume() {}, + getWidth() {}, + hidePlayReplayControls() {}, + isMuted() {}, + isPlaying() {}, + load() { + changeData(data => (data.loadCalled = true)); + }, + mute() {}, + on(type, fn) { + changeData(({ on }) => { + if (!on.has(type)) { + on.set(type, new Set()); + } + on.get(type).add(fn); + }); + }, + off(type, fn) { + changeData(({ on }) => { + on.get(type)?.delete(fn); + }); + }, + overrideMacro() {}, + pause() {}, + play() {}, + playVideoByIndex() {}, + removeEventListener() {}, + seekTo() {}, + sendBirthDate() {}, + sendKey() {}, + setup(s) { + changeData(data => (data.setup = s)); + return this; + }, + setVideosToPlay() {}, + setVolume() {}, + showPlayReplayControls() {}, + toggleFullscreen() {}, + toggleMute() {}, + togglePlay() {}, + updateBid() {}, + version() {}, + volume() {}, + }; + + const vidible = { + ADVERT_CLOSED: "advertClosed", + AD_END: "adend", + AD_META: "admeta", + AD_PAUSED: "adpaused", + AD_PLAY: "adplay", + AD_START: "adstart", + AD_TIMEUPDATE: "adtimeupdate", + AD_WAITING: "adwaiting", + AGE_GATE_DISPLAYED: "agegatedisplayed", + BID_UPDATED: "BidUpdated", + CAROUSEL_CLICK: "CarouselClick", + CONTEXT_ENDED: "contextended", + CONTEXT_STARTED: "contextstarted", + ENTER_FULLSCREEN: "playerenterfullscreen", + EXIT_FULLSCREEN: "playerexitfullscreen", + FALLBACK: "fallback", + FLOAT_END_ACTION: "floatended", + FLOAT_START_ACTION: "floatstarted", + HIDE_PLAY_REPLAY_BUTTON: "hideplayreplaybutton", + LIGHTBOX_ACTIVATED: "lightboxactivated", + LIGHTBOX_DEACTIVATED: "lightboxdeactivated", + MUTE: "Mute", + PLAYER_CONTROLS_STATE_CHANGE: "playercontrolsstatechaned", + PLAYER_DOCKED: "playerDocked", + PLAYER_ERROR: "playererror", + PLAYER_FLOATING: "playerFloating", + PLAYER_READY: "playerready", + PLAYER_RESIZE: "playerresize", + PLAYLIST_END: "playlistend", + SEEK_END: "SeekEnd", + SEEK_START: "SeekStart", + SHARE_SCREEN_CLOSED: "sharescreenclosed", + SHARE_SCREEN_OPENED: "sharescreenopened", + SHOW_PLAY_REPLAY_BUTTON: "showplayreplaybutton", + SUBTITLES_DISABLED: "subtitlesdisabled", + SUBTITLES_ENABLED: "subtitlesenabled", + SUBTITLES_READY: "subtitlesready", + UNMUTE: "Unmute", + VIDEO_DATA_LOADED: "videodataloaded", + VIDEO_END: "videoend", + VIDEO_META: "videometadata", + VIDEO_MODULE_CREATED: "videomodulecreated", + VIDEO_PAUSE: "videopause", + VIDEO_PLAY: "videoplay", + VIDEO_SEEKEND: "videoseekend", + VIDEO_SELECTED: "videoselected", + VIDEO_START: "videostart", + VIDEO_TIMEUPDATE: "videotimeupdate", + VIDEO_VOLUME_CHANGED: "videovolumechanged", + VOLUME: "Volume", + _getContexts: () => [], + "content.CLICK": "content.click", + "content.IMPRESSION": "content.impression", + "content.QUARTILE": "content.quartile", + "content.VIEW": "content.view", + createPlayer: parent => new Player(parent), + createPlayerAsync: parent => new Player(parent), + createVPAIDPlayer: parent => new Player(parent), + destroyAll() {}, + extension() {}, + getContext() {}, + player: parent => new Player(parent), + playerInceptionTime() { + return { undefined: 1620149827713 }; + }, + registerExtra(a, b, c) { + extras.push([a, b, c]); + }, + version: () => "21.1.313", + }; + + window.vidible = Shimmer.proxy(vidible); + + for (const jsonp of getJSONPVideoPlacements()) { + const player = new Player(jsonp); + const { placeholder } = playerData.get(player); + jsonp.parentNode.insertBefore(placeholder, jsonp); + } +} diff --git a/browser/extensions/webcompat/shims/vmad.xml b/browser/extensions/webcompat/shims/vmad.xml new file mode 100644 index 0000000000..5bb9a5a5d5 --- /dev/null +++ b/browser/extensions/webcompat/shims/vmad.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. + + Bug 1713693 - Shim Doubleclick + + Some sites rely on an XML VMAD Ad response from Doubleclick, or will + break (showing black boxes instead of videos, etc). This shim mitigates + such breakage. +--> +<vmap:AdBreak></vmap:AdBreak> diff --git a/browser/extensions/webcompat/shims/webtrends.js b/browser/extensions/webcompat/shims/webtrends.js new file mode 100644 index 0000000000..c7ef0069da --- /dev/null +++ b/browser/extensions/webcompat/shims/webtrends.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Bug 1766414 - Shim WebTrends Core Tag and Advanced Link Tracking + * + * Sites using WebTrends Core Tag or Link Tracking can break if they are + * are blocked. This shim mitigates that breakage by loading an empty module. + */ + +if (!window.dcsMultiTrack) { + window.dcsMultiTrack = o => { + o?.callback?.({}); + }; +} + +if (!window.WebTrends) { + class dcs { + addSelector() { + return this; + } + addTransform() { + return this; + } + DCSext = {}; + init(obj) { + return this; + } + track() { + return this; + } + } + + window.Webtrends = window.WebTrends = { + dcs, + multiTrack: window.dcsMultiTrack, + }; + + window.requestAnimationFrame(() => { + window.webtrendsAsyncLoad?.(dcs); + window.webtrendsAsyncInit?.(); + }); +} diff --git a/browser/extensions/webcompat/tests/browser/browser.ini b/browser/extensions/webcompat/tests/browser/browser.ini new file mode 100644 index 0000000000..f15b901240 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/browser.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = + head.js + shims_test.js + shims_test_2.js + shims_test_3.js + iframe_test.html + shims_test.html + shims_test_2.html + shims_test_3.html + +[browser_aboutcompat.js] +[browser_shims.js] +https_first_disabled = true +skip-if = verify diff --git a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js new file mode 100644 index 0000000000..a448269294 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js @@ -0,0 +1,27 @@ +"use strict"; + +add_task(async function test_about_compat_loads_properly() { + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:compat", + waitForLoad: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("#overrides tr[data-id]"), + "UA overrides are listed" + ); + await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("#interventions tr[data-id]"), + "interventions are listed" + ); + await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("#smartblock tr[data-id]"), + "SmartBlock shims are listed" + ); + ok(true, "Interventions are listed"); + }); + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/extensions/webcompat/tests/browser/browser_shims.js b/browser/extensions/webcompat/tests/browser/browser_shims.js new file mode 100644 index 0000000000..4de900a4c6 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/browser_shims.js @@ -0,0 +1,73 @@ +"use strict"; + +registerCleanupFunction(() => { + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(TRACKING_PREF); +}); + +add_setup(async function () { + await UrlClassifierTestUtils.addTestTrackers(); +}); + +add_task(async function test_shim_disabled_by_own_pref() { + // Test that a shim will not apply if disabled in about:config + + Services.prefs.setBoolPref(DISABLE_SHIM1_PREF, true); + Services.prefs.setBoolPref(TRACKING_PREF, true); + + await testShimDoesNotRun(); + + Services.prefs.clearUserPref(DISABLE_SHIM1_PREF); + Services.prefs.clearUserPref(TRACKING_PREF); +}); + +add_task(async function test_shim_disabled_by_global_pref() { + // Test that a shim will not apply if disabled in about:config + + Services.prefs.setBoolPref(GLOBAL_PREF, false); + Services.prefs.setBoolPref(DISABLE_SHIM1_PREF, false); + Services.prefs.setBoolPref(TRACKING_PREF, true); + + await testShimDoesNotRun(); + + Services.prefs.clearUserPref(GLOBAL_PREF); + Services.prefs.clearUserPref(DISABLE_SHIM1_PREF); + Services.prefs.clearUserPref(TRACKING_PREF); +}); + +add_task(async function test_shim_disabled_hosts_notHosts() { + Services.prefs.setBoolPref(TRACKING_PREF, true); + + await testShimDoesNotRun(false, SHIMMABLE_TEST_PAGE_3); + + Services.prefs.clearUserPref(TRACKING_PREF); +}); + +add_task(async function test_shim_disabled_overridden_by_pref() { + Services.prefs.setBoolPref(TRACKING_PREF, true); + + await testShimDoesNotRun(false, SHIMMABLE_TEST_PAGE_2); + + Services.prefs.setBoolPref(DISABLE_SHIM2_PREF, false); + + await testShimRuns(SHIMMABLE_TEST_PAGE_2); + + Services.prefs.clearUserPref(TRACKING_PREF); + Services.prefs.clearUserPref(DISABLE_SHIM2_PREF); +}); + +add_task(async function test_shim() { + // Test that a shim which only runs in strict mode works, and that it + // is permitted to opt into showing normally-blocked tracking content. + + Services.prefs.setBoolPref(TRACKING_PREF, true); + + await testShimRuns(SHIMMABLE_TEST_PAGE); + + // test that if the user opts in on one domain, they will still have to opt + // in on another domain which embeds an iframe to the first one. + + await testShimRuns(EMBEDDING_TEST_PAGE, 0, false, false); + + Services.prefs.clearUserPref(TRACKING_PREF); +}); diff --git a/browser/extensions/webcompat/tests/browser/head.js b/browser/extensions/webcompat/tests/browser/head.js new file mode 100644 index 0000000000..7fe8c3c171 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/head.js @@ -0,0 +1,140 @@ +"use strict"; + +const TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +const THIRD_PARTY_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.net" +); + +const SHIMMABLE_TEST_PAGE = `${TEST_ROOT}shims_test.html`; +const SHIMMABLE_TEST_PAGE_2 = `${TEST_ROOT}shims_test_2.html`; +const SHIMMABLE_TEST_PAGE_3 = `${TEST_ROOT}shims_test_3.html`; +const EMBEDDING_TEST_PAGE = `${THIRD_PARTY_ROOT}iframe_test.html`; + +const BLOCKED_TRACKER_URL = + "//trackertest.org/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"; + +const DISABLE_SHIM1_PREF = "extensions.webcompat.disabled_shims.MochitestShim"; +const DISABLE_SHIM2_PREF = "extensions.webcompat.disabled_shims.MochitestShim2"; +const DISABLE_SHIM3_PREF = "extensions.webcompat.disabled_shims.MochitestShim3"; +const DISABLE_SHIM4_PREF = "extensions.webcompat.disabled_shims.MochitestShim4"; +const GLOBAL_PREF = "extensions.webcompat.enable_shims"; +const TRACKING_PREF = "privacy.trackingprotection.enabled"; + +const { UrlClassifierTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/UrlClassifierTestUtils.sys.mjs" +); + +async function testShimRuns( + testPage, + frame, + trackersAllowed = true, + expectOptIn = true +) { + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: testPage, + waitForLoad: true, + }); + + const TrackingProtection = + tab.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the tab"); + ok(TrackingProtection.enabled, "TP is enabled"); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [[trackersAllowed, BLOCKED_TRACKER_URL, expectOptIn], frame], + async (args, _frame) => { + const window = _frame === undefined ? content : content.frames[_frame]; + + await SpecialPowers.spawn( + window, + args, + async (_trackersAllowed, trackerUrl, _expectOptIn) => { + const shimResult = await content.wrappedJSObject.shimPromise; + is("shimmed", shimResult, "Shim activated"); + + const optInResult = await content.wrappedJSObject.optInPromise; + is(_expectOptIn, optInResult, "Shim allowed opt in if appropriate"); + + const o = content.document.getElementById("shims"); + const cl = o.classList; + const opts = JSON.parse(o.innerText); + is( + undefined, + opts.branchValue, + "Shim script did not receive option for other branch" + ); + is( + undefined, + opts.platformValue, + "Shim script did not receive option for other platform" + ); + is( + true, + opts.simpleOption, + "Shim script received simple option correctly" + ); + ok(opts.complexOption, "Shim script received complex option"); + is( + 1, + opts.complexOption.a, + "Shim script received complex options correctly #1" + ); + is( + "test", + opts.complexOption.b, + "Shim script received complex options correctly #2" + ); + ok(cl.contains("green"), "Shim affected page correctly"); + } + ); + } + ); + + await BrowserTestUtils.removeTab(tab); +} + +async function testShimDoesNotRun( + trackersAllowed = false, + testPage = SHIMMABLE_TEST_PAGE +) { + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: testPage, + waitForLoad: true, + }); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [trackersAllowed, BLOCKED_TRACKER_URL], + async (_trackersAllowed, trackerUrl) => { + const shimResult = await content.wrappedJSObject.shimPromise; + is("did not shim", shimResult, "Shim did not activate"); + + ok( + !content.document.getElementById("shims").classList.contains("green"), + "Shim script did not run" + ); + + is( + _trackersAllowed ? "ALLOWED" : "BLOCKED", + await new Promise(resolve => { + const s = content.document.createElement("script"); + s.src = trackerUrl; + s.onload = () => resolve("ALLOWED"); + s.onerror = () => resolve("BLOCKED"); + content.document.head.appendChild(s); + }), + "Normally-blocked resources blocked if appropriate" + ); + } + ); + + await BrowserTestUtils.removeTab(tab); +} diff --git a/browser/extensions/webcompat/tests/browser/iframe_test.html b/browser/extensions/webcompat/tests/browser/iframe_test.html new file mode 100644 index 0000000000..baf1ee9024 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/iframe_test.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf8" /> + <script> + window.shimPromise = new Promise(resolve => { + window.shimPromiseResolve = resolve; + }); + window.optInPromise = new Promise(resolve => { + window.optInPromiseResolve = resolve; + }); + </script> + </head> + <body> + <iframe + src="http://example.com/browser/browser/extensions/webcompat/tests/browser/shims_test.html" + ></iframe> + </body> +</html> diff --git a/browser/extensions/webcompat/tests/browser/shims_test.html b/browser/extensions/webcompat/tests/browser/shims_test.html new file mode 100644 index 0000000000..ebe877316d --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf8" /> + <script> + window.shimPromise = new Promise(resolve => { + window.shimPromiseResolve = resolve; + }); + window.optInPromise = new Promise(resolve => { + window.optInPromiseResolve = resolve; + }); + </script> + <script + onerror="window.shimPromiseResolve('error')" + src="shims_test.js" + ></script> + </head> + <body> + <div id="shims"></div> + </body> +</html> diff --git a/browser/extensions/webcompat/tests/browser/shims_test.js b/browser/extensions/webcompat/tests/browser/shims_test.js new file mode 100644 index 0000000000..4a55bee7ed --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +if (window.doingOptIn) { + window.optInPromiseResolve(true); +} else { + window.shimPromiseResolve("did not shim"); +} diff --git a/browser/extensions/webcompat/tests/browser/shims_test_2.html b/browser/extensions/webcompat/tests/browser/shims_test_2.html new file mode 100644 index 0000000000..b080f74f6e --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test_2.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf8" /> + <script> + window.shimPromise = new Promise(resolve => { + window.shimPromiseResolve = resolve; + }); + window.optInPromise = new Promise(resolve => { + window.optInPromiseResolve = resolve; + }); + </script> + <script + onerror="window.shimPromiseResolve('error')" + src="shims_test_2.js" + ></script> + </head> + <body> + <div id="shims"></div> + </body> +</html> diff --git a/browser/extensions/webcompat/tests/browser/shims_test_2.js b/browser/extensions/webcompat/tests/browser/shims_test_2.js new file mode 100644 index 0000000000..4a55bee7ed --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test_2.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +if (window.doingOptIn) { + window.optInPromiseResolve(true); +} else { + window.shimPromiseResolve("did not shim"); +} diff --git a/browser/extensions/webcompat/tests/browser/shims_test_3.html b/browser/extensions/webcompat/tests/browser/shims_test_3.html new file mode 100644 index 0000000000..bcb6f12043 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test_3.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf8" /> + <script> + window.shimPromise = new Promise(resolve => { + window.shimPromiseResolve = resolve; + }); + window.optInPromise = new Promise(resolve => { + window.optInPromiseResolve = resolve; + }); + </script> + <script + onerror="window.shimPromiseResolve('error')" + src="shims_test_3.js" + ></script> + </head> + <body> + <div id="shims"></div> + </body> +</html> diff --git a/browser/extensions/webcompat/tests/browser/shims_test_3.js b/browser/extensions/webcompat/tests/browser/shims_test_3.js new file mode 100644 index 0000000000..9acb6cdcf1 --- /dev/null +++ b/browser/extensions/webcompat/tests/browser/shims_test_3.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +window.shimPromiseResolve("did not shim"); |