summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat/about-compat
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/extensions/webcompat/about-compat/AboutCompat.jsm42
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.css187
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.html51
-rw-r--r--browser/extensions/webcompat/about-compat/aboutCompat.js283
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.js46
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPage.json6
-rw-r--r--browser/extensions/webcompat/about-compat/aboutPageProcessScript.js34
7 files changed, 649 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
+ );
+}