diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /browser/extensions/report-site-issue | |
parent | Initial commit. (diff) | |
download | firefox-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')
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> |