summaryrefslogtreecommitdiffstats
path: root/browser/extensions/report-site-issue
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/extensions/report-site-issue
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/extensions/report-site-issue')
-rw-r--r--browser/extensions/report-site-issue/.eslintrc.js55
-rw-r--r--browser/extensions/report-site-issue/background.js82
-rw-r--r--browser/extensions/report-site-issue/experimentalAPIs/helpMenu.js38
-rw-r--r--browser/extensions/report-site-issue/experimentalAPIs/helpMenu.json28
-rw-r--r--browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js53
-rw-r--r--browser/extensions/report-site-issue/experimentalAPIs/tabExtras.json37
-rw-r--r--browser/extensions/report-site-issue/locales/en-US/webcompat.properties10
-rw-r--r--browser/extensions/report-site-issue/locales/jar.mn8
-rw-r--r--browser/extensions/report-site-issue/locales/moz.build7
-rw-r--r--browser/extensions/report-site-issue/manifest.json42
-rw-r--r--browser/extensions/report-site-issue/moz.build31
-rw-r--r--browser/extensions/report-site-issue/test/browser/browser.toml17
-rw-r--r--browser/extensions/report-site-issue/test/browser/browser_button_state.js52
-rw-r--r--browser/extensions/report-site-issue/test/browser/browser_disabled_cleanup.js41
-rw-r--r--browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js311
-rw-r--r--browser/extensions/report-site-issue/test/browser/fastclick.html11
-rw-r--r--browser/extensions/report-site-issue/test/browser/frameworks.html8
-rw-r--r--browser/extensions/report-site-issue/test/browser/head.js119
-rw-r--r--browser/extensions/report-site-issue/test/browser/test.html40
-rw-r--r--browser/extensions/report-site-issue/test/browser/webcompat.html85
20 files changed, 1075 insertions, 0 deletions
diff --git a/browser/extensions/report-site-issue/.eslintrc.js b/browser/extensions/report-site-issue/.eslintrc.js
new file mode 100644
index 0000000000..a20deab67f
--- /dev/null
+++ b/browser/extensions/report-site-issue/.eslintrc.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";
+
+module.exports = {
+ rules: {
+ // Rules from the mozilla plugin
+ "mozilla/balanced-listeners": "error",
+ "mozilla/no-aArgs": "error",
+ "mozilla/var-only-at-top-level": "error",
+
+ // No expressions where a statement is expected
+ "no-unused-expressions": "error",
+
+ // No declaring variables that are never used
+ "no-unused-vars": "error",
+
+ // Disallow using variables outside the blocks they are defined (especially
+ // since only let and const are used, see "no-var").
+ "block-scoped-var": "error",
+
+ // Warn about cyclomatic complexity in functions.
+ complexity: ["error", { max: 26 }],
+
+ // Maximum depth callbacks can be nested.
+ "max-nested-callbacks": ["error", 4],
+
+ // Allow the console API aside from console.log.
+ "no-console": ["error", { allow: ["error", "info", "trace", "warn"] }],
+
+ // Disallow use of multiline strings (use template strings instead).
+ "no-multi-str": "error",
+
+ // Disallow usage of __proto__ property.
+ "no-proto": "error",
+
+ // Disallow use of assignment in return statement. It is preferable for a
+ // single line of code to have only one easily predictable effect.
+ "no-return-assign": "error",
+
+ // Require use of the second argument for parseInt().
+ radix: "error",
+
+ // Require "use strict" to be defined globally in the script.
+ strict: ["error", "global"],
+
+ // Disallow Yoda conditions (where literal value comes first).
+ yoda: "error",
+
+ // Disallow function or variable declarations in nested blocks
+ "no-inner-declarations": "error",
+ },
+};
diff --git a/browser/extensions/report-site-issue/background.js b/browser/extensions/report-site-issue/background.js
new file mode 100644
index 0000000000..d495f33c25
--- /dev/null
+++ b/browser/extensions/report-site-issue/background.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 */
+
+const desktopReporterConfig = {
+ src: "desktop-reporter",
+ utm_campaign: "report-site-issue-button",
+ utm_source: "desktop-reporter",
+};
+
+const androidReporterConfig = {
+ src: "android-components-reporter",
+ utm_campaign: "report-site-issue-button",
+ utm_source: "android-components-reporter",
+};
+
+let reporterConfig = desktopReporterConfig;
+
+(async () => {
+ const permissions = ["nativeMessaging"];
+ if (await browser.permissions.contains({ permissions })) {
+ reporterConfig = androidReporterConfig;
+
+ const port = browser.runtime.connectNative("mozacWebcompatReporter");
+ port.onMessage.addListener(message => {
+ if ("productName" in message) {
+ reporterConfig.productName = message.productName;
+
+ // For now, setting the productName is the only use for this port, and that's only happening
+ // once after startup, so let's disconnect the port when we're done.
+ port.disconnect();
+ }
+ });
+ }
+})();
+
+async function loadTab(url) {
+ const newTab = await browser.tabs.create({ url });
+ return new Promise(resolve => {
+ const listener = (tabId, changeInfo, tab) => {
+ if (
+ tabId == newTab.id &&
+ tab.url !== "about:blank" &&
+ changeInfo.status == "complete"
+ ) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve(newTab);
+ }
+ };
+ browser.tabs.onUpdated.addListener(listener);
+ });
+}
+
+async function captureAndSendReport(tab) {
+ const { id, url } = tab;
+ try {
+ const { endpointUrl, webcompatInfo } =
+ await browser.tabExtras.getWebcompatInfo(id);
+ const dataToSend = {
+ endpointUrl,
+ reportUrl: url,
+ reporterConfig,
+ webcompatInfo,
+ };
+ const newTab = await loadTab(endpointUrl);
+ browser.tabExtras.sendWebcompatInfo(newTab.id, dataToSend);
+ } catch (err) {
+ console.error("WebCompat Reporter: unexpected error", err);
+ }
+}
+
+if ("helpMenu" in browser) {
+ // desktop
+ browser.helpMenu.onHelpMenuCommand.addListener(captureAndSendReport);
+} else {
+ // Android
+ browser.pageAction.onClicked.addListener(captureAndSendReport);
+}
diff --git a/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.js b/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.js
new file mode 100644
index 0000000000..804f4b08d5
--- /dev/null
+++ b/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.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";
+
+/* global ExtensionAPI, ExtensionCommon, Services */
+
+const TOPIC = "report-site-issue";
+
+this.helpMenu = class extends ExtensionAPI {
+ getAPI(context) {
+ const { tabManager } = context.extension;
+ let EventManager = ExtensionCommon.EventManager;
+
+ return {
+ helpMenu: {
+ onHelpMenuCommand: new EventManager({
+ context,
+ name: "helpMenu",
+ register: fire => {
+ let observer = (subject, topic, data) => {
+ let nativeTab = subject.wrappedJSObject;
+ let tab = tabManager.convert(nativeTab);
+ fire.async(tab);
+ };
+
+ Services.obs.addObserver(observer, TOPIC);
+
+ return () => {
+ Services.obs.removeObserver(observer, TOPIC);
+ };
+ },
+ }).api(),
+ },
+ };
+ }
+};
diff --git a/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.json b/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.json
new file mode 100644
index 0000000000..e7c3a8c405
--- /dev/null
+++ b/browser/extensions/report-site-issue/experimentalAPIs/helpMenu.json
@@ -0,0 +1,28 @@
+[
+ {
+ "namespace": "helpMenu",
+ "events": [
+ {
+ "name": "onHelpMenuCommand",
+ "type": "function",
+ "async": "callback",
+ "description": "Fired when the command event for the Report Site Issue menuitem in Help is fired.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab",
+ "optional": true,
+ "description": "Details about the selected tab in the window where the menuitem command fired."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js b/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js
new file mode 100644
index 0000000000..48ef117630
--- /dev/null
+++ b/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.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 */
+
+const lazy = {};
+
+const DEFAULT_NEW_REPORT_ENDPOINT = "https://webcompat.com/issues/new";
+const NEW_REPORT_ENDPOINT_PREF =
+ "ui.new-webcompat-reporter.new-report-endpoint";
+
+this.tabExtras = class extends ExtensionAPI {
+ getAPI(context) {
+ const { tabManager } = context.extension;
+ const queryReportBrokenSiteActor = (tabId, name, params) => {
+ const { browser } = tabManager.get(tabId);
+ const windowGlobal = browser.browsingContext.currentWindowGlobal;
+ if (!windowGlobal) {
+ return null;
+ }
+ return windowGlobal.getActor("ReportBrokenSite").sendQuery(name, params);
+ };
+ return {
+ tabExtras: {
+ async getWebcompatInfo(tabId) {
+ const endpointUrl = Services.prefs.getStringPref(
+ NEW_REPORT_ENDPOINT_PREF,
+ DEFAULT_NEW_REPORT_ENDPOINT
+ );
+ const webcompatInfo = await queryReportBrokenSiteActor(
+ tabId,
+ "GetWebCompatInfo"
+ );
+ return {
+ webcompatInfo,
+ endpointUrl,
+ };
+ },
+ async sendWebcompatInfo(tabId, info) {
+ console.error(info);
+ return queryReportBrokenSiteActor(
+ tabId,
+ "SendDataToWebcompatCom",
+ info
+ );
+ },
+ },
+ };
+ }
+};
diff --git a/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.json b/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.json
new file mode 100644
index 0000000000..5e0098ab32
--- /dev/null
+++ b/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.json
@@ -0,0 +1,37 @@
+[
+ {
+ "namespace": "tabExtras",
+ "description": "experimental tab API extensions",
+ "functions": [
+ {
+ "name": "getWebcompatInfo",
+ "type": "function",
+ "description": "Gets the content blocking status and script log for a given tab",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ }
+ ],
+ "async": true
+ },
+ {
+ "name": "sendWebcompatInfo",
+ "type": "function",
+ "description": "Sends the given webcompat info to the given tab",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ },
+ {
+ "type": "any"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
diff --git a/browser/extensions/report-site-issue/locales/en-US/webcompat.properties b/browser/extensions/report-site-issue/locales/en-US/webcompat.properties
new file mode 100644
index 0000000000..ee8cab2cf0
--- /dev/null
+++ b/browser/extensions/report-site-issue/locales/en-US/webcompat.properties
@@ -0,0 +1,10 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE(wc-reporter.label2): This string will be used in the
+# Firefox page actions menu. Localized length should be considered.
+wc-reporter.label2=Report Site Issue…
+# LOCALIZATION NOTE(wc-reporter.tooltip): A site compatibility issue is
+# a website bug that exists in one browser (Firefox), but not another.
+wc-reporter.tooltip=Report a site compatibility issue
diff --git a/browser/extensions/report-site-issue/locales/jar.mn b/browser/extensions/report-site-issue/locales/jar.mn
new file mode 100644
index 0000000000..3422f6248f
--- /dev/null
+++ b/browser/extensions/report-site-issue/locales/jar.mn
@@ -0,0 +1,8 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[features/webcompat-reporter@mozilla.org] @AB_CD@.jar:
+% locale report-site-issue @AB_CD@ %locale/@AB_CD@/
+ locale/@AB_CD@/webcompat.properties (%webcompat.properties)
diff --git a/browser/extensions/report-site-issue/locales/moz.build b/browser/extensions/report-site-issue/locales/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/browser/extensions/report-site-issue/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/browser/extensions/report-site-issue/manifest.json b/browser/extensions/report-site-issue/manifest.json
new file mode 100644
index 0000000000..5108716475
--- /dev/null
+++ b/browser/extensions/report-site-issue/manifest.json
@@ -0,0 +1,42 @@
+{
+ "manifest_version": 2,
+ "name": "WebCompat Reporter",
+ "description": "Report site compatibility issues on webcompat.com",
+ "author": "Thomas Wisniewski <twisniewski@mozilla.com>",
+ "version": "2.0.0",
+ "homepage_url": "https://github.com/mozilla/webcompat-reporter",
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "webcompat-reporter@mozilla.org"
+ }
+ },
+ "experiment_apis": {
+ "helpMenu": {
+ "schema": "experimentalAPIs/helpMenu.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experimentalAPIs/helpMenu.js",
+ "paths": [["helpMenu"]]
+ }
+ },
+ "tabExtras": {
+ "schema": "experimentalAPIs/tabExtras.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "experimentalAPIs/tabExtras.js",
+ "paths": [["tabExtras"]]
+ }
+ }
+ },
+ "icons": {
+ "16": "icons/lightbulb.svg",
+ "32": "icons/lightbulb.svg",
+ "48": "icons/lightbulb.svg",
+ "96": "icons/lightbulb.svg",
+ "128": "icons/lightbulb.svg"
+ },
+ "permissions": ["tabs", "<all_urls>"],
+ "background": {
+ "scripts": ["background.js"]
+ }
+}
diff --git a/browser/extensions/report-site-issue/moz.build b/browser/extensions/report-site-issue/moz.build
new file mode 100644
index 0000000000..54367c1475
--- /dev/null
+++ b/browser/extensions/report-site-issue/moz.build
@@ -0,0 +1,31 @@
+# -*- 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"]
+
+DIRS += ["locales"]
+
+FINAL_TARGET_FILES.features["webcompat-reporter@mozilla.org"] += [
+ "background.js",
+ "manifest.json",
+]
+
+FINAL_TARGET_FILES.features["webcompat-reporter@mozilla.org"].experimentalAPIs += [
+ "experimentalAPIs/helpMenu.js",
+ "experimentalAPIs/helpMenu.json",
+ "experimentalAPIs/tabExtras.js",
+ "experimentalAPIs/tabExtras.json",
+]
+
+FINAL_TARGET_FILES.features["webcompat-reporter@mozilla.org"].icons += [
+ "../../../toolkit/themes/shared/icons/lightbulb.svg"
+]
+
+BROWSER_CHROME_MANIFESTS += ["test/browser/browser.toml"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Web Compatibility", "Tooling & Investigations")
diff --git a/browser/extensions/report-site-issue/test/browser/browser.toml b/browser/extensions/report-site-issue/test/browser/browser.toml
new file mode 100644
index 0000000000..9e2931b1d3
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/browser.toml
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files = [
+ "frameworks.html",
+ "fastclick.html",
+ "head.js",
+ "test.html",
+ "webcompat.html",
+]
+
+["browser_button_state.js"]
+skip-if = ["true"] # Disabled until we figure out why it is failing in bug 1775526
+
+["browser_disabled_cleanup.js"]
+skip-if = ["true"] # Disabled until we figure out why it is failing in bug 1775526
+
+["browser_report_site_issue.js"]
+skip-if = ["true"] # Disabled until we figure out why it is failing in bug 1775526
diff --git a/browser/extensions/report-site-issue/test/browser/browser_button_state.js b/browser/extensions/report-site-issue/test/browser/browser_button_state.js
new file mode 100644
index 0000000000..6111a26975
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/browser_button_state.js
@@ -0,0 +1,52 @@
+"use strict";
+
+const REPORTABLE_PAGE = "http://example.com/";
+const REPORTABLE_PAGE2 = "https://example.com/";
+const NONREPORTABLE_PAGE = "about:mozilla";
+
+/* Test that the Report Site Issue help menu item is enabled for http and https tabs,
+ on page load, or TabSelect, and disabled for everything else. */
+add_task(async function test_button_state_disabled() {
+ await SpecialPowers.pushPrefEnv({ set: [[PREF_WC_REPORTER_ENABLED, true]] });
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ REPORTABLE_PAGE
+ );
+ const menu = new HelpMenuHelper();
+ await menu.open();
+ is(
+ menu.isItemEnabled(),
+ true,
+ "Check that panel item is enabled for reportable schemes on tab load"
+ );
+ await menu.close();
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ NONREPORTABLE_PAGE
+ );
+ await menu.open();
+ is(
+ menu.isItemEnabled(),
+ false,
+ "Check that panel item is disabled for non-reportable schemes on tab load"
+ );
+ await menu.close();
+
+ let tab3 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ REPORTABLE_PAGE2
+ );
+ await menu.open();
+ is(
+ menu.isItemEnabled(),
+ true,
+ "Check that panel item is enabled for reportable schemes on tab load"
+ );
+ await menu.close();
+
+ await BrowserTestUtils.removeTab(tab1);
+ await BrowserTestUtils.removeTab(tab2);
+ await BrowserTestUtils.removeTab(tab3);
+});
diff --git a/browser/extensions/report-site-issue/test/browser/browser_disabled_cleanup.js b/browser/extensions/report-site-issue/test/browser/browser_disabled_cleanup.js
new file mode 100644
index 0000000000..65b6ff7369
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/browser_disabled_cleanup.js
@@ -0,0 +1,41 @@
+"use strict";
+
+// Test the addon is cleaning up after itself when disabled.
+add_task(async function test_disabled() {
+ await promiseAddonEnabled();
+
+ SpecialPowers.Services.prefs.setBoolPref(PREF_WC_REPORTER_ENABLED, false);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "http://example.com" },
+ async function () {
+ const menu = new HelpMenuHelper();
+ await menu.open();
+ is(
+ menu.isItemHidden(),
+ true,
+ "Report Site Issue help menu item is hidden."
+ );
+ await menu.close();
+ }
+ );
+
+ await promiseAddonEnabled();
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "http://example.com" },
+ async function () {
+ const menu = new HelpMenuHelper();
+ await menu.open();
+ is(
+ await menu.isItemHidden(),
+ false,
+ "Report Site Issue help menu item is visible."
+ );
+ await menu.close();
+ }
+ );
+
+ // Shut down the addon at the end,or the new instance started when we re-enabled it will "leak".
+ SpecialPowers.Services.prefs.setBoolPref(PREF_WC_REPORTER_ENABLED, false);
+});
diff --git a/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js b/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
new file mode 100644
index 0000000000..ef01f94a26
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
@@ -0,0 +1,311 @@
+"use strict";
+
+async function clickToReportAndAwaitReportTabLoad() {
+ const helpMenu = new HelpMenuHelper();
+ await helpMenu.open();
+
+ // click on "report site issue" and wait for the new tab to open
+ const tab = await new Promise(resolve => {
+ gBrowser.tabContainer.addEventListener(
+ "TabOpen",
+ event => {
+ resolve(event.target);
+ },
+ { once: true }
+ );
+ document.getElementById("help_reportSiteIssue").click();
+ });
+
+ // wait for the new tab to acknowledge that it received a screenshot
+ await BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "ScreenshotReceived",
+ false,
+ null,
+ true
+ );
+
+ await helpMenu.close();
+
+ return tab;
+}
+
+add_task(async function start_issue_server() {
+ requestLongerTimeout(2);
+
+ const serverLanding = await startIssueServer();
+
+ // ./head.js sets the value for PREF_WC_REPORTER_ENDPOINT
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["datareporting.healthreport.uploadEnabled", true],
+ [PREF_WC_REPORTER_ENABLED, true],
+ [PREF_WC_REPORTER_ENDPOINT, serverLanding],
+ ],
+ });
+});
+
+/* Test that clicking on the Report Site Issue button opens a new tab
+ and sends a postMessaged blob to it. */
+add_task(async function test_opened_page() {
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
+ let tab2 = await clickToReportAndAwaitReportTabLoad();
+
+ await SpecialPowers.spawn(
+ tab2.linkedBrowser,
+ [{ TEST_PAGE }],
+ async function (args) {
+ async function isGreen(dataUrl) {
+ const getPixel = await new Promise(resolve => {
+ const myCanvas = content.document.createElement("canvas");
+ const ctx = myCanvas.getContext("2d");
+ const img = new content.Image();
+ img.onload = () => {
+ ctx.drawImage(img, 0, 0);
+ resolve((x, y) => {
+ return ctx.getImageData(x, y, 1, 1).data;
+ });
+ };
+ img.src = dataUrl;
+ });
+ function isPixelGreenFuzzy(p) {
+ // jpeg, so it will be off slightly
+ const fuzz = 4;
+ return p[0] < fuzz && Math.abs(p[1] - 128) < fuzz && p[2] < fuzz;
+ }
+ ok(isPixelGreenFuzzy(getPixel(0, 0)), "The pixels were green");
+ }
+
+ let doc = content.document;
+ let urlParam = doc.getElementById("url").innerText;
+ let preview = doc.getElementById("screenshot-preview");
+ const URL =
+ "http://example.com/browser/browser/extensions/report-site-issue/test/browser/test.html";
+ is(
+ urlParam,
+ args.TEST_PAGE,
+ "Reported page is correctly added to the url param"
+ );
+
+ let docShell = content.docShell;
+ is(
+ typeof docShell.getHasTrackingContentBlocked,
+ "function",
+ "docShell.hasTrackingContentBlocked is available"
+ );
+
+ let detailsParam = doc.getElementById("details").innerText;
+ const details = JSON.parse(detailsParam);
+ Assert.equal(
+ typeof details,
+ "object",
+ "Details param is a stringified JSON object."
+ );
+ ok(Array.isArray(details.consoleLog), "Details has a consoleLog array.");
+
+ const log1 = details.consoleLog[0];
+ is(log1.log[0], null, "Can handle degenerate console logs");
+ is(log1.level, "log", "Reports correct log level");
+ is(log1.uri, URL, "Reports correct url");
+ is(log1.pos, "7:13", "Reports correct line and column");
+
+ const log2 = details.consoleLog[1];
+ is(log2.log[0], "colored message", "Can handle fancy console logs");
+ is(log2.level, "error", "Reports correct log level");
+ is(log2.uri, URL, "Reports correct url");
+ is(log2.pos, "8:13", "Reports correct line and column");
+
+ const log3 = details.consoleLog[2];
+ const loggedObject = log3.log[0];
+ is(loggedObject.testobj, "{...}", "Reports object inside object");
+ is(
+ loggedObject.testSymbol,
+ "Symbol(sym)",
+ "Reports symbol inside object"
+ );
+ is(loggedObject.testnumber, 1, "Reports number inside object");
+ is(loggedObject.testArray, "(4)[...]", "Reports array inside object");
+ is(loggedObject.testUndf, "undefined", "Reports undefined inside object");
+ is(loggedObject.testNull, null, "Reports null inside object");
+ is(
+ loggedObject.testFunc,
+ undefined,
+ "Reports function inside object as undefined due to security reasons"
+ );
+ is(loggedObject.testString, "string", "Reports string inside object");
+ is(loggedObject.c, "{...}", "Reports circular reference inside object");
+ is(
+ Object.keys(loggedObject).length,
+ 10,
+ "Preview has 10 keys inside object"
+ );
+ is(log3.level, "log", "Reports correct log level");
+ is(log3.uri, URL, "Reports correct url");
+ is(log3.pos, "24:13", "Reports correct line and column");
+
+ const log4 = details.consoleLog[3];
+ const loggedArray = log4.log[0];
+ is(loggedArray[0], "string", "Reports string inside array");
+ is(loggedArray[1], "{...}", "Reports object inside array");
+ is(loggedArray[2], null, "Reports null inside array");
+ is(loggedArray[3], 90, "Reports number inside array");
+ is(loggedArray[4], "undefined", "Reports undefined inside array");
+ is(
+ loggedArray[5],
+ "undefined",
+ "Reports function inside array as undefined due to security reasons"
+ );
+ is(loggedArray[6], "(4)[...]", "Reports array inside array");
+ is(loggedArray[7], "(8)[...]", "Reports circular array inside array");
+
+ const log5 = details.consoleLog[4];
+ ok(
+ log5.log[0].match(/TypeError: .*document\.access is undefined/),
+ "Script errors are logged"
+ );
+ is(log5.level, "error", "Reports correct log level");
+ is(log5.uri, URL, "Reports correct url");
+ is(log5.pos, "36:5", "Reports correct line and column");
+
+ Assert.equal(
+ typeof details.buildID,
+ "string",
+ "Details has a buildID string."
+ );
+ Assert.equal(
+ typeof details.channel,
+ "string",
+ "Details has a channel string."
+ );
+ Assert.equal(
+ typeof details.hasTouchScreen,
+ "boolean",
+ "Details has a hasTouchScreen flag."
+ );
+ Assert.equal(
+ typeof details.hasFastClick,
+ "undefined",
+ "Details does not have FastClick if not found."
+ );
+ Assert.equal(
+ typeof details.hasMobify,
+ "undefined",
+ "Details does not have Mobify if not found."
+ );
+ Assert.equal(
+ typeof details.hasMarfeel,
+ "undefined",
+ "Details does not have Marfeel if not found."
+ );
+ Assert.equal(
+ typeof details["mixed active content blocked"],
+ "boolean",
+ "Details has a mixed active content blocked flag."
+ );
+ Assert.equal(
+ typeof details["mixed passive content blocked"],
+ "boolean",
+ "Details has a mixed passive content blocked flag."
+ );
+ Assert.equal(
+ typeof details["tracking content blocked"],
+ "string",
+ "Details has a tracking content blocked string."
+ );
+ Assert.equal(
+ typeof details["gfx.webrender.all"],
+ "boolean",
+ "Details has gfx.webrender.all."
+ );
+
+ is(
+ preview.innerText,
+ "Pass",
+ "A Blob object was successfully transferred to the test page."
+ );
+
+ const bgUrl = preview.style.backgroundImage.match(/url\(\"(.*)\"\)/)[1];
+ ok(
+ bgUrl.startsWith("data:image/jpeg;base64,"),
+ "A jpeg screenshot was successfully postMessaged"
+ );
+ await isGreen(bgUrl);
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(async function test_framework_detection() {
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ FRAMEWORKS_TEST_PAGE
+ );
+ let tab2 = await clickToReportAndAwaitReportTabLoad();
+
+ await SpecialPowers.spawn(tab2.linkedBrowser, [], async function (args) {
+ let doc = content.document;
+ let detailsParam = doc.getElementById("details").innerText;
+ const details = JSON.parse(detailsParam);
+ Assert.equal(
+ typeof details,
+ "object",
+ "Details param is a stringified JSON object."
+ );
+ is(details.hasFastClick, true, "FastClick was found.");
+ is(details.hasMobify, true, "Mobify was found.");
+ is(details.hasMarfeel, true, "Marfeel was found.");
+ });
+
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(async function test_fastclick_detection() {
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ FASTCLICK_TEST_PAGE
+ );
+ let tab2 = await clickToReportAndAwaitReportTabLoad();
+
+ await SpecialPowers.spawn(tab2.linkedBrowser, [], async function (args) {
+ let doc = content.document;
+ let detailsParam = doc.getElementById("details").innerText;
+ const details = JSON.parse(detailsParam);
+ Assert.equal(
+ typeof details,
+ "object",
+ "Details param is a stringified JSON object."
+ );
+ is(details.hasFastClick, true, "FastClick was found.");
+ });
+
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(async function test_framework_label() {
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ FRAMEWORKS_TEST_PAGE
+ );
+ let tab2 = await clickToReportAndAwaitReportTabLoad();
+
+ await SpecialPowers.spawn(tab2.linkedBrowser, [], async function (args) {
+ let doc = content.document;
+ let labelParam = doc.getElementById("label").innerText;
+ const label = JSON.parse(labelParam);
+ Assert.equal(
+ typeof label,
+ "object",
+ "Label param is a stringified JSON object."
+ );
+ is(label.includes("type-fastclick"), true, "FastClick was found.");
+ is(label.includes("type-mobify"), true, "Mobify was found.");
+ is(label.includes("type-marfeel"), true, "Marfeel was found.");
+ });
+
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/extensions/report-site-issue/test/browser/fastclick.html b/browser/extensions/report-site-issue/test/browser/fastclick.html
new file mode 100644
index 0000000000..e13329dfd7
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/fastclick.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ "use strict";
+ function ObscuredFastClick() {
+ }
+ ObscuredFastClick.prototype = {
+ needsClick: () => {},
+ };
+ window.someRandomVar = new ObscuredFastClick();
+</script>
diff --git a/browser/extensions/report-site-issue/test/browser/frameworks.html b/browser/extensions/report-site-issue/test/browser/frameworks.html
new file mode 100644
index 0000000000..14df387ec9
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/frameworks.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ "use strict";
+ function FastClick() {}
+ function marfeel() {}
+ var Mobify = {Tag: "something"};
+</script>
diff --git a/browser/extensions/report-site-issue/test/browser/head.js b/browser/extensions/report-site-issue/test/browser/head.js
new file mode 100644
index 0000000000..f4c74ab768
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/head.js
@@ -0,0 +1,119 @@
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+});
+
+const { Management } = ChromeUtils.importESModule(
+ "resource://gre/modules/Extension.sys.mjs"
+);
+
+const PREF_WC_REPORTER_ENABLED = "extensions.webcompat-reporter.enabled";
+const PREF_WC_REPORTER_ENDPOINT =
+ "extensions.webcompat-reporter.newIssueEndpoint";
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_PAGE = TEST_ROOT + "test.html";
+const FRAMEWORKS_TEST_PAGE = TEST_ROOT + "frameworks.html";
+const FASTCLICK_TEST_PAGE = TEST_ROOT + "fastclick.html";
+const NEW_ISSUE_PAGE = TEST_ROOT + "webcompat.html";
+
+const WC_ADDON_ID = "webcompat-reporter@mozilla.org";
+
+async function promiseAddonEnabled() {
+ const addon = await AddonManager.getAddonByID(WC_ADDON_ID);
+ if (addon.isActive) {
+ return;
+ }
+ const pref = SpecialPowers.Services.prefs.getBoolPref(
+ PREF_WC_REPORTER_ENABLED,
+ false
+ );
+ if (!pref) {
+ SpecialPowers.Services.prefs.setBoolPref(PREF_WC_REPORTER_ENABLED, true);
+ }
+}
+
+class HelpMenuHelper {
+ #popup = null;
+
+ async open() {
+ this.popup = document.getElementById("menu_HelpPopup");
+ ok(this.popup, "Help menu should exist");
+
+ const menuOpen = BrowserTestUtils.waitForEvent(this.popup, "popupshown");
+
+ // This event-faking method was copied from browser_title_case_menus.js so
+ // this can be tested on MacOS (where the actual menus cannot be opened in
+ // tests, but we only need the help menu to populate itself).
+ this.popup.dispatchEvent(new MouseEvent("popupshowing", { bubbles: true }));
+ this.popup.dispatchEvent(new MouseEvent("popupshown", { bubbles: true }));
+
+ await menuOpen;
+ }
+
+ async close() {
+ if (this.popup) {
+ const menuClose = BrowserTestUtils.waitForEvent(
+ this.popup,
+ "popuphidden"
+ );
+
+ // (Also copied from browser_title_case_menus.js)
+ // Just for good measure, we'll fire the popuphiding/popuphidden events
+ // after we close the menupopups.
+ this.popup.dispatchEvent(
+ new MouseEvent("popuphiding", { bubbles: true })
+ );
+ this.popup.dispatchEvent(
+ new MouseEvent("popuphidden", { bubbles: true })
+ );
+
+ await menuClose;
+ this.popup = null;
+ }
+ }
+
+ isItemHidden() {
+ const item = document.getElementById("help_reportSiteIssue");
+ return item && item.hidden;
+ }
+
+ isItemEnabled() {
+ const item = document.getElementById("help_reportSiteIssue");
+ return item && !item.hidden && !item.disabled;
+ }
+}
+
+async function startIssueServer() {
+ const landingTemplate = await new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", NEW_ISSUE_PAGE);
+ xhr.onload = () => {
+ resolve(xhr.responseText);
+ };
+ xhr.onerror = reject;
+ xhr.send();
+ });
+
+ const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+ );
+ const server = new HttpServer();
+
+ registerCleanupFunction(async function cleanup() {
+ await new Promise(resolve => server.stop(resolve));
+ });
+
+ server.registerPathHandler("/new", function (request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(landingTemplate);
+ });
+
+ server.start(-1);
+ return `http://localhost:${server.identity.primaryPort}/new`;
+}
diff --git a/browser/extensions/report-site-issue/test/browser/test.html b/browser/extensions/report-site-issue/test/browser/test.html
new file mode 100644
index 0000000000..ed1844f530
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/test.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ /* eslint-disable no-console */
+ /* eslint-disable no-unused-expressions */
+ "use strict";
+ console.log(null);
+ console.error("%ccolored message", "background:green; color:white");
+ const obj = {
+ testSymbol: Symbol("sym"),
+ testobj: {},
+ testnumber: 1,
+ testArray: [1, {}, 2, 555],
+ testUndf: undefined,
+ testNull: null,
+ testFunc() {},
+ testString: 'string',
+ prop1: 'prop1',
+ prop2: 'prop2'
+ };
+ obj.c = obj;
+ obj.prop3 = 'prop3';
+ obj.prop4 = 'prop4';
+ console.log(obj);
+ const arr = [
+ 'string',
+ {test: 'obj'},
+ null,
+ 90,
+ undefined,
+ function() {},
+ [1, {}, 2, 555]
+ ];
+ arr.push(arr);
+ console.log(arr);
+ document.access.non.existent.property.to.trigger.error;
+</script>
+<style>
+ body {background: rgb(0, 128, 0);}
+</style>
diff --git a/browser/extensions/report-site-issue/test/browser/webcompat.html b/browser/extensions/report-site-issue/test/browser/webcompat.html
new file mode 100644
index 0000000000..872c8917b7
--- /dev/null
+++ b/browser/extensions/report-site-issue/test/browser/webcompat.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #screenshot-preview {width: 200px; height: 200px;}
+</style>
+<div id="url">$$URL$$</div>
+<div id="details">$$DETAILS$$</div>
+<div id="label">$$LABEL$$</div>
+<div id="screenshot-preview">Fail</div>
+<script>
+"use strict";
+let preview = document.getElementById("screenshot-preview");
+const CONFIG = {
+ url: {
+ element: document.getElementById("url")
+ },
+ details: {
+ element: document.getElementById("details"),
+ toStringify: true
+ },
+ extra_labels: {
+ element: document.getElementById("label"),
+ toStringify: true
+ },
+};
+
+function getBlobAsDataURL(blob) {
+ return new Promise((resolve, reject) => {
+ let reader = new FileReader();
+
+ // eslint-disable-next-line mozilla/balanced-listeners
+ reader.addEventListener("error", (e) => {
+ reject(`There was an error reading the blob: ${e.type}`);
+ });
+
+ // eslint-disable-next-line mozilla/balanced-listeners
+ reader.addEventListener("load", (e) => {
+ resolve(e.target.result);
+ });
+
+ reader.readAsDataURL(blob);
+ });
+}
+
+function setPreviewBG(backgroundData) {
+ return new Promise((resolve) => {
+ preview.style.background = `url(${backgroundData})`;
+ resolve();
+ });
+}
+
+function sendReceivedEvent() {
+ window.dispatchEvent(new CustomEvent("ScreenshotReceived", {bubbles: true}));
+}
+
+function prepareContent(toStringify, content) {
+ if (toStringify) {
+ return JSON.stringify(content)
+ }
+
+ return content;
+}
+
+function appendMessage(message) {
+ for (const key in CONFIG) {
+ if (key in message) {
+ const field = CONFIG[key];
+ field.element.innerText = prepareContent(field.toStringify, message[key]);
+ }
+ }
+}
+
+// eslint-disable-next-line mozilla/balanced-listeners
+window.addEventListener("message", function(event) {
+ if (event.data.screenshot instanceof Blob) {
+ preview.innerText = "Pass";
+ }
+
+ if (event.data.message) {
+ appendMessage(event.data.message);
+ }
+
+ getBlobAsDataURL(event.data.screenshot).then(setPreviewBG).then(sendReceivedEvent);
+});
+</script>