summaryrefslogtreecommitdiffstats
path: root/browser/components/reportbrokensite/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/reportbrokensite/test/browser')
-rw-r--r--browser/components/reportbrokensite/test/browser/browser.toml39
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_antitracking_data_sent.js113
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_back_buttons.js37
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_error_messages.js64
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_keyboard_navigation.js113
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_parent_menuitems.js107
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_prefers_contrast.js62
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_reason_dropdown.js161
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_report_send.js79
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_report_site_issue_fallback.js89
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_send_more_info.js68
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_site_not_working_fallback.js35
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_tab_key_order.js133
-rw-r--r--browser/components/reportbrokensite/test/browser/browser_tab_switch_handling.js81
-rw-r--r--browser/components/reportbrokensite/test/browser/example_report_page.html22
-rw-r--r--browser/components/reportbrokensite/test/browser/head.js863
-rw-r--r--browser/components/reportbrokensite/test/browser/send.js290
-rw-r--r--browser/components/reportbrokensite/test/browser/sendMoreInfoTestEndpoint.html27
-rw-r--r--browser/components/reportbrokensite/test/browser/send_more_info.js215
19 files changed, 2598 insertions, 0 deletions
diff --git a/browser/components/reportbrokensite/test/browser/browser.toml b/browser/components/reportbrokensite/test/browser/browser.toml
new file mode 100644
index 0000000000..09e4b72079
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser.toml
@@ -0,0 +1,39 @@
+[DEFAULT]
+tags = "report-broken-site"
+support-files = [
+ "example_report_page.html",
+ "head.js",
+ "sendMoreInfoTestEndpoint.html",
+]
+
+["browser_antitracking_data_sent.js"]
+support-files = [ "send_more_info.js" ]
+
+["browser_back_buttons.js"]
+
+["browser_error_messages.js"]
+
+["browser_keyboard_navigation.js"]
+
+["browser_parent_menuitems.js"]
+
+["browser_prefers_contrast.js"]
+
+["browser_reason_dropdown.js"]
+
+["browser_report_send.js"]
+support-files = [ "send.js" ]
+
+["browser_report_site_issue_fallback.js"]
+
+["browser_send_more_info.js"]
+support-files = [
+ "send_more_info.js",
+ "../../../../../toolkit/components/gfx/content/videotest.mp4",
+]
+
+["browser_site_not_working_fallback.js"]
+
+["browser_tab_key_order.js"]
+
+["browser_tab_switch_handling.js"]
diff --git a/browser/components/reportbrokensite/test/browser/browser_antitracking_data_sent.js b/browser/components/reportbrokensite/test/browser/browser_antitracking_data_sent.js
new file mode 100644
index 0000000000..794fcd35a0
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_antitracking_data_sent.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that the right data is sent for
+ * private windows and when ETP blocks content.
+ */
+
+/* import-globals-from send.js */
+/* import-globals-from send_more_info.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "send_more_info.js",
+ this
+);
+
+add_common_setup();
+
+add_task(setupStrictETP);
+
+add_task(async function testSendButton() {
+ ensureReportBrokenSitePreffedOn();
+ ensureReasonOptional();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ const blockedPromise = waitForContentBlockingEvent(4, win);
+ const tab = await openTab(REPORTABLE_PAGE_URL3, win);
+ await blockedPromise;
+
+ await testSend(tab, AppMenu(win), {
+ breakageCategory: "adblockers",
+ description: "another test description",
+ antitracking: {
+ blockList: "strict",
+ isPrivateBrowsing: true,
+ hasTrackingContentBlocked: true,
+ hasMixedActiveContentBlocked: true,
+ hasMixedDisplayContentBlocked: true,
+ },
+ frameworks: {
+ fastclick: true,
+ marfeel: true,
+ mobify: true,
+ },
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function testSendingMoreInfo() {
+ ensureReportBrokenSitePreffedOn();
+ ensureSendMoreInfoEnabled();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ const blockedPromise = waitForContentBlockingEvent(4, win);
+ const tab = await openTab(REPORTABLE_PAGE_URL3, win);
+ await blockedPromise;
+
+ await testSendMoreInfo(tab, HelpMenu(win), {
+ antitracking: {
+ blockList: "strict",
+ isPrivateBrowsing: true,
+ hasTrackingContentBlocked: true,
+ hasMixedActiveContentBlocked: true,
+ hasMixedDisplayContentBlocked: true,
+ },
+ frameworks: { fastclick: true, mobify: true, marfeel: true },
+ consoleLog: [
+ {
+ level: "error",
+ log(actual) {
+ // "Blocked loading mixed display content http://example.com/tests/image/test/mochitest/blue.png"
+ return (
+ Array.isArray(actual) &&
+ actual.length == 1 &&
+ actual[0].includes("blue.png")
+ );
+ },
+ pos: "0:1",
+ uri: REPORTABLE_PAGE_URL3,
+ },
+ {
+ level: "error",
+ log(actual) {
+ // "Blocked loading mixed active content http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html",
+ return (
+ Array.isArray(actual) &&
+ actual.length == 1 &&
+ actual[0].includes("benignPage.html")
+ );
+ },
+ pos: "0:1",
+ uri: REPORTABLE_PAGE_URL3,
+ },
+ {
+ level: "warn",
+ log(actual) {
+ // "The resource at https://trackertest.org/ was blocked because content blocking is enabled.",
+ return (
+ Array.isArray(actual) &&
+ actual.length == 1 &&
+ actual[0].includes("trackertest.org")
+ );
+ },
+ pos: "0:0",
+ uri: REPORTABLE_PAGE_URL3,
+ },
+ ],
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_back_buttons.js b/browser/components/reportbrokensite/test/browser/browser_back_buttons.js
new file mode 100644
index 0000000000..b8de5f8e95
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_back_buttons.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that Report Broken Site popups will be
+ * reset to whichever tab the user is on as they change
+ * between windows and tabs. */
+
+"use strict";
+
+add_common_setup();
+
+add_task(async function testBackButtonsAreAdded() {
+ ensureReportBrokenSitePreffedOn();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ let rbs = await AppMenu().openReportBrokenSite();
+ rbs.isBackButtonEnabled();
+ await rbs.clickBack();
+ await rbs.close();
+
+ rbs = await HelpMenu().openReportBrokenSite();
+ ok(!rbs.backButton, "Back button is not shown for Help Menu");
+ await rbs.close();
+
+ rbs = await ProtectionsPanel().openReportBrokenSite();
+ rbs.isBackButtonEnabled();
+ await rbs.clickBack();
+ await rbs.close();
+
+ rbs = await HelpMenu().openReportBrokenSite();
+ ok(!rbs.backButton, "Back button is not shown for Help Menu");
+ await rbs.close();
+ }
+ );
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_error_messages.js b/browser/components/reportbrokensite/test/browser/browser_error_messages.js
new file mode 100644
index 0000000000..54b93cb2da
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_error_messages.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that the Report Broken Site errors messages are shown on
+ * the UI if the user enters an invalid URL or clicks the send
+ * button while it is disabled due to not selecting a "reason"
+ */
+
+"use strict";
+
+add_common_setup();
+
+add_task(async function test() {
+ ensureReportBrokenSitePreffedOn();
+ ensureReasonRequired();
+
+ await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
+ for (const menu of [AppMenu(), ProtectionsPanel(), HelpMenu()]) {
+ const rbs = await menu.openReportBrokenSite();
+ const { sendButton, URLInput } = rbs;
+
+ rbs.isURLInvalidMessageHidden();
+ rbs.isReasonNeededMessageHidden();
+
+ rbs.setURL("");
+ window.document.activeElement.blur();
+ rbs.isURLInvalidMessageShown();
+ rbs.isReasonNeededMessageHidden();
+
+ rbs.setURL("https://asdf");
+ window.document.activeElement.blur();
+ rbs.isURLInvalidMessageHidden();
+ rbs.isReasonNeededMessageHidden();
+
+ rbs.setURL("http:/ /asdf");
+ window.document.activeElement.blur();
+ rbs.isURLInvalidMessageShown();
+ rbs.isReasonNeededMessageHidden();
+
+ rbs.setURL("https://asdf");
+ const selectPromise = BrowserTestUtils.waitForSelectPopupShown(window);
+ EventUtils.synthesizeMouseAtCenter(sendButton, {}, window);
+ await selectPromise;
+ rbs.isURLInvalidMessageHidden();
+ rbs.isReasonNeededMessageShown();
+ await rbs.dismissDropdownPopup();
+
+ rbs.chooseReason("slow");
+ rbs.isURLInvalidMessageHidden();
+ rbs.isReasonNeededMessageHidden();
+
+ rbs.setURL("");
+ rbs.chooseReason("choose");
+ window.ownerGlobal.document.activeElement?.blur();
+ const focusPromise = BrowserTestUtils.waitForEvent(URLInput, "focus");
+ EventUtils.synthesizeMouseAtCenter(sendButton, {}, window);
+ await focusPromise;
+ rbs.isURLInvalidMessageShown();
+ rbs.isReasonNeededMessageShown();
+
+ rbs.clickCancel();
+ }
+ });
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_keyboard_navigation.js b/browser/components/reportbrokensite/test/browser/browser_keyboard_navigation.js
new file mode 100644
index 0000000000..4c37866628
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_keyboard_navigation.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that sending or canceling reports with
+ * the Send and Cancel buttons work (as well as the Okay button)
+ */
+
+"use strict";
+
+add_common_setup();
+
+requestLongerTimeout(2);
+
+async function testPressingKey(key, tabToMatch, makePromise, followUp) {
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ for (const menu of [AppMenu(), ProtectionsPanel(), HelpMenu()]) {
+ info(
+ `Opening RBS to test pressing ${key} for ${tabToMatch} on ${menu.menuDescription}`
+ );
+ const rbs = await menu.openReportBrokenSite();
+ const promise = makePromise(rbs);
+ if (tabToMatch) {
+ if (await tabTo(tabToMatch)) {
+ await pressKeyAndAwait(promise, key);
+ followUp && (await followUp(rbs));
+ await rbs.close();
+ ok(true, `was able to activate ${tabToMatch} with keyboard`);
+ } else {
+ await rbs.close();
+ ok(false, `could not tab to ${tabToMatch}`);
+ }
+ } else {
+ await pressKeyAndAwait(promise, key);
+ followUp && (await followUp(rbs));
+ await rbs.close();
+ ok(true, `was able to use keyboard`);
+ }
+ }
+ }
+ );
+}
+
+add_task(async function testSendMoreInfo() {
+ ensureReportBrokenSitePreffedOn();
+ ensureSendMoreInfoEnabled();
+ await testPressingKey(
+ "KEY_Enter",
+ "#report-broken-site-popup-send-more-info-link",
+ rbs => rbs.waitForSendMoreInfoTab(),
+ () => gBrowser.removeCurrentTab()
+ );
+});
+
+add_task(async function testCancel() {
+ ensureReportBrokenSitePreffedOn();
+ await testPressingKey(
+ "KEY_Enter",
+ "#report-broken-site-popup-cancel-button",
+ rbs => BrowserTestUtils.waitForEvent(rbs.mainView, "ViewHiding")
+ );
+});
+
+add_task(async function testSendAndOkay() {
+ ensureReportBrokenSitePreffedOn();
+ await testPressingKey(
+ "KEY_Enter",
+ "#report-broken-site-popup-send-button",
+ rbs => rbs.awaitReportSentViewOpened(),
+ async rbs => {
+ await tabTo("#report-broken-site-popup-okay-button");
+ const promise = BrowserTestUtils.waitForEvent(rbs.sentView, "ViewHiding");
+ await pressKeyAndAwait(promise, "KEY_Enter");
+ }
+ );
+});
+
+add_task(async function testESCOnMain() {
+ ensureReportBrokenSitePreffedOn();
+ await testPressingKey("KEY_Escape", undefined, rbs =>
+ BrowserTestUtils.waitForEvent(rbs.mainView, "ViewHiding")
+ );
+});
+
+add_task(async function testESCOnSent() {
+ ensureReportBrokenSitePreffedOn();
+ await testPressingKey(
+ "KEY_Enter",
+ "#report-broken-site-popup-send-button",
+ rbs => rbs.awaitReportSentViewOpened(),
+ async rbs => {
+ const promise = BrowserTestUtils.waitForEvent(rbs.sentView, "ViewHiding");
+ await pressKeyAndAwait(promise, "KEY_Escape");
+ }
+ );
+});
+
+add_task(async function testBackButtons() {
+ ensureReportBrokenSitePreffedOn();
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ for (const menu of [AppMenu(), ProtectionsPanel()]) {
+ await menu.openReportBrokenSite();
+ await tabTo("#report-broken-site-popup-mainView .subviewbutton-back");
+ const promise = BrowserTestUtils.waitForEvent(menu.popup, "ViewShown");
+ await pressKeyAndAwait(promise, "KEY_Enter");
+ menu.close();
+ }
+ }
+ );
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_parent_menuitems.js b/browser/components/reportbrokensite/test/browser/browser_parent_menuitems.js
new file mode 100644
index 0000000000..11b2ebc5ec
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_parent_menuitems.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that the Report Broken Site menu items are disabled
+ * when the active tab is not on a reportable URL, and is hidden
+ * when the feature is disabled via pref. Also ensure that the
+ * Report Broken Site item that is automatically generated in
+ * the app menu's help sub-menu is hidden.
+ */
+
+"use strict";
+
+add_common_setup();
+
+add_task(async function testAppMenuHelpSubmenuItemIsHidden() {
+ ensureReportBrokenSitePreffedOn();
+ const menu = AppMenuHelpSubmenu();
+ await menu.open();
+ isMenuItemHidden(menu.reportBrokenSite);
+ await menu.close();
+
+ ensureReportBrokenSitePreffedOff();
+ await menu.open();
+ isMenuItemHidden(menu.reportBrokenSite);
+ await menu.close();
+
+ await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
+ await menu.open();
+ isMenuItemHidden(menu.reportBrokenSite);
+ await menu.close();
+
+ ensureReportBrokenSitePreffedOn();
+ await menu.open();
+ isMenuItemHidden(menu.reportBrokenSite);
+ await menu.close();
+ });
+});
+
+add_task(async function testOtherMenus() {
+ ensureReportBrokenSitePreffedOff();
+
+ const appMenu = AppMenu();
+ const menus = [appMenu, ProtectionsPanel(), HelpMenu()];
+
+ async function forceMenuItemStateUpdate() {
+ window.ReportBrokenSite.enableOrDisableMenuitems(window);
+
+ // the hidden/disabled state of all of the menuitems may not update until one
+ // is rendered; then the related <command>'s state is propagated to them all.
+ await appMenu.open();
+ await appMenu.close();
+ }
+
+ await BrowserTestUtils.withNewTab("about:blank", async function () {
+ await forceMenuItemStateUpdate();
+ for (const { menuDescription, reportBrokenSite } of menus) {
+ isMenuItemHidden(
+ reportBrokenSite,
+ `${menuDescription} option hidden on invalid page when preffed off`
+ );
+ }
+ });
+
+ await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
+ await forceMenuItemStateUpdate();
+ for (const { menuDescription, reportBrokenSite } of menus) {
+ isMenuItemHidden(
+ reportBrokenSite,
+ `${menuDescription} option hidden on valid page when preffed off`
+ );
+ }
+ });
+
+ ensureReportBrokenSitePreffedOn();
+
+ await BrowserTestUtils.withNewTab("about:blank", async function () {
+ await forceMenuItemStateUpdate();
+ for (const { menuDescription, reportBrokenSite } of menus) {
+ isMenuItemDisabled(
+ reportBrokenSite,
+ `${menuDescription} option disabled on invalid page when preffed on`
+ );
+ }
+ });
+
+ await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
+ await forceMenuItemStateUpdate();
+ for (const { menuDescription, reportBrokenSite } of menus) {
+ isMenuItemEnabled(
+ reportBrokenSite,
+ `${menuDescription} option enabled on valid page when preffed on`
+ );
+ }
+ });
+
+ ensureReportBrokenSitePreffedOff();
+
+ await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
+ await forceMenuItemStateUpdate();
+ for (const { menuDescription, reportBrokenSite } of menus) {
+ isMenuItemHidden(
+ reportBrokenSite,
+ `${menuDescription} option hidden again when pref toggled back off`
+ );
+ }
+ });
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_prefers_contrast.js b/browser/components/reportbrokensite/test/browser/browser_prefers_contrast.js
new file mode 100644
index 0000000000..7097a662e5
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_prefers_contrast.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that the background color of the "report sent"
+ * view is not green in non-default contrast modes.
+ */
+
+"use strict";
+
+add_common_setup();
+
+const HIGH_CONTRAST_MODE_OFF = [
+ [PREFS.USE_ACCESSIBILITY_THEME, 0],
+ [PREFS.PREFERS_CONTRAST_ENABLED, true],
+];
+
+const HIGH_CONTRAST_MODE_ON = [
+ [PREFS.USE_ACCESSIBILITY_THEME, 1],
+ [PREFS.PREFERS_CONTRAST_ENABLED, true],
+];
+
+add_task(async function testReportSentViewBGColor() {
+ ensureReportBrokenSitePreffedOn();
+ ensureReasonDisabled();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ const { defaultView } = browser.ownerGlobal.document;
+
+ const menu = AppMenu();
+
+ await SpecialPowers.pushPrefEnv({ set: HIGH_CONTRAST_MODE_OFF });
+ const rbs = await menu.openReportBrokenSite();
+ const { mainView, sentView } = rbs;
+ mainView.style.backgroundColor = "var(--color-background-success)";
+ const expectedReportSentBGColor =
+ defaultView.getComputedStyle(mainView).backgroundColor;
+ mainView.style.backgroundColor = "";
+ const expectedPrefersReducedBGColor =
+ defaultView.getComputedStyle(mainView).backgroundColor;
+
+ await rbs.clickSend();
+ is(
+ defaultView.getComputedStyle(sentView).backgroundColor,
+ expectedReportSentBGColor,
+ "Using green bgcolor when not prefers-contrast"
+ );
+ await rbs.clickOkay();
+
+ await SpecialPowers.pushPrefEnv({ set: HIGH_CONTRAST_MODE_ON });
+ await menu.openReportBrokenSite();
+ await rbs.clickSend();
+ is(
+ defaultView.getComputedStyle(sentView).backgroundColor,
+ expectedPrefersReducedBGColor,
+ "Using default bgcolor when prefers-contrast"
+ );
+ await rbs.clickOkay();
+ }
+ );
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_reason_dropdown.js b/browser/components/reportbrokensite/test/browser/browser_reason_dropdown.js
new file mode 100644
index 0000000000..0f5545fcc4
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_reason_dropdown.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that the reason dropdown is shown or hidden
+ * based on its pref, and that its optional and required modes affect
+ * the Send button and report appropriately.
+ */
+
+"use strict";
+
+add_common_setup();
+
+requestLongerTimeout(2);
+
+async function clickSendAndCheckPing(rbs, expectedReason = null) {
+ const pingCheck = new Promise(resolve => {
+ GleanPings.brokenSiteReport.testBeforeNextSubmit(() => {
+ Assert.equal(
+ Glean.brokenSiteReport.breakageCategory.testGetValue(),
+ expectedReason
+ );
+ resolve();
+ });
+ });
+ await rbs.clickSend();
+ return pingCheck;
+}
+
+add_task(async function testReasonDropdown() {
+ ensureReportBrokenSitePreffedOn();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ ensureReasonDisabled();
+
+ let rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isReasonHidden();
+ await rbs.isSendButtonEnabled();
+ await clickSendAndCheckPing(rbs);
+ await rbs.clickOkay();
+
+ ensureReasonOptional();
+ rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isReasonOptional();
+ await rbs.isSendButtonEnabled();
+ await clickSendAndCheckPing(rbs);
+ await rbs.clickOkay();
+
+ rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isReasonOptional();
+ rbs.chooseReason("slow");
+ await rbs.isSendButtonEnabled();
+ await clickSendAndCheckPing(rbs, "slow");
+ await rbs.clickOkay();
+
+ ensureReasonRequired();
+ rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isReasonRequired();
+ await rbs.isSendButtonEnabled();
+ const selectPromise = BrowserTestUtils.waitForSelectPopupShown(window);
+ EventUtils.synthesizeMouseAtCenter(rbs.sendButton, {}, window);
+ await selectPromise;
+ rbs.chooseReason("media");
+ await rbs.dismissDropdownPopup();
+ await rbs.isSendButtonEnabled();
+ await clickSendAndCheckPing(rbs, "media");
+ await rbs.clickOkay();
+ }
+ );
+});
+
+async function getListItems(rbs) {
+ const items = Array.from(rbs.reasonInput.querySelectorAll("option")).map(i =>
+ i.id.replace("report-broken-site-popup-reason-", "")
+ );
+ Assert.equal(items[0], "choose", "First option is always 'choose'");
+ return items.join(",");
+}
+
+add_task(async function testReasonDropdownRandomized() {
+ ensureReportBrokenSitePreffedOn();
+ ensureReasonOptional();
+
+ const USER_ID_PREF = "app.normandy.user_id";
+ const RANDOMIZE_PREF = "ui.new-webcompat-reporter.reason-dropdown.randomized";
+
+ const origNormandyUserID = Services.prefs.getCharPref(
+ USER_ID_PREF,
+ undefined
+ );
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ // confirm that the default order is initially used
+ Services.prefs.setBoolPref(RANDOMIZE_PREF, false);
+ const rbs = await AppMenu().openReportBrokenSite();
+ const defaultOrder = [
+ "choose",
+ "slow",
+ "media",
+ "content",
+ "account",
+ "adblockers",
+ "other",
+ ];
+ Assert.deepEqual(
+ await getListItems(rbs),
+ defaultOrder,
+ "non-random order is correct"
+ );
+
+ // confirm that a random order happens per user
+ let randomOrder;
+ let isRandomized = false;
+ Services.prefs.setBoolPref(RANDOMIZE_PREF, true);
+
+ // This becomes ClientEnvironment.randomizationId, which we can set to
+ // any value which results in a different order from the default ordering.
+ Services.prefs.setCharPref("app.normandy.user_id", "dummy");
+
+ // clicking cancel triggers a reset, which is when the randomization
+ // logic is called. so we must click cancel after pref-changes here.
+ rbs.clickCancel();
+ await AppMenu().openReportBrokenSite();
+ randomOrder = await getListItems(rbs);
+ Assert.ok(
+ randomOrder != defaultOrder,
+ "options are randomized with pref on"
+ );
+
+ // confirm that the order doesn't change per user
+ isRandomized = false;
+ for (let attempt = 0; attempt < 5; ++attempt) {
+ rbs.clickCancel();
+ await AppMenu().openReportBrokenSite();
+ const order = await getListItems(rbs);
+
+ if (order != randomOrder) {
+ isRandomized = true;
+ break;
+ }
+ }
+ Assert.ok(!isRandomized, "options keep the same order per user");
+
+ // confirm that the order reverts to the default if pref flipped to false
+ Services.prefs.setBoolPref(RANDOMIZE_PREF, false);
+ rbs.clickCancel();
+ await AppMenu().openReportBrokenSite();
+ Assert.deepEqual(
+ defaultOrder,
+ await getListItems(rbs),
+ "reverts to non-random order correctly"
+ );
+ rbs.clickCancel();
+ }
+ );
+
+ Services.prefs.setCharPref(USER_ID_PREF, origNormandyUserID);
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_report_send.js b/browser/components/reportbrokensite/test/browser/browser_report_send.js
new file mode 100644
index 0000000000..bf849776d7
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_report_send.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that sending or canceling reports with
+ * the Send and Cancel buttons work (as well as the Okay button)
+ */
+
+/* import-globals-from send.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "send.js",
+ this
+);
+
+add_common_setup();
+
+requestLongerTimeout(10);
+
+async function testCancel(menu, url, description) {
+ let rbs = await menu.openAndPrefillReportBrokenSite(url, description);
+ await rbs.clickCancel();
+ ok(!rbs.opened, "clicking Cancel closes Report Broken Site");
+
+ // re-opening the panel, the url and description should be reset
+ rbs = await menu.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+}
+
+add_task(async function testSendButton() {
+ ensureReportBrokenSitePreffedOn();
+ ensureReasonOptional();
+
+ const tab1 = await openTab(REPORTABLE_PAGE_URL);
+
+ await testSend(tab1, AppMenu());
+
+ const tab2 = await openTab(REPORTABLE_PAGE_URL);
+
+ await testSend(tab2, ProtectionsPanel(), {
+ url: "https://test.org/test/#fake",
+ breakageCategory: "media",
+ description: "test description",
+ });
+
+ closeTab(tab1);
+ closeTab(tab2);
+});
+
+add_task(async function testCancelButton() {
+ ensureReportBrokenSitePreffedOn();
+
+ const tab1 = await openTab(REPORTABLE_PAGE_URL);
+
+ await testCancel(AppMenu());
+ await testCancel(ProtectionsPanel());
+ await testCancel(HelpMenu());
+
+ const tab2 = await openTab(REPORTABLE_PAGE_URL);
+
+ await testCancel(AppMenu());
+ await testCancel(ProtectionsPanel());
+ await testCancel(HelpMenu());
+
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ const tab3 = await openTab(REPORTABLE_PAGE_URL2, win2);
+
+ await testCancel(AppMenu(win2));
+ await testCancel(ProtectionsPanel(win2));
+ await testCancel(HelpMenu(win2));
+
+ closeTab(tab3);
+ await BrowserTestUtils.closeWindow(win2);
+
+ closeTab(tab1);
+ closeTab(tab2);
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_report_site_issue_fallback.js b/browser/components/reportbrokensite/test/browser/browser_report_site_issue_fallback.js
new file mode 100644
index 0000000000..26101d77b9
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_report_site_issue_fallback.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests that when Report Broken Site is active,
+ * Report Site Issue is hidden.
+ */
+
+"use strict";
+
+add_common_setup();
+
+async function testDisabledByReportBrokenSite(menu) {
+ ensureReportBrokenSitePreffedOn();
+ ensureReportSiteIssuePreffedOn();
+
+ await menu.open();
+ menu.isReportSiteIssueHidden();
+ await menu.close();
+}
+
+async function testDisabledByPref(menu) {
+ ensureReportBrokenSitePreffedOff();
+ ensureReportSiteIssuePreffedOff();
+
+ await menu.open();
+ menu.isReportSiteIssueHidden();
+ await menu.close();
+}
+
+async function testDisabledForInvalidURLs(menu) {
+ ensureReportBrokenSitePreffedOff();
+ ensureReportSiteIssuePreffedOn();
+
+ await menu.open();
+ menu.isReportSiteIssueDisabled();
+ await menu.close();
+}
+
+async function testEnabledForValidURLs(menu) {
+ ensureReportBrokenSitePreffedOff();
+ ensureReportSiteIssuePreffedOn();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ await menu.open();
+ menu.isReportSiteIssueEnabled();
+ await menu.close();
+ }
+ );
+}
+
+// AppMenu help sub-menu
+
+add_task(async function testDisabledByReportBrokenSiteAppMenuHelpSubmenu() {
+ await testDisabledByReportBrokenSite(AppMenuHelpSubmenu());
+});
+
+// disabled for now due to bug 1775402
+//add_task(async function testDisabledByPrefAppMenuHelpSubmenu() {
+// await testDisabledByPref(AppMenuHelpSubmenu());
+//});
+
+add_task(async function testDisabledForInvalidURLsAppMenuHelpSubmenu() {
+ await testDisabledForInvalidURLs(AppMenuHelpSubmenu());
+});
+
+add_task(async function testEnabledForValidURLsAppMenuHelpSubmenu() {
+ await testEnabledForValidURLs(AppMenuHelpSubmenu());
+});
+
+// Help menu
+
+add_task(async function testDisabledByReportBrokenSiteHelpMenu() {
+ await testDisabledByReportBrokenSite(HelpMenu());
+});
+
+// disabled for now due to bug 1775402
+//add_task(async function testDisabledByPrefHelpMenu() {
+// await testDisabledByPref(HelpMenu());
+//});
+
+add_task(async function testDisabledForInvalidURLsHelpMenu() {
+ await testDisabledForInvalidURLs(HelpMenu());
+});
+
+add_task(async function testEnabledForValidURLsHelpMenu() {
+ await testEnabledForValidURLs(HelpMenu());
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_send_more_info.js b/browser/components/reportbrokensite/test/browser/browser_send_more_info.js
new file mode 100644
index 0000000000..edce03e0e0
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_send_more_info.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests that the send more info link appears only when its pref
+ * is set to true, and that when clicked it will open a tab to
+ * the webcompat.com endpoint and send the right data.
+ */
+
+/* import-globals-from send_more_info.js */
+
+"use strict";
+
+const VIDEO_URL = `${BASE_URL}/videotest.mp4`;
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "send_more_info.js",
+ this
+);
+
+add_common_setup();
+
+requestLongerTimeout(2);
+
+add_task(async function testSendMoreInfoPref() {
+ ensureReportBrokenSitePreffedOn();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ await changeTab(gBrowser.selectedTab, REPORTABLE_PAGE_URL);
+
+ ensureSendMoreInfoDisabled();
+ let rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isSendMoreInfoHidden();
+ await rbs.close();
+
+ ensureSendMoreInfoEnabled();
+ rbs = await AppMenu().openReportBrokenSite();
+ await rbs.isSendMoreInfoShown();
+ await rbs.close();
+ }
+ );
+});
+
+add_task(async function testSendingMoreInfo() {
+ ensureReportBrokenSitePreffedOn();
+ ensureSendMoreInfoEnabled();
+
+ const tab = await openTab(REPORTABLE_PAGE_URL);
+
+ await testSendMoreInfo(tab, AppMenu());
+
+ await changeTab(tab, REPORTABLE_PAGE_URL2);
+
+ await testSendMoreInfo(tab, ProtectionsPanel(), {
+ url: "https://override.com",
+ description: "another",
+ expectNoTabDetails: true,
+ });
+
+ // also load a video to ensure system codec
+ // information is loaded and properly sent
+ const tab2 = await openTab(VIDEO_URL);
+ await testSendMoreInfo(tab2, HelpMenu());
+ closeTab(tab2);
+
+ closeTab(tab);
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_site_not_working_fallback.js b/browser/components/reportbrokensite/test/browser/browser_site_not_working_fallback.js
new file mode 100644
index 0000000000..e424a14be9
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_site_not_working_fallback.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests that when Report Broken Site is active,
+ * "Site not working?" is hidden on the protections panel.
+ */
+
+"use strict";
+
+add_common_setup();
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+
+const TRACKING_PAGE =
+ "https://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+const SITE_NOT_WORKING = "protections-popup-tp-switch-section-footer";
+
+add_task(async function testSiteNotWorking() {
+ await SpecialPowers.pushPrefEnv({ set: [[TP_PREF, true]] });
+ await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function () {
+ const menu = ProtectionsPanel();
+
+ ensureReportBrokenSitePreffedOn();
+ await menu.open();
+ const siteNotWorking = document.getElementById(SITE_NOT_WORKING);
+ isMenuItemHidden(siteNotWorking, "Site not working is hidden");
+ await menu.close();
+
+ ensureReportBrokenSitePreffedOff();
+ await menu.open();
+ isMenuItemEnabled(siteNotWorking, "Site not working is shown");
+ await menu.close();
+ });
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_tab_key_order.js b/browser/components/reportbrokensite/test/browser/browser_tab_key_order.js
new file mode 100644
index 0000000000..d398c7c4d3
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_tab_key_order.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests of the expected tab key element focus order */
+
+"use strict";
+
+add_common_setup();
+
+async function ensureTabOrder(order, win = window) {
+ const config = { window: win };
+ for (let matches of order) {
+ // We need to tab through all elements in each match array in any order
+ if (!Array.isArray(matches)) {
+ matches = [matches];
+ }
+ let matchesLeft = matches.length;
+ while (matchesLeft--) {
+ const target = await pressKeyAndGetFocus("VK_TAB", config);
+ let foundMatch = false;
+ for (const [i, selector] of matches.entries()) {
+ foundMatch = selector && target.matches(selector);
+ if (foundMatch) {
+ matches[i] = "";
+ break;
+ }
+ }
+ ok(
+ foundMatch,
+ `Expected [${matches}] next, got id=${target.id}, class=${target.className}, ${target}`
+ );
+ if (!foundMatch) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+async function ensureExpectedTabOrder(
+ expectBackButton,
+ expectReason,
+ expectSendMoreInfo
+) {
+ const { activeElement } = window.document;
+ is(
+ activeElement?.id,
+ "report-broken-site-popup-url",
+ "URL is already focused"
+ );
+ const order = [];
+ if (expectReason) {
+ order.push("#report-broken-site-popup-reason");
+ }
+ order.push("#report-broken-site-popup-description");
+ if (expectSendMoreInfo) {
+ order.push("#report-broken-site-popup-send-more-info-link");
+ }
+ // moz-button-groups swap the order of buttons to follow
+ // platform conventions, so the order of send/cancel will vary.
+ order.push([
+ "#report-broken-site-popup-cancel-button",
+ "#report-broken-site-popup-send-button",
+ ]);
+ if (expectBackButton) {
+ order.push(".subviewbutton-back");
+ }
+ order.push("#report-broken-site-popup-url"); // check that we've cycled back
+ return ensureTabOrder(order);
+}
+
+async function testTabOrder(menu) {
+ ensureReasonDisabled();
+ ensureSendMoreInfoDisabled();
+
+ const { showsBackButton } = menu;
+
+ let rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, false, false);
+ await rbs.close();
+
+ ensureSendMoreInfoEnabled();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, false, true);
+ await rbs.close();
+
+ ensureReasonOptional();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, true, true);
+ await rbs.close();
+
+ ensureReasonRequired();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, true, true);
+ await rbs.close();
+ rbs = await menu.openReportBrokenSite();
+ rbs.chooseReason("slow");
+ await ensureExpectedTabOrder(showsBackButton, true, true);
+ await rbs.clickCancel();
+
+ ensureSendMoreInfoDisabled();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, true, false);
+ await rbs.close();
+ rbs = await menu.openReportBrokenSite();
+ rbs.chooseReason("slow");
+ await ensureExpectedTabOrder(showsBackButton, true, false);
+ await rbs.clickCancel();
+
+ ensureReasonOptional();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, true, false);
+ await rbs.close();
+
+ ensureReasonDisabled();
+ rbs = await menu.openReportBrokenSite();
+ await ensureExpectedTabOrder(showsBackButton, false, false);
+ await rbs.close();
+}
+
+add_task(async function testTabOrdering() {
+ ensureReportBrokenSitePreffedOn();
+ ensureSendMoreInfoEnabled();
+
+ await BrowserTestUtils.withNewTab(
+ REPORTABLE_PAGE_URL,
+ async function (browser) {
+ await testTabOrder(AppMenu());
+ await testTabOrder(ProtectionsPanel());
+ await testTabOrder(HelpMenu());
+ }
+ );
+});
diff --git a/browser/components/reportbrokensite/test/browser/browser_tab_switch_handling.js b/browser/components/reportbrokensite/test/browser/browser_tab_switch_handling.js
new file mode 100644
index 0000000000..db1190b893
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/browser_tab_switch_handling.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests to ensure that Report Broken Site popups will be
+ * reset to whichever tab the user is on as they change
+ * between windows and tabs. */
+
+"use strict";
+
+add_common_setup();
+
+add_task(async function testResetsProperlyOnTabSwitch() {
+ ensureReportBrokenSitePreffedOn();
+
+ const badTab = await openTab("about:blank");
+ const goodTab1 = await openTab(REPORTABLE_PAGE_URL);
+ const goodTab2 = await openTab(REPORTABLE_PAGE_URL2);
+
+ const appMenu = AppMenu();
+ const protPanel = ProtectionsPanel();
+
+ let rbs = await appMenu.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+
+ gBrowser.selectedTab = goodTab1;
+
+ rbs = await protPanel.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+
+ gBrowser.selectedTab = badTab;
+ await appMenu.open();
+ appMenu.isReportBrokenSiteDisabled();
+ await appMenu.close();
+
+ gBrowser.selectedTab = goodTab1;
+ rbs = await protPanel.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+
+ closeTab(badTab);
+ closeTab(goodTab1);
+ closeTab(goodTab2);
+});
+
+add_task(async function testResetsProperlyOnWindowSwitch() {
+ ensureReportBrokenSitePreffedOn();
+
+ const tab1 = await openTab(REPORTABLE_PAGE_URL);
+
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ const tab2 = await openTab(REPORTABLE_PAGE_URL2, win2);
+
+ const appMenu1 = AppMenu();
+ const appMenu2 = ProtectionsPanel(win2);
+
+ let rbs2 = await appMenu2.openReportBrokenSite();
+ rbs2.isMainViewResetToCurrentTab();
+ rbs2.close();
+
+ // flip back to tab1's window and ensure its URL pops up instead of tab2's URL
+ await switchToWindow(window);
+ isSelectedTab(window, tab1); // sanity check
+
+ let rbs1 = await appMenu1.openReportBrokenSite();
+ rbs1.isMainViewResetToCurrentTab();
+ rbs1.close();
+
+ // likewise flip back to tab2's window and ensure its URL pops up instead of tab1's URL
+ await switchToWindow(win2);
+ isSelectedTab(win2, tab2); // sanity check
+
+ rbs2 = await appMenu2.openReportBrokenSite();
+ rbs2.isMainViewResetToCurrentTab();
+ rbs2.close();
+
+ closeTab(tab1);
+ closeTab(tab2);
+ await BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/components/reportbrokensite/test/browser/example_report_page.html b/browser/components/reportbrokensite/test/browser/example_report_page.html
new file mode 100644
index 0000000000..07602e3fbe
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/example_report_page.html
@@ -0,0 +1,22 @@
+<!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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <script>
+ window.marfeel = 1;
+ window.Mobify = { Tag: 1 };
+ window.FastClick = 1;
+ </script>
+ </head>
+ <body>
+ <!-- blocked tracking content -->
+ <iframe src="https://trackertest.org/"></iframe>
+ <!-- mixed active content -->
+ <iframe src="http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"></iframe>
+ <!-- mixed display content -->
+ <img src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/components/reportbrokensite/test/browser/head.js b/browser/components/reportbrokensite/test/browser/head.js
new file mode 100644
index 0000000000..e3f4451b5b
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/head.js
@@ -0,0 +1,863 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+const BASE_URL =
+ "https://example.com/browser/browser/components/reportbrokensite/test/browser/";
+
+const REPORTABLE_PAGE_URL = "https://example.com";
+
+const REPORTABLE_PAGE_URL2 = REPORTABLE_PAGE_URL.replace(".com", ".org");
+
+const REPORTABLE_PAGE_URL3 = `${BASE_URL}example_report_page.html`;
+
+const NEW_REPORT_ENDPOINT_TEST_URL = `${BASE_URL}sendMoreInfoTestEndpoint.html`;
+
+const PREFS = {
+ DATAREPORTING_ENABLED: "datareporting.healthreport.uploadEnabled",
+ REPORTER_ENABLED: "ui.new-webcompat-reporter.enabled",
+ REASON: "ui.new-webcompat-reporter.reason-dropdown",
+ SEND_MORE_INFO: "ui.new-webcompat-reporter.send-more-info-link",
+ NEW_REPORT_ENDPOINT: "ui.new-webcompat-reporter.new-report-endpoint",
+ REPORT_SITE_ISSUE_ENABLED: "extensions.webcompat-reporter.enabled",
+ PREFERS_CONTRAST_ENABLED: "layout.css.prefers-contrast.enabled",
+ USE_ACCESSIBILITY_THEME: "ui.useAccessibilityTheme",
+};
+
+function add_common_setup() {
+ add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREFS.NEW_REPORT_ENDPOINT, NEW_REPORT_ENDPOINT_TEST_URL]],
+ });
+ registerCleanupFunction(function () {
+ for (const prefName of Object.values(PREFS)) {
+ Services.prefs.clearUserPref(prefName);
+ }
+ });
+ });
+}
+
+function areObjectsEqual(actual, expected, path = "") {
+ if (typeof expected == "function") {
+ try {
+ const passes = expected(actual);
+ if (!passes) {
+ info(`${path} not pass check function: ${actual}`);
+ }
+ return passes;
+ } catch (e) {
+ info(`${path} threw exception:
+ got: ${typeof actual}, ${actual}
+ expected: ${typeof expected}, ${expected}
+ exception: ${e.message}
+ ${e.stack}`);
+ return false;
+ }
+ }
+
+ if (typeof actual != typeof expected) {
+ info(`${path} types do not match:
+ got: ${typeof actual}, ${actual}
+ expected: ${typeof expected}, ${expected}`);
+ return false;
+ }
+ if (typeof actual != "object" || actual === null || expected === null) {
+ if (actual !== expected) {
+ info(`${path} does not match
+ got: ${typeof actual}, ${actual}
+ expected: ${typeof expected}, ${expected}`);
+ return false;
+ }
+ return true;
+ }
+ const prefix = path ? `${path}.` : path;
+ for (const [key, val] of Object.entries(actual)) {
+ if (!(key in expected)) {
+ info(`Extra ${prefix}${key}: ${val}`);
+ return false;
+ }
+ }
+ let result = true;
+ for (const [key, expectedVal] of Object.entries(expected)) {
+ if (key in actual) {
+ if (!areObjectsEqual(actual[key], expectedVal, `${prefix}${key}`)) {
+ result = false;
+ }
+ } else {
+ info(`Missing ${prefix}${key} (${expectedVal})`);
+ result = false;
+ }
+ }
+ return result;
+}
+
+function clickAndAwait(toClick, evt, target) {
+ const menuPromise = BrowserTestUtils.waitForEvent(target, evt);
+ EventUtils.synthesizeMouseAtCenter(toClick, {}, window);
+ return menuPromise;
+}
+
+async function openTab(url, win) {
+ const options = {
+ gBrowser:
+ win?.gBrowser ||
+ Services.wm.getMostRecentWindow("navigator:browser").gBrowser,
+ url,
+ };
+ return BrowserTestUtils.openNewForegroundTab(options);
+}
+
+async function changeTab(tab, url) {
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+}
+
+function closeTab(tab) {
+ BrowserTestUtils.removeTab(tab);
+}
+
+function switchToWindow(win) {
+ const promises = [
+ BrowserTestUtils.waitForEvent(win, "focus"),
+ BrowserTestUtils.waitForEvent(win, "activate"),
+ ];
+ win.focus();
+ return Promise.all(promises);
+}
+
+function isSelectedTab(win, tab) {
+ const selectedTab = win.document.querySelector(".tabbrowser-tab[selected]");
+ is(selectedTab, tab);
+}
+
+function ensureReportBrokenSitePreffedOn() {
+ Services.prefs.setBoolPref(PREFS.DATAREPORTING_ENABLED, true);
+ Services.prefs.setBoolPref(PREFS.REPORTER_ENABLED, true);
+ ensureReasonDisabled();
+}
+
+function ensureReportBrokenSitePreffedOff() {
+ Services.prefs.setBoolPref(PREFS.REPORTER_ENABLED, false);
+}
+
+function ensureReportSiteIssuePreffedOn() {
+ Services.prefs.setBoolPref(PREFS.REPORT_SITE_ISSUE_ENABLED, true);
+}
+
+function ensureReportSiteIssuePreffedOff() {
+ Services.prefs.setBoolPref(PREFS.REPORT_SITE_ISSUE_ENABLED, false);
+}
+
+function ensureSendMoreInfoEnabled() {
+ Services.prefs.setBoolPref(PREFS.SEND_MORE_INFO, true);
+}
+
+function ensureSendMoreInfoDisabled() {
+ Services.prefs.setBoolPref(PREFS.SEND_MORE_INFO, false);
+}
+
+function ensureReasonDisabled() {
+ Services.prefs.setIntPref(PREFS.REASON, 0);
+}
+
+function ensureReasonOptional() {
+ Services.prefs.setIntPref(PREFS.REASON, 1);
+}
+
+function ensureReasonRequired() {
+ Services.prefs.setIntPref(PREFS.REASON, 2);
+}
+
+function isMenuItemEnabled(menuItem, itemDesc) {
+ ok(!menuItem.hidden, `${itemDesc} menu item is shown`);
+ ok(!menuItem.disabled, `${itemDesc} menu item is enabled`);
+}
+
+function isMenuItemHidden(menuItem, itemDesc) {
+ ok(
+ !menuItem || menuItem.hidden || !BrowserTestUtils.isVisible(menuItem),
+ `${itemDesc} menu item is hidden`
+ );
+}
+
+function isMenuItemDisabled(menuItem, itemDesc) {
+ ok(!menuItem.hidden, `${itemDesc} menu item is shown`);
+ ok(menuItem.disabled, `${itemDesc} menu item is disabled`);
+}
+
+class ReportBrokenSiteHelper {
+ sourceMenu = undefined;
+ win = undefined;
+
+ constructor(sourceMenu) {
+ this.sourceMenu = sourceMenu;
+ this.win = sourceMenu.win;
+ }
+
+ getViewNode(id) {
+ return PanelMultiView.getViewNode(this.win.document, id);
+ }
+
+ get mainView() {
+ return this.getViewNode("report-broken-site-popup-mainView");
+ }
+
+ get sentView() {
+ return this.getViewNode("report-broken-site-popup-reportSentView");
+ }
+
+ get openPanel() {
+ return this.mainView?.closest("panel");
+ }
+
+ get opened() {
+ return this.openPanel?.hasAttribute("panelopen");
+ }
+
+ async open(triggerMenuItem) {
+ const window = triggerMenuItem.ownerGlobal;
+ const shownPromise = BrowserTestUtils.waitForEvent(
+ this.mainView,
+ "ViewShown"
+ );
+ const focusPromise = BrowserTestUtils.waitForEvent(this.URLInput, "focus");
+ await EventUtils.synthesizeMouseAtCenter(triggerMenuItem, {}, window);
+ await shownPromise;
+ await focusPromise;
+ }
+
+ async #assertClickAndViewChanges(button, view, newView, newFocus) {
+ ok(view.closest("panel").hasAttribute("panelopen"), "Panel is open");
+ ok(BrowserTestUtils.isVisible(button), "Button is visible");
+ ok(!button.disabled, "Button is enabled");
+ const promises = [];
+ if (newView) {
+ if (newView.nodeName == "panel") {
+ promises.push(BrowserTestUtils.waitForEvent(newView, "popupshown"));
+ } else {
+ promises.push(BrowserTestUtils.waitForEvent(newView, "ViewShown"));
+ }
+ } else {
+ promises.push(BrowserTestUtils.waitForEvent(view, "ViewHiding"));
+ }
+ if (newFocus) {
+ promises.push(BrowserTestUtils.waitForEvent(newFocus, "focus"));
+ }
+ EventUtils.synthesizeMouseAtCenter(button, {}, this.win);
+ await Promise.all(promises);
+ }
+
+ async awaitReportSentViewOpened() {
+ await Promise.all([
+ BrowserTestUtils.waitForEvent(this.sentView, "ViewShown"),
+ BrowserTestUtils.waitForEvent(this.okayButton, "focus"),
+ ]);
+ }
+
+ async clickSend() {
+ await this.#assertClickAndViewChanges(
+ this.sendButton,
+ this.mainView,
+ this.sentView,
+ this.okayButton
+ );
+ }
+
+ waitForSendMoreInfoTab() {
+ return BrowserTestUtils.waitForNewTab(
+ this.win.gBrowser,
+ NEW_REPORT_ENDPOINT_TEST_URL
+ );
+ }
+
+ async clickSendMoreInfo() {
+ const newTabPromise = this.waitForSendMoreInfoTab();
+ EventUtils.synthesizeMouseAtCenter(this.sendMoreInfoLink, {}, this.win);
+ const newTab = await newTabPromise;
+ const receivedData = await SpecialPowers.spawn(
+ newTab.linkedBrowser,
+ [],
+ async function () {
+ await content.wrappedJSObject.messageArrived;
+ return content.wrappedJSObject.message;
+ }
+ );
+ this.win.gBrowser.removeCurrentTab();
+ return receivedData;
+ }
+
+ async clickCancel() {
+ await this.#assertClickAndViewChanges(this.cancelButton, this.mainView);
+ }
+
+ async clickOkay() {
+ await this.#assertClickAndViewChanges(this.okayButton, this.sentView);
+ }
+
+ async clickBack() {
+ await this.#assertClickAndViewChanges(
+ this.backButton,
+ this.sourceMenu.popup
+ );
+ }
+
+ isBackButtonEnabled() {
+ ok(BrowserTestUtils.isVisible(this.backButton), "Back button is visible");
+ ok(!this.backButton.disabled, "Back button is enabled");
+ }
+
+ close() {
+ if (this.opened) {
+ this.openPanel?.hidePopup(false);
+ }
+ this.sourceMenu?.close();
+ }
+
+ // UI element getters
+ get URLInput() {
+ return this.getViewNode("report-broken-site-popup-url");
+ }
+
+ get URLInvalidMessage() {
+ return this.getViewNode("report-broken-site-popup-invalid-url-msg");
+ }
+
+ get reasonInput() {
+ return this.getViewNode("report-broken-site-popup-reason");
+ }
+
+ get reasonDropdownPopup() {
+ return this.win.document.getElementById("ContentSelectDropdown").menupopup;
+ }
+
+ get reasonRequiredMessage() {
+ return this.getViewNode("report-broken-site-popup-missing-reason-msg");
+ }
+
+ get reasonLabelRequired() {
+ return this.getViewNode("report-broken-site-popup-reason-label");
+ }
+
+ get reasonLabelOptional() {
+ return this.getViewNode("report-broken-site-popup-reason-optional-label");
+ }
+
+ get descriptionTextarea() {
+ return this.getViewNode("report-broken-site-popup-description");
+ }
+
+ get sendMoreInfoLink() {
+ return this.getViewNode("report-broken-site-popup-send-more-info-link");
+ }
+
+ get backButton() {
+ return this.mainView.querySelector(".subviewbutton-back");
+ }
+
+ get sendButton() {
+ return this.getViewNode("report-broken-site-popup-send-button");
+ }
+
+ get cancelButton() {
+ return this.getViewNode("report-broken-site-popup-cancel-button");
+ }
+
+ get okayButton() {
+ return this.getViewNode("report-broken-site-popup-okay-button");
+ }
+
+ // Test helpers
+
+ #setInput(input, value) {
+ input.value = value;
+ input.dispatchEvent(
+ new UIEvent("input", { bubbles: true, view: this.win })
+ );
+ }
+
+ setURL(value) {
+ this.#setInput(this.URLInput, value);
+ }
+
+ chooseReason(value) {
+ const item = this.getViewNode(`report-broken-site-popup-reason-${value}`);
+ this.reasonInput.selectedIndex = item.index;
+ }
+
+ dismissDropdownPopup() {
+ const popup = this.reasonDropdownPopup;
+ const menuPromise = BrowserTestUtils.waitForPopupEvent(popup, "hidden");
+ popup.hidePopup();
+ return menuPromise;
+ }
+
+ setDescription(value) {
+ this.#setInput(this.descriptionTextarea, value);
+ }
+
+ isURL(expected) {
+ is(this.URLInput.value, expected);
+ }
+
+ isURLInvalidMessageShown() {
+ ok(
+ BrowserTestUtils.isVisible(this.URLInvalidMessage),
+ "'Please enter a valid URL' message is shown"
+ );
+ }
+
+ isURLInvalidMessageHidden() {
+ ok(
+ !BrowserTestUtils.isVisible(this.URLInvalidMessage),
+ "'Please enter a valid URL' message is hidden"
+ );
+ }
+
+ isReasonNeededMessageShown() {
+ ok(
+ BrowserTestUtils.isVisible(this.reasonRequiredMessage),
+ "'Please choose a reason' message is shown"
+ );
+ }
+
+ isReasonNeededMessageHidden() {
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonRequiredMessage),
+ "'Please choose a reason' message is hidden"
+ );
+ }
+
+ isSendButtonEnabled() {
+ ok(BrowserTestUtils.isVisible(this.sendButton), "Send button is visible");
+ ok(!this.sendButton.disabled, "Send button is enabled");
+ }
+
+ isSendButtonDisabled() {
+ ok(BrowserTestUtils.isVisible(this.sendButton), "Send button is visible");
+ ok(this.sendButton.disabled, "Send button is disabled");
+ }
+
+ isSendMoreInfoShown() {
+ ok(
+ BrowserTestUtils.isVisible(this.sendMoreInfoLink),
+ "send more info is shown"
+ );
+ }
+
+ isSendMoreInfoHidden() {
+ ok(
+ !BrowserTestUtils.isVisible(this.sendMoreInfoLink),
+ "send more info is hidden"
+ );
+ }
+
+ isSendMoreInfoShownOrHiddenAppropriately() {
+ if (Services.prefs.getBoolPref(PREFS.SEND_MORE_INFO)) {
+ this.isSendMoreInfoShown();
+ } else {
+ this.isSendMoreInfoHidden();
+ }
+ }
+
+ isReasonHidden() {
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonInput),
+ "reason drop-down is hidden"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonLabelOptional),
+ "optional reason label is hidden"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonLabelRequired),
+ "required reason label is hidden"
+ );
+ }
+
+ isReasonRequired() {
+ ok(
+ BrowserTestUtils.isVisible(this.reasonInput),
+ "reason drop-down is shown"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonLabelOptional),
+ "optional reason label is hidden"
+ );
+ ok(
+ BrowserTestUtils.isVisible(this.reasonLabelRequired),
+ "required reason label is shown"
+ );
+ }
+
+ isReasonOptional() {
+ ok(
+ BrowserTestUtils.isVisible(this.reasonInput),
+ "reason drop-down is shown"
+ );
+ ok(
+ BrowserTestUtils.isVisible(this.reasonLabelOptional),
+ "optional reason label is shown"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(this.reasonLabelRequired),
+ "required reason label is hidden"
+ );
+ }
+
+ isReasonShownOrHiddenAppropriately() {
+ const pref = Services.prefs.getIntPref(PREFS.REASON);
+ if (pref == 2) {
+ this.isReasonOptional();
+ } else if (pref == 1) {
+ this.isReasonOptional();
+ } else {
+ this.isReasonHidden();
+ }
+ }
+
+ isDescription(expected) {
+ return this.descriptionTextarea.value == expected;
+ }
+
+ isMainViewResetToCurrentTab() {
+ this.isURL(this.win.gBrowser.selectedBrowser.currentURI.spec);
+ this.isDescription("");
+ this.isReasonShownOrHiddenAppropriately();
+ this.isSendMoreInfoShownOrHiddenAppropriately();
+ }
+}
+
+class MenuHelper {
+ menuDescription = undefined;
+
+ win = undefined;
+
+ constructor(win = window) {
+ this.win = win;
+ }
+
+ getViewNode(id) {
+ return PanelMultiView.getViewNode(this.win.document, id);
+ }
+
+ get showsBackButton() {
+ return true;
+ }
+
+ get reportBrokenSite() {}
+
+ get reportSiteIssue() {}
+
+ get popup() {}
+
+ get opened() {
+ return this.popup?.hasAttribute("panelopen");
+ }
+
+ async open() {}
+
+ async close() {}
+
+ isReportBrokenSiteDisabled() {
+ return isMenuItemDisabled(this.reportBrokenSite, this.menuDescription);
+ }
+
+ isReportBrokenSiteEnabled() {
+ return isMenuItemEnabled(this.reportBrokenSite, this.menuDescription);
+ }
+
+ isReportBrokenSiteHidden() {
+ return isMenuItemHidden(this.reportBrokenSite, this.menuDescription);
+ }
+
+ isReportSiteIssueDisabled() {
+ return isMenuItemDisabled(this.reportSiteIssue, this.menuDescription);
+ }
+
+ isReportSiteIssueEnabled() {
+ return isMenuItemEnabled(this.reportSiteIssue, this.menuDescription);
+ }
+
+ isReportSiteIssueHidden() {
+ return isMenuItemHidden(this.reportSiteIssue, this.menuDescription);
+ }
+
+ async openReportBrokenSite() {
+ if (!this.opened) {
+ await this.open();
+ }
+ isMenuItemEnabled(this.reportBrokenSite, this.menuDescription);
+ const rbs = new ReportBrokenSiteHelper(this);
+ await rbs.open(this.reportBrokenSite);
+ return rbs;
+ }
+
+ async openAndPrefillReportBrokenSite(url = null, description = "") {
+ let rbs = await this.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ if (url) {
+ rbs.setURL(url);
+ }
+ if (description) {
+ rbs.setDescription(description);
+ }
+ return rbs;
+ }
+}
+
+class AppMenuHelper extends MenuHelper {
+ menuDescription = "AppMenu";
+
+ get reportBrokenSite() {
+ return this.getViewNode("appMenu-report-broken-site-button");
+ }
+
+ get reportSiteIssue() {
+ return undefined;
+ }
+
+ get popup() {
+ return this.win.document.getElementById("appMenu-popup");
+ }
+
+ async open() {
+ await new CustomizableUITestUtils(this.win).openMainMenu();
+ }
+
+ async close() {
+ if (this.opened) {
+ await new CustomizableUITestUtils(this.win).hideMainMenu();
+ }
+ }
+}
+
+class AppMenuHelpSubmenuHelper extends MenuHelper {
+ menuDescription = "AppMenu help sub-menu";
+
+ get reportBrokenSite() {
+ return this.getViewNode("appMenu_help_reportBrokenSite");
+ }
+
+ get reportSiteIssue() {
+ return this.getViewNode("appMenu_help_reportSiteIssue");
+ }
+
+ get popup() {
+ return this.win.document.getElementById("appMenu-popup");
+ }
+
+ async open() {
+ await new CustomizableUITestUtils(this.win).openMainMenu();
+
+ const anchor = this.win.document.getElementById("PanelUI-menu-button");
+ this.win.PanelUI.showHelpView(anchor);
+
+ const appMenuHelpSubview = this.getViewNode("PanelUI-helpView");
+ await BrowserTestUtils.waitForEvent(appMenuHelpSubview, "ViewShown");
+ }
+
+ async close() {
+ if (this.opened) {
+ await new CustomizableUITestUtils(this.win).hideMainMenu();
+ }
+ }
+}
+
+class HelpMenuHelper extends MenuHelper {
+ menuDescription = "Help Menu";
+
+ get showsBackButton() {
+ return false;
+ }
+
+ get reportBrokenSite() {
+ return this.win.document.getElementById("help_reportBrokenSite");
+ }
+
+ get reportSiteIssue() {
+ return this.win.document.getElementById("help_reportSiteIssue");
+ }
+
+ get popup() {
+ return this.getViewNode("PanelUI-helpView");
+ }
+
+ get helpMenu() {
+ return this.win.document.getElementById("menu_HelpPopup");
+ }
+
+ async openReportBrokenSite() {
+ // We can't actually open the Help menu properly in testing, so the best
+ // we can do to open its Report Broken Site panel is to force its DOM to be
+ // prepared, and then soft-click the Report Broken Site menuitem to open it.
+ await this.open();
+ const shownPromise = BrowserTestUtils.waitForEvent(
+ this.win,
+ "ViewShown",
+ true,
+ e => e.target.classList.contains("report-broken-site-view")
+ );
+ this.reportBrokenSite.click();
+ await shownPromise;
+ return new ReportBrokenSiteHelper(this);
+ }
+
+ async open() {
+ const { helpMenu } = this;
+ const promise = BrowserTestUtils.waitForEvent(helpMenu, "popupshown");
+
+ // This event-faking method was copied from browser_title_case_menus.js.
+ // We can't actually open the Help menu in testing, but this lets us
+ // force its DOM to be properly built.
+ helpMenu.dispatchEvent(new MouseEvent("popupshowing", { bubbles: true }));
+ helpMenu.dispatchEvent(new MouseEvent("popupshown", { bubbles: true }));
+
+ await promise;
+ }
+
+ async close() {
+ const { helpMenu } = this;
+ const promise = BrowserTestUtils.waitForPopupEvent(helpMenu, "hidden");
+
+ // (Also copied from browser_title_case_menus.js)
+ // Just for good measure, we'll fire the popuphiding/popuphidden events
+ // after we close the menupopups.
+ helpMenu.dispatchEvent(new MouseEvent("popuphiding", { bubbles: true }));
+ helpMenu.dispatchEvent(new MouseEvent("popuphidden", { bubbles: true }));
+
+ await promise;
+ }
+}
+
+class ProtectionsPanelHelper extends MenuHelper {
+ menuDescription = "Protections Panel";
+
+ get reportBrokenSite() {
+ this.win.gProtectionsHandler._initializePopup();
+ return this.getViewNode("protections-popup-report-broken-site-button");
+ }
+
+ get reportSiteIssue() {
+ return undefined;
+ }
+
+ get popup() {
+ this.win.gProtectionsHandler._initializePopup();
+ return this.win.document.getElementById("protections-popup");
+ }
+
+ async open() {
+ const promise = BrowserTestUtils.waitForEvent(
+ this.win,
+ "popupshown",
+ true,
+ e => e.target.id == "protections-popup"
+ );
+ this.win.gProtectionsHandler.showProtectionsPopup();
+ await promise;
+ }
+
+ async close() {
+ if (this.opened) {
+ const popup = this.popup;
+ const promise = BrowserTestUtils.waitForPopupEvent(popup, "hidden");
+ PanelMultiView.hidePopup(popup, false);
+ await promise;
+ }
+ }
+}
+
+function AppMenu(win = window) {
+ return new AppMenuHelper(win);
+}
+
+function AppMenuHelpSubmenu(win = window) {
+ return new AppMenuHelpSubmenuHelper(win);
+}
+
+function HelpMenu(win = window) {
+ return new HelpMenuHelper(win);
+}
+
+function ProtectionsPanel(win = window) {
+ return new ProtectionsPanelHelper(win);
+}
+
+function pressKeyAndAwait(event, key, config = {}) {
+ const win = config.window || window;
+ if (!event.then) {
+ event = BrowserTestUtils.waitForEvent(win, event, config.timeout || 200);
+ }
+ EventUtils.synthesizeKey(key, config, win);
+ return event;
+}
+
+async function pressKeyAndGetFocus(key, config = {}) {
+ return (await pressKeyAndAwait("focus", key, config)).target;
+}
+
+async function tabTo(match, win = window) {
+ const config = { window: win };
+ const { activeElement } = win.document;
+ if (activeElement?.matches(match)) {
+ return activeElement;
+ }
+ let initial = await pressKeyAndGetFocus("VK_TAB", config);
+ let target = initial;
+ do {
+ if (target.matches(match)) {
+ return target;
+ }
+ target = await pressKeyAndGetFocus("VK_TAB", config);
+ } while (target && target !== initial);
+ return undefined;
+}
+
+async function setupStrictETP(fn) {
+ await UrlClassifierTestUtils.addTestTrackers();
+ registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", true],
+ ["security.mixed_content.upgrade_display_content", false],
+ [
+ "urlclassifier.trackingTable",
+ "content-track-digest256,mochitest2-track-simple",
+ ],
+ ],
+ });
+}
+
+// copied from browser/base/content/test/protectionsUI/head.js
+function waitForContentBlockingEvent(numChanges = 1, win = null) {
+ if (!win) {
+ win = window;
+ }
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onContentBlockingEvent(webProgress, request, event) {
+ n = n + 1;
+ info(
+ `Received onContentBlockingEvent event: ${event} (${n} of ${numChanges})`
+ );
+ if (n >= numChanges) {
+ win.gBrowser.removeProgressListener(listener);
+ resolve(n);
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(listener);
+ });
+}
diff --git a/browser/components/reportbrokensite/test/browser/send.js b/browser/components/reportbrokensite/test/browser/send.js
new file mode 100644
index 0000000000..a8599741ac
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/send.js
@@ -0,0 +1,290 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Helper methods for testing sending reports with
+ * the Report Broken Site feature.
+ */
+
+/* import-globals-from head.js */
+
+"use strict";
+
+const { Troubleshoot } = ChromeUtils.importESModule(
+ "resource://gre/modules/Troubleshoot.sys.mjs"
+);
+
+function getSysinfoProperty(propertyName, defaultValue) {
+ try {
+ return Services.sysinfo.getProperty(propertyName);
+ } catch (e) {}
+ return defaultValue;
+}
+
+function securityStringToArray(str) {
+ return str ? str.split(";") : null;
+}
+
+function getExpectedGraphicsDevices(snapshot) {
+ const { graphics } = snapshot;
+ return [
+ graphics.adapterDeviceID,
+ graphics.adapterVendorID,
+ graphics.adapterDeviceID2,
+ graphics.adapterVendorID2,
+ ]
+ .filter(i => i)
+ .sort();
+}
+
+function compareGraphicsDevices(expected, rawActual) {
+ const actual = rawActual
+ .map(({ deviceID, vendorID }) => [deviceID, vendorID])
+ .flat()
+ .filter(i => i)
+ .sort();
+ return areObjectsEqual(actual, expected);
+}
+
+function getExpectedGraphicsDrivers(snapshot) {
+ const { graphics } = snapshot;
+ const expected = [];
+ for (let i = 1; i < 3; ++i) {
+ const version = graphics[`webgl${i}Version`];
+ if (version && version != "-") {
+ expected.push(graphics[`webgl${i}Renderer`]);
+ expected.push(version);
+ }
+ }
+ return expected.filter(i => i).sort();
+}
+
+function compareGraphicsDrivers(expected, rawActual) {
+ const actual = rawActual
+ .map(({ renderer, version }) => [renderer, version])
+ .flat()
+ .filter(i => i)
+ .sort();
+ return areObjectsEqual(actual, expected);
+}
+
+function getExpectedGraphicsFeatures(snapshot) {
+ const expected = {};
+ for (let { name, log, status } of snapshot.graphics.featureLog.features) {
+ for (const item of log?.reverse() ?? []) {
+ if (item.failureId && item.status == status) {
+ status = `${status} (${item.message || item.failureId})`;
+ }
+ }
+ expected[name] = status;
+ }
+ return expected;
+}
+
+async function getExpectedWebCompatInfo(tab, snapshot, fullAppData = false) {
+ const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ const { application, graphics, intl, securitySoftware } = snapshot;
+
+ const { fissionAutoStart, memorySizeBytes, updateChannel, userAgent } =
+ application;
+
+ const app = {
+ defaultLocales: intl.localeService.available,
+ defaultUseragentString: userAgent,
+ fissionEnabled: fissionAutoStart,
+ };
+ if (fullAppData) {
+ app.applicationName = application.name;
+ app.osArchitecture = getSysinfoProperty("arch", null);
+ app.osName = getSysinfoProperty("name", null);
+ app.osVersion = getSysinfoProperty("version", null);
+ app.updateChannel = updateChannel;
+ app.version = application.version;
+ }
+
+ const hasTouchScreen = graphics.info.ApzTouchInput == 1;
+
+ const { registeredAntiVirus, registeredAntiSpyware, registeredFirewall } =
+ securitySoftware;
+
+ const browserInfo = {
+ app,
+ graphics: {
+ devicesJson(actualStr) {
+ const expected = getExpectedGraphicsDevices(snapshot);
+ // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON).
+ // We should stop using JSON like this in bug 1875185.
+ if (!actualStr || actualStr == "undefined") {
+ return !expected.length;
+ }
+ return compareGraphicsDevices(expected, JSON.parse(actualStr));
+ },
+ driversJson(actualStr) {
+ const expected = getExpectedGraphicsDrivers(snapshot);
+ // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON).
+ // We should stop using JSON like this in bug 1875185.
+ if (!actualStr || actualStr == "undefined") {
+ return !expected.length;
+ }
+ return compareGraphicsDrivers(expected, JSON.parse(actualStr));
+ },
+ featuresJson(actualStr) {
+ const expected = getExpectedGraphicsFeatures(snapshot);
+ // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON).
+ // We should stop using JSON like this in bug 1875185.
+ if (!actualStr || actualStr == "undefined") {
+ return !expected.length;
+ }
+ return areObjectsEqual(JSON.parse(actualStr), expected);
+ },
+ hasTouchScreen,
+ monitorsJson(actualStr) {
+ // We don't care about monitor data on Android right now.
+ if (AppConstants.platform == "android") {
+ return actualStr == "undefined";
+ }
+ return actualStr == JSON.stringify(gfxInfo.getMonitors());
+ },
+ },
+ prefs: {
+ cookieBehavior: Services.prefs.getIntPref(
+ "network.cookie.cookieBehavior",
+ -1
+ ),
+ forcedAcceleratedLayers: Services.prefs.getBoolPref(
+ "layers.acceleration.force-enabled",
+ false
+ ),
+ globalPrivacyControlEnabled: Services.prefs.getBoolPref(
+ "privacy.globalprivacycontrol.enabled",
+ false
+ ),
+ installtriggerEnabled: Services.prefs.getBoolPref(
+ "extensions.InstallTrigger.enabled",
+ false
+ ),
+ opaqueResponseBlocking: Services.prefs.getBoolPref(
+ "browser.opaqueResponseBlocking",
+ false
+ ),
+ resistFingerprintingEnabled: Services.prefs.getBoolPref(
+ "privacy.resistFingerprinting",
+ false
+ ),
+ softwareWebrender: Services.prefs.getBoolPref(
+ "gfx.webrender.software",
+ false
+ ),
+ },
+ security: {
+ antispyware: securityStringToArray(registeredAntiSpyware),
+ antivirus: securityStringToArray(registeredAntiVirus),
+ firewall: securityStringToArray(registeredFirewall),
+ },
+ system: {
+ isTablet: getSysinfoProperty("tablet", false),
+ memory: Math.round(memorySizeBytes / 1024 / 1024),
+ },
+ };
+
+ const tabInfo = await tab.linkedBrowser.ownerGlobal.SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ return {
+ devicePixelRatio: `${content.devicePixelRatio}`,
+ antitracking: {
+ blockList: "basic",
+ isPrivateBrowsing: false,
+ hasTrackingContentBlocked: false,
+ hasMixedActiveContentBlocked: false,
+ hasMixedDisplayContentBlocked: false,
+ },
+ frameworks: {
+ fastclick: false,
+ marfeel: false,
+ mobify: false,
+ },
+ languages: content.navigator.languages,
+ useragentString: content.navigator.userAgent,
+ };
+ }
+ );
+
+ browserInfo.graphics.devicePixelRatio = tabInfo.devicePixelRatio;
+ delete tabInfo.devicePixelRatio;
+
+ return { browserInfo, tabInfo };
+}
+
+function extractPingData(branch) {
+ const data = {};
+ for (const [name, value] of Object.entries(branch)) {
+ data[name] = value.testGetValue();
+ }
+ return data;
+}
+
+function extractBrokenSiteReportFromGleanPing(Glean) {
+ const ping = extractPingData(Glean.brokenSiteReport);
+ ping.tabInfo = extractPingData(Glean.brokenSiteReportTabInfo);
+ ping.tabInfo.antitracking = extractPingData(
+ Glean.brokenSiteReportTabInfoAntitracking
+ );
+ ping.tabInfo.frameworks = extractPingData(
+ Glean.brokenSiteReportTabInfoFrameworks
+ );
+ ping.browserInfo = {
+ app: extractPingData(Glean.brokenSiteReportBrowserInfoApp),
+ graphics: extractPingData(Glean.brokenSiteReportBrowserInfoGraphics),
+ prefs: extractPingData(Glean.brokenSiteReportBrowserInfoPrefs),
+ security: extractPingData(Glean.brokenSiteReportBrowserInfoSecurity),
+ system: extractPingData(Glean.brokenSiteReportBrowserInfoSystem),
+ };
+ return ping;
+}
+
+async function testSend(tab, menu, expectedOverrides = {}) {
+ const url = expectedOverrides.url ?? menu.win.gBrowser.currentURI.spec;
+ const description = expectedOverrides.description ?? "";
+ const breakageCategory = expectedOverrides.breakageCategory ?? null;
+
+ let rbs = await menu.openAndPrefillReportBrokenSite(url, description);
+
+ const snapshot = await Troubleshoot.snapshot();
+ const expected = await getExpectedWebCompatInfo(tab, snapshot);
+
+ expected.url = url;
+ expected.description = description;
+ expected.breakageCategory = breakageCategory;
+
+ if (expectedOverrides.antitracking) {
+ expected.tabInfo.antitracking = expectedOverrides.antitracking;
+ }
+
+ if (expectedOverrides.frameworks) {
+ expected.tabInfo.frameworks = expectedOverrides.frameworks;
+ }
+
+ if (breakageCategory) {
+ rbs.chooseReason(breakageCategory);
+ }
+
+ const pingCheck = new Promise(resolve => {
+ Services.fog.testResetFOG();
+ GleanPings.brokenSiteReport.testBeforeNextSubmit(() => {
+ const ping = extractBrokenSiteReportFromGleanPing(Glean);
+ ok(areObjectsEqual(ping, expected), "ping matches expectations");
+ resolve();
+ });
+ });
+
+ await rbs.clickSend();
+ await pingCheck;
+ await rbs.clickOkay();
+
+ // re-opening the panel, the url and description should be reset
+ rbs = await menu.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+}
diff --git a/browser/components/reportbrokensite/test/browser/sendMoreInfoTestEndpoint.html b/browser/components/reportbrokensite/test/browser/sendMoreInfoTestEndpoint.html
new file mode 100644
index 0000000000..39b1b3d25a
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/sendMoreInfoTestEndpoint.html
@@ -0,0 +1,27 @@
+<!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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <script>
+ let ready;
+ window.wrtReady = new Promise(r => ready = r);
+
+ let arrived;
+ window.messageArrived = new Promise(r => arrived = r);
+
+ window.addEventListener("message", e => {
+ window.message = e.data;
+ arrived();
+ });
+
+ window.addEventListener("load", () => {
+ setTimeout(ready, 100);
+ });
+ </script>
+ </body>
+</html>
diff --git a/browser/components/reportbrokensite/test/browser/send_more_info.js b/browser/components/reportbrokensite/test/browser/send_more_info.js
new file mode 100644
index 0000000000..6803403f63
--- /dev/null
+++ b/browser/components/reportbrokensite/test/browser/send_more_info.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Helper methods for testing the "send more info" link
+ * of the Report Broken Site feature.
+ */
+
+/* import-globals-from head.js */
+/* import-globals-from send.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "send.js",
+ this
+);
+
+async function reformatExpectedWebCompatInfo(tab, overrides) {
+ const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+ const snapshot = await Troubleshoot.snapshot();
+ const expected = await getExpectedWebCompatInfo(tab, snapshot, true);
+ const { browserInfo, tabInfo } = expected;
+ const { app, graphics, prefs, security } = browserInfo;
+ const {
+ applicationName,
+ defaultUseragentString,
+ fissionEnabled,
+ osArchitecture,
+ osName,
+ osVersion,
+ updateChannel,
+ version,
+ } = app;
+ const { devicePixelRatio, hasTouchScreen } = graphics;
+ const { antitracking, languages, useragentString } = tabInfo;
+
+ const atOverrides = overrides.antitracking;
+ const blockList = atOverrides?.blockList ?? antitracking.blockList;
+ const hasMixedActiveContentBlocked =
+ atOverrides?.hasMixedActiveContentBlocked ??
+ antitracking.hasMixedActiveContentBlocked;
+ const hasMixedDisplayContentBlocked =
+ atOverrides?.hasMixedDisplayContentBlocked ??
+ antitracking.hasMixedDisplayContentBlocked;
+ const hasTrackingContentBlocked =
+ atOverrides?.hasTrackingContentBlocked ??
+ antitracking.hasTrackingContentBlocked;
+ const isPrivateBrowsing =
+ atOverrides?.isPrivateBrowsing ?? antitracking.isPrivateBrowsing;
+
+ const extra_labels = [];
+ const frameworks = overrides.frameworks ?? {
+ fastclick: false,
+ mobify: false,
+ marfeel: false,
+ };
+
+ // ignore the console log unless explicily testing for it.
+ const consoleLog = overrides.consoleLog ?? (() => true);
+
+ const finalPrefs = {};
+ for (const [key, pref] of Object.entries({
+ cookieBehavior: "network.cookie.cookieBehavior",
+ forcedAcceleratedLayers: "layers.acceleration.force-enabled",
+ globalPrivacyControlEnabled: "privacy.globalprivacycontrol.enabled",
+ installtriggerEnabled: "extensions.InstallTrigger.enabled",
+ opaqueResponseBlocking: "browser.opaqueResponseBlocking",
+ resistFingerprintingEnabled: "privacy.resistFingerprinting",
+ softwareWebrender: "gfx.webrender.software",
+ })) {
+ if (key in prefs) {
+ finalPrefs[pref] = prefs[key];
+ }
+ }
+
+ const reformatted = {
+ blockList,
+ details: {
+ additionalData: {
+ applicationName,
+ blockList,
+ devicePixelRatio: parseInt(devicePixelRatio),
+ finalUserAgent: useragentString,
+ fissionEnabled,
+ gfxData: {
+ devices(actual) {
+ const devices = getExpectedGraphicsDevices(snapshot);
+ return compareGraphicsDevices(devices, actual);
+ },
+ drivers(actual) {
+ const drvs = getExpectedGraphicsDrivers(snapshot);
+ return compareGraphicsDrivers(drvs, actual);
+ },
+ features(actual) {
+ const features = getExpectedGraphicsFeatures(snapshot);
+ return areObjectsEqual(actual, features);
+ },
+ hasTouchScreen,
+ monitors(actual) {
+ // We don't care about monitor data on Android right now.
+ if (AppConstants.platform === "android") {
+ return actual == undefined;
+ }
+ return areObjectsEqual(actual, gfxInfo.getMonitors());
+ },
+ },
+ hasMixedActiveContentBlocked,
+ hasMixedDisplayContentBlocked,
+ hasTrackingContentBlocked,
+ isPB: isPrivateBrowsing,
+ languages,
+ osArchitecture,
+ osName,
+ osVersion,
+ prefs: finalPrefs,
+ updateChannel,
+ userAgent: defaultUseragentString,
+ version,
+ },
+ blockList,
+ consoleLog,
+ frameworks,
+ hasTouchScreen,
+ "gfx.webrender.software": prefs.softwareWebrender,
+ "mixed active content blocked": hasMixedActiveContentBlocked,
+ "mixed passive content blocked": hasMixedDisplayContentBlocked,
+ "tracking content blocked": hasTrackingContentBlocked
+ ? `true (${blockList})`
+ : "false",
+ },
+ extra_labels,
+ src: "desktop-reporter",
+ utm_campaign: "report-broken-site",
+ utm_source: "desktop-reporter",
+ };
+
+ // We only care about this pref on Linux right now on webcompat.com.
+ if (AppConstants.platform != "linux") {
+ delete finalPrefs["layers.acceleration.force-enabled"];
+ } else {
+ reformatted.details["layers.acceleration.force-enabled"] =
+ finalPrefs["layers.acceleration.force-enabled"];
+ }
+
+ // Only bother adding the security key if it has any data
+ if (Object.values(security).filter(e => e).length) {
+ reformatted.details.additionalData.sec = security;
+ }
+
+ const expectedCodecs = snapshot.media.codecSupportInfo
+ .replaceAll(" NONE", "")
+ .split("\n")
+ .sort()
+ .join("\n");
+ if (expectedCodecs) {
+ reformatted.details.additionalData.gfxData.codecSupport = rawActual => {
+ const actual = Object.entries(rawActual)
+ .map(([name, { hardware, software }]) =>
+ `${name} ${software ? "SW" : ""} ${hardware ? "HW" : ""}`.trim()
+ )
+ .sort()
+ .join("\n");
+ return areObjectsEqual(actual, expectedCodecs);
+ };
+ }
+
+ if (blockList != "basic") {
+ extra_labels.push(`type-tracking-protection-${blockList}`);
+ }
+
+ if (overrides.expectNoTabDetails) {
+ delete reformatted.details.frameworks;
+ delete reformatted.details.consoleLog;
+ delete reformatted.details["mixed active content blocked"];
+ delete reformatted.details["mixed passive content blocked"];
+ delete reformatted.details["tracking content blocked"];
+ } else {
+ const { fastclick, mobify, marfeel } = frameworks;
+ if (fastclick) {
+ extra_labels.push("type-fastclick");
+ reformatted.details.fastclick = true;
+ }
+ if (mobify) {
+ extra_labels.push("type-mobify");
+ reformatted.details.mobify = true;
+ }
+ if (marfeel) {
+ extra_labels.push("type-marfeel");
+ reformatted.details.marfeel = true;
+ }
+ }
+
+ return reformatted;
+}
+
+async function testSendMoreInfo(tab, menu, expectedOverrides = {}) {
+ const url = expectedOverrides.url ?? menu.win.gBrowser.currentURI.spec;
+ const description = expectedOverrides.description ?? "";
+
+ let rbs = await menu.openAndPrefillReportBrokenSite(url, description);
+
+ const receivedData = await rbs.clickSendMoreInfo();
+ const { message } = receivedData;
+
+ const expected = await reformatExpectedWebCompatInfo(tab, expectedOverrides);
+ expected.url = url;
+ expected.description = description;
+
+ ok(areObjectsEqual(message, expected), "ping matches expectations");
+
+ // re-opening the panel, the url and description should be reset
+ rbs = await menu.openReportBrokenSite();
+ rbs.isMainViewResetToCurrentTab();
+ rbs.close();
+}