summaryrefslogtreecommitdiffstats
path: root/browser/components/protections/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/components/protections/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/protections/test')
-rw-r--r--browser/components/protections/test/browser/browser.toml18
-rw-r--r--browser/components/protections/test/browser/browser_protections_lockwise.js290
-rw-r--r--browser/components/protections/test/browser/browser_protections_monitor.js161
-rw-r--r--browser/components/protections/test/browser/browser_protections_proxy.js107
-rw-r--r--browser/components/protections/test/browser/browser_protections_report_ui.js1129
-rw-r--r--browser/components/protections/test/browser/browser_protections_telemetry.js1123
-rw-r--r--browser/components/protections/test/browser/browser_protections_vpn.js282
-rw-r--r--browser/components/protections/test/browser/head.js96
8 files changed, 3206 insertions, 0 deletions
diff --git a/browser/components/protections/test/browser/browser.toml b/browser/components/protections/test/browser/browser.toml
new file mode 100644
index 0000000000..2cea61d997
--- /dev/null
+++ b/browser/components/protections/test/browser/browser.toml
@@ -0,0 +1,18 @@
+[DEFAULT]
+prefs = ["toolkit.telemetry.ipcBatchTimeout=0"]
+support-files = [
+ "head.js",
+ "!/browser/base/content/test/protectionsUI/trackingPage.html",
+]
+
+["browser_protections_lockwise.js"]
+
+["browser_protections_monitor.js"]
+
+["browser_protections_proxy.js"]
+
+["browser_protections_report_ui.js"]
+
+["browser_protections_telemetry.js"]
+
+["browser_protections_vpn.js"]
diff --git a/browser/components/protections/test/browser/browser_protections_lockwise.js b/browser/components/protections/test/browser/browser_protections_lockwise.js
new file mode 100644
index 0000000000..72e1795455
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_lockwise.js
@@ -0,0 +1,290 @@
+/* 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";
+
+requestLongerTimeout(2);
+
+const { AboutProtectionsParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutProtectionsParent.sys.mjs"
+);
+const ABOUT_LOGINS_URL = "about:logins";
+
+add_task(async function testNoLoginsLockwiseCardUI() {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ const aboutLoginsPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ ABOUT_LOGINS_URL
+ );
+
+ info(
+ "Check that the correct lockwise card content is displayed for non-logged in users."
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const lockwiseCard = content.document.querySelector(".lockwise-card");
+ return ContentTaskUtils.isVisible(lockwiseCard);
+ }, "Lockwise card for user with no logins is visible.");
+
+ const lockwiseHowItWorks = content.document.querySelector(
+ "#lockwise-how-it-works"
+ );
+ ok(
+ ContentTaskUtils.isHidden(lockwiseHowItWorks),
+ "How it works link is hidden"
+ );
+
+ const lockwiseHeaderContent = content.document.querySelector(
+ "#lockwise-header-content span"
+ );
+ await content.document.l10n.translateElements([lockwiseHeaderContent]);
+ is(
+ lockwiseHeaderContent.dataset.l10nId,
+ "passwords-header-content",
+ "lockwiseHeaderContent contents should match l10n-id attribute set on the element"
+ );
+
+ const lockwiseScannedWrapper = content.document.querySelector(
+ ".lockwise-scanned-wrapper"
+ );
+ ok(
+ ContentTaskUtils.isHidden(lockwiseScannedWrapper),
+ "Lockwise scanned wrapper is hidden"
+ );
+
+ const managePasswordsButton = content.document.querySelector(
+ "#manage-passwords-button"
+ );
+ ok(
+ ContentTaskUtils.isHidden(managePasswordsButton),
+ "Manage passwords button is hidden"
+ );
+
+ const savePasswordsButton = content.document.querySelector(
+ "#save-passwords-button"
+ );
+ ok(
+ ContentTaskUtils.isVisible(savePasswordsButton),
+ "Save passwords button is visible in the header"
+ );
+ info(
+ "Click on the save passwords button and check that it opens about:logins in a new tab"
+ );
+ savePasswordsButton.click();
+ });
+ const loginsTab = await aboutLoginsPromise;
+ info("about:logins was successfully opened in a new tab");
+ gBrowser.removeTab(loginsTab);
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testLockwiseCardUIWithLogins() {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ const aboutLoginsPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ ABOUT_LOGINS_URL
+ );
+
+ info(
+ "Add a login and check that lockwise card content for a logged in user is displayed correctly"
+ );
+ await Services.logins.addLoginAsync(TEST_LOGIN1);
+ await BrowserTestUtils.reloadTab(tab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const hasLogins = content.document.querySelector(".lockwise-card");
+ return ContentTaskUtils.isVisible(hasLogins);
+ }, "Lockwise card for user with logins is visible");
+
+ const lockwiseTitle = content.document.querySelector("#lockwise-title");
+ await content.document.l10n.translateElements([lockwiseTitle]);
+ await ContentTaskUtils.waitForCondition(
+ () => lockwiseTitle.textContent == "Manage your passwords",
+ "Waiting for Fluent to provide the title translation"
+ );
+ is(
+ lockwiseTitle.textContent,
+ "Manage your passwords",
+ "Correct passwords title is shown"
+ );
+
+ const lockwiseHowItWorks = content.document.querySelector(
+ "#lockwise-how-it-works"
+ );
+ ok(
+ ContentTaskUtils.isVisible(lockwiseHowItWorks),
+ "How it works link is visible"
+ );
+
+ const lockwiseHeaderContent = content.document.querySelector(
+ "#lockwise-header-content span"
+ );
+ await content.document.l10n.translateElements([lockwiseHeaderContent]);
+ is(
+ lockwiseHeaderContent.dataset.l10nId,
+ "lockwise-header-content-logged-in",
+ "lockwiseHeaderContent contents should match l10n-id attribute set on the element"
+ );
+
+ const lockwiseScannedWrapper = content.document.querySelector(
+ ".lockwise-scanned-wrapper"
+ );
+ ok(
+ ContentTaskUtils.isVisible(lockwiseScannedWrapper),
+ "Lockwise scanned wrapper is visible"
+ );
+
+ const lockwiseScannedText = content.document.querySelector(
+ "#lockwise-scanned-text"
+ );
+ await content.document.l10n.translateElements([lockwiseScannedText]);
+ is(
+ lockwiseScannedText.textContent,
+ "1 password stored securely.",
+ "Correct lockwise scanned text is shown"
+ );
+
+ const savePasswordsButton = content.document.querySelector(
+ "#save-passwords-button"
+ );
+ ok(
+ ContentTaskUtils.isHidden(savePasswordsButton),
+ "Save passwords button is hidden"
+ );
+
+ const managePasswordsButton = content.document.querySelector(
+ "#manage-passwords-button"
+ );
+ ok(
+ ContentTaskUtils.isVisible(managePasswordsButton),
+ "Manage passwords button is visible"
+ );
+ info(
+ "Click on the manage passwords button and check that it opens about:logins in a new tab"
+ );
+ managePasswordsButton.click();
+ });
+ const loginsTab = await aboutLoginsPromise;
+ info("about:logins was successfully opened in a new tab");
+ gBrowser.removeTab(loginsTab);
+
+ info(
+ "Add another login and check that the scanned text about stored logins is updated after reload."
+ );
+ await Services.logins.addLoginAsync(TEST_LOGIN2);
+ await BrowserTestUtils.reloadTab(tab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const lockwiseScannedText = content.document.querySelector(
+ "#lockwise-scanned-text"
+ ).textContent;
+ ContentTaskUtils.waitForCondition(
+ () =>
+ lockwiseScannedText.textContent ==
+ "Your passwords are being stored securely.",
+ "Correct lockwise scanned text is shown"
+ );
+ });
+
+ Services.logins.removeLogin(TEST_LOGIN1);
+ Services.logins.removeLogin(TEST_LOGIN2);
+
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testLockwiseCardUIWithBreachedLogins() {
+ info(
+ "Add a breached login and test that the lockwise scanned text is displayed correctly"
+ );
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await Services.logins.addLoginAsync(TEST_LOGIN1);
+
+ info("Mock monitor data with a breached login to test the Lockwise UI");
+ AboutProtectionsParent.setTestOverride(
+ mockGetLoginDataWithSyncedDevices(false, 1)
+ );
+ await BrowserTestUtils.reloadTab(tab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const lockwiseScannedText = content.document.querySelector(
+ "#lockwise-scanned-text"
+ );
+ ok(
+ ContentTaskUtils.isVisible(lockwiseScannedText),
+ "Lockwise scanned text is visible"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ lockwiseScannedText.textContent ==
+ "1 password may have been exposed in a data breach."
+ );
+ info("Correct lockwise scanned text is shown");
+ });
+
+ info(
+ "Mock monitor data with more than one breached logins to test the Lockwise UI"
+ );
+ AboutProtectionsParent.setTestOverride(
+ mockGetLoginDataWithSyncedDevices(false, 2)
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const lockwiseScannedText = content.document.querySelector(
+ "#lockwise-scanned-text"
+ );
+ ok(
+ ContentTaskUtils.isVisible(lockwiseScannedText),
+ "Lockwise scanned text is visible"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ lockwiseScannedText.textContent ==
+ "2 passwords may have been exposed in a data breach."
+ );
+ info("Correct lockwise scanned text is shown");
+ });
+
+ AboutProtectionsParent.setTestOverride(null);
+ Services.logins.removeLogin(TEST_LOGIN1);
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testLockwiseCardPref() {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("Disable showing the Lockwise card.");
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.lockwise.enabled",
+ false
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const lockwiseCard = content.document.querySelector(".lockwise-card");
+ await ContentTaskUtils.waitForCondition(() => {
+ return !lockwiseCard["data-enabled"];
+ }, "Lockwise card is not enabled.");
+
+ ok(ContentTaskUtils.isHidden(lockwiseCard), "Lockwise card is hidden.");
+ });
+
+ // Set the pref back to displaying the card.
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.lockwise.enabled",
+ true
+ );
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/protections/test/browser/browser_protections_monitor.js b/browser/components/protections/test/browser/browser_protections_monitor.js
new file mode 100644
index 0000000000..b24d8de55c
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_monitor.js
@@ -0,0 +1,161 @@
+/* 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";
+
+const { AboutProtectionsParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutProtectionsParent.sys.mjs"
+);
+
+const monitorErrorData = {
+ error: true,
+};
+
+const mockMonitorData = {
+ numBreaches: 11,
+ numBreachesResolved: 0,
+};
+
+add_task(async function () {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ await BrowserTestUtils.reloadTab(tab);
+
+ const monitorCardEnabled = Services.prefs.getBoolPref(
+ "browser.contentblocking.report.monitor.enabled"
+ );
+
+ // Only run monitor card tests if it's enabled.
+ if (monitorCardEnabled) {
+ info(
+ "Check that the correct content is displayed for users with no logins."
+ );
+ await checkNoLoginsContentIsDisplayed(tab, "monitor-sign-up");
+
+ info(
+ "Check that the correct content is displayed for users with monitor data."
+ );
+ await Services.logins.addLoginAsync(TEST_LOGIN1);
+ AboutProtectionsParent.setTestOverride(mockGetMonitorData(mockMonitorData));
+ await BrowserTestUtils.reloadTab(tab);
+
+ Assert.ok(
+ true,
+ "Error was not thrown for trying to reach the Monitor endpoint, the cache has worked."
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const hasLogins = content.document.querySelector(
+ ".monitor-card.has-logins"
+ );
+ return hasLogins && ContentTaskUtils.isVisible(hasLogins);
+ }, "Monitor card for user with stored logins is shown.");
+
+ const hasLoginsHeaderContent = content.document.querySelector(
+ "#monitor-header-content span"
+ );
+ const cardBody = content.document.querySelector(
+ ".monitor-card .card-body"
+ );
+
+ ok(
+ ContentTaskUtils.isVisible(cardBody),
+ "Card body is shown for users monitor data."
+ );
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ hasLoginsHeaderContent.textContent ==
+ "Firefox Monitor warns you if your info has appeared in a known data breach."
+ );
+ }, "Header content for user with monitor data is correct.");
+
+ info("Make sure correct numbers for monitor stats are displayed.");
+ const emails = content.document.querySelector(
+ ".monitor-stat span[data-type='stored-emails']"
+ );
+ const passwords = content.document.querySelector(
+ ".monitor-stat span[data-type='exposed-passwords']"
+ );
+ const breaches = content.document.querySelector(
+ ".monitor-stat span[data-type='known-breaches']"
+ );
+
+ is(emails.textContent, 1, "1 monitored email is displayed");
+ is(passwords.textContent, 8, "8 exposed passwords are displayed");
+ is(breaches.textContent, 11, "11 known data breaches are displayed.");
+ });
+
+ info(
+ "Check that correct content is displayed when monitor data contains an error message."
+ );
+ AboutProtectionsParent.setTestOverride(
+ mockGetMonitorData(monitorErrorData)
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await checkNoLoginsContentIsDisplayed(tab);
+
+ info("Disable showing the Monitor card.");
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.monitor.enabled",
+ false
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ }
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const monitorCard = content.document.querySelector(".monitor-card");
+ return !monitorCard["data-enabled"];
+ }, "Monitor card is not enabled.");
+
+ const monitorCard = content.document.querySelector(".monitor-card");
+ ok(ContentTaskUtils.isHidden(monitorCard), "Monitor card is hidden.");
+ });
+
+ if (monitorCardEnabled) {
+ // set the pref back to displaying the card.
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.monitor.enabled",
+ true
+ );
+
+ // remove logins
+ Services.logins.removeLogin(TEST_LOGIN1);
+
+ // restore original test functions
+ AboutProtectionsParent.setTestOverride(null);
+ }
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+async function checkNoLoginsContentIsDisplayed(tab, expectedLinkContent) {
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const noLogins = content.document.querySelector(
+ ".monitor-card.no-logins"
+ );
+ return noLogins && ContentTaskUtils.isVisible(noLogins);
+ }, "Monitor card for user with no logins is shown.");
+
+ const noLoginsHeaderContent = content.document.querySelector(
+ "#monitor-header-content span"
+ );
+ const cardBody = content.document.querySelector(".monitor-card .card-body");
+
+ ok(
+ ContentTaskUtils.isHidden(cardBody),
+ "Card body is hidden for users with no logins."
+ );
+ is(
+ noLoginsHeaderContent.getAttribute("data-l10n-id"),
+ "monitor-header-content-no-account",
+ "Header content for user with no logins is correct"
+ );
+ });
+}
diff --git a/browser/components/protections/test/browser/browser_protections_proxy.js b/browser/components/protections/test/browser/browser_protections_proxy.js
new file mode 100644
index 0000000000..b5d5e396f5
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_proxy.js
@@ -0,0 +1,107 @@
+/* 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";
+
+ChromeUtils.defineESModuleGetters(this, {
+ Region: "resource://gre/modules/Region.sys.mjs",
+});
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.vpn_promo.enabled", false],
+ ],
+ });
+});
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("Secure Proxy card should be hidden by default");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ const proxyCard = content.document.querySelector(".proxy-card");
+ return !proxyCard["data-enabled"];
+ }, "Proxy card is not enabled.");
+
+ const proxyCard = content.document.querySelector(".proxy-card");
+ ok(ContentTaskUtils.isHidden(proxyCard), "Proxy card is hidden.");
+ });
+
+ info("Enable showing the Secure Proxy card");
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.proxy.enabled",
+ true
+ );
+
+ info(
+ "Check that secure proxy card is hidden if user's language is not en-US"
+ );
+ Services.prefs.setCharPref("intl.accept_languages", "en-CA");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkProxyCardVisibility(tab, true);
+
+ info(
+ "Check that secure proxy card is shown if user's location is in the US."
+ );
+ // Set language back to en-US
+ Services.prefs.setCharPref("intl.accept_languages", "en-US");
+ Region._setHomeRegion("US", false);
+ await BrowserTestUtils.reloadTab(tab);
+ await checkProxyCardVisibility(tab, false);
+
+ info(
+ "Check that secure proxy card is hidden if user's location is not in the US."
+ );
+ Region._setHomeRegion("CA", false);
+ await BrowserTestUtils.reloadTab(tab);
+ await checkProxyCardVisibility(tab, true);
+
+ info(
+ "Check that secure proxy card is hidden if the extension is already installed."
+ );
+ // Make sure we set the region back to "US"
+ Region._setHomeRegion("US", false);
+ const id = "secure-proxy@mozilla.com";
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ name: "Firefox Proxy",
+ },
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+ await BrowserTestUtils.reloadTab(tab);
+ await checkProxyCardVisibility(tab, true);
+ await extension.unload();
+
+ Services.prefs.setBoolPref(
+ "browser.contentblocking.report.proxy.enabled",
+ false
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+async function checkProxyCardVisibility(tab, shouldBeHidden) {
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ _shouldBeHidden: shouldBeHidden }],
+ async function ({ _shouldBeHidden }) {
+ await ContentTaskUtils.waitForCondition(() => {
+ const proxyCard = content.document.querySelector(".proxy-card");
+ return ContentTaskUtils.isHidden(proxyCard) === _shouldBeHidden;
+ });
+
+ const visibilityState = _shouldBeHidden ? "hidden" : "shown";
+ ok(true, `Proxy card is ${visibilityState}.`);
+ }
+ );
+}
diff --git a/browser/components/protections/test/browser/browser_protections_report_ui.js b/browser/components/protections/test/browser/browser_protections_report_ui.js
new file mode 100644
index 0000000000..bc099a10a9
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_report_ui.js
@@ -0,0 +1,1129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Note: This test may cause intermittents if run at exactly midnight.
+
+const { Sqlite } = ChromeUtils.importESModule(
+ "resource://gre/modules/Sqlite.sys.mjs"
+);
+const { AboutProtectionsParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutProtectionsParent.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "TrackingDBService",
+ "@mozilla.org/tracking-db-service;1",
+ "nsITrackingDBService"
+);
+
+ChromeUtils.defineLazyGetter(this, "DB_PATH", function () {
+ return PathUtils.join(PathUtils.profileDir, "protections.sqlite");
+});
+
+const SQL = {
+ insertCustomTimeEvent:
+ "INSERT INTO events (type, count, timestamp)" +
+ "VALUES (:type, :count, date(:timestamp));",
+
+ selectAll: "SELECT * FROM events",
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.database.enabled", true],
+ ["browser.vpn_promo.enabled", false],
+ ],
+ });
+});
+
+add_task(async function test_graph_display() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ let date = new Date().toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ date = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ date = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ date = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 3,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 8,
+ timestamp: date,
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const DATA_TYPES = [
+ "cryptominer",
+ "fingerprinter",
+ "tracker",
+ "cookie",
+ "social",
+ ];
+ let allBars = null;
+ await ContentTaskUtils.waitForCondition(() => {
+ allBars = content.document.querySelectorAll(".graph-bar");
+ return allBars.length;
+ }, "The graph has been built");
+
+ Assert.equal(allBars.length, 7, "7 bars have been found on the graph");
+
+ // For accessibility, test if the graph is a table
+ // and has a correct column count (number of data types + total + day)
+ Assert.equal(
+ content.document.getElementById("graph").getAttribute("role"),
+ "table",
+ "Graph is an accessible table"
+ );
+ Assert.equal(
+ content.document.getElementById("graph").getAttribute("aria-colcount"),
+ DATA_TYPES.length + 2,
+ "Table has the right number of columns"
+ );
+ Assert.equal(
+ content.document.getElementById("graph").getAttribute("aria-labelledby"),
+ "graphLegendDescription",
+ "Table has an accessible label"
+ );
+
+ // today has each type
+ // yesterday will have no tracking cookies
+ // 2 days ago will have no fingerprinters
+ // 3 days ago will have no cryptominers
+ // 4 days ago will have no trackers
+ // 5 days ago will have no social (when we add social)
+ // 6 days ago will be empty
+ Assert.equal(
+ allBars[6].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length,
+ "today has all of the data types shown"
+ );
+ Assert.equal(
+ allBars[6].getAttribute("role"),
+ "row",
+ "Today has the correct role"
+ );
+ Assert.equal(
+ allBars[6].getAttribute("aria-owns"),
+ "day0 count0 cryptominer0 fingerprinter0 tracker0 cookie0 social0",
+ "Row has the columns in the right order"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").style.height,
+ "10%",
+ "trackers take 10%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").parentNode.getAttribute("role"),
+ "cell",
+ "Trackers have the correct role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").getAttribute("role"),
+ "img",
+ "Tracker bar has the correct image role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").getAttribute("aria-label"),
+ "1 tracking content (10%)",
+ "Trackers have the correct accessible text"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cryptominer-bar").style.height,
+ "20%",
+ "cryptominers take 20%"
+ );
+ Assert.equal(
+ allBars[6]
+ .querySelector(".cryptominer-bar")
+ .parentNode.getAttribute("role"),
+ "cell",
+ "Cryptominers have the correct role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cryptominer-bar").getAttribute("role"),
+ "img",
+ "Cryptominer bar has the correct image role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cryptominer-bar").getAttribute("aria-label"),
+ "2 cryptominers (20%)",
+ "Cryptominers have the correct accessible label"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".fingerprinter-bar").style.height,
+ "20%",
+ "fingerprinters take 20%"
+ );
+ Assert.equal(
+ allBars[6]
+ .querySelector(".fingerprinter-bar")
+ .parentNode.getAttribute("role"),
+ "cell",
+ "Fingerprinters have the correct role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".fingerprinter-bar").getAttribute("role"),
+ "img",
+ "Fingerprinter bar has the correct image role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".fingerprinter-bar").getAttribute("aria-label"),
+ "2 fingerprinters (20%)",
+ "Fingerprinters have the correct accessible label"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").style.height,
+ "40%",
+ "cross site tracking cookies take 40%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").parentNode.getAttribute("role"),
+ "cell",
+ "cross site tracking cookies have the correct role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").getAttribute("role"),
+ "img",
+ "Cross site tracking cookies bar has the correct image role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").getAttribute("aria-label"),
+ "4 cross-site tracking cookies (40%)",
+ "cross site tracking cookies have the correct accessible label"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").style.height,
+ "10%",
+ "social trackers take 10%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").parentNode.getAttribute("role"),
+ "cell",
+ "social trackers have the correct role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").getAttribute("role"),
+ "img",
+ "social tracker bar has the correct image role"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").getAttribute("aria-label"),
+ "1 social media tracker (10%)",
+ "social trackers have the correct accessible text"
+ );
+
+ Assert.equal(
+ allBars[5].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length - 1,
+ "1 day ago is missing one type"
+ );
+ Assert.ok(
+ !allBars[5].querySelector(".cookie-bar"),
+ "there is no cross site tracking cookie section 1 day ago."
+ );
+ Assert.equal(
+ allBars[5].getAttribute("aria-owns"),
+ "day1 count1 cryptominer1 fingerprinter1 tracker1 social1",
+ "Row has the columns in the right order"
+ );
+
+ Assert.equal(
+ allBars[4].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length - 1,
+ "2 days ago is missing one type"
+ );
+ Assert.ok(
+ !allBars[4].querySelector(".fingerprinter-bar"),
+ "there is no fingerprinter section 1 day ago."
+ );
+ Assert.equal(
+ allBars[4].getAttribute("aria-owns"),
+ "day2 count2 cryptominer2 tracker2 cookie2 social2",
+ "Row has the columns in the right order"
+ );
+
+ Assert.equal(
+ allBars[3].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length - 1,
+ "3 days ago is missing one type"
+ );
+ Assert.ok(
+ !allBars[3].querySelector(".cryptominer-bar"),
+ "there is no cryptominer section 1 day ago."
+ );
+ Assert.equal(
+ allBars[3].getAttribute("aria-owns"),
+ "day3 count3 fingerprinter3 tracker3 cookie3 social3",
+ "Row has the columns in the right order"
+ );
+
+ Assert.equal(
+ allBars[2].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length - 1,
+ "4 days ago is missing one type"
+ );
+ Assert.ok(
+ !allBars[2].querySelector(".tracker-bar"),
+ "there is no tracker section 1 day ago."
+ );
+ Assert.equal(
+ allBars[2].getAttribute("aria-owns"),
+ "day4 count4 cryptominer4 fingerprinter4 cookie4 social4",
+ "Row has the columns in the right order"
+ );
+
+ Assert.equal(
+ allBars[1].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length - 1,
+ "5 days ago is missing one type"
+ );
+ Assert.ok(
+ !allBars[1].querySelector(".social-bar"),
+ "there is no social section 1 day ago."
+ );
+ Assert.equal(
+ allBars[1].getAttribute("aria-owns"),
+ "day5 count5 cryptominer5 fingerprinter5 tracker5 cookie5",
+ "Row has the columns in the right order"
+ );
+
+ Assert.equal(
+ allBars[0].querySelectorAll(".inner-bar").length,
+ 0,
+ "6 days ago has no content"
+ );
+ Assert.ok(
+ allBars[0].classList.contains("empty"),
+ "6 days ago is an empty bar"
+ );
+ Assert.equal(
+ allBars[0].getAttribute("aria-owns"),
+ "day6 ",
+ "Row has the columns in the right order"
+ );
+
+ // Check that each tab has the correct aria-labelledby and aria-describedby
+ // values. This helps screen readers know what type of tracker the reported
+ // tab number is referencing.
+ const socialTab = content.document.getElementById("tab-social");
+ Assert.equal(
+ socialTab.getAttribute("aria-labelledby"),
+ "socialLabel socialTitle",
+ "aria-labelledby attribute is socialLabel socialTitle"
+ );
+ Assert.equal(
+ socialTab.getAttribute("aria-describedby"),
+ "socialContent",
+ "aria-describedby attribute is socialContent"
+ );
+
+ const cookieTab = content.document.getElementById("tab-cookie");
+ Assert.equal(
+ cookieTab.getAttribute("aria-labelledby"),
+ "cookieLabel cookieTitle",
+ "aria-labelledby attribute is cookieLabel cookieTitle"
+ );
+ Assert.equal(
+ cookieTab.getAttribute("aria-describedby"),
+ "cookieContent",
+ "aria-describedby attribute is cookieContent"
+ );
+
+ const trackerTab = content.document.getElementById("tab-tracker");
+ Assert.equal(
+ trackerTab.getAttribute("aria-labelledby"),
+ "trackerLabel trackerTitle",
+ "aria-labelledby attribute is trackerLabel trackerTitle"
+ );
+ Assert.equal(
+ trackerTab.getAttribute("aria-describedby"),
+ "trackerContent",
+ "aria-describedby attribute is trackerContent"
+ );
+
+ const fingerprinterTab =
+ content.document.getElementById("tab-fingerprinter");
+ Assert.equal(
+ fingerprinterTab.getAttribute("aria-labelledby"),
+ "fingerprinterLabel fingerprinterTitle",
+ "aria-labelledby attribute is fingerprinterLabel fingerprinterTitle"
+ );
+ Assert.equal(
+ fingerprinterTab.getAttribute("aria-describedby"),
+ "fingerprinterContent",
+ "aria-describedby attribute is fingerprinterContent"
+ );
+
+ const cryptominerTab = content.document.getElementById("tab-cryptominer");
+ Assert.equal(
+ cryptominerTab.getAttribute("aria-labelledby"),
+ "cryptominerLabel cryptominerTitle",
+ "aria-labelledby attribute is cryptominerLabel cryptominerTitle"
+ );
+ Assert.equal(
+ cryptominerTab.getAttribute("aria-describedby"),
+ "cryptominerContent",
+ "aria-describedby attribute is cryptominerContent"
+ );
+ });
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ let rows = await db.execute(SQL.selectAll);
+ is(rows.length, 0, "length is 0");
+ await db.close();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Ensure that the number of suspicious fingerprinter is aggregated into the
+// fingerprinter category on about:protection page.
+add_task(async function test_suspicious_fingerprinter() {
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ // Inserting data for today. It won't contain a fingerprinter entry but only
+ // a suspicious fingerprinter entry.
+ let date = new Date().toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SUSPICIOUS_FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ // Inserting data for 1 day age. It contains both a fingerprinter entry and
+ // a suspicious fingerprinter entry.
+ date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.FINGERPRINTERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SUSPICIOUS_FINGERPRINTERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const DATA_TYPES = [
+ "cryptominer",
+ "fingerprinter",
+ "tracker",
+ "cookie",
+ "social",
+ ];
+ let allBars = null;
+ await ContentTaskUtils.waitForMutationCondition(
+ content.document.body,
+ { childList: true, subtree: true },
+ () => {
+ allBars = content.document.querySelectorAll(".graph-bar");
+ return !!allBars.length;
+ }
+ );
+ info("The graph has been built");
+
+ Assert.equal(allBars.length, 7, "7 bars have been found on the graph");
+
+ // Verify today's data. The fingerprinter category should take 20%.
+ Assert.equal(
+ allBars[6].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length,
+ "today has all of the data types shown"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").style.height,
+ "10%",
+ "trackers take 10%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cryptominer-bar").style.height,
+ "20%",
+ "cryptominers take 20%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".fingerprinter-bar").style.height,
+ "20%",
+ "fingerprinters take 20%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").style.height,
+ "40%",
+ "cross site tracking cookies take 40%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").style.height,
+ "10%",
+ "social trackers take 10%"
+ );
+
+ // Verify one day age data. The fingerprinter category should take 20%.
+ Assert.equal(
+ allBars[5].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length,
+ "today has all of the data types shown"
+ );
+ Assert.equal(
+ allBars[5].querySelector(".tracker-bar").style.height,
+ "10%",
+ "trackers take 10%"
+ );
+ Assert.equal(
+ allBars[5].querySelector(".cryptominer-bar").style.height,
+ "20%",
+ "cryptominers take 20%"
+ );
+ Assert.equal(
+ allBars[5].querySelector(".fingerprinter-bar").style.height,
+ "20%",
+ "fingerprinters take 20%"
+ );
+ Assert.equal(
+ allBars[5].querySelector(".cookie-bar").style.height,
+ "40%",
+ "cross site tracking cookies take 40%"
+ );
+ Assert.equal(
+ allBars[5].querySelector(".social-bar").style.height,
+ "10%",
+ "social trackers take 10%"
+ );
+ });
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ let rows = await db.execute(SQL.selectAll);
+ is(rows.length, 0, "length is 0");
+ await db.close();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Ensure that the number of suspicious fingerprinter is displayed even if the
+// fingerprinter blocking is disabled.
+add_task(async function test_suspicious_fingerprinter_without_fp_blocking() {
+ // Disable fingerprinter blocking
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ false
+ );
+
+ // This creates the schema.
+ await TrackingDBService.saveEvents(JSON.stringify({}));
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+
+ // Inserting data for today. It won't contain a fingerprinter entry but only
+ // a suspicious fingerprinter entry.
+ let date = new Date().toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.CRYPTOMINERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SUSPICIOUS_FINGERPRINTERS_ID,
+ count: 2,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKING_COOKIES_ID,
+ count: 4,
+ timestamp: date,
+ });
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.SOCIAL_ID,
+ count: 1,
+ timestamp: date,
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const DATA_TYPES = [
+ "cryptominer",
+ "fingerprinter",
+ "tracker",
+ "cookie",
+ "social",
+ ];
+ let allBars = null;
+ await ContentTaskUtils.waitForMutationCondition(
+ content.document.body,
+ { childList: true, subtree: true },
+ () => {
+ allBars = content.document.querySelectorAll(".graph-bar");
+ return !!allBars.length;
+ }
+ );
+ info("The graph has been built");
+
+ Assert.equal(allBars.length, 7, "7 bars have been found on the graph");
+
+ // Verify today's data. The fingerprinter category should take 20%.
+ Assert.equal(
+ allBars[6].querySelectorAll(".inner-bar").length,
+ DATA_TYPES.length,
+ "today has all of the data types shown"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".tracker-bar").style.height,
+ "10%",
+ "trackers take 10%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cryptominer-bar").style.height,
+ "20%",
+ "cryptominers take 20%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".fingerprinter-bar").style.height,
+ "20%",
+ "fingerprinters take 20%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".cookie-bar").style.height,
+ "40%",
+ "cross site tracking cookies take 40%"
+ );
+ Assert.equal(
+ allBars[6].querySelector(".social-bar").style.height,
+ "10%",
+ "social trackers take 10%"
+ );
+ });
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ let rows = await db.execute(SQL.selectAll);
+ is(rows.length, 0, "length is 0");
+ await db.close();
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(
+ "privacy.trackingprotection.fingerprinting.enabled"
+ );
+});
+
+// Ensure that each type of tracker is hidden from the graph if there are no recorded
+// trackers of that type and the user has chosen to not block that type.
+add_task(async function test_etp_custom_settings() {
+ Services.prefs.setStringPref("browser.contentblocking.category", "strict");
+ Services.prefs.setBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ true
+ );
+ // hide cookies from the graph
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let legend = content.document.getElementById("legend");
+ return ContentTaskUtils.isVisible(legend);
+ }, "The legend is visible");
+
+ let label = content.document.getElementById("cookieLabel");
+ Assert.ok(ContentTaskUtils.isHidden(label), "Cookie Label is hidden");
+
+ label = content.document.getElementById("trackerLabel");
+ Assert.ok(ContentTaskUtils.isVisible(label), "Tracker Label is visible");
+ label = content.document.getElementById("socialLabel");
+ Assert.ok(ContentTaskUtils.isVisible(label), "Social Label is visible");
+ label = content.document.getElementById("cryptominerLabel");
+ Assert.ok(
+ ContentTaskUtils.isVisible(label),
+ "Cryptominer Label is visible"
+ );
+ label = content.document.getElementById("fingerprinterLabel");
+ Assert.ok(
+ ContentTaskUtils.isVisible(label),
+ "Fingerprinter Label is visible"
+ );
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // hide ad trackers from the graph
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false);
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let legend = content.document.getElementById("legend");
+ return ContentTaskUtils.isVisible(legend);
+ }, "The legend is visible");
+
+ let label = content.document.querySelector("#trackerLabel");
+ Assert.ok(ContentTaskUtils.isHidden(label), "Tracker Label is hidden");
+
+ label = content.document.querySelector("#socialLabel");
+ Assert.ok(ContentTaskUtils.isHidden(label), "Social Label is hidden");
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // hide social from the graph
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.socialtracking.enabled",
+ false
+ );
+ Services.prefs.setBoolPref(
+ "privacy.socialtracking.block_cookies.enabled",
+ false
+ );
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let legend = content.document.getElementById("legend");
+ return ContentTaskUtils.isVisible(legend);
+ }, "The legend is visible");
+
+ let label = content.document.querySelector("#socialLabel");
+ Assert.ok(ContentTaskUtils.isHidden(label), "Social Label is hidden");
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // hide fingerprinting from the graph
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ false
+ );
+ Services.prefs.setBoolPref("privacy.fingerprintingProtection", false);
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let legend = content.document.getElementById("legend");
+ return ContentTaskUtils.isVisible(legend);
+ }, "The legend is visible");
+
+ let label = content.document.querySelector("#fingerprinterLabel");
+ Assert.ok(
+ ContentTaskUtils.isHidden(label),
+ "Fingerprinter Label is hidden"
+ );
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // hide cryptomining from the graph
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.cryptomining.enabled",
+ false
+ );
+ // Turn fingerprinting on so that all protectionsare not turned off, otherwise we will get a special card.
+ Services.prefs.setBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ true
+ );
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let legend = content.document.getElementById("legend");
+ return ContentTaskUtils.isVisible(legend);
+ }, "The legend is visible");
+
+ let label = content.document.querySelector("#cryptominerLabel");
+ Assert.ok(ContentTaskUtils.isHidden(label), "Cryptominer Label is hidden");
+ });
+ Services.prefs.clearUserPref("browser.contentblocking.category");
+ Services.prefs.clearUserPref(
+ "privacy.trackingprotection.fingerprinting.enabled"
+ );
+ Services.prefs.clearUserPref("privacy.fingerprintingProtection");
+ Services.prefs.clearUserPref(
+ "privacy.trackingprotection.cryptomining.enabled"
+ );
+ Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref("privacy.socialtracking.block_cookies.enabled");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Ensure that the Custom manage Protections card is shown if the user has all protections turned off.
+add_task(async function test_etp_custom_protections_off() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.category", "custom"],
+ ["network.cookie.cookieBehavior", 0], // not blocking
+ ["privacy.trackingprotection.cryptomining.enabled", false], // not blocking
+ ["privacy.trackingprotection.fingerprinting.enabled", false],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.socialtracking.enabled", false],
+ ["privacy.socialtracking.block_cookies.enabled", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ let aboutPreferencesPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let etpCard = content.document.querySelector(".etp-card");
+ return etpCard.classList.contains("custom-not-blocking");
+ }, "The custom protections warning card is showing");
+
+ let manageProtectionsButton =
+ content.document.getElementById("manage-protections");
+ Assert.ok(
+ ContentTaskUtils.isVisible(manageProtectionsButton),
+ "Button to manage protections is displayed"
+ );
+ });
+
+ // Custom protection card should show, even if there would otherwise be data on the graph.
+ let db = await Sqlite.openConnection({ path: DB_PATH });
+ let date = new Date().toISOString();
+ await db.execute(SQL.insertCustomTimeEvent, {
+ type: TrackingDBService.TRACKERS_ID,
+ count: 1,
+ timestamp: date,
+ });
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let etpCard = content.document.querySelector(".etp-card");
+ return etpCard.classList.contains("custom-not-blocking");
+ }, "The custom protections warning card is showing");
+
+ let manageProtectionsButton =
+ content.document.getElementById("manage-protections");
+ Assert.ok(
+ ContentTaskUtils.isVisible(manageProtectionsButton),
+ "Button to manage protections is displayed"
+ );
+
+ manageProtectionsButton.click();
+ });
+ let aboutPreferencesTab = await aboutPreferencesPromise;
+ info("about:preferences#privacy was successfully opened in a new tab");
+ gBrowser.removeTab(aboutPreferencesTab);
+
+ Services.prefs.setStringPref("browser.contentblocking.category", "standard");
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+ // Make sure the data was deleted.
+ let rows = await db.execute(SQL.selectAll);
+ is(rows.length, 0, "length is 0");
+ await db.close();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Ensure that the ETP mobile promotion card is shown when the pref is on and
+// there are no mobile devices connected.
+add_task(async function test_etp_mobile_promotion_pref_on() {
+ AboutProtectionsParent.setTestOverride(mockGetLoginDataWithSyncedDevices());
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.contentblocking.report.show_mobile_app", true]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let mobilePromotion = content.document.getElementById("mobile-hanger");
+ Assert.ok(
+ ContentTaskUtils.isVisible(mobilePromotion),
+ "Mobile promotions card is displayed when pref is on and there are no synced mobile devices"
+ );
+
+ // Card should hide after the X is clicked.
+ mobilePromotion.querySelector(".exit-icon").click();
+ Assert.ok(
+ ContentTaskUtils.isHidden(mobilePromotion),
+ "Mobile promotions card is no longer displayed after clicking the X button"
+ );
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // Add a mock mobile device. The promotion should now be hidden.
+ AboutProtectionsParent.setTestOverride(
+ mockGetLoginDataWithSyncedDevices(true)
+ );
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let mobilePromotion = content.document.getElementById("mobile-hanger");
+ Assert.ok(
+ ContentTaskUtils.isHidden(mobilePromotion),
+ "Mobile promotions card is hidden when pref is on if there are synced mobile devices"
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ AboutProtectionsParent.setTestOverride(null);
+});
+
+// Test that ETP mobile promotion is not shown when the pref is off,
+// even if no mobile devices are synced.
+add_task(async function test_etp_mobile_promotion_pref_on() {
+ AboutProtectionsParent.setTestOverride(mockGetLoginDataWithSyncedDevices());
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.contentblocking.report.show_mobile_app", false]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let mobilePromotion = content.document.getElementById("mobile-hanger");
+ Assert.ok(
+ ContentTaskUtils.isHidden(mobilePromotion),
+ "Mobile promotions card is not displayed when pref is off and there are no synced mobile devices"
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ AboutProtectionsParent.setTestOverride(
+ mockGetLoginDataWithSyncedDevices(true)
+ );
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let mobilePromotion = content.document.getElementById("mobile-hanger");
+ Assert.ok(
+ ContentTaskUtils.isHidden(mobilePromotion),
+ "Mobile promotions card is not displayed when pref is off even if there are synced mobile devices"
+ );
+ });
+ BrowserTestUtils.removeTab(tab);
+ AboutProtectionsParent.setTestOverride(null);
+});
+
+// Test that clicking on the link to settings in the header properly opens the settings page.
+add_task(async function test_settings_links() {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+ let aboutPreferencesPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const protectionSettings = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("protection-settings");
+ }, "protection-settings link exists");
+
+ protectionSettings.click();
+ });
+ let aboutPreferencesTab = await aboutPreferencesPromise;
+ info("about:preferences#privacy was successfully opened in a new tab");
+ gBrowser.removeTab(aboutPreferencesTab);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/protections/test/browser/browser_protections_telemetry.js b/browser/components/protections/test/browser/browser_protections_telemetry.js
new file mode 100644
index 0000000000..2073be23e9
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_telemetry.js
@@ -0,0 +1,1123 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "TrackingDBService",
+ "@mozilla.org/tracking-db-service;1",
+ "nsITrackingDBService"
+);
+
+const { AboutProtectionsParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutProtectionsParent.sys.mjs"
+);
+
+const LOG = {
+ "https://1.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1],
+ ],
+ "https://2.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT, true, 1],
+ ],
+ "https://3.example.com": [
+ [Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT, true, 2],
+ ],
+ "https://4.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 3],
+ ],
+ "https://5.example.com": [
+ [Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, true, 1],
+ ],
+ // Cookie blocked for other reason, then identified as a tracker
+ "https://6.example.com": [
+ [
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL |
+ Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT,
+ true,
+ 4,
+ ],
+ ],
+};
+
+requestLongerTimeout(2);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb"],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+
+ // Change the endpoints to prevent non-local network connections when landing on the page.
+ ["browser.contentblocking.report.monitor.url", ""],
+ ["browser.contentblocking.report.monitor.sign_in_url", ""],
+ ["browser.contentblocking.report.social.url", ""],
+ ["browser.contentblocking.report.cookie.url", ""],
+ ["browser.contentblocking.report.tracker.url", ""],
+ ["browser.contentblocking.report.fingerprinter.url", ""],
+ ["browser.contentblocking.report.cryptominer.url", ""],
+ ["browser.contentblocking.report.mobile-ios.url", ""],
+ ["browser.contentblocking.report.mobile-android.url", ""],
+ ["browser.contentblocking.report.monitor.home_page_url", ""],
+ ["browser.contentblocking.report.monitor.preferences_url", ""],
+ ["browser.contentblocking.report.vpn.url", ""],
+ ["browser.contentblocking.report.vpn-promo.url", ""],
+ ["browser.contentblocking.report.vpn-android.url", ""],
+ ["browser.contentblocking.report.vpn-ios.url", ""],
+ ],
+ });
+
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ // AboutProtectionsParent.setTestOverride(null);
+ });
+});
+
+add_task(async function checkTelemetryLoadEvents() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.database.enabled", false],
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["browser.vpn_promo.enabled", false],
+ ],
+ });
+ await addArbitraryTimeout();
+
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+
+ Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ let loadEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e => e[1] == "security.ui.protections" && e[2] == "show"
+ );
+ if (events.length == 1) {
+ return events;
+ }
+ }
+ return null;
+ }, "recorded telemetry for showing the report");
+
+ is(loadEvents.length, 1, `recorded telemetry for showing the report`);
+ await BrowserTestUtils.reloadTab(tab);
+ loadEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e => e[1] == "security.ui.protections" && e[2] == "close"
+ );
+ if (events.length == 1) {
+ return events;
+ }
+ }
+ return null;
+ }, "recorded telemetry for closing the report");
+
+ is(loadEvents.length, 1, `recorded telemetry for closing the report`);
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+function waitForTelemetryEventCount(count) {
+ info("waiting for telemetry event count of " + count);
+ return TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ ).content;
+ if (!events) {
+ return null;
+ }
+ // Ignore irrelevant events from other parts of the browser.
+ events = events.filter(e => e[1] == "security.ui.protections");
+ info("got " + (events && events.length) + " events");
+ if (events.length == count) {
+ return events;
+ }
+ return null;
+ }, "waiting for telemetry event count of: " + count);
+}
+
+let addArbitraryTimeout = async () => {
+ // There's an arbitrary interval of 2 seconds in which the content
+ // processes sync their event data with the parent process, we wait
+ // this out to ensure that we clear everything that is left over from
+ // previous tests and don't receive random events in the middle of our tests.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(c => setTimeout(c, 2000));
+};
+
+add_task(async function checkTelemetryClickEvents() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.database.enabled", true],
+ ["browser.contentblocking.report.monitor.enabled", true],
+ ["browser.contentblocking.report.lockwise.enabled", true],
+ ["browser.contentblocking.report.proxy.enabled", true],
+ ["browser.vpn_promo.enabled", false],
+ ],
+ });
+ await addArbitraryTimeout();
+
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+
+ Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ // Add user logins.
+ await Services.logins.addLoginAsync(TEST_LOGIN1);
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const managePasswordsButton = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("manage-passwords-button");
+ },
+ "Manage passwords button exists"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(managePasswordsButton),
+ "manage passwords button is visible"
+ );
+ managePasswordsButton.click();
+ });
+
+ let events = await waitForTelemetryEventCount(4);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "lw_open_button" &&
+ e[4] == "manage_passwords"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for lw_open_button when there are no breached passwords`
+ );
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Add breached logins.
+ AboutProtectionsParent.setTestOverride(
+ mockGetLoginDataWithSyncedDevices(false, 4)
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const managePasswordsButton = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("manage-passwords-button");
+ },
+ "Manage passwords button exists"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(managePasswordsButton),
+ "manage passwords button is visible"
+ );
+ managePasswordsButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(7);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "lw_open_button" &&
+ e[4] == "manage_breached_passwords"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for lw_open_button when there are breached passwords`
+ );
+ AboutProtectionsParent.setTestOverride(null);
+ Services.logins.removeLogin(TEST_LOGIN1);
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ await BrowserTestUtils.reloadTab(tab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ // Show all elements, so we can click on them, even though our user is not logged in.
+ let hidden_elements = content.document.querySelectorAll(".hidden");
+ for (let el of hidden_elements) {
+ el.style.display = "block ";
+ }
+
+ const savePasswordsButton = await ContentTaskUtils.waitForCondition(() => {
+ // Opens an extra tab
+ return content.document.getElementById("save-passwords-button");
+ }, "Save Passwords button exists");
+
+ savePasswordsButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(10);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "lw_open_button" &&
+ e[4] == "save_passwords"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for lw_open_button when there are no stored passwords`
+ );
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const lockwiseAboutLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("lockwise-how-it-works");
+ }, "lockwiseReportLink exists");
+
+ lockwiseAboutLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(11);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "lw_about_link"
+ );
+ is(events.length, 1, `recorded telemetry for lw_about_link`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let monitorAboutLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("monitor-link");
+ }, "monitorAboutLink exists");
+
+ monitorAboutLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(12);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_about_link"
+ );
+ is(events.length, 1, `recorded telemetry for mtr_about_link`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const signUpForMonitorLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("sign-up-for-monitor-link");
+ }, "signUpForMonitorLink exists");
+
+ signUpForMonitorLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(13);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_signup_button"
+ );
+ is(events.length, 1, `recorded telemetry for mtr_signup_button`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const socialLearnMoreLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("social-link");
+ }, "Learn more link for social tab exists");
+
+ socialLearnMoreLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(14);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "trackers_about_link" &&
+ e[4] == "social"
+ );
+ is(events.length, 1, `recorded telemetry for social trackers_about_link`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const cookieLearnMoreLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("cookie-link");
+ }, "Learn more link for cookie tab exists");
+
+ cookieLearnMoreLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(15);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "trackers_about_link" &&
+ e[4] == "cookie"
+ );
+ is(events.length, 1, `recorded telemetry for cookie trackers_about_link`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const trackerLearnMoreLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("tracker-link");
+ }, "Learn more link for tracker tab exists");
+
+ trackerLearnMoreLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(16);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "trackers_about_link" &&
+ e[4] == "tracker"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for content tracker trackers_about_link`
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const fingerprinterLearnMoreLink = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("fingerprinter-link");
+ },
+ "Learn more link for fingerprinter tab exists"
+ );
+
+ fingerprinterLearnMoreLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(17);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "trackers_about_link" &&
+ e[4] == "fingerprinter"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for fingerprinter trackers_about_link`
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const cryptominerLearnMoreLink = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("cryptominer-link");
+ },
+ "Learn more link for cryptominer tab exists"
+ );
+
+ cryptominerLearnMoreLink.click();
+ });
+
+ events = await waitForTelemetryEventCount(18);
+
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "trackers_about_link" &&
+ e[4] == "cryptominer"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for cryptominer trackers_about_link`
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const protectionSettings = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("protection-settings");
+ }, "protection-settings link exists");
+
+ protectionSettings.click();
+ });
+
+ events = await waitForTelemetryEventCount(19);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "settings_link" &&
+ e[4] == "header-settings"
+ );
+ is(events.length, 1, `recorded telemetry for settings_link header-settings`);
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const customProtectionSettings = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("manage-protections");
+ },
+ "manage-protections link exists"
+ );
+ // Show element so we can click on it
+ customProtectionSettings.style.display = "block";
+
+ customProtectionSettings.click();
+ });
+
+ events = await waitForTelemetryEventCount(20);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "settings_link" &&
+ e[4] == "custom-card-settings"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for settings_link custom-card-settings`
+ );
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Add breached logins and some resolved breaches.
+ AboutProtectionsParent.setTestOverride(
+ mockGetMonitorData({
+ potentiallyBreachedLogins: 4,
+ numBreaches: 3,
+ numBreachesResolved: 1,
+ })
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const resolveBreachesButton = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("monitor-partial-breaches-link");
+ },
+ "Monitor resolve breaches button exists"
+ );
+
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(resolveBreachesButton),
+ "Resolve breaches button is visible"
+ );
+
+ resolveBreachesButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(23);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "resolve_breaches"
+ );
+ is(events.length, 1, `recorded telemetry for resolve breaches button`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const monitorKnownBreachesBlock = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("monitor-known-breaches-link");
+ },
+ "Monitor card known breaches block exists"
+ );
+
+ monitorKnownBreachesBlock.click();
+ });
+
+ events = await waitForTelemetryEventCount(24);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "known_resolved_breaches"
+ );
+ is(events.length, 1, `recorded telemetry for monitor known breaches block`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const monitorExposedPasswordsBlock =
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById(
+ "monitor-exposed-passwords-link"
+ );
+ }, "Monitor card exposed passwords block exists");
+
+ monitorExposedPasswordsBlock.click();
+ });
+
+ events = await waitForTelemetryEventCount(25);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "exposed_passwords_unresolved_breaches"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for monitor exposed passwords block`
+ );
+
+ // Add breached logins and no resolved breaches.
+ AboutProtectionsParent.setTestOverride(
+ mockGetMonitorData({
+ potentiallyBreachedLogins: 4,
+ numBreaches: 3,
+ numBreachesResolved: 0,
+ })
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const manageBreachesButton = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("monitor-breaches-link");
+ }, "Monitor manage breaches button exists");
+
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(manageBreachesButton),
+ "Manage breaches button is visible"
+ );
+
+ manageBreachesButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(28);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "manage_breaches"
+ );
+ is(events.length, 1, `recorded telemetry for manage breaches button`);
+
+ // All breaches are resolved.
+ AboutProtectionsParent.setTestOverride(
+ mockGetMonitorData({
+ potentiallyBreachedLogins: 4,
+ numBreaches: 3,
+ numBreachesResolved: 3,
+ })
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const viewReportButton = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("monitor-breaches-link");
+ }, "Monitor view report button exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(viewReportButton),
+ "View report button is visible"
+ );
+
+ viewReportButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(31);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "view_report"
+ );
+ is(events.length, 1, `recorded telemetry for view report button`);
+
+ // No breaches are present.
+ AboutProtectionsParent.setTestOverride(
+ mockGetMonitorData({
+ potentiallyBreachedLogins: 4,
+ numBreaches: 0,
+ numBreachesResolved: 0,
+ })
+ );
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const viewReportButton = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("monitor-breaches-link");
+ }, "Monitor view report button exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(viewReportButton),
+ "View report button is visible"
+ );
+
+ viewReportButton.click();
+ });
+
+ events = await waitForTelemetryEventCount(34);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "view_report"
+ );
+ is(events.length, 2, `recorded telemetry for view report button`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const monitorEmailBlock = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("monitor-stored-emails-link");
+ }, "Monitor card email block exists");
+
+ monitorEmailBlock.click();
+ });
+
+ events = await waitForTelemetryEventCount(35);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "stored_emails"
+ );
+ is(events.length, 1, `recorded telemetry for monitor email block`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const monitorKnownBreachesBlock = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.getElementById("monitor-known-breaches-link");
+ },
+ "Monitor card known breaches block exists"
+ );
+
+ monitorKnownBreachesBlock.click();
+ });
+
+ events = await waitForTelemetryEventCount(36);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "known_unresolved_breaches"
+ );
+ is(events.length, 1, `recorded telemetry for monitor known breaches block`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const monitorExposedPasswordsBlock =
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById(
+ "monitor-exposed-passwords-link"
+ );
+ }, "Monitor card exposed passwords block exists");
+
+ monitorExposedPasswordsBlock.click();
+ });
+
+ events = await waitForTelemetryEventCount(37);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "mtr_report_link" &&
+ e[4] == "exposed_passwords_all_breaches"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for monitor exposed passwords block`
+ );
+
+ // Clean up.
+ AboutProtectionsParent.setTestOverride(null);
+ await BrowserTestUtils.removeTab(tab);
+});
+
+// This tests that telemetry is sent when saveEvents is called.
+add_task(async function test_save_telemetry() {
+ // Clear all scalar telemetry.
+ Services.telemetry.clearScalars();
+
+ await TrackingDBService.saveEvents(JSON.stringify(LOG));
+
+ const scalars = Services.telemetry.getSnapshotForScalars(
+ "main",
+ false
+ ).parent;
+ is(scalars["contentblocking.trackers_blocked_count"], 6);
+
+ // Use the TrackingDBService API to delete the data.
+ await TrackingDBService.clearAll();
+});
+
+// Test that telemetry is sent if entrypoint param is included,
+// and test that it is recorded as default if entrypoint param is not properly included
+add_task(async function checkTelemetryLoadEventForEntrypoint() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.database.enabled", false],
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["browser.vpn_promo.enabled", false],
+ ],
+ });
+ await addArbitraryTimeout();
+
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+
+ Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
+
+ info("Typo in 'entrypoint' should not be recorded");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections?entryPoint=newPage",
+ gBrowser,
+ });
+
+ let loadEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "show" &&
+ e[4] == "direct"
+ );
+ if (events.length == 1) {
+ return events;
+ }
+ }
+ return null;
+ }, "recorded telemetry for showing the report contains default 'direct' entrypoint");
+
+ is(
+ loadEvents.length,
+ 1,
+ `recorded telemetry for showing the report contains default 'direct' entrypoint`
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+
+ tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections?entrypoint=page",
+ gBrowser,
+ });
+
+ loadEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" && e[2] == "show" && e[4] == "page"
+ );
+ if (events.length == 1) {
+ return events;
+ }
+ }
+ return null;
+ }, "recorded telemetry for showing the report contains correct entrypoint");
+
+ is(
+ loadEvents.length,
+ 1,
+ "recorded telemetry for showing the report contains correct entrypoint"
+ );
+
+ // Clean up.
+ await BrowserTestUtils.removeTab(tab);
+});
+
+// This test is skipping due to failures on try, it passes locally.
+// Test that telemetry is sent from the vpn card
+add_task(async function checkTelemetryClickEventsVPN() {
+ if (Services.sysinfo.getProperty("name") != "Windows_NT") {
+ ok(true, "User is on an unsupported platform, the vpn card will not show");
+ return;
+ }
+ await addArbitraryTimeout();
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+ Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
+
+ // user is not subscribed to VPN, and is in the us
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false, "us"));
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb"],
+ ["browser.contentblocking.database.enabled", false],
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["browser.contentblocking.report.hide_vpn_banner", true],
+ ["browser.contentblocking.report.vpn-android.url", ""],
+ ["browser.contentblocking.report.vpn-ios.url", ""],
+ ["browser.contentblocking.report.vpn.url", ""],
+ ],
+ });
+ Services.locale.availableLocales = ["en-US"];
+ Services.locale.requestedLocales = ["en-US"];
+ await promiseSetHomeRegion("US");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("checking for vpn link");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const getVPNLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("get-vpn-link");
+ }, "get vpn link exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(getVPNLink),
+ "get vpn link is visible"
+ );
+ EventUtils.sendMouseEvent(
+ { type: "click", button: 1 },
+ getVPNLink,
+ content
+ );
+ });
+
+ let events = await waitForTelemetryEventCount(2);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "vpn_card_link"
+ );
+ is(
+ events.length,
+ 1,
+ `recorded telemetry for vpn_card_link when user is not subscribed`
+ );
+
+ // User is subscribed to VPN
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(true, "us"));
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const androidVPNLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("vpn-google-playstore-link");
+ }, "android vpn link exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(androidVPNLink),
+ "android vpn link is visible"
+ );
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document
+ .querySelector(".vpn-card")
+ .classList.contains("subscribed");
+ }, "subscribed class is added to the vpn card");
+
+ EventUtils.sendMouseEvent(
+ { type: "click", button: 1 },
+ androidVPNLink,
+ content
+ );
+ });
+
+ events = await waitForTelemetryEventCount(5);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "vpn_app_link_android"
+ );
+ is(events.length, 1, `recorded telemetry for vpn_app_link_android link`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const iosVPNLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("vpn-app-store-link");
+ }, "ios vpn link exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(iosVPNLink),
+ "ios vpn link is visible"
+ );
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document
+ .querySelector(".vpn-card")
+ .classList.contains("subscribed");
+ }, "subscribed class is added to the vpn card");
+
+ EventUtils.sendMouseEvent(
+ { type: "click", button: 1 },
+ iosVPNLink,
+ content
+ );
+ });
+
+ events = await waitForTelemetryEventCount(6);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "vpn_app_link_ios"
+ );
+ is(events.length, 1, `recorded telemetry for vpn_app_link_ios link`);
+
+ // Clean up.
+ await BrowserTestUtils.removeTab(tab);
+}).skip();
+
+// This test is skipping due to failures on try, it passes locally.
+// Test that telemetry is sent from the vpn banner
+add_task(async function checkTelemetryEventsVPNBanner() {
+ if (Services.sysinfo.getProperty("name") != "Windows_NT") {
+ ok(true, "User is on an unsupported platform, the vpn card will not show");
+ return;
+ }
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false, "us"));
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb"],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+ ["browser.contentblocking.database.enabled", false],
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["browser.contentblocking.report.hide_vpn_banner", false],
+ ["browser.contentblocking.report.vpn-promo.url", ""],
+ ],
+ });
+ await addArbitraryTimeout();
+
+ // The VPN banner only shows if the user is in en*
+ Services.locale.availableLocales = ["en-US"];
+ Services.locale.requestedLocales = ["en-US"];
+
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+
+ Services.telemetry.setEventRecordingEnabled("security.ui.protections", true);
+ // User is not subscribed to VPN
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false, "us"));
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const bannerVPNLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("vpn-banner-link");
+ }, "vpn banner link exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(bannerVPNLink),
+ "vpn banner link is visible"
+ );
+ EventUtils.sendMouseEvent(
+ { type: "click", button: 1 },
+ bannerVPNLink,
+ content
+ );
+ });
+
+ let events = await waitForTelemetryEventCount(3);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "vpn_banner_link"
+ );
+ is(events.length, 1, `recorded telemetry for vpn_banner_link`);
+
+ // VPN Banner flips this pref each time it shows, flip back between each instruction.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.contentblocking.report.hide_vpn_banner", false]],
+ });
+
+ await BrowserTestUtils.reloadTab(tab);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const bannerExitLink = await ContentTaskUtils.waitForCondition(() => {
+ return content.document.querySelector(".vpn-banner .exit-icon");
+ }, "vpn banner exit link exists");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(bannerExitLink),
+ "vpn banner exit link is visible"
+ );
+ EventUtils.sendMouseEvent(
+ { type: "click", button: 1 },
+ bannerExitLink,
+ content
+ );
+ });
+
+ events = await waitForTelemetryEventCount(7);
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.protections" &&
+ e[2] == "click" &&
+ e[3] == "vpn_banner_close"
+ );
+ is(events.length, 1, `recorded telemetry for vpn_banner_close`);
+
+ // Clean up.
+ await BrowserTestUtils.removeTab(tab);
+}).skip();
diff --git a/browser/components/protections/test/browser/browser_protections_vpn.js b/browser/components/protections/test/browser/browser_protections_vpn.js
new file mode 100644
index 0000000000..2b54982ba9
--- /dev/null
+++ b/browser/components/protections/test/browser/browser_protections_vpn.js
@@ -0,0 +1,282 @@
+/* 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";
+
+const { AboutProtectionsParent } = ChromeUtils.importESModule(
+ "resource:///actors/AboutProtectionsParent.sys.mjs"
+);
+
+let { Region } = ChromeUtils.importESModule(
+ "resource://gre/modules/Region.sys.mjs"
+);
+
+const initialHomeRegion = Region._home;
+const initialCurrentRegion = Region._current;
+
+async function checkVPNCardVisibility(tab, shouldBeHidden, subscribed = false) {
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ _shouldBeHidden: shouldBeHidden, _subscribed: subscribed }],
+ async function ({ _shouldBeHidden, _subscribed }) {
+ await ContentTaskUtils.waitForCondition(() => {
+ const vpnCard = content.document.querySelector(".vpn-card");
+ const subscribedStateCorrect =
+ vpnCard.classList.contains("subscribed") == _subscribed;
+ return (
+ ContentTaskUtils.isHidden(vpnCard) === _shouldBeHidden &&
+ subscribedStateCorrect
+ );
+ });
+
+ const visibilityState = _shouldBeHidden ? "hidden" : "shown";
+ ok(true, `VPN card is ${visibilityState}.`);
+ }
+ );
+}
+
+async function checkVPNPromoBannerVisibility(tab, shouldBeHidden) {
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ _shouldBeHidden: shouldBeHidden }],
+ async function ({ _shouldBeHidden }) {
+ await ContentTaskUtils.waitForCondition(() => {
+ const vpnBanner = content.document.querySelector(".vpn-banner");
+ return ContentTaskUtils.isHidden(vpnBanner) === _shouldBeHidden;
+ });
+
+ const visibilityState = _shouldBeHidden ? "hidden" : "shown";
+ ok(true, `VPN banner is ${visibilityState}.`);
+ }
+ );
+}
+
+async function setCurrentRegion(region) {
+ Region._setCurrentRegion(region);
+}
+
+async function setHomeRegion(region) {
+ // _setHomeRegion sets a char pref to the value of region. A non-string value will result in an error, so default to an empty string when region is falsey.
+ Region._setHomeRegion(region || "");
+}
+
+async function revertRegions() {
+ setCurrentRegion(initialCurrentRegion);
+ setHomeRegion(initialHomeRegion);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.vpn_promo.enabled", true],
+ ],
+ });
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("us");
+ const avLocales = Services.locale.availableLocales;
+
+ registerCleanupFunction(() => {
+ Services.locale.availableLocales = avLocales;
+ });
+});
+
+add_task(async function testVPNCardVisibility() {
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ await promiseSetHomeRegion("us");
+ setCurrentRegion("us");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("Enable showing the VPN card");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb"],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+ ],
+ });
+
+ info(
+ "Check that vpn card is hidden if neither the user's home nor current location is on the regions list."
+ );
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("ls");
+ await promiseSetHomeRegion("ls");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, true);
+
+ info(
+ "Check that vpn card is hidden if user's location is in the list of disallowed regions."
+ );
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("sy");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, true);
+
+ info(
+ "Check that vpn card shows a different version if user has subscribed to Mozilla vpn."
+ );
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(true));
+ setCurrentRegion("us");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, false, true);
+
+ info(
+ "VPN card should be hidden when vpn not enabled, though all other conditions are true"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.vpn_promo.enabled", false]],
+ });
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, true);
+
+ await BrowserTestUtils.removeTab(tab);
+ revertRegions();
+});
+
+add_task(async function testVPNPromoBanner() {
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("Enable showing the VPN card and banner");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb"],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+ ["browser.contentblocking.report.hide_vpn_banner", false],
+ ],
+ });
+
+ info("Check that vpn banner is shown if user's region is supported");
+ setCurrentRegion("us");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, false);
+
+ is(
+ Services.prefs.getBoolPref(
+ "browser.contentblocking.report.hide_vpn_banner",
+ false
+ ),
+ true,
+ "After showing the banner once, the pref to hide the VPN banner is flipped"
+ );
+ info("The banner does not show when the pref to hide it is flipped");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+
+ // VPN Banner flips this pref each time it shows, flip back between each instruction.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.contentblocking.report.hide_vpn_banner", false]],
+ });
+
+ info(
+ "Check that VPN banner is hidden if user's location is not on the regions list."
+ );
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("ls");
+ await setHomeRegion("ls'");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+
+ info(
+ "Check that VPN banner is hidden if user's location is in the disallowed regions list."
+ );
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("sy");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+
+ info(
+ "VPN banner should be hidden when vpn not enabled, though all other conditions are true"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", false],
+ ["browser.contentblocking.report.hide_vpn_banner", false],
+ ],
+ });
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.hide_vpn_banner", false],
+ ],
+ });
+
+ info("If user is subscribed to VPN already the promo banner should not show");
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(true));
+ setCurrentRegion("us");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+
+ await BrowserTestUtils.removeTab(tab);
+ revertRegions();
+});
+
+// Expect the vpn card and banner to not show as we are expressly excluding China. Even when cn is in the supported region pref.
+add_task(async function testVPNDoesNotShowChina() {
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ setCurrentRegion("us");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: "about:protections",
+ gBrowser,
+ });
+
+ info("Enable showing the VPN card and banners");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.vpn_promo.enabled", true],
+ ["browser.contentblocking.report.vpn_regions", "us,ca,nz,sg,my,gb,cn"],
+ [
+ "browser.vpn_promo.disallowed_regions",
+ "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
+ ],
+ ["browser.contentblocking.report.hide_vpn_banner", false],
+ ],
+ });
+
+ info(
+ "set home location to China, even though user is currently in the US, expect vpn card to be hidden"
+ );
+ await promiseSetHomeRegion("CN");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, true);
+
+ // VPN Banner flips this pref each time it shows, flip back between each instruction.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.contentblocking.report.hide_vpn_banner", false]],
+ });
+
+ info("home region is US, but current location is China");
+ AboutProtectionsParent.setTestOverride(getVPNOverrides(false));
+ await promiseSetHomeRegion("US");
+ setCurrentRegion("CN");
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNPromoBannerVisibility(tab, true);
+ await BrowserTestUtils.reloadTab(tab);
+ await checkVPNCardVisibility(tab, true);
+
+ await BrowserTestUtils.removeTab(tab);
+ revertRegions();
+});
diff --git a/browser/components/protections/test/browser/head.js b/browser/components/protections/test/browser/head.js
new file mode 100644
index 0000000000..9815869ee5
--- /dev/null
+++ b/browser/components/protections/test/browser/head.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-disable no-unused-vars */
+
+"use strict";
+
+const nsLoginInfo = new Components.Constructor(
+ "@mozilla.org/login-manager/loginInfo;1",
+ Ci.nsILoginInfo,
+ "init"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ Region: "resource://gre/modules/Region.sys.mjs",
+});
+
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+
+const TEST_LOGIN1 = new nsLoginInfo(
+ "https://example.com/",
+ "https://example.com/",
+ null,
+ "user1",
+ "pass1",
+ "username",
+ "password"
+);
+
+const TEST_LOGIN2 = new nsLoginInfo(
+ "https://2.example.com/",
+ "https://2.example.com/",
+ null,
+ "user2",
+ "pass2",
+ "username",
+ "password"
+);
+
+// Used to replace AboutProtectionsHandler.getLoginData in front-end tests.
+const mockGetLoginDataWithSyncedDevices = (
+ mobileDeviceConnected = false,
+ potentiallyBreachedLogins = 0
+) => {
+ return {
+ getLoginData: () => {
+ return {
+ numLogins: Services.logins.countLogins("", "", ""),
+ potentiallyBreachedLogins,
+ mobileDeviceConnected,
+ };
+ },
+ };
+};
+
+// Used to replace AboutProtectionsHandler.getMonitorData in front-end tests.
+const mockGetMonitorData = data => {
+ return {
+ getMonitorData: () => {
+ if (data.error) {
+ return data;
+ }
+
+ return {
+ monitoredEmails: 1,
+ numBreaches: data.numBreaches,
+ passwords: 8,
+ numBreachesResolved: data.numBreachesResolved,
+ passwordsResolved: 1,
+ error: false,
+ };
+ },
+ };
+};
+
+registerCleanupFunction(function head_cleanup() {
+ Services.logins.removeAllUserFacingLogins();
+});
+
+// Used to replace AboutProtectionsParent.VPNSubStatus
+const getVPNOverrides = (hasSubscription = false) => {
+ return {
+ vpnOverrides: () => {
+ return hasSubscription;
+ },
+ };
+};
+
+const promiseSetHomeRegion = async region => {
+ let promise = SearchTestUtils.promiseSearchNotification("engines-reloaded");
+ Region._setHomeRegion(region);
+ await promise;
+};