diff options
Diffstat (limited to '')
36 files changed, 5796 insertions, 0 deletions
diff --git a/browser/base/content/test/protectionsUI/benignPage.html b/browser/base/content/test/protectionsUI/benignPage.html new file mode 100644 index 0000000000..0be1cbc1c7 --- /dev/null +++ b/browser/base/content/test/protectionsUI/benignPage.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <!--TODO: We used to have an iframe here, to double-check that benign--> + <!--iframes may be included in pages. However, the cookie restrictions--> + <!--project introduced a change that declared blockable content to be--> + <!--found on any page that embeds iframes, rendering this unusable for--> + <!--our purposes. That's not ideal and we intend to restore this iframe.--> + <!--(See bug 1511303 for a more detailed technical explanation.)--> + <!--<iframe src="http://not-tracking.example.com/"></iframe>--> + </body> +</html> diff --git a/browser/base/content/test/protectionsUI/browser.ini b/browser/base/content/test/protectionsUI/browser.ini new file mode 100644 index 0000000000..ddfbff4883 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser.ini @@ -0,0 +1,61 @@ +[DEFAULT] +tags = trackingprotection +support-files = + head.js + benignPage.html + containerPage.html + cookiePage.html + cookieSetterPage.html + cookieServer.sjs + embeddedPage.html + trackingAPI.js + trackingPage.html + +[browser_protectionsUI.js] +https_first_disabled = true +skip-if = + os == 'linux' && !debug && tsan # Bug 1675107 +[browser_protectionsUI_3.js] +[browser_protectionsUI_background_tabs.js] +https_first_disabled = true +[browser_protectionsUI_categories.js] +https_first_disabled = true +[browser_protectionsUI_cookie_banner.js] +[browser_protectionsUI_cookies_subview.js] +https_first_disabled = true +[browser_protectionsUI_cryptominers.js] +https_first_disabled = true +[browser_protectionsUI_fetch.js] +https_first_disabled = true +support-files = + file_protectionsUI_fetch.html + file_protectionsUI_fetch.js + file_protectionsUI_fetch.js^headers^ +[browser_protectionsUI_fingerprinters.js] +https_first_disabled = true +[browser_protectionsUI_icon_state.js] +https_first_disabled = true +[browser_protectionsUI_milestones.js] +[browser_protectionsUI_open_preferences.js] +https_first_disabled = true +[browser_protectionsUI_pbmode_exceptions.js] +https_first_disabled = true +[browser_protectionsUI_report_breakage.js] +https_first_disabled = true +skip-if = debug || asan # Bug 1546797 +[browser_protectionsUI_socialtracking.js] +https_first_disabled = true +[browser_protectionsUI_shield_visibility.js] +support-files = + sandboxed.html + sandboxed.html^headers^ +[browser_protectionsUI_state.js] +https_first_disabled = true +[browser_protectionsUI_state_reset.js] +https_first_disabled = true +[browser_protectionsUI_telemetry.js] +https_first_disabled = true +[browser_protectionsUI_trackers_subview.js] +https_first_disabled = true +[browser_protectionsUI_subview_shim.js] +https_first_disabled = true diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI.js b/browser/base/content/test/protectionsUI/browser_protectionsUI.js new file mode 100644 index 0000000000..8dd901ea69 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI.js @@ -0,0 +1,712 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Basic UI tests for the protections panel */ + +"use strict"; + +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; + +ChromeUtils.defineModuleGetter( + this, + "ContentBlockingAllowList", + "resource://gre/modules/ContentBlockingAllowList.jsm" +); + +const { CustomizableUITestUtils } = ChromeUtils.import( + "resource://testing-common/CustomizableUITestUtils.jsm" +); + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Set the auto hide timing to 100ms for blocking the test less. + ["browser.protections_panel.toast.timeout", 100], + // Hide protections cards so as not to trigger more async messaging + // when landing on the page. + ["browser.contentblocking.report.monitor.enabled", false], + ["browser.contentblocking.report.lockwise.enabled", false], + ["browser.contentblocking.report.proxy.enabled", false], + ["privacy.trackingprotection.enabled", true], + ], + }); + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + Services.telemetry.clearEvents(); + + registerCleanupFunction(() => { + Services.telemetry.canRecordExtended = oldCanRecord; + Services.telemetry.clearEvents(); + }); +}); + +add_task(async function testToggleSwitch() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TRACKING_PAGE + ); + + await openProtectionsPanel(); + + await TestUtils.waitForCondition(() => { + return gProtectionsHandler._protectionsPopup.hasAttribute("blocking"); + }); + + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + let buttonEvents = events.filter( + e => + e[1] == "security.ui.protectionspopup" && + e[2] == "open" && + e[3] == "protections_popup" + ); + is(buttonEvents.length, 1, "recorded telemetry for opening the popup"); + + // Check the visibility of the "Site not working?" link. + ok( + BrowserTestUtils.is_visible( + gProtectionsHandler._protectionsPopupTPSwitchBreakageLink + ), + "The 'Site not working?' link should be visible." + ); + + // The 'Site Fixed?' link should be hidden. + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink + ), + "The 'Site Fixed?' link should be hidden." + ); + + // Navigate through the 'Site Not Working?' flow and back to the main view, + // checking for telemetry on the way. + let siteNotWorkingView = document.getElementById( + "protections-popup-siteNotWorkingView" + ); + let viewShown = BrowserTestUtils.waitForEvent( + siteNotWorkingView, + "ViewShown" + ); + gProtectionsHandler._protectionsPopupTPSwitchBreakageLink.click(); + await viewShown; + + checkClickTelemetry("sitenotworking_link"); + + let sendReportButton = document.getElementById( + "protections-popup-siteNotWorkingView-sendReport" + ); + let sendReportView = document.getElementById( + "protections-popup-sendReportView" + ); + viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown"); + sendReportButton.click(); + await viewShown; + + checkClickTelemetry("send_report_link"); + + viewShown = BrowserTestUtils.waitForEvent(siteNotWorkingView, "ViewShown"); + sendReportView.querySelector(".subviewbutton-back").click(); + await viewShown; + + let mainView = document.getElementById("protections-popup-mainView"); + + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + siteNotWorkingView.querySelector(".subviewbutton-back").click(); + await viewShown; + + ok( + gProtectionsHandler._protectionsPopupTPSwitch.hasAttribute("enabled"), + "TP Switch should be enabled" + ); + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gProtectionsHandler._protectionsPopupTPSwitch.click(); + + // The 'Site not working?' link should be hidden after clicking the TP switch. + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageLink + ), + "The 'Site not working?' link should be hidden after TP switch turns to off." + ); + // Same for the 'Site Fixed?' link + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink + ), + "The 'Site Fixed?' link should be hidden." + ); + + await popuphiddenPromise; + checkClickTelemetry("etp_toggle_off"); + + // We need to wait toast's popup shown and popup hidden events. It won't fire + // the popup shown event if we open the protections panel while the toast is + // opening. + let popupShownPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popupshown" + ); + popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + + await browserLoadedPromise; + + // Wait until the toast is shown and hidden. + await popupShownPromise; + await popuphiddenPromise; + + await openProtectionsPanel(); + ok( + !gProtectionsHandler._protectionsPopupTPSwitch.hasAttribute("enabled"), + "TP Switch should be disabled" + ); + + // The 'Site not working?' link should be hidden if the TP is off. + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageLink + ), + "The 'Site not working?' link should be hidden if TP is off." + ); + + // The 'Site Fixed?' link should be shown if TP is off. + ok( + BrowserTestUtils.is_visible( + gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink + ), + "The 'Site Fixed?' link should be visible." + ); + + // Check telemetry for 'Site Fixed?' link. + viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown"); + gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink.click(); + await viewShown; + + checkClickTelemetry("sitenotworking_link", "sitefixed"); + + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + sendReportView.querySelector(".subviewbutton-back").click(); + await viewShown; + + // Click the TP switch again and check the visibility of the 'Site not + // Working?'. It should be hidden after toggling the TP switch. + browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gProtectionsHandler._protectionsPopupTPSwitch.click(); + + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageLink + ), + `The 'Site not working?' link should be still hidden after toggling TP + switch to on from off.` + ); + ok( + BrowserTestUtils.is_hidden( + gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink + ), + "The 'Site Fixed?' link should be hidden." + ); + + await browserLoadedPromise; + checkClickTelemetry("etp_toggle_on"); + + ContentBlockingAllowList.remove(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for the protection settings button. + */ +add_task(async function testSettingsButton() { + // Open a tab and its protection panel. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await openProtectionsPanel(); + + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + let newTabPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + "about:preferences#privacy" + ); + gProtectionsHandler._protectionsPopupSettingsButton.click(); + + // The protection popup should be hidden after clicking settings button. + await popuphiddenPromise; + // Wait until the about:preferences has been opened correctly. + let newTab = await newTabPromise; + + ok(true, "about:preferences has been opened successfully"); + checkClickTelemetry("settings"); + + BrowserTestUtils.removeTab(newTab); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for ensuring Tracking Protection label is shown correctly + */ +add_task(async function testTrackingProtectionLabel() { + // Open a tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await openProtectionsPanel(); + + let trackingProtectionLabel = document.getElementById( + "protections-popup-footer-protection-type-label" + ); + + is( + trackingProtectionLabel.textContent, + "Custom", + "The label is correctly set to Custom." + ); + await closeProtectionsPanel(); + + Services.prefs.setStringPref("browser.contentblocking.category", "standard"); + await openProtectionsPanel(); + + is( + trackingProtectionLabel.textContent, + "Standard", + "The label is correctly set to Standard." + ); + await closeProtectionsPanel(); + + Services.prefs.setStringPref("browser.contentblocking.category", "strict"); + await openProtectionsPanel(); + is( + trackingProtectionLabel.textContent, + "Strict", + "The label is correctly set to Strict." + ); + + await closeProtectionsPanel(); + Services.prefs.setStringPref("browser.contentblocking.category", "custom"); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for the 'Show Full Report' button in the footer section. + */ +add_task(async function testShowFullReportButton() { + // Open a tab and its protection panel. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await openProtectionsPanel(); + + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + let newTabPromise = waitForAboutProtectionsTab(); + let showFullReportButton = document.getElementById( + "protections-popup-show-report-button" + ); + + showFullReportButton.click(); + + // The protection popup should be hidden after clicking the link. + await popuphiddenPromise; + // Wait until the 'about:protections' has been opened correctly. + let newTab = await newTabPromise; + + ok(true, "about:protections has been opened successfully"); + + checkClickTelemetry("full_report"); + + BrowserTestUtils.removeTab(newTab); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for ensuring the mini panel is working correctly + */ +add_task(async function testMiniPanel() { + // Open a tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + + // Open the mini panel. + await openProtectionsPanel(true); + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + + // Check that only the header is displayed. + let mainView = document.getElementById("protections-popup-mainView"); + for (let item of mainView.childNodes) { + if (item.id !== "protections-popup-mainView-panel-header-section") { + ok( + !BrowserTestUtils.is_visible(item), + `The section '${item.id}' is hidden in the toast.` + ); + } else { + ok( + BrowserTestUtils.is_visible(item), + "The panel header is displayed as the content of the toast." + ); + } + } + + // Wait until the auto hide is happening. + await popuphiddenPromise; + + ok(true, "The mini panel hides automatically."); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for the toggle switch flow + */ +add_task(async function testToggleSwitchFlow() { + // Open a tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await openProtectionsPanel(); + + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + let popupShownPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popupshown" + ); + let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // Click the TP switch, from On -> Off. + gProtectionsHandler._protectionsPopupTPSwitch.click(); + + // Check that the icon state has been changed. + ok( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + "The tracking protection icon state has been changed to disabled." + ); + + // The panel should be closed and the mini panel will show up after refresh. + await popuphiddenPromise; + await browserLoadedPromise; + await popupShownPromise; + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("toast"), + "The protections popup should have the 'toast' attribute." + ); + + // Click on the mini panel and making sure the protection popup shows up. + popupShownPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popupshown" + ); + popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + document.getElementById("protections-popup-mainView-panel-header").click(); + await popuphiddenPromise; + await popupShownPromise; + + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("toast"), + "The 'toast' attribute should be cleared on the protections popup." + ); + + // Click the TP switch again, from Off -> On. + popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + popupShownPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popupshown" + ); + browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gProtectionsHandler._protectionsPopupTPSwitch.click(); + + // Check that the icon state has been changed. + ok( + !gProtectionsHandler.iconBox.hasAttribute("hasException"), + "The tracking protection icon state has been changed to enabled." + ); + + // Protections popup hidden -> Page refresh -> Mini panel shows up. + await popuphiddenPromise; + popuphiddenPromise = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + await browserLoadedPromise; + await popupShownPromise; + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("toast"), + "The protections popup should have the 'toast' attribute." + ); + + // Wait until the auto hide is happening. + await popuphiddenPromise; + + // Clean up the TP state. + ContentBlockingAllowList.remove(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for ensuring the tracking protection icon will show a correct + * icon according to the TP enabling state. + */ +add_task(async function testTrackingProtectionIcon() { + // Open a tab and its protection panel. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + + let TPIcon = document.getElementById("tracking-protection-icon"); + // Check the icon url. It will show a shield icon if TP is enabled. + is( + gBrowser.ownerGlobal + .getComputedStyle(TPIcon) + .getPropertyValue("list-style-image"), + `url("chrome://browser/skin/tracking-protection.svg")`, + "The tracking protection icon shows a shield icon." + ); + + // Disable the tracking protection. + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + "https://example.com/" + ); + gProtectionsHandler.disableForCurrentPage(); + await browserLoadedPromise; + + // Check that the tracking protection icon should show a strike-through shield + // icon after page is reloaded. + is( + gBrowser.ownerGlobal + .getComputedStyle(TPIcon) + .getPropertyValue("list-style-image"), + `url("chrome://browser/skin/tracking-protection-disabled.svg")`, + "The tracking protection icon shows a strike through shield icon." + ); + + // Clean up the TP state. + ContentBlockingAllowList.remove(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); +}); + +/** + * A test for ensuring the number of blocked trackers is displayed properly. + */ +add_task(async function testNumberOfBlockedTrackers() { + // First, clear the tracking database. + await TrackingDBService.clearAll(); + + // Open a tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await openProtectionsPanel(); + + let trackerCounterBox = document.getElementById( + "protections-popup-trackers-blocked-counter-box" + ); + let trackerCounterDesc = document.getElementById( + "protections-popup-trackers-blocked-counter-description" + ); + + // Check that whether the counter is not shown if the number of blocked + // trackers is zero. + ok( + BrowserTestUtils.is_hidden(trackerCounterBox), + "The blocked tracker counter is hidden if there is no blocked tracker." + ); + + await closeProtectionsPanel(); + + // Add one tracker into the database and check that the tracker counter is + // properly shown. + await addTrackerDataIntoDB(1); + + // A promise for waiting the `showing` attributes has been set to the counter + // box. This means the database access is finished. + let counterShownPromise = BrowserTestUtils.waitForAttribute( + "showing", + trackerCounterBox + ); + + await openProtectionsPanel(); + await counterShownPromise; + + // Check that the number of blocked trackers is shown. + ok( + BrowserTestUtils.is_visible(trackerCounterBox), + "The blocked tracker counter is shown if there is one blocked tracker." + ); + is( + trackerCounterDesc.textContent, + "1 Blocked", + "The blocked tracker counter is correct." + ); + + await closeProtectionsPanel(); + await TrackingDBService.clearAll(); + + // Add trackers into the database and check that the tracker counter is + // properly shown as well as whether the pre-fetch is triggered by the + // keyboard navigation. + await addTrackerDataIntoDB(10); + + // We cannot wait for the change of "showing" attribute here since this + // attribute will only be set if the previous counter is zero. Instead, we + // wait for the change of the text content of the counter. + let updateCounterPromise = new Promise(resolve => { + let mut = new MutationObserver(mutations => { + resolve(); + mut.disconnect(); + }); + + mut.observe(trackerCounterDesc, { + childList: true, + }); + }); + + await openProtectionsPanelWithKeyNav(); + await updateCounterPromise; + + // Check that the number of blocked trackers is shown. + ok( + BrowserTestUtils.is_visible(trackerCounterBox), + "The blocked tracker counter is shown if there are more than one blocked tracker." + ); + is( + trackerCounterDesc.textContent, + "10 Blocked", + "The blocked tracker counter is correct." + ); + + await closeProtectionsPanel(); + await TrackingDBService.clearAll(); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testSubViewTelemetry() { + let items = [ + ["protections-popup-category-trackers", "trackers"], + ["protections-popup-category-socialblock", "social"], + ["protections-popup-category-cookies", "cookies"], + ["protections-popup-category-cryptominers", "cryptominers"], + ["protections-popup-category-fingerprinters", "fingerprinters"], + ].map(item => [document.getElementById(item[0]), item[1]]); + + for (let [item, telemetryId] of items) { + await BrowserTestUtils.withNewTab("http://www.example.com", async () => { + await openProtectionsPanel(); + + item.classList.remove("notFound"); // Force visible for test + gProtectionsHandler._categoryItemOrderInvalidated = true; + gProtectionsHandler.reorderCategoryItems(); + + let viewShownEvent = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopupMultiView, + "ViewShown" + ); + item.click(); + let panelView = (await viewShownEvent).originalTarget; + checkClickTelemetry(telemetryId); + let prefsTabPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + "about:preferences#privacy" + ); + panelView.querySelector(".panel-subview-footer-button").click(); + let prefsTab = await prefsTabPromise; + BrowserTestUtils.removeTab(prefsTab); + checkClickTelemetry("subview_settings", telemetryId); + }); + } +}); + +/** + * A test to make sure the TP state won't apply incorrectly if we quickly switch + * tab after toggling the TP switch. + */ +add_task(async function testQuickSwitchTabAfterTogglingTPSwitch() { + const FIRST_TEST_SITE = "https://example.com/"; + const SECOND_TEST_SITE = "https://example.org/"; + + // First, clear the tracking database. + await TrackingDBService.clearAll(); + + // Open two tabs with different origins. + let tabOne = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + FIRST_TEST_SITE + ); + let tabTwo = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + SECOND_TEST_SITE + ); + + // Open the protection panel of the second tab. + await openProtectionsPanel(); + + // A promise to check the reload happens on the second tab. + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tabTwo.linkedBrowser, + false, + SECOND_TEST_SITE + ); + + // Toggle the TP state and switch tab without waiting it to be finished. + gProtectionsHandler._protectionsPopupTPSwitch.click(); + gBrowser.selectedTab = tabOne; + + // Wait for the second tab to be reloaded. + await browserLoadedPromise; + + // Check that the first tab is still with ETP enabled. + ok( + !ContentBlockingAllowList.includes(gBrowser.selectedBrowser), + "The ETP state of the first tab is still enabled." + ); + + // Check the ETP is disabled on the second origin. + ok( + ContentBlockingAllowList.includes(tabTwo.linkedBrowser), + "The ETP state of the second tab has been changed to disabled." + ); + + // Clean up the state of the allow list for the second tab. + ContentBlockingAllowList.remove(tabTwo.linkedBrowser); + + BrowserTestUtils.removeTab(tabOne); + BrowserTestUtils.removeTab(tabTwo); + + // Finally, clear the tracking database. + await TrackingDBService.clearAll(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js new file mode 100644 index 0000000000..1a747e054b --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js @@ -0,0 +1,64 @@ +/* + * Test that the Tracking Protection is correctly enabled / disabled + * in both normal and private windows given all possible states of the prefs: + * privacy.trackingprotection.enabled + * privacy.trackingprotection.pbmode.enabled + * See also Bug 1178985. + */ + +const PREF = "privacy.trackingprotection.enabled"; +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(PB_PREF); +}); + +add_task(async function testNormalBrowsing() { + let { + TrackingProtection, + } = gBrowser.ownerGlobal.gProtectionsHandler.blockers; + ok( + TrackingProtection, + "Normal window gProtectionsHandler should have TrackingProtection blocker." + ); + + Services.prefs.setBoolPref(PREF, true); + Services.prefs.setBoolPref(PB_PREF, false); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)"); + + Services.prefs.setBoolPref(PREF, false); + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=true)"); +}); + +add_task(async function testPrivateBrowsing() { + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let { + TrackingProtection, + } = privateWin.gBrowser.ownerGlobal.gProtectionsHandler.blockers; + ok( + TrackingProtection, + "Private window gProtectionsHandler should have TrackingProtection blocker." + ); + + Services.prefs.setBoolPref(PREF, true); + Services.prefs.setBoolPref(PB_PREF, false); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)"); + + Services.prefs.setBoolPref(PREF, false); + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=false,PB=true)"); + + privateWin.close(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js new file mode 100644 index 0000000000..055c75aae8 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; + +const TP_PREF = "privacy.trackingprotection.enabled"; + +add_task(async function testBackgroundTabs() { + info( + "Testing receiving and storing content blocking events in non-selected tabs." + ); + + await SpecialPowers.pushPrefEnv({ + set: [[TP_PREF, true]], + }); + await UrlClassifierTestUtils.addTestTrackers(); + + registerCleanupFunction(() => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BENIGN_PAGE); + + let backgroundTab = BrowserTestUtils.addTab(gBrowser); + let browser = backgroundTab.linkedBrowser; + let hasContentBlockingEvent = TestUtils.waitForCondition( + () => browser.getContentBlockingEvents() != 0 + ); + await promiseTabLoadEvent(backgroundTab, TRACKING_PAGE); + await hasContentBlockingEvent; + + is( + browser.getContentBlockingEvents(), + Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, + "Background tab has the correct content blocking event." + ); + + is( + tab.linkedBrowser.getContentBlockingEvents(), + 0, + "Foreground tab has the correct content blocking event." + ); + + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + await BrowserTestUtils.switchTab(gBrowser, backgroundTab); + + is( + browser.getContentBlockingEvents(), + Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, + "Background tab still has the correct content blocking event." + ); + + is( + tab.linkedBrowser.getContentBlockingEvents(), + 0, + "Foreground tab still has the correct content blocking event." + ); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + + gBrowser.removeTab(backgroundTab); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js new file mode 100644 index 0000000000..f8a327c9e1 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js @@ -0,0 +1,290 @@ +const CAT_PREF = "browser.contentblocking.category"; +const TP_PREF = "privacy.trackingprotection.enabled"; +const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const TPC_PREF = "network.cookie.cookieBehavior"; +const CM_PREF = "privacy.trackingprotection.cryptomining.enabled"; +const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled"; +const ST_PREF = "privacy.trackingprotection.socialtracking.enabled"; +const STC_PREF = "privacy.socialtracking.block_cookies.enabled"; + +const { CustomizableUITestUtils } = ChromeUtils.import( + "resource://testing-common/CustomizableUITestUtils.jsm" +); + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(CAT_PREF); + Services.prefs.clearUserPref(TP_PREF); + Services.prefs.clearUserPref(TP_PB_PREF); + Services.prefs.clearUserPref(TPC_PREF); + Services.prefs.clearUserPref(CM_PREF); + Services.prefs.clearUserPref(FP_PREF); + Services.prefs.clearUserPref(ST_PREF); + Services.prefs.clearUserPref(STC_PREF); +}); + +add_task(async function testCookieCategoryLabel() { + await BrowserTestUtils.withNewTab("http://www.example.com", async function() { + // Ensure the category nodes exist. + await openProtectionsPanel(); + await closeProtectionsPanel(); + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + let categoryLabel = document.getElementById( + "protections-popup-cookies-category-label" + ); + + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + await TestUtils.waitForCondition( + () => !categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(!categoryItem.classList.contains("blocked")); + + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT); + await TestUtils.waitForCondition( + () => categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(categoryItem.classList.contains("blocked")); + await TestUtils.waitForCondition( + () => + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blockingAll2.label" + ), + "The category label has updated correctly" + ); + ok( + categoryLabel.textContent == + gNavigatorBundle.getString("contentBlocking.cookies.blockingAll2.label") + ); + + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN + ); + await TestUtils.waitForCondition( + () => categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(categoryItem.classList.contains("blocked")); + await TestUtils.waitForCondition( + () => + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blocking3rdParty2.label" + ), + "The category label has updated correctly" + ); + ok( + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blocking3rdParty2.label" + ) + ); + + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + await TestUtils.waitForCondition( + () => categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(categoryItem.classList.contains("blocked")); + await TestUtils.waitForCondition( + () => + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blockingTrackers3.label" + ), + "The category label has updated correctly" + ); + ok( + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blockingTrackers3.label" + ) + ); + + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN + ); + await TestUtils.waitForCondition( + () => categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(categoryItem.classList.contains("blocked")); + await TestUtils.waitForCondition( + () => + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blockingTrackers3.label" + ), + "The category label has updated correctly" + ); + ok( + categoryLabel.textContent == + gNavigatorBundle.getString( + "contentBlocking.cookies.blockingTrackers3.label" + ) + ); + + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN + ); + await TestUtils.waitForCondition( + () => !categoryItem.classList.contains("blocked"), + "The category label has updated correctly" + ); + ok(!categoryItem.classList.contains("blocked")); + }); +}); + +let categoryEnabledPrefs = [TP_PREF, STC_PREF, TPC_PREF, CM_PREF, FP_PREF]; + +let detectedStateFlags = [ + Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, + Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT, + Ci.nsIWebProgressListener.STATE_COOKIES_LOADED, + Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT, + Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT, +]; + +async function waitForClass(item, className, shouldBePresent = true) { + await TestUtils.waitForCondition(() => { + return item.classList.contains(className) == shouldBePresent; + }, `Target class ${className} should be ${shouldBePresent ? "present" : "not present"} on item ${item.id}`); + + ok( + item.classList.contains(className) == shouldBePresent, + `item.classList.contains(${className}) is ${shouldBePresent} for ${item.id}` + ); +} + +add_task(async function testCategorySections() { + Services.prefs.setBoolPref(ST_PREF, true); + + for (let pref of categoryEnabledPrefs) { + if (pref == TPC_PREF) { + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + } else { + Services.prefs.setBoolPref(pref, false); + } + } + + await BrowserTestUtils.withNewTab("http://www.example.com", async function() { + // Ensure the category nodes exist. + await openProtectionsPanel(); + await closeProtectionsPanel(); + + let categoryItems = [ + "protections-popup-category-trackers", + "protections-popup-category-socialblock", + "protections-popup-category-cookies", + "protections-popup-category-cryptominers", + "protections-popup-category-fingerprinters", + ].map(id => document.getElementById(id)); + + for (let item of categoryItems) { + await waitForClass(item, "notFound"); + await waitForClass(item, "blocked", false); + } + + // For every item, we enable the category and spoof a content blocking event, + // and check that .notFound goes away and .blocked is set. Then we disable the + // category and checks that .blocked goes away, and .notFound is still unset. + let contentBlockingState = 0; + for (let i = 0; i < categoryItems.length; i++) { + let itemToTest = categoryItems[i]; + let enabledPref = categoryEnabledPrefs[i]; + contentBlockingState |= detectedStateFlags[i]; + if (enabledPref == TPC_PREF) { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT + ); + } else { + Services.prefs.setBoolPref(enabledPref, true); + } + gProtectionsHandler.onContentBlockingEvent(contentBlockingState); + gProtectionsHandler.updatePanelForBlockingEvent(contentBlockingState); + await waitForClass(itemToTest, "notFound", false); + await waitForClass(itemToTest, "blocked", true); + if (enabledPref == TPC_PREF) { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_ACCEPT + ); + } else { + Services.prefs.setBoolPref(enabledPref, false); + } + await waitForClass(itemToTest, "notFound", false); + await waitForClass(itemToTest, "blocked", false); + } + }); +}); + +/** + * Check that when we open the popup in a new window, the initial state is correct + * wrt the pref. + */ +add_task(async function testCategorySectionInitial() { + let categoryItems = [ + "protections-popup-category-trackers", + "protections-popup-category-socialblock", + "protections-popup-category-cookies", + "protections-popup-category-cryptominers", + "protections-popup-category-fingerprinters", + ]; + for (let i = 0; i < categoryItems.length; i++) { + for (let shouldBlock of [true, false]) { + let win = await BrowserTestUtils.openNewBrowserWindow(); + // Open non-about: page so our protections are active. + await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + "https://example.com/" + ); + let enabledPref = categoryEnabledPrefs[i]; + let contentBlockingState = detectedStateFlags[i]; + if (enabledPref == TPC_PREF) { + Services.prefs.setIntPref( + TPC_PREF, + shouldBlock + ? Ci.nsICookieService.BEHAVIOR_REJECT + : Ci.nsICookieService.BEHAVIOR_ACCEPT + ); + } else { + Services.prefs.setBoolPref(enabledPref, shouldBlock); + } + win.gProtectionsHandler.onContentBlockingEvent(contentBlockingState); + await openProtectionsPanel(false, win); + let categoryItem = win.document.getElementById(categoryItems[i]); + let expectedFound = true; + // Accepting cookies outright won't mark this as found. + if (i == 2 && !shouldBlock) { + // See bug 1653019 + expectedFound = false; + } + is( + categoryItem.classList.contains("notFound"), + !expectedFound, + `Should have found ${categoryItems[i]} when it was ${ + shouldBlock ? "blocked" : "allowed" + }` + ); + is( + categoryItem.classList.contains("blocked"), + shouldBlock, + `Should ${shouldBlock ? "have blocked" : "not have blocked"} ${ + categoryItems[i] + }` + ); + await closeProtectionsPanel(win); + await BrowserTestUtils.closeWindow(win); + } + } +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js new file mode 100644 index 0000000000..511a0c33e7 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js @@ -0,0 +1,388 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the cookie banner handling section in the protections panel. + */ + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const { + MODE_DISABLED, + MODE_REJECT, + MODE_REJECT_OR_ACCEPT, + MODE_DETECT_ONLY, + MODE_UNSET, +} = Ci.nsICookieBannerService; + +/** + * Determines whether the cookie banner section in the protections panel should + * be visible with the given configuration. + * @param {*} options - Configuration to test. + * @param {Number} options.featureMode - nsICookieBannerService::Modes value for + * normal browsing. + * @param {Number} options.featureModePBM - nsICookieBannerService::Modes value + * for private browsing. + * @param {boolean} options.visibilityPref - State of the cookie banner UI + * visibility pref. + * @param {boolean} options.testPBM - Whether the window is in private browsing + * mode (true) or not (false). + * @returns {boolean} Whether the section should be visible for the given + * config. + */ +function cookieBannerSectionIsVisible({ + featureMode, + featureModePBM, + visibilityPref, + testPBM, +}) { + if (!visibilityPref) { + return false; + } + + return ( + (testPBM && + featureModePBM != MODE_DISABLED && + featureModePBM != MODE_DETECT_ONLY) || + (!testPBM && + featureMode != MODE_DISABLED && + featureMode != MODE_DETECT_ONLY) + ); +} + +/** + * Runs a visibility test of the cookie banner section in the protections panel. + * @param {*} options - Test options. + * @param {Window} options.win - Browser window to use for testing. It's + * browsing mode should match the testPBM variable. + * @param {Number} options.featureMode - nsICookieBannerService::Modes value for + * normal browsing. + * @param {Number} options.featureModePBM - nsICookieBannerService::Modes value + * for private browsing. + * @param {boolean} options.visibilityPref - State of the cookie banner UI + * visibility pref. + * @param {boolean} options.testPBM - Whether the window is in private browsing + * mode (true) or not (false). + * @returns {Promise} Resolves once the test is complete. + */ +async function testSectionVisibility({ + win, + featureMode, + featureModePBM, + visibilityPref, + testPBM, +}) { + info( + "testSectionVisibility " + + JSON.stringify({ featureMode, featureModePBM, visibilityPref, testPBM }) + ); + // initialize the pref environment + await SpecialPowers.pushPrefEnv({ + set: [ + ["cookiebanners.service.mode", featureMode], + ["cookiebanners.service.mode.privateBrowsing", featureModePBM], + ["cookiebanners.ui.desktop.enabled", visibilityPref], + ], + }); + + // Open a tab with example.com so the protections panel can be opened. + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url: "https://example.com" }, + async () => { + await openProtectionsPanel(null, win); + + // Get panel elements to test + let el = { + section: win.document.getElementById( + "protections-popup-cookie-banner-section" + ), + sectionSeparator: win.document.getElementById( + "protections-popup-cookie-banner-section-separator" + ), + switch: win.document.getElementById( + "protections-popup-cookie-banner-switch" + ), + }; + + let expectVisible = cookieBannerSectionIsVisible({ + featureMode, + featureModePBM, + visibilityPref, + testPBM, + }); + is( + BrowserTestUtils.is_visible(el.section), + expectVisible, + `Cookie banner section should be ${ + expectVisible ? "visible" : "not visible" + }.` + ); + is( + BrowserTestUtils.is_visible(el.sectionSeparator), + expectVisible, + `Cookie banner section separator should be ${ + expectVisible ? "visible" : "not visible" + }.` + ); + is( + BrowserTestUtils.is_visible(el.switch), + expectVisible, + `Cookie banner switch should be ${ + expectVisible ? "visible" : "not visible" + }.` + ); + } + ); + + await SpecialPowers.popPrefEnv(); +} + +/** + * Tests cookie banner section visibility state in different configurations. + */ +add_task(async function test_section_visibility() { + // Test all combinations of cookie banner service modes and normal and + // private browsing. + for (let testPBM of [false, true]) { + let win = window; + // Open a new private window to test the panel in for testing PBM, otherwise + // reuse the existing window. + if (testPBM) { + win = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + win.focus(); + } + + for (let featureMode of [ + MODE_DISABLED, + MODE_REJECT, + MODE_REJECT_OR_ACCEPT, + MODE_DETECT_ONLY, + ]) { + for (let featureModePBM of [ + MODE_DISABLED, + MODE_REJECT, + MODE_REJECT_OR_ACCEPT, + MODE_DETECT_ONLY, + ]) { + await testSectionVisibility({ + win, + featureMode, + featureModePBM, + testPBM, + visibilityPref: true, + }); + } + } + + if (testPBM) { + await BrowserTestUtils.closeWindow(win); + } + } +}); + +/** + * Tests that the cookie banner section is only visible if enabled by UI pref. + */ +add_task(async function test_section_visibility_pref() { + for (let visibilityPref of [false, true]) { + await testSectionVisibility({ + win: window, + featureMode: MODE_REJECT, + featureModePBM: MODE_DISABLED, + testPBM: false, + visibilityPref, + }); + } +}); + +/** + * Test the state of the per-site exception switch in the cookie banner section + * and whether a matching per-site exception is set. + * @param {*} options + * @param {Window} options.win - Chrome window to test exception for (selected + * tab). + * @param {boolean} options.isPBM - Whether the given window is in private + * browsing mode. + * @param {boolean} options.expectEnabled - Whether the switch is expected to be + * enabled, this also indicates no exception set. + */ +function assertSwitchAndPrefState({ win, isPBM, expectEnabled }) { + let el = { + switch: win.document.getElementById( + "protections-popup-cookie-banner-switch" + ), + labelON: win.document.querySelector( + ".protections-popup-cookie-banner-switch-on-header" + ), + labelOFF: win.document.querySelector( + ".protections-popup-cookie-banner-switch-off-header" + ), + }; + + info("Test switch state."); + ok(BrowserTestUtils.is_visible(el.switch), "Switch should be visible"); + is( + el.switch.hasAttribute("enabled"), + expectEnabled, + `Switch is ${expectEnabled ? "enabled" : "disabled"}.` + ); + + info("Test switch labels."); + if (expectEnabled) { + ok(BrowserTestUtils.is_visible(el.labelON), "ON label should be visible"); + ok( + !BrowserTestUtils.is_visible(el.labelOFF), + "OFF label should not be visible" + ); + } else { + ok( + !BrowserTestUtils.is_visible(el.labelON), + "ON label should not be visible" + ); + ok(BrowserTestUtils.is_visible(el.labelOFF), "OFF label should be visible"); + } + + info("Test per-site exception state."); + let currentURI = win.gBrowser.currentURI; + let pref = Services.cookieBanners.getDomainPref(currentURI, isPBM); + + if (expectEnabled) { + is( + pref, + MODE_UNSET, + `There should be no per-site exception for ${currentURI.spec}.` + ); + } else { + is( + pref, + MODE_DISABLED, + `There should be a per-site exception for ${currentURI.spec}.` + ); + } +} + +/** + * Test the telemetry associated with the cookie banner toggle. To be called + * after interacting with the toggle. + * @param {*} options + * @param {boolean|null} - Expected telemetry state matching the button state. + * button on = true = cookieb_toggle_on event. Pass null to expect no event + * recorded. + */ +function assertTelemetryState({ expectEnabled = null } = {}) { + info("Test telemetry state."); + + let events = []; + const CATEGORY = "security.ui.protectionspopup"; + const METHOD = "click"; + + if (expectEnabled != null) { + events.push({ + category: CATEGORY, + method: METHOD, + object: expectEnabled ? "cookieb_toggle_on" : "cookieb_toggle_off", + }); + } + + // Assert event state and clear event list. + TelemetryTestUtils.assertEvents(events, { + category: CATEGORY, + method: METHOD, + }); +} + +/** + * Tests the cookie banner section per-site preference toggle. + */ +add_task(async function test_section_toggle() { + // initialize the pref environment + await SpecialPowers.pushPrefEnv({ + set: [ + ["cookiebanners.service.mode", MODE_REJECT_OR_ACCEPT], + ["cookiebanners.service.mode.privateBrowsing", MODE_REJECT], + ["cookiebanners.ui.desktop.enabled", true], + ], + }); + + // Test both normal and private browsing windows. For normal windows we reuse + // the existing one, for private windows we need to open a new window. + for (let testPBM of [false, true]) { + let win = window; + if (testPBM) { + win = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + } + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url: "https://example.com" }, + async () => { + await openProtectionsPanel(null, win); + + let switchEl = win.document.getElementById( + "protections-popup-cookie-banner-switch" + ); + + info("Testing initial switch ON state."); + assertSwitchAndPrefState({ + win, + isPBM: testPBM, + switchEl, + expectEnabled: true, + }); + assertTelemetryState(); + + info("Testing switch state after toggle OFF"); + switchEl.click(); + assertSwitchAndPrefState({ + win, + isPBM: testPBM, + switchEl, + expectEnabled: false, + }); + assertTelemetryState({ expectEnabled: false }); + + info("Reopen the panel to test the initial switch OFF state."); + await closeProtectionsPanel(win); + await openProtectionsPanel(null, win); + assertSwitchAndPrefState({ + win, + isPBM: testPBM, + switchEl, + expectEnabled: false, + }); + assertTelemetryState(); + + info("Testing switch state after toggle ON."); + switchEl.click(); + assertSwitchAndPrefState({ + win, + isPBM: testPBM, + switchEl, + expectEnabled: true, + }); + assertTelemetryState({ expectEnabled: true }); + + info("Reopen the panel to test the initial switch ON state."); + await closeProtectionsPanel(win); + await openProtectionsPanel(null, win); + assertSwitchAndPrefState({ + win, + isPBM: testPBM, + switchEl, + expectEnabled: true, + }); + assertTelemetryState(); + } + ); + + if (testPBM) { + await BrowserTestUtils.closeWindow(win); + } + } +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js new file mode 100644 index 0000000000..1fc28a3770 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js @@ -0,0 +1,522 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +const COOKIE_PAGE = + "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html"; +const CONTAINER_PAGE = + "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/containerPage.html"; + +const TPC_PREF = "network.cookie.cookieBehavior"; + +add_setup(async function() { + await UrlClassifierTestUtils.addTestTrackers(); + + registerCleanupFunction(() => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); +}); + +/* + * Accepts an array containing 6 elements that identify the testcase: + * [0] - boolean indicating whether trackers are blocked. + * [1] - boolean indicating whether third party cookies are blocked. + * [2] - boolean indicating whether first party cookies are blocked. + * [3] - integer indicating number of expected content blocking events. + * [4] - integer indicating number of expected subview list headers. + * [5] - integer indicating number of expected cookie list items. + * [6] - integer indicating number of expected cookie list items + * after loading a cookie-setting third party URL in an iframe + * [7] - integer indicating number of expected cookie list items + * after loading a cookie-setting first party URL in an iframe + */ +async function assertSitesListed(testCase) { + let sitesListedTestCases = [ + [true, false, false, 4, 1, 1, 1, 1], + [true, true, false, 5, 1, 1, 2, 2], + [true, true, true, 6, 2, 2, 3, 3], + [false, false, false, 3, 1, 1, 1, 1], + ]; + let [ + trackersBlocked, + thirdPartyBlocked, + firstPartyBlocked, + contentBlockingEventCount, + listHeaderCount, + cookieItemsCount1, + cookieItemsCount2, + cookieItemsCount3, + ] = sitesListedTestCases[testCase]; + let promise = BrowserTestUtils.openNewForegroundTab({ + url: COOKIE_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([ + promise, + waitForContentBlockingEvent(contentBlockingEventCount), + ]); + let browser = tab.linkedBrowser; + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let cookiesView = document.getElementById("protections-popup-cookiesView"); + let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + let listHeaders = cookiesView.querySelectorAll( + ".protections-popup-cookiesView-list-header" + ); + is( + listHeaders.length, + listHeaderCount, + `We have ${listHeaderCount} list headers.` + ); + if (listHeaderCount == 1) { + ok( + !BrowserTestUtils.is_visible(listHeaders[0]), + "Only one header, should be hidden" + ); + } else { + for (let header of listHeaders) { + ok( + BrowserTestUtils.is_visible(header), + "Multiple list headers - all should be visible." + ); + } + } + + let emptyLabels = cookiesView.querySelectorAll( + ".protections-popup-empty-label" + ); + is(emptyLabels.length, 0, `We have no empty labels`); + + let listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is( + listItems.length, + cookieItemsCount1, + `We have ${cookieItemsCount1} cookies in the list` + ); + + if (trackersBlocked) { + let trackerTestItem; + for (let item of listItems) { + let label = item.querySelector(".protections-popup-list-host-label"); + if (label.value == "http://trackertest.org") { + trackerTestItem = item; + break; + } + } + ok(trackerTestItem, "Has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(trackerTestItem), "List item is visible"); + } + + if (firstPartyBlocked) { + let notTrackingExampleItem; + for (let item of listItems) { + let label = item.querySelector(".protections-popup-list-host-label"); + if (label.value == "http://not-tracking.example.com") { + notTrackingExampleItem = item; + break; + } + } + ok(notTrackingExampleItem, "Has an item for not-tracking.example.com"); + ok( + BrowserTestUtils.is_visible(notTrackingExampleItem), + "List item is visible" + ); + } + let mainView = document.getElementById("protections-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = cookiesView.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + let change = waitForContentBlockingEvent(); + let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); + + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("third-party-cookie", "*"); + }); + + let result = await Promise.race([change, timeoutPromise]); + is(result, undefined, "No contentBlockingEvent events should be received"); + + viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label"); + is(emptyLabels.length, 0, `We have no empty labels`); + + listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is( + listItems.length, + cookieItemsCount2, + `We have ${cookieItemsCount2} cookies in the list` + ); + + if (thirdPartyBlocked) { + let test1ExampleItem; + for (let item of listItems) { + let label = item.querySelector(".protections-popup-list-host-label"); + if (label.value == "https://test1.example.org") { + test1ExampleItem = item; + break; + } + } + ok(test1ExampleItem, "Has an item for test1.example.org"); + ok(BrowserTestUtils.is_visible(test1ExampleItem), "List item is visible"); + } + + if (trackersBlocked || thirdPartyBlocked || firstPartyBlocked) { + let trackerTestItem; + for (let item of listItems) { + let label = item.querySelector(".protections-popup-list-host-label"); + if (label.value == "http://trackertest.org") { + trackerTestItem = item; + break; + } + } + ok(trackerTestItem, "List item should exist for http://trackertest.org"); + ok(BrowserTestUtils.is_visible(trackerTestItem), "List item is visible"); + } + + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + change = waitForSecurityChange(); + timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); + + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("first-party-cookie", "*"); + }); + + result = await Promise.race([change, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label"); + is(emptyLabels.length, 0, "We have no empty labels"); + + listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is( + listItems.length, + cookieItemsCount3, + `We have ${cookieItemsCount3} cookies in the list` + ); + + if (firstPartyBlocked) { + let notTrackingExampleItem; + for (let item of listItems) { + let label = item.querySelector(".protections-popup-list-host-label"); + if (label.value == "http://not-tracking.example.com") { + notTrackingExampleItem = item; + break; + } + } + ok(notTrackingExampleItem, "Has an item for not-tracking.example.com"); + ok( + BrowserTestUtils.is_visible(notTrackingExampleItem), + "List item is visible" + ); + } + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function testCookiesSubView() { + info("Testing cookies subview with reject tracking cookies."); + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + let testCaseIndex = 0; + await assertSitesListed(testCaseIndex++); + info("Testing cookies subview with reject third party cookies."); + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN + ); + await assertSitesListed(testCaseIndex++); + info("Testing cookies subview with reject all cookies."); + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT); + await assertSitesListed(testCaseIndex++); + info("Testing cookies subview with accept all cookies."); + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + await assertSitesListed(testCaseIndex++); + + Services.prefs.clearUserPref(TPC_PREF); +}); + +add_task(async function testCookiesSubViewAllowed() { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://trackertest.org/" + ); + Services.perms.addFromPrincipal( + principal, + "cookie", + Services.perms.ALLOW_ACTION + ); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: COOKIE_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let cookiesView = document.getElementById("protections-popup-cookiesView"); + let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + let listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 cookie in the list"); + + let listItem = listItems[0]; + let label = listItem.querySelector(".protections-popup-list-host-label"); + is(label.value, "http://trackertest.org", "has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(listItem), "list item is visible"); + ok( + listItem.classList.contains("allowed"), + "indicates whether the cookie was blocked or allowed" + ); + + let stateLabel = listItem.querySelector( + ".protections-popup-list-state-label" + ); + ok(stateLabel, "List item has a state label"); + ok(BrowserTestUtils.is_visible(stateLabel), "State label is visible"); + is( + stateLabel.value, + gNavigatorBundle.getString("contentBlocking.cookiesView.allowed.label"), + "State label has correct text" + ); + + let button = listItem.querySelector( + ".permission-popup-permission-remove-button" + ); + ok( + BrowserTestUtils.is_visible(button), + "Permission remove button is visible" + ); + button.click(); + is( + Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), + Services.perms.UNKNOWN_ACTION, + "Button click should remove cookie pref." + ); + ok(!listItem.classList.contains("allowed"), "Has removed the allowed class"); + + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(TPC_PREF); +}); + +add_task(async function testCookiesSubViewAllowedHeuristic() { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://not-tracking.example.com/" + ); + + // Pretend that the tracker has already been interacted with + let trackerPrincipal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://trackertest.org/" + ); + Services.perms.addFromPrincipal( + trackerPrincipal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: COOKIE_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(5)]); + let browser = tab.linkedBrowser; + + let popup; + let windowCreated = TestUtils.topicObserved( + "chrome-document-global-created", + (subject, data) => { + popup = subject; + return true; + } + ); + let permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => { + return ( + subject && + subject.QueryInterface(Ci.nsIPermission).type == + "3rdPartyStorage^http://trackertest.org" && + subject.principal.origin == principal.origin && + data == "added" + ); + }); + + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("window-open", "*"); + }); + await Promise.all([windowCreated, permChanged]); + + await new Promise(resolve => waitForFocus(resolve, popup)); + await new Promise(resolve => waitForFocus(resolve, window)); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let cookiesView = document.getElementById("protections-popup-cookiesView"); + let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + let listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 cookie in the list"); + + let listItem = listItems[0]; + let label = listItem.querySelector(".protections-popup-list-host-label"); + is(label.value, "http://trackertest.org", "has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(listItem), "list item is visible"); + ok( + listItem.classList.contains("allowed"), + "indicates whether the cookie was blocked or allowed" + ); + + let button = listItem.querySelector( + ".permission-popup-permission-remove-button" + ); + ok( + BrowserTestUtils.is_visible(button), + "Permission remove button is visible" + ); + button.click(); + is( + Services.perms.testExactPermissionFromPrincipal( + principal, + "3rdPartyStorage^http://trackertest.org" + ), + Services.perms.UNKNOWN_ACTION, + "Button click should remove the storage pref." + ); + ok(!listItem.classList.contains("allowed"), "Has removed the allowed class"); + + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("window-close", "*"); + }); + + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(TPC_PREF); +}); + +add_task(async function testCookiesSubViewBlockedDoublyNested() { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: CONTAINER_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let cookiesView = document.getElementById("protections-popup-cookiesView"); + let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + let listItems = cookiesView.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 cookie in the list"); + + let listItem = listItems[0]; + let label = listItem.querySelector(".protections-popup-list-host-label"); + is(label.value, "http://trackertest.org", "has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(listItem), "list item is visible"); + ok( + !listItem.classList.contains("allowed"), + "indicates whether the cookie was blocked or allowed" + ); + + let button = listItem.querySelector( + ".permission-popup-permission-remove-button" + ); + ok(!button, "Permission remove button doesn't exist"); + + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(TPC_PREF); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js new file mode 100644 index 0000000000..105bbff7d3 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js @@ -0,0 +1,304 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = + "http://example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const CM_PROTECTION_PREF = "privacy.trackingprotection.cryptomining.enabled"; +let cmHistogram; + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "urlclassifier.features.cryptomining.blacklistHosts", + "cryptomining.example.com", + ], + [ + "urlclassifier.features.cryptomining.annotate.blacklistHosts", + "cryptomining.example.com", + ], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["privacy.trackingprotection.fingerprinting.enabled", false], + ["urlclassifier.features.fingerprinting.annotate.blacklistHosts", ""], + ], + }); + cmHistogram = Services.telemetry.getHistogramById( + "CRYPTOMINERS_BLOCKED_COUNT" + ); + registerCleanupFunction(() => { + cmHistogram.clear(); + }); +}); + +async function testIdentityState(hasException) { + cmHistogram.clear(); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + await openProtectionsPanel(); + + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "cryptominers are not detected" + ); + + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible regardless the exception" + ); + + promise = waitForContentBlockingEvent(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("cryptomining", "*"); + }); + + await promise; + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are detected" + ); + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + is( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + hasException, + "Shows an exception when appropriate" + ); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + let loads = hasException ? 3 : 1; + testTelemetry(loads, 1, hasException); + + BrowserTestUtils.removeTab(tab); +} + +async function testSubview(hasException) { + cmHistogram.clear(); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + promise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("cryptomining", "*"); + }); + await promise; + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cryptominers" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + // We have to wait until the ContentBlockingLog gets updated in the content. + // Unfortunately, we need to use the setTimeout here since we don't have an + // easy to know whether the log is updated in the content. This should be + // removed after the log been removed in the content (Bug 1599046). + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + /* eslint-enable mozilla/no-arbitrary-setTimeout */ + + let subview = document.getElementById("protections-popup-cryptominersView"); + let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown"); + categoryItem.click(); + await viewShown; + + let trackersViewShimHint = document.getElementById( + "protections-popup-cryptominersView-shim-allow-hint" + ); + ok(trackersViewShimHint.hidden, "Shim hint is hidden"); + + let listItems = subview.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 item in the list"); + let listItem = listItems[0]; + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is( + listItem.querySelector("label").value, + "http://cryptomining.example.com", + "Has the correct host" + ); + is( + listItem.classList.contains("allowed"), + hasException, + "Indicates the miner was blocked or allowed" + ); + + let mainView = document.getElementById("protections-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = subview.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + let loads = hasException ? 3 : 1; + testTelemetry(loads, 1, hasException); + + BrowserTestUtils.removeTab(tab); +} + +async function testCategoryItem() { + Services.prefs.setBoolPref(CM_PROTECTION_PREF, false); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cryptominers" + ); + + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + Services.prefs.setBoolPref(CM_PROTECTION_PREF, true); + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + Services.prefs.setBoolPref(CM_PROTECTION_PREF, false); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + await closeProtectionsPanel(); + + promise = waitForContentBlockingEvent(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("cryptomining", "*"); + }); + + await promise; + + await openProtectionsPanel(); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + Services.prefs.setBoolPref(CM_PROTECTION_PREF, true); + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + Services.prefs.setBoolPref(CM_PROTECTION_PREF, false); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + await closeProtectionsPanel(); + + BrowserTestUtils.removeTab(tab); +} + +function testTelemetry(pagesVisited, pagesWithBlockableContent, hasException) { + let results = cmHistogram.snapshot(); + Assert.equal( + results.values[0], + pagesVisited, + "The correct number of page loads have been recorded" + ); + let expectedValue = hasException ? 2 : 1; + Assert.equal( + results.values[expectedValue], + pagesWithBlockableContent, + "The correct number of cryptominers have been recorded as blocked or allowed." + ); +} + +add_task(async function test() { + Services.prefs.setBoolPref(CM_PROTECTION_PREF, true); + + await testIdentityState(false); + await testIdentityState(true); + + await testSubview(false); + await testSubview(true); + + await testCategoryItem(); + + Services.prefs.clearUserPref(CM_PROTECTION_PREF); + Services.prefs.setStringPref("browser.contentblocking.category", "standard"); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js new file mode 100644 index 0000000000..56db385ee0 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js @@ -0,0 +1,38 @@ +const URL = + "http://mochi.test:8888/browser/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html"; + +add_task(async function test_fetch() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.trackingprotection.enabled", true]], + }); + + await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function( + newTabBrowser + ) { + let contentBlockingEvent = waitForContentBlockingEvent(); + await SpecialPowers.spawn(newTabBrowser, [], async function() { + await content.wrappedJSObject + .test_fetch() + .then(response => Assert.ok(false, "should have denied the request")) + .catch(e => Assert.ok(true, `Caught exception: ${e}`)); + }); + await contentBlockingEvent; + + let gProtectionsHandler = newTabBrowser.ownerGlobal.gProtectionsHandler; + ok(gProtectionsHandler, "got CB object"); + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "has detected content blocking" + ); + ok( + gProtectionsHandler.iconBox.hasAttribute("active"), + "icon box is active" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + gNavigatorBundle.getString("trackingProtection.icon.activeTooltip2"), + "correct tooltip" + ); + }); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js new file mode 100644 index 0000000000..fcbeac2d16 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js @@ -0,0 +1,302 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = + "http://example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const FP_PROTECTION_PREF = "privacy.trackingprotection.fingerprinting.enabled"; +let fpHistogram; + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "urlclassifier.features.fingerprinting.blacklistHosts", + "fingerprinting.example.com", + ], + [ + "urlclassifier.features.fingerprinting.annotate.blacklistHosts", + "fingerprinting.example.com", + ], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", false], + ["privacy.trackingprotection.cryptomining.enabled", false], + ["urlclassifier.features.cryptomining.annotate.blacklistHosts", ""], + ["urlclassifier.features.cryptomining.annotate.blacklistTables", ""], + ], + }); + fpHistogram = Services.telemetry.getHistogramById( + "FINGERPRINTERS_BLOCKED_COUNT" + ); + registerCleanupFunction(() => { + fpHistogram.clear(); + }); +}); + +async function testIdentityState(hasException) { + fpHistogram.clear(); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + await openProtectionsPanel(); + + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "fingerprinters are not detected" + ); + ok( + !BrowserTestUtils.is_hidden(gProtectionsHandler.iconBox), + "icon box is visible regardless the exception" + ); + + promise = waitForContentBlockingEvent(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("fingerprinting", "*"); + }); + + await promise; + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are detected" + ); + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + is( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + hasException, + "Shows an exception when appropriate" + ); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + let loads = hasException ? 3 : 1; + testTelemetry(loads, 1, hasException); + + BrowserTestUtils.removeTab(tab); +} + +async function testCategoryItem() { + Services.prefs.setBoolPref(FP_PROTECTION_PREF, false); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + await openProtectionsPanel(); + let categoryItem = document.getElementById( + "protections-popup-category-fingerprinters" + ); + + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + Services.prefs.setBoolPref(FP_PROTECTION_PREF, true); + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + Services.prefs.setBoolPref(FP_PROTECTION_PREF, false); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + await closeProtectionsPanel(); + + promise = waitForContentBlockingEvent(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("fingerprinting", "*"); + }); + + await promise; + + await openProtectionsPanel(); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + Services.prefs.setBoolPref(FP_PROTECTION_PREF, true); + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + Services.prefs.setBoolPref(FP_PROTECTION_PREF, false); + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + await closeProtectionsPanel(); + + BrowserTestUtils.removeTab(tab); +} + +async function testSubview(hasException) { + fpHistogram.clear(); + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + promise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("fingerprinting", "*"); + }); + await promise; + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-fingerprinters" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + // We have to wait until the ContentBlockingLog gets updated in the content. + // Unfortunately, we need to use the setTimeout here since we don't have an + // easy to know whether the log is updated in the content. This should be + // removed after the log been removed in the content (Bug 1599046). + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + /* eslint-enable mozilla/no-arbitrary-setTimeout */ + + let subview = document.getElementById("protections-popup-fingerprintersView"); + let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown"); + categoryItem.click(); + await viewShown; + + let trackersViewShimHint = document.getElementById( + "protections-popup-fingerprintersView-shim-allow-hint" + ); + ok(trackersViewShimHint.hidden, "Shim hint is hidden"); + + let listItems = subview.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 item in the list"); + let listItem = listItems[0]; + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is( + listItem.querySelector("label").value, + "https://fingerprinting.example.com", + "Has the correct host" + ); + is( + listItem.classList.contains("allowed"), + hasException, + "Indicates the fingerprinter was blocked or allowed" + ); + + let mainView = document.getElementById("protections-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = subview.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + let loads = hasException ? 3 : 1; + testTelemetry(loads, 1, hasException); + + BrowserTestUtils.removeTab(tab); +} + +function testTelemetry(pagesVisited, pagesWithBlockableContent, hasException) { + let results = fpHistogram.snapshot(); + Assert.equal( + results.values[0], + pagesVisited, + "The correct number of page loads have been recorded" + ); + let expectedValue = hasException ? 2 : 1; + Assert.equal( + results.values[expectedValue], + pagesWithBlockableContent, + "The correct number of fingerprinters have been recorded as blocked or allowed." + ); +} + +add_task(async function test() { + Services.prefs.setBoolPref(FP_PROTECTION_PREF, true); + + await testIdentityState(false); + await testIdentityState(true); + + await testSubview(false); + await testSubview(true); + + await testCategoryItem(); + + Services.prefs.clearUserPref(FP_PROTECTION_PREF); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js new file mode 100644 index 0000000000..ddfb49dc9f --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js @@ -0,0 +1,223 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +/* + * Test that the Content Blocking icon state is properly updated in the identity + * block when loading tabs and switching between tabs. + * See also Bug 1175858. + */ + +const TP_PREF = "privacy.trackingprotection.enabled"; +const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const APS_PREF = + "privacy.partition.always_partition_third_party_non_cookie_storage"; +const NCB_PREF = "network.cookie.cookieBehavior"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const COOKIE_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/cookiePage.html"; +const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications"; + +registerCleanupFunction(function() { + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(TP_PREF); + Services.prefs.clearUserPref(TP_PB_PREF); + Services.prefs.clearUserPref(NCB_PREF); + Services.prefs.clearUserPref(DTSCBN_PREF); +}); + +async function testTrackingProtectionIconState(tabbrowser) { + Services.prefs.setBoolPref(DTSCBN_PREF, true); + + info("Load a test page not containing tracking elements"); + let benignTab = await BrowserTestUtils.openNewForegroundTab( + tabbrowser, + BENIGN_PAGE + ); + let gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler; + + ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox not active"); + + info("Load a test page containing tracking elements"); + let trackingTab = await BrowserTestUtils.openNewForegroundTab( + tabbrowser, + TRACKING_PAGE + ); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Load a test page containing tracking cookies"); + let trackingCookiesTab = await BrowserTestUtils.openNewForegroundTab( + tabbrowser, + COOKIE_PAGE + ); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Switch from tracking cookie -> benign tab"); + let securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + tabbrowser.selectedTab = benignTab; + await securityChanged; + + ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox not active"); + + info("Switch from benign -> tracking tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + tabbrowser.selectedTab = trackingTab; + await securityChanged; + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Switch from tracking -> tracking cookies tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + tabbrowser.selectedTab = trackingCookiesTab; + await securityChanged; + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Reload tracking cookies tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + let contentBlockingEvent = waitForContentBlockingEvent( + 2, + tabbrowser.ownerGlobal + ); + tabbrowser.reload(); + await Promise.all([securityChanged, contentBlockingEvent]); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Reload tracking tab"); + securityChanged = waitForSecurityChange(2, tabbrowser.ownerGlobal); + contentBlockingEvent = waitForContentBlockingEvent(3, tabbrowser.ownerGlobal); + tabbrowser.selectedTab = trackingTab; + tabbrowser.reload(); + await Promise.all([securityChanged, contentBlockingEvent]); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Inject tracking cookie inside tracking tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + let timeoutPromise = new Promise(resolve => setTimeout(resolve, 500)); + await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function() { + content.postMessage("cookie", "*"); + }); + let result = await Promise.race([securityChanged, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Inject tracking element inside tracking tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + timeoutPromise = new Promise(resolve => setTimeout(resolve, 500)); + await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function() { + content.postMessage("tracking", "*"); + }); + result = await Promise.race([securityChanged, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + tabbrowser.selectedTab = trackingCookiesTab; + + info("Inject tracking cookie inside tracking cookies tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + timeoutPromise = new Promise(resolve => setTimeout(resolve, 500)); + await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function() { + content.postMessage("cookie", "*"); + }); + result = await Promise.race([securityChanged, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + info("Inject tracking element inside tracking cookies tab"); + securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal); + timeoutPromise = new Promise(resolve => setTimeout(resolve, 500)); + await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function() { + content.postMessage("tracking", "*"); + }); + result = await Promise.race([securityChanged, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active"); + + while (tabbrowser.tabs.length > 1) { + tabbrowser.removeCurrentTab(); + } +} + +add_task(async function testNormalBrowsing() { + await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] }); + + await UrlClassifierTestUtils.addTestTrackers(); + + let gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler; + ok( + gProtectionsHandler, + "gProtectionsHandler is attached to the browser window" + ); + + let { + TrackingProtection, + } = gBrowser.ownerGlobal.gProtectionsHandler.blockers; + ok(TrackingProtection, "TP is attached to the browser window"); + + let { ThirdPartyCookies } = gBrowser.ownerGlobal.gProtectionsHandler.blockers; + ok(ThirdPartyCookies, "TPC is attached to the browser window"); + + Services.prefs.setBoolPref(TP_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + Services.prefs.setIntPref( + NCB_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + ok( + ThirdPartyCookies.enabled, + "ThirdPartyCookies is enabled after setting the pref" + ); + + await testTrackingProtectionIconState(gBrowser); +}); + +add_task(async function testPrivateBrowsing() { + await SpecialPowers.pushPrefEnv({ + set: [ + [APS_PREF, false], + ["dom.security.https_first_pbm", false], + ], + }); + + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let tabbrowser = privateWin.gBrowser; + + let gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler; + ok( + gProtectionsHandler, + "gProtectionsHandler is attached to the private window" + ); + let { + TrackingProtection, + } = tabbrowser.ownerGlobal.gProtectionsHandler.blockers; + ok(TrackingProtection, "TP is attached to the private window"); + let { + ThirdPartyCookies, + } = tabbrowser.ownerGlobal.gProtectionsHandler.blockers; + ok(ThirdPartyCookies, "TPC is attached to the browser window"); + + Services.prefs.setBoolPref(TP_PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + Services.prefs.setIntPref( + NCB_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + ok( + ThirdPartyCookies.enabled, + "ThirdPartyCookies is enabled after setting the pref" + ); + + await testTrackingProtectionIconState(tabbrowser); + + privateWin.close(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js new file mode 100644 index 0000000000..c61fd7a401 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Hide protections cards so as not to trigger more async messaging + // when landing on the page. + ["browser.contentblocking.report.monitor.enabled", false], + ["browser.contentblocking.report.lockwise.enabled", false], + ["browser.contentblocking.report.proxy.enabled", false], + ["browser.contentblocking.cfr-milestone.update-interval", 0], + ], + }); +}); + +add_task(async function doTest() { + // This also ensures that the DB tables have been initialized. + await TrackingDBService.clearAll(); + + let milestones = JSON.parse( + Services.prefs.getStringPref( + "browser.contentblocking.cfr-milestone.milestones" + ) + ); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + + for (let milestone of milestones) { + Services.telemetry.clearEvents(); + // Trigger the milestone feature. + Services.prefs.setIntPref( + "browser.contentblocking.cfr-milestone.milestone-achieved", + milestone + ); + await TestUtils.waitForCondition( + () => gProtectionsHandler._milestoneTextSet + ); + // We set the shown-time pref to pretend that the CFR has been + // shown, so that we can test the panel. + // TODO: Full integration test for robustness. + Services.prefs.setStringPref( + "browser.contentblocking.cfr-milestone.milestone-shown-time", + Date.now().toString() + ); + await openProtectionsPanel(); + + ok( + BrowserTestUtils.is_visible( + gProtectionsHandler._protectionsPopupMilestonesText + ), + "Milestones section should be visible in the panel." + ); + + await closeProtectionsPanel(); + await openProtectionsPanel(); + + ok( + BrowserTestUtils.is_visible( + gProtectionsHandler._protectionsPopupMilestonesText + ), + "Milestones section should still be visible in the panel." + ); + + let newTabPromise = waitForAboutProtectionsTab(); + await EventUtils.synthesizeMouseAtCenter( + document.getElementById("protections-popup-milestones-content"), + {} + ); + let protectionsTab = await newTabPromise; + + ok(true, "about:protections has been opened as expected."); + + BrowserTestUtils.removeTab(protectionsTab); + + await openProtectionsPanel(); + + ok( + !BrowserTestUtils.is_visible( + gProtectionsHandler._protectionsPopupMilestonesText + ), + "Milestones section should no longer be visible in the panel." + ); + + checkClickTelemetry("milestone_message"); + + await closeProtectionsPanel(); + } + + BrowserTestUtils.removeTab(tab); + await TrackingDBService.clearAll(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js new file mode 100644 index 0000000000..1b52fe55bb --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TP_PREF = "privacy.trackingprotection.enabled"; +const TPC_PREF = "network.cookie.cookieBehavior"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const COOKIE_PAGE = + "http://tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html"; + +async function waitAndAssertPreferencesShown(_spotlight) { + await BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + await TestUtils.waitForCondition( + () => gBrowser.currentURI.spec == "about:preferences#privacy", + "Should open about:preferences." + ); + + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [_spotlight], + async spotlight => { + let doc = content.document; + let section = await ContentTaskUtils.waitForCondition( + () => doc.querySelector(".spotlight"), + "The spotlight should appear." + ); + Assert.equal( + section.getAttribute("data-subcategory"), + spotlight, + "The correct section is spotlighted." + ); + } + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_setup(async function() { + await UrlClassifierTestUtils.addTestTrackers(); + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(() => { + Services.telemetry.canRecordExtended = oldCanRecord; + UrlClassifierTestUtils.cleanupTestTrackers(); + }); +}); + +// Tests that pressing the preferences button in the trackers subview +// links to about:preferences +add_task(async function testOpenPreferencesFromTrackersSubview() { + Services.prefs.setBoolPref(TP_PREF, true); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + + // Wait for 2 content blocking events - one for the load and one for the tracker. + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-trackers" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let trackersView = document.getElementById("protections-popup-trackersView"); + let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Trackers view was shown"); + + let preferencesButton = document.getElementById( + "protections-popup-trackersView-settings-button" + ); + + ok( + BrowserTestUtils.is_visible(preferencesButton), + "The preferences button is shown." + ); + + let shown = waitAndAssertPreferencesShown("trackingprotection"); + preferencesButton.click(); + await shown; + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(TP_PREF); +}); + +// Tests that pressing the preferences button in the cookies subview +// links to about:preferences +add_task(async function testOpenPreferencesFromCookiesSubview() { + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: COOKIE_PAGE, + gBrowser, + }); + + // Wait for 2 content blocking events - one for the load and one for the tracker. + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-cookies" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let cookiesView = document.getElementById("protections-popup-cookiesView"); + let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Cookies view was shown"); + + let preferencesButton = document.getElementById( + "protections-popup-cookiesView-settings-button" + ); + + ok( + BrowserTestUtils.is_visible(preferencesButton), + "The preferences button is shown." + ); + + let shown = waitAndAssertPreferencesShown("trackingprotection"); + preferencesButton.click(); + await shown; + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(TPC_PREF); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js new file mode 100644 index 0000000000..478904359c --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js @@ -0,0 +1,174 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that sites added to the Tracking Protection whitelist in private +// browsing mode don't persist once the private browsing window closes. + +const TP_PB_PREF = "privacy.trackingprotection.enabled"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications"; +var TrackingProtection = null; +var gProtectionsHandler = null; +var browser = null; + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(TP_PB_PREF); + Services.prefs.clearUserPref(DTSCBN_PREF); + gProtectionsHandler = TrackingProtection = browser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); +}); + +function hidden(sel) { + let win = browser.ownerGlobal; + let el = win.document.querySelector(sel); + let display = win.getComputedStyle(el).getPropertyValue("display", null); + return display === "none"; +} + +function protectionsPopupState() { + let win = browser.ownerGlobal; + return win.document.getElementById("protections-popup")?.state || "closed"; +} + +function clickButton(sel) { + let win = browser.ownerGlobal; + let el = win.document.querySelector(sel); + el.doCommand(); +} + +function testTrackingPage() { + info("Tracking content must be blocked"); + ok(gProtectionsHandler.anyDetected, "trackers are detected"); + ok(!gProtectionsHandler.hasException, "content shows no exception"); + + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + ok( + !gProtectionsHandler.iconBox.hasAttribute("hasException"), + "icon box shows no exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + gNavigatorBundle.getString("trackingProtection.icon.activeTooltip2"), + "correct tooltip" + ); +} + +function testTrackingPageUnblocked() { + info("Tracking content must be allowlisted and not blocked"); + ok(gProtectionsHandler.anyDetected, "trackers are detected"); + ok(gProtectionsHandler.hasException, "content shows exception"); + + ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + ok( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + "shield shows exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip2"), + "correct tooltip" + ); + + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); +} + +add_task(async function testExceptionAddition() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first_pbm", false]], + }); + + await UrlClassifierTestUtils.addTestTrackers(); + Services.prefs.setBoolPref(DTSCBN_PREF, true); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + browser = privateWin.gBrowser; + let tab = await BrowserTestUtils.openNewForegroundTab(browser); + + gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler; + ok(gProtectionsHandler, "CB is attached to the private window"); + + TrackingProtection = + browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + + Services.prefs.setBoolPref(TP_PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + info("Load a test page containing tracking elements"); + await Promise.all([ + promiseTabLoadEvent(tab, TRACKING_PAGE), + waitForContentBlockingEvent(2, tab.ownerGlobal), + ]); + + testTrackingPage(tab.ownerGlobal); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + gProtectionsHandler.disableForCurrentPage(); + is(protectionsPopupState(), "closed", "protections popup is closed"); + + await tabReloadPromise; + testTrackingPageUnblocked(); + + info( + "Test that the exception is remembered across tabs in the same private window" + ); + tab = browser.selectedTab = BrowserTestUtils.addTab(browser); + + info("Load a test page containing tracking elements"); + await promiseTabLoadEvent(tab, TRACKING_PAGE); + testTrackingPageUnblocked(); + + await BrowserTestUtils.closeWindow(privateWin); +}); + +add_task(async function testExceptionPersistence() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first_pbm", false]], + }); + + info("Open another private browsing window"); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + browser = privateWin.gBrowser; + let tab = await BrowserTestUtils.openNewForegroundTab(browser); + + gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler; + ok(gProtectionsHandler, "CB is attached to the private window"); + TrackingProtection = + browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + + ok(TrackingProtection.enabled, "TP is still enabled"); + + info("Load a test page containing tracking elements"); + await Promise.all([ + promiseTabLoadEvent(tab, TRACKING_PAGE), + waitForContentBlockingEvent(2, tab.ownerGlobal), + ]); + + testTrackingPage(tab.ownerGlobal); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + gProtectionsHandler.disableForCurrentPage(); + is(protectionsPopupState(), "closed", "protections popup is closed"); + + await Promise.all([ + tabReloadPromise, + waitForContentBlockingEvent(2, tab.ownerGlobal), + ]); + testTrackingPageUnblocked(); + + await BrowserTestUtils.closeWindow(privateWin); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js new file mode 100644 index 0000000000..f2ad19f58e --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js @@ -0,0 +1,399 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; +const COOKIE_PAGE = + "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html"; + +const CM_PREF = "privacy.trackingprotection.cryptomining.enabled"; +const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled"; +const TP_PREF = "privacy.trackingprotection.enabled"; +const CB_PREF = "network.cookie.cookieBehavior"; + +const PREF_REPORT_BREAKAGE_URL = "browser.contentblocking.reportBreakage.url"; + +let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); +let { CommonUtils } = ChromeUtils.import("resource://services-common/utils.js"); +let { Preferences } = ChromeUtils.importESModule( + "resource://gre/modules/Preferences.sys.mjs" +); + +add_setup(async function() { + await UrlClassifierTestUtils.addTestTrackers(); + + registerCleanupFunction(() => { + // Clear prefs that are touched in this test again for sanity. + Services.prefs.clearUserPref(TP_PREF); + Services.prefs.clearUserPref(CB_PREF); + Services.prefs.clearUserPref(FP_PREF); + Services.prefs.clearUserPref(CM_PREF); + Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL); + + UrlClassifierTestUtils.cleanupTestTrackers(); + }); + + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "urlclassifier.features.fingerprinting.blacklistHosts", + "fingerprinting.example.com", + ], + [ + "urlclassifier.features.fingerprinting.annotate.blacklistHosts", + "fingerprinting.example.com", + ], + ["privacy.trackingprotection.cryptomining.enabled", true], + [ + "urlclassifier.features.cryptomining.blacklistHosts", + "cryptomining.example.com", + ], + [ + "urlclassifier.features.cryptomining.annotate.blacklistHosts", + "cryptomining.example.com", + ], + ], + }); +}); + +add_task(async function testReportBreakageCancel() { + Services.prefs.setBoolPref(TP_PREF, true); + + await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function() { + await openProtectionsPanel(); + await TestUtils.waitForCondition(() => + gProtectionsHandler._protectionsPopup.hasAttribute("blocking") + ); + + let siteNotWorkingButton = document.getElementById( + "protections-popup-tp-switch-breakage-link" + ); + ok( + BrowserTestUtils.is_visible(siteNotWorkingButton), + "site not working button is visible" + ); + let siteNotWorkingView = document.getElementById( + "protections-popup-siteNotWorkingView" + ); + let viewShown = BrowserTestUtils.waitForEvent( + siteNotWorkingView, + "ViewShown" + ); + siteNotWorkingButton.click(); + await viewShown; + + let sendReportButton = document.getElementById( + "protections-popup-siteNotWorkingView-sendReport" + ); + let sendReportView = document.getElementById( + "protections-popup-sendReportView" + ); + viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown"); + sendReportButton.click(); + await viewShown; + + ok(true, "Report breakage view was shown"); + + viewShown = BrowserTestUtils.waitForEvent(siteNotWorkingView, "ViewShown"); + let cancelButton = document.getElementById( + "protections-popup-sendReportView-cancel" + ); + cancelButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + }); + + Services.prefs.clearUserPref(TP_PREF); +}); + +add_task(async function testReportBreakageSiteException() { + Services.prefs.setBoolPref(TP_PREF, true); + + let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊"; + + await BrowserTestUtils.withNewTab(url, async browser => { + let loaded = BrowserTestUtils.browserLoaded(browser, false); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + + await openProtectionsPanel(); + + let siteFixedButton = document.getElementById( + "protections-popup-tp-switch-breakage-fixed-link" + ); + ok( + BrowserTestUtils.is_visible(siteFixedButton), + "site fixed button is visible" + ); + let sendReportView = document.getElementById( + "protections-popup-sendReportView" + ); + let viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown"); + siteFixedButton.click(); + await viewShown; + + ok(true, "Report breakage view was shown"); + + await testReportBreakageSubmit( + TRACKING_PAGE, + "trackingprotection", + false, + true + ); + + // Pass false for shouldReload - there's no need since the tab is going away. + gProtectionsHandler.enableForCurrentPage(false); + }); + + Services.prefs.clearUserPref(TP_PREF); +}); + +add_task(async function testNoTracking() { + await BrowserTestUtils.withNewTab(BENIGN_PAGE, async function() { + await openProtectionsPanel(); + + let siteNotWorkingButton = document.getElementById( + "protections-popup-tp-switch-breakage-link" + ); + ok( + BrowserTestUtils.is_hidden(siteNotWorkingButton), + "site not working button is not visible" + ); + }); +}); + +add_task(async function testReportBreakageError() { + Services.prefs.setBoolPref(TP_PREF, true); + // Make sure that we correctly strip the query. + let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊"; + await BrowserTestUtils.withNewTab(url, async function() { + await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection", true); + }); + + Services.prefs.clearUserPref(TP_PREF); +}); + +add_task(async function testTP() { + Services.prefs.setBoolPref(TP_PREF, true); + // Make sure that we correctly strip the query. + let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊"; + await BrowserTestUtils.withNewTab(url, async function() { + await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection"); + }); + + Services.prefs.clearUserPref(TP_PREF); +}); + +add_task(async function testCR() { + Services.prefs.setIntPref( + CB_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + // Make sure that we correctly strip the query. + let url = COOKIE_PAGE + "?a=b&1=abc&unicode=🦊"; + await BrowserTestUtils.withNewTab(url, async function() { + await openAndTestReportBreakage(COOKIE_PAGE, "cookierestrictions"); + }); + + Services.prefs.clearUserPref(CB_PREF); +}); + +add_task(async function testFP() { + Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + Services.prefs.setBoolPref(FP_PREF, true); + // Make sure that we correctly strip the query. + let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊"; + await BrowserTestUtils.withNewTab(url, async function(browser) { + let promise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("fingerprinting", "*"); + }); + await promise; + + await openAndTestReportBreakage(TRACKING_PAGE, "fingerprinting", true); + }); + + Services.prefs.clearUserPref(FP_PREF); + Services.prefs.clearUserPref(CB_PREF); +}); + +add_task(async function testCM() { + Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + Services.prefs.setBoolPref(CM_PREF, true); + // Make sure that we correctly strip the query. + let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊"; + await BrowserTestUtils.withNewTab(url, async function(browser) { + let promise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(browser, [], function() { + content.postMessage("cryptomining", "*"); + }); + await promise; + + await openAndTestReportBreakage(TRACKING_PAGE, "cryptomining", true); + }); + + Services.prefs.clearUserPref(CM_PREF); + Services.prefs.clearUserPref(CB_PREF); +}); + +async function openAndTestReportBreakage(url, tags, error = false) { + await openProtectionsPanel(); + + let siteNotWorkingButton = document.getElementById( + "protections-popup-tp-switch-breakage-link" + ); + ok( + BrowserTestUtils.is_visible(siteNotWorkingButton), + "site not working button is visible" + ); + let siteNotWorkingView = document.getElementById( + "protections-popup-siteNotWorkingView" + ); + let viewShown = BrowserTestUtils.waitForEvent( + siteNotWorkingView, + "ViewShown" + ); + siteNotWorkingButton.click(); + await viewShown; + + let sendReportButton = document.getElementById( + "protections-popup-siteNotWorkingView-sendReport" + ); + let sendReportView = document.getElementById( + "protections-popup-sendReportView" + ); + viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown"); + sendReportButton.click(); + await viewShown; + + ok(true, "Report breakage view was shown"); + + await testReportBreakageSubmit(url, tags, error, false); +} + +// This function assumes that the breakage report view is ready. +async function testReportBreakageSubmit(url, tags, error, hasException) { + // Setup a mock server for receiving breakage reports. + let server = new HttpServer(); + server.start(-1); + let i = server.identity; + let path = + i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/"; + + Services.prefs.setStringPref(PREF_REPORT_BREAKAGE_URL, path); + + let comments = document.getElementById( + "protections-popup-sendReportView-collection-comments" + ); + is(comments.value, "", "Comments textarea should initially be empty"); + + let submitButton = document.getElementById( + "protections-popup-sendReportView-submit" + ); + let reportURL = document.getElementById( + "protections-popup-sendReportView-collection-url" + ).value; + + is(reportURL, url, "Shows the correct URL in the report UI."); + + // Make sure that sending the report closes the identity popup. + let popuphidden = BrowserTestUtils.waitForEvent( + gProtectionsHandler._protectionsPopup, + "popuphidden" + ); + + // Check that we're receiving a good report. + await new Promise(resolve => { + server.registerPathHandler("/", async (request, response) => { + is(request.method, "POST", "request was a post"); + + // Extract and "parse" the form data in the request body. + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let boundary = request + .getHeader("Content-Type") + .match(/boundary=-+([^-]*)/i)[1]; + let regex = new RegExp("-+" + boundary + "-*\\s+"); + let sections = body.split(regex); + + let prefs = [ + "privacy.trackingprotection.enabled", + "privacy.trackingprotection.pbmode.enabled", + "urlclassifier.trackingTable", + "network.http.referer.defaultPolicy", + "network.http.referer.defaultPolicy.pbmode", + "network.cookie.cookieBehavior", + "privacy.annotate_channels.strict_list.enabled", + "privacy.restrict3rdpartystorage.expiration", + "privacy.trackingprotection.fingerprinting.enabled", + "privacy.trackingprotection.cryptomining.enabled", + ]; + let prefsBody = ""; + + for (let pref of prefs) { + prefsBody += `${pref}: ${Preferences.get(pref)}\r\n`; + } + + Assert.deepEqual( + sections, + [ + "", + `Content-Disposition: form-data; name=\"title\"\r\n\r\n${ + Services.io.newURI(reportURL).host + }\r\n`, + 'Content-Disposition: form-data; name="body"\r\n\r\n' + + `Full URL: ${reportURL + "?"}\r\n` + + `userAgent: ${navigator.userAgent}\r\n\r\n` + + "**Preferences**\r\n" + + `${prefsBody}\r\n` + + `hasException: ${hasException}\r\n\r\n` + + "**Comments**\r\n" + + "This is a comment\r\n", + 'Content-Disposition: form-data; name="labels"\r\n\r\n' + + `${hasException ? "" : tags}\r\n`, + "", + ], + "Should send the correct form data" + ); + + if (error) { + response.setStatusLine(request.httpVersion, 500, "Request failed"); + } else { + response.setStatusLine(request.httpVersion, 201, "Entry created"); + } + + resolve(); + }); + + comments.value = "This is a comment"; + submitButton.click(); + }); + + let errorMessage = document.getElementById( + "protections-popup-sendReportView-report-error" + ); + if (error) { + await TestUtils.waitForCondition(() => + BrowserTestUtils.is_visible(errorMessage) + ); + is( + comments.value, + "This is a comment", + "Comment not cleared in case of an error" + ); + gProtectionsHandler._protectionsPopup.hidePopup(); + } else { + ok(BrowserTestUtils.is_hidden(errorMessage), "Error message not shown"); + } + + await popuphidden; + + // Stop the server. + await new Promise(r => server.stop(r)); + + Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL); +} diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js new file mode 100644 index 0000000000..6b6ae53c95 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* This test checks pages of different URL variants (mostly differing in scheme) + * and verifies that the shield is only shown when content blocking can deal + * with the specific variant. */ + +const TEST_CASES = [ + { + type: "http", + testURL: "http://example.com", + hidden: false, + }, + { + type: "https", + testURL: "https://example.com", + hidden: false, + }, + { + type: "chrome page", + testURL: "chrome://global/skin/in-content/info-pages.css", + hidden: true, + }, + { + type: "content-privileged about page", + testURL: "about:robots", + hidden: true, + }, + { + type: "non-chrome about page", + testURL: "about:about", + hidden: true, + }, + { + type: "chrome about page", + testURL: "about:preferences", + hidden: true, + }, + { + type: "file", + testURL: "benignPage.html", + hidden: true, + }, + { + type: "certificateError", + testURL: "https://self-signed.example.com", + hidden: true, + }, + { + type: "localhost", + testURL: "http://127.0.0.1", + hidden: false, + }, + { + type: "data URI", + testURL: "data:text/html,<div>", + hidden: true, + }, + { + type: "view-source HTTP", + testURL: "view-source:http://example.com/", + hidden: true, + }, + { + type: "view-source HTTPS", + testURL: "view-source:https://example.com/", + hidden: true, + }, + { + type: "top level sandbox", + testURL: + "https://example.com/browser/browser/base/content/test/protectionsUI/sandboxed.html", + hidden: false, + }, +]; + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + // By default, proxies don't apply to 127.0.0.1. We need them to for this test, though: + ["network.proxy.allow_hijacking_localhost", true], + ], + }); + + for (let testData of TEST_CASES) { + info(`Testing for ${testData.type}`); + let testURL = testData.testURL; + + // Overwrite the url if it is testing the file url. + if (testData.type === "file") { + let dir = getChromeDir(getResolvedURI(gTestPath)); + dir.append(testURL); + dir.normalize(); + testURL = Services.io.newFileURI(dir).spec; + } + + let pageLoaded; + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, testURL); + let browser = gBrowser.selectedBrowser; + if (testData.type === "certificateError") { + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + } else { + pageLoaded = BrowserTestUtils.browserLoaded(browser, true); + } + }, + false + ); + await pageLoaded; + + is( + BrowserTestUtils.is_hidden( + gProtectionsHandler._trackingProtectionIconContainer + ), + testData.hidden, + "tracking protection icon container is correctly displayed" + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js new file mode 100644 index 0000000000..ffea33f685 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js @@ -0,0 +1,320 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TRACKING_PAGE = + "http://example.com/browser/browser/base/content/test/protectionsUI/trackingPage.html"; + +const ST_PROTECTION_PREF = "privacy.trackingprotection.socialtracking.enabled"; +const ST_BLOCK_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled"; + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ST_BLOCK_COOKIES_PREF, true], + [ + "urlclassifier.features.socialtracking.blacklistHosts", + "social-tracking.example.org", + ], + [ + "urlclassifier.features.socialtracking.annotate.blacklistHosts", + "social-tracking.example.org", + ], + // Whitelist trackertest.org loaded by default in trackingPage.html + ["urlclassifier.trackingSkipURLs", "trackertest.org"], + ["urlclassifier.trackingAnnotationSkipURLs", "trackertest.org"], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.annotate_channels", true], + ], + }); +}); + +async function testIdentityState(hasException) { + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + await openProtectionsPanel(); + let categoryItem = document.getElementById( + "protections-popup-category-socialblock" + ); + + ok( + categoryItem.classList.contains("notFound"), + "socialtrackings are not detected" + ); + + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible regardless the exception" + ); + await closeProtectionsPanel(); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("socialtracking", "*"); + }); + await openProtectionsPanel(); + + await TestUtils.waitForCondition(() => { + return !categoryItem.classList.contains("notFound"); + }); + + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are detected" + ); + ok( + !categoryItem.classList.contains("notFound"), + "social trackers are detected" + ); + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + is( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + hasException, + "Shows an exception when appropriate" + ); + await closeProtectionsPanel(); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + BrowserTestUtils.removeTab(tab); +} + +async function testSubview(hasException) { + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.disableForCurrentPage(); + await loaded; + } + + promise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("socialtracking", "*"); + }); + await promise; + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-socialblock" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "STP category item is visible"); + ok( + categoryItem.classList.contains("blocked"), + "STP category item is blocked" + ); + + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + // We have to wait until the ContentBlockingLog gets updated in the content. + // Unfortunately, we need to use the setTimeout here since we don't have an + // easy to know whether the log is updated in the content. This should be + // removed after the log been removed in the content (Bug 1599046). + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + /* eslint-enable mozilla/no-arbitrary-setTimeout */ + + let subview = document.getElementById("protections-popup-socialblockView"); + let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown"); + categoryItem.click(); + await viewShown; + + let trackersViewShimHint = document.getElementById( + "protections-popup-socialblockView-shim-allow-hint" + ); + ok(trackersViewShimHint.hidden, "Shim hint is hidden"); + + let listItems = subview.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 item in the list"); + let listItem = listItems[0]; + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is( + listItem.querySelector("label").value, + "https://social-tracking.example.org", + "Has the correct host" + ); + + let mainView = document.getElementById("protections-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = subview.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + if (hasException) { + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + TRACKING_PAGE + ); + gProtectionsHandler.enableForCurrentPage(); + await loaded; + } + + BrowserTestUtils.removeTab(tab); +} + +async function testCategoryItem(blockLoads) { + if (blockLoads) { + Services.prefs.setBoolPref(ST_PROTECTION_PREF, true); + } + + Services.prefs.setBoolPref(ST_BLOCK_COOKIES_PREF, false); + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-socialblock" + ); + + let noTrackersDetectedDesc = document.getElementById( + "protections-popup-no-trackers-found-description" + ); + + ok(categoryItem.hasAttribute("uidisabled"), "Category should be uidisabled"); + + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok(!BrowserTestUtils.is_visible(categoryItem), "Item should be hidden"); + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are not detected" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("socialtracking", "*"); + }); + + ok( + !categoryItem.classList.contains("blocked"), + "Category not marked as blocked" + ); + ok(!BrowserTestUtils.is_visible(categoryItem), "Item should be hidden"); + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are not detected" + ); + ok( + BrowserTestUtils.is_visible(noTrackersDetectedDesc), + "No Trackers detected should be shown" + ); + + BrowserTestUtils.removeTab(tab); + + Services.prefs.setBoolPref(ST_BLOCK_COOKIES_PREF, true); + + promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + await openProtectionsPanel(); + + ok(!categoryItem.hasAttribute("uidisabled"), "Item shouldn't be uidisabled"); + + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + categoryItem.classList.contains("notFound"), + "Category marked as not found" + ); + // At this point we should still be showing "No Trackers Detected" + ok(!BrowserTestUtils.is_visible(categoryItem), "Item should not be visible"); + ok( + BrowserTestUtils.is_visible(noTrackersDetectedDesc), + "No Trackers detected should be shown" + ); + ok( + !gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are not detected" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("socialtracking", "*"); + }); + + await TestUtils.waitForCondition(() => { + return !categoryItem.classList.contains("notFound"); + }); + + ok(categoryItem.classList.contains("blocked"), "Category marked as blocked"); + ok( + !categoryItem.classList.contains("notFound"), + "Category not marked as not found" + ); + ok(BrowserTestUtils.is_visible(categoryItem), "Item should be visible"); + ok( + !BrowserTestUtils.is_visible(noTrackersDetectedDesc), + "No Trackers detected should be hidden" + ); + ok( + gProtectionsHandler._protectionsPopup.hasAttribute("detected"), + "trackers are not detected" + ); + + BrowserTestUtils.removeTab(tab); + + Services.prefs.clearUserPref(ST_PROTECTION_PREF); +} + +add_task(async function testIdentityUI() { + requestLongerTimeout(2); + + await testIdentityState(false); + await testIdentityState(true); + + await testSubview(false); + await testSubview(true); + + await testCategoryItem(false); + await testCategoryItem(true); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js new file mode 100644 index 0000000000..d3b81af950 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js @@ -0,0 +1,398 @@ +/* + * Test that the Tracking Protection section is visible in the Control Center + * and has the correct state for the cases when: + * + * In a normal window as well as a private window, + * With TP enabled + * 1) A page with no tracking elements is loaded. + * 2) A page with tracking elements is loaded and they are blocked. + * 3) A page with tracking elements is loaded and they are not blocked. + * With TP disabled + * 1) A page with no tracking elements is loaded. + * 2) A page with tracking elements is loaded. + * + * See also Bugs 1175327, 1043801, 1178985 + */ + +const TP_PREF = "privacy.trackingprotection.enabled"; +const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const APS_PREF = + "privacy.partition.always_partition_third_party_non_cookie_storage"; +const TPC_PREF = "network.cookie.cookieBehavior"; +const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const COOKIE_PAGE = + "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html"; +var gProtectionsHandler = null; +var TrackingProtection = null; +var ThirdPartyCookies = null; +var tabbrowser = null; +var gTrackingPageURL = TRACKING_PAGE; + +const sBrandBundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" +); +const sNoTrackerIconTooltip = gNavigatorBundle.getFormattedString( + "trackingProtection.icon.noTrackersDetectedTooltip", + [sBrandBundle.GetStringFromName("brandShortName")] +); +const sActiveIconTooltip = gNavigatorBundle.getString( + "trackingProtection.icon.activeTooltip2" +); +const sDisabledIconTooltip = gNavigatorBundle.getString( + "trackingProtection.icon.disabledTooltip2" +); + +registerCleanupFunction(function() { + TrackingProtection = gProtectionsHandler = ThirdPartyCookies = tabbrowser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(TP_PREF); + Services.prefs.clearUserPref(TP_PB_PREF); + Services.prefs.clearUserPref(TPC_PREF); + Services.prefs.clearUserPref(DTSCBN_PREF); +}); + +function notFound(id) { + let doc = tabbrowser.ownerGlobal.document; + return doc.getElementById(id).classList.contains("notFound"); +} + +async function testBenignPage() { + info("Non-tracking content must not be blocked"); + ok(!gProtectionsHandler.anyDetected, "no trackers are detected"); + ok(!gProtectionsHandler.hasException, "content shows no exception"); + + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + ok( + !gProtectionsHandler.iconBox.hasAttribute("hasException"), + "icon box shows no exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + sNoTrackerIconTooltip, + "correct tooltip" + ); + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + + let win = tabbrowser.ownerGlobal; + await openProtectionsPanel(false, win); + ok( + notFound("protections-popup-category-cookies"), + "Cookie restrictions category is not found" + ); + ok( + notFound("protections-popup-category-trackers"), + "Trackers category is not found" + ); + await closeProtectionsPanel(win); +} + +async function testBenignPageWithException() { + info("Non-tracking content must not be blocked"); + ok(!gProtectionsHandler.anyDetected, "no trackers are detected"); + ok(gProtectionsHandler.hasException, "content shows exception"); + + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + ok( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + "shield shows exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + sDisabledIconTooltip, + "correct tooltip" + ); + + ok( + !BrowserTestUtils.is_hidden(gProtectionsHandler.iconBox), + "icon box is not hidden" + ); + + let win = tabbrowser.ownerGlobal; + await openProtectionsPanel(false, win); + ok( + notFound("protections-popup-category-cookies"), + "Cookie restrictions category is not found" + ); + ok( + notFound("protections-popup-category-trackers"), + "Trackers category is not found" + ); + await closeProtectionsPanel(win); +} + +function areTrackersBlocked(isPrivateBrowsing) { + let blockedByTP = Services.prefs.getBoolPref( + isPrivateBrowsing ? TP_PB_PREF : TP_PREF + ); + let blockedByTPC = [ + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ].includes(Services.prefs.getIntPref(TPC_PREF)); + return blockedByTP || blockedByTPC; +} + +async function testTrackingPage(window) { + info("Tracking content must be blocked"); + ok(gProtectionsHandler.anyDetected, "trackers are detected"); + ok(!gProtectionsHandler.hasException, "content shows no exception"); + + let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window); + let blockedByTP = areTrackersBlocked(isWindowPrivate); + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is always visible" + ); + is( + gProtectionsHandler.iconBox.hasAttribute("active"), + blockedByTP, + "shield is" + (blockedByTP ? "" : " not") + " active" + ); + ok( + !gProtectionsHandler.iconBox.hasAttribute("hasException"), + "icon box shows no exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + blockedByTP ? sActiveIconTooltip : sNoTrackerIconTooltip, + "correct tooltip" + ); + + await openProtectionsPanel(false, window); + ok( + !notFound("protections-popup-category-trackers"), + "Trackers category is detected" + ); + if (gTrackingPageURL == COOKIE_PAGE) { + ok( + !notFound("protections-popup-category-cookies"), + "Cookie restrictions category is detected" + ); + } else { + ok( + notFound("protections-popup-category-cookies"), + "Cookie restrictions category is not found" + ); + } + await closeProtectionsPanel(window); +} + +async function testTrackingPageUnblocked(blockedByTP, window) { + info("Tracking content must be in the exception list and not blocked"); + ok(gProtectionsHandler.anyDetected, "trackers are detected"); + ok(gProtectionsHandler.hasException, "content shows exception"); + + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + ok( + gProtectionsHandler.iconBox.hasAttribute("hasException"), + "shield shows exception" + ); + is( + gProtectionsHandler._trackingProtectionIconTooltipLabel.textContent, + sDisabledIconTooltip, + "correct tooltip" + ); + + ok( + BrowserTestUtils.is_visible(gProtectionsHandler.iconBox), + "icon box is visible" + ); + + await openProtectionsPanel(false, window); + ok( + !notFound("protections-popup-category-trackers"), + "Trackers category is detected" + ); + if (gTrackingPageURL == COOKIE_PAGE) { + ok( + !notFound("protections-popup-category-cookies"), + "Cookie restrictions category is detected" + ); + } else { + ok( + notFound("protections-popup-category-cookies"), + "Cookie restrictions category is not found" + ); + } + await closeProtectionsPanel(window); +} + +async function testContentBlocking(tab) { + info("Testing with Tracking Protection ENABLED."); + + info("Load a test page not containing tracking elements"); + await promiseTabLoadEvent(tab, BENIGN_PAGE); + await testBenignPage(); + + info( + "Load a test page not containing tracking elements which has an exception." + ); + + await promiseTabLoadEvent(tab, "https://example.org/?round=1"); + + ContentBlockingAllowList.add(tab.linkedBrowser); + // Load another page from the same origin to ensure there is an onlocationchange + // notification which would trigger an oncontentblocking notification for us. + await promiseTabLoadEvent(tab, "https://example.org/?round=2"); + + await testBenignPageWithException(); + + ContentBlockingAllowList.remove(tab.linkedBrowser); + + info("Load a test page containing tracking elements"); + await promiseTabLoadEvent(tab, gTrackingPageURL); + await testTrackingPage(tab.ownerGlobal); + + info("Disable CB for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + tab.ownerGlobal.gProtectionsHandler.disableForCurrentPage(); + await tabReloadPromise; + let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(tab.ownerGlobal); + let blockedByTP = areTrackersBlocked(isPrivateBrowsing); + await testTrackingPageUnblocked(blockedByTP, tab.ownerGlobal); + + info("Re-enable TP for the page (which reloads the page)"); + tabReloadPromise = promiseTabLoadEvent(tab); + tab.ownerGlobal.gProtectionsHandler.enableForCurrentPage(); + await tabReloadPromise; + await testTrackingPage(tab.ownerGlobal); +} + +add_task(async function testNormalBrowsing() { + await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] }); + + await UrlClassifierTestUtils.addTestTrackers(); + + Services.prefs.setBoolPref(DTSCBN_PREF, true); + + tabbrowser = gBrowser; + let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser)); + + gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler; + ok( + gProtectionsHandler, + "gProtectionsHandler is attached to the browser window" + ); + + TrackingProtection = + gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + is( + TrackingProtection.enabled, + Services.prefs.getBoolPref(TP_PREF), + "TP.enabled is based on the original pref value" + ); + + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + + await testContentBlocking(tab); + + Services.prefs.setBoolPref(TP_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + await testContentBlocking(tab); + + gBrowser.removeCurrentTab(); + + Services.prefs.clearUserPref(TPC_PREF); +}); + +add_task(async function testPrivateBrowsing() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.security.https_first_pbm", false], + [APS_PREF, false], + ], + }); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + tabbrowser = privateWin.gBrowser; + let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser)); + + // Set the normal mode pref to false to check the pbmode pref. + Services.prefs.setBoolPref(TP_PREF, false); + + Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT); + + gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler; + ok( + gProtectionsHandler, + "gProtectionsHandler is attached to the private window" + ); + + TrackingProtection = + tabbrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + is( + TrackingProtection.enabled, + Services.prefs.getBoolPref(TP_PB_PREF), + "TP.enabled is based on the pb pref value" + ); + + await testContentBlocking(tab); + + Services.prefs.setBoolPref(TP_PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + await testContentBlocking(tab); + + privateWin.close(); + + Services.prefs.clearUserPref(TPC_PREF); +}); + +add_task(async function testThirdPartyCookies() { + await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] }); + + await UrlClassifierTestUtils.addTestTrackers(); + gTrackingPageURL = COOKIE_PAGE; + + tabbrowser = gBrowser; + let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser)); + + gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler; + ok( + gProtectionsHandler, + "gProtectionsHandler is attached to the browser window" + ); + ThirdPartyCookies = + gBrowser.ownerGlobal.gProtectionsHandler.blockers.ThirdPartyCookies; + ok(ThirdPartyCookies, "TP is attached to the browser window"); + is( + ThirdPartyCookies.enabled, + [ + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ].includes(Services.prefs.getIntPref(TPC_PREF)), + "TPC.enabled is based on the original pref value" + ); + + await testContentBlocking(tab); + + Services.prefs.setIntPref( + TPC_PREF, + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER + ); + ok(ThirdPartyCookies.enabled, "TPC is enabled after setting the pref"); + + await testContentBlocking(tab); + + Services.prefs.clearUserPref(TPC_PREF); + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js new file mode 100644 index 0000000000..d110149adb --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TP_PREF = "privacy.trackingprotection.enabled"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; +const ABOUT_PAGE = "about:preferences"; + +/* This asserts that the content blocking event state is correctly reset + * when navigating to a new location, and that the user is correctly + * reset when switching between tabs. */ + +add_task(async function testResetOnLocationChange() { + Services.prefs.setBoolPref(TP_PREF, true); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BENIGN_PAGE); + let browser = tab.linkedBrowser; + + is( + browser.getContentBlockingEvents(), + 0, + "Benign page has no content blocking event" + ); + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + await Promise.all([ + promiseTabLoadEvent(tab, TRACKING_PAGE), + waitForContentBlockingEvent(2), + ]); + + is( + browser.getContentBlockingEvents(), + Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, + "Tracking page has a content blocking event" + ); + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + + await promiseTabLoadEvent(tab, BENIGN_PAGE); + + is( + browser.getContentBlockingEvents(), + 0, + "Benign page has no content blocking event" + ); + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + let contentBlockingEvent = waitForContentBlockingEvent(3); + let trackingTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TRACKING_PAGE + ); + await contentBlockingEvent; + + is( + trackingTab.linkedBrowser.getContentBlockingEvents(), + Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, + "Tracking page has a content blocking event" + ); + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + + gBrowser.selectedTab = tab; + is( + browser.getContentBlockingEvents(), + 0, + "Benign page has no content blocking event" + ); + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + gBrowser.removeTab(trackingTab); + gBrowser.removeTab(tab); + + Services.prefs.clearUserPref(TP_PREF); +}); + +/* Test that the content blocking icon is correctly reset + * when changing tabs or navigating to an about: page */ +add_task(async function testResetOnTabChange() { + Services.prefs.setBoolPref(TP_PREF, true); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, ABOUT_PAGE); + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + await Promise.all([ + promiseTabLoadEvent(tab, TRACKING_PAGE), + waitForContentBlockingEvent(3), + ]); + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + + await promiseTabLoadEvent(tab, ABOUT_PAGE); + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + let contentBlockingEvent = waitForContentBlockingEvent(3); + let trackingTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TRACKING_PAGE + ); + await contentBlockingEvent; + ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active"); + + gBrowser.selectedTab = tab; + ok( + !gProtectionsHandler.iconBox.hasAttribute("active"), + "shield is not active" + ); + + gBrowser.removeTab(trackingTab); + gBrowser.removeTab(tab); + + Services.prefs.clearUserPref(TP_PREF); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js new file mode 100644 index 0000000000..b3ae1b827e --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js @@ -0,0 +1,399 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the warning and list indicators that are shown in the protections panel + * subview when a tracking channel is allowed via the + * "urlclassifier-before-block-channel" event. + */ + +// Choose origin so that all tracking origins used are third-parties. +const TRACKING_PAGE = + "http://example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html"; + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.trackingprotection.enabled", true], + ["privacy.trackingprotection.annotate_channels", true], + ["privacy.trackingprotection.cryptomining.enabled", true], + ["privacy.trackingprotection.socialtracking.enabled", true], + ["privacy.trackingprotection.fingerprinting.enabled", true], + ["privacy.socialtracking.block_cookies.enabled", true], + // Allowlist trackertest.org loaded by default in trackingPage.html + ["urlclassifier.trackingSkipURLs", "trackertest.org"], + ["urlclassifier.trackingAnnotationSkipURLs", "trackertest.org"], + // Additional denylisted hosts. + [ + "urlclassifier.trackingAnnotationTable.testEntries", + "tracking.example.com", + ], + [ + "urlclassifier.features.cryptomining.blacklistHosts", + "cryptomining.example.com", + ], + [ + "urlclassifier.features.cryptomining.annotate.blacklistHosts", + "cryptomining.example.com", + ], + [ + "urlclassifier.features.fingerprinting.blacklistHosts", + "fingerprinting.example.com", + ], + [ + "urlclassifier.features.fingerprinting.annotate.blacklistHosts", + "fingerprinting.example.com", + ], + ], + }); + + await UrlClassifierTestUtils.addTestTrackers(); + registerCleanupFunction(() => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); +}); + +async function assertSubViewState(category, expectedState) { + await openProtectionsPanel(); + + // Sort the expected state by origin and transform it into an array. + let expectedStateSorted = Object.keys(expectedState) + .sort() + .reduce((stateArr, key) => { + let obj = expectedState[key]; + obj.origin = key; + stateArr.push(obj); + return stateArr; + }, []); + + if (!expectedStateSorted.length) { + ok( + BrowserTestUtils.is_visible( + document.getElementById( + "protections-popup-no-trackers-found-description" + ) + ), + "No Trackers detected should be shown" + ); + return; + } + + let categoryItem = document.getElementById( + `protections-popup-category-${category}` + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok( + BrowserTestUtils.is_visible(categoryItem), + `${category} category item is visible` + ); + + ok(!categoryItem.disabled, `${category} category item is enabled`); + + let subView = document.getElementById(`protections-popup-${category}View`); + let viewShown = BrowserTestUtils.waitForEvent(subView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, `${category} subView was shown`); + + info("Testing tracker list"); + + // Get the listed trackers in the UI and sort them by origin. + let items = Array.from( + subView.querySelectorAll( + `#protections-popup-${category}View-list .protections-popup-list-item` + ) + ).sort((a, b) => { + let originA = a.querySelector("label").value; + let originB = b.querySelector("label").value; + return originA.localeCompare(originB); + }); + + is( + items.length, + expectedStateSorted.length, + "List has expected amount of entries" + ); + + for (let i = 0; i < expectedStateSorted.length; i += 1) { + let expected = expectedStateSorted[i]; + let item = items[i]; + + let label = item.querySelector(".protections-popup-list-host-label"); + ok(label, "Item has label."); + is(label.tooltipText, expected.origin, "Label has correct tooltip."); + is(label.value, expected.origin, "Label has correct text."); + + is( + item.classList.contains("allowed"), + !expected.block, + "Item has allowed class if tracker is not blocked" + ); + + let shimAllowIndicator = item.querySelector( + ".protections-popup-list-host-shim-allow-indicator" + ); + + if (expected.shimAllow) { + is(item.childNodes.length, 2, "Item has two childNodes."); + ok(shimAllowIndicator, "Item has shim allow indicator icon."); + ok( + shimAllowIndicator.tooltipText, + "Shim allow indicator icon has tooltip text" + ); + } else { + is(item.childNodes.length, 1, "Item has one childNode."); + ok(!shimAllowIndicator, "Item does not have shim allow indicator icon."); + } + } + + let shimAllowSection = document.getElementById( + `protections-popup-${category}View-shim-allow-hint` + ); + ok(shimAllowSection, `Category ${category} has shim-allow hint.`); + + if (Object.values(expectedState).some(entry => entry.shimAllow)) { + BrowserTestUtils.is_visible( + shimAllowSection, + "Shim allow hint is visible." + ); + } else { + BrowserTestUtils.is_hidden(shimAllowSection, "Shim allow hint is hidden."); + } + + await closeProtectionsPanel(); +} + +async function runTestForCategoryAndState(category, action) { + // Maps the protection categories to the test tracking origins defined in + // ./trackingAPI.js and the UI class identifiers to look for in the + // protections UI. + let categoryToTestData = { + tracking: { + apiMessage: "more-tracking", + origin: "https://itisatracker.org", + elementId: "trackers", + }, + socialtracking: { + origin: "https://social-tracking.example.org", + elementId: "socialblock", + }, + cryptomining: { + origin: "http://cryptomining.example.com", + elementId: "cryptominers", + }, + fingerprinting: { + origin: "https://fingerprinting.example.com", + elementId: "fingerprinters", + }, + }; + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + // Wait for the tab to load and the initial blocking events from the + // classifier. + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + let { + origin: trackingOrigin, + elementId: categoryElementId, + apiMessage, + } = categoryToTestData[category]; + if (!apiMessage) { + apiMessage = category; + } + + // For allow or replace actions we need to hook into before-block-channel. + // If we don't hook into the event, the tracking channel will be blocked. + let beforeBlockChannelPromise; + if (action != "block") { + beforeBlockChannelPromise = UrlClassifierTestUtils.handleBeforeBlockChannel( + { + filterOrigin: trackingOrigin, + action, + } + ); + } + // Load the test tracker matching the category. + await SpecialPowers.spawn(tab.linkedBrowser, [{ apiMessage }], function( + args + ) { + content.postMessage(args.apiMessage, "*"); + }); + await beforeBlockChannelPromise; + + // Next, test if the UI state is correct for the given category and action. + let expectedState = {}; + expectedState[trackingOrigin] = { + block: action == "block", + shimAllow: action == "allow", + }; + + await assertSubViewState(categoryElementId, expectedState); + + BrowserTestUtils.removeTab(tab); +} + +/** + * Test mixed allow/block/replace states for the tracking protection category. + * @param {Object} options - States to test. + * @param {boolean} options.block - Test tracker block state. + * @param {boolean} options.allow - Test tracker allow state. + * @param {boolean} options.replace - Test tracker replace state. + */ +async function runTestMixed({ block, allow, replace }) { + const ORIGIN_BLOCK = "https://trackertest.org"; + const ORIGIN_ALLOW = "https://itisatracker.org"; + const ORIGIN_REPLACE = "https://tracking.example.com"; + + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + + let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); + + if (block) { + // Temporarily remove trackertest.org from the allowlist. + await SpecialPowers.pushPrefEnv({ + clear: [ + ["urlclassifier.trackingSkipURLs"], + ["urlclassifier.trackingAnnotationSkipURLs"], + ], + }); + let blockEventPromise = waitForContentBlockingEvent(); + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("tracking", "*"); + }); + await blockEventPromise; + await SpecialPowers.popPrefEnv(); + } + + if (allow) { + let promiseEvent = waitForContentBlockingEvent(); + let promiseAllow = UrlClassifierTestUtils.handleBeforeBlockChannel({ + filterOrigin: ORIGIN_ALLOW, + action: "allow", + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("more-tracking", "*"); + }); + + await promiseAllow; + await promiseEvent; + } + + if (replace) { + let promiseReplace = UrlClassifierTestUtils.handleBeforeBlockChannel({ + filterOrigin: ORIGIN_REPLACE, + action: "replace", + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("more-tracking-2", "*"); + }); + + await promiseReplace; + } + + let expectedState = {}; + + if (block) { + expectedState[ORIGIN_BLOCK] = { + shimAllow: false, + block: true, + }; + } + + if (replace) { + expectedState[ORIGIN_REPLACE] = { + shimAllow: false, + block: false, + }; + } + + if (allow) { + expectedState[ORIGIN_ALLOW] = { + shimAllow: true, + block: false, + }; + } + + // Check the protection categories subview with the block list. + await assertSubViewState("trackers", expectedState); + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function testNoShim() { + await runTestMixed({ + allow: false, + replace: false, + block: false, + }); + await runTestMixed({ + allow: false, + replace: false, + block: true, + }); +}); + +add_task(async function testShimAllow() { + await runTestMixed({ + allow: true, + replace: false, + block: false, + }); + await runTestMixed({ + allow: true, + replace: false, + block: true, + }); +}); + +add_task(async function testShimReplace() { + await runTestMixed({ + allow: false, + replace: true, + block: false, + }); + await runTestMixed({ + allow: false, + replace: true, + block: true, + }); +}); + +add_task(async function testShimMixed() { + await runTestMixed({ + allow: true, + replace: true, + block: true, + }); +}); + +add_task(async function testShimCategorySubviews() { + let categories = [ + "tracking", + "socialtracking", + "cryptomining", + "fingerprinting", + ]; + for (let category of categories) { + for (let action of ["block", "allow", "replace"]) { + info(`Test category subview. category: ${category}, action: ${action}`); + await runTestForCategoryAndState(category, action); + } + } +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js new file mode 100644 index 0000000000..ffc8dedc1a --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js @@ -0,0 +1,87 @@ +/* + * Test telemetry for Tracking Protection + */ + +const PREF = "privacy.trackingprotection.enabled"; +const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications"; +const BENIGN_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"; +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; + +/** + * Enable local telemetry recording for the duration of the tests. + */ +var oldCanRecord = Services.telemetry.canRecordExtended; +Services.telemetry.canRecordExtended = true; +registerCleanupFunction(function() { + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.telemetry.canRecordExtended = oldCanRecord; + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(DTSCBN_PREF); +}); + +function getShieldHistogram() { + return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD"); +} + +function getShieldCounts() { + return getShieldHistogram().snapshot().values; +} + +add_setup(async function() { + await UrlClassifierTestUtils.addTestTrackers(); + Services.prefs.setBoolPref(DTSCBN_PREF, true); + + let TrackingProtection = + gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + ok(!TrackingProtection.enabled, "TP is not enabled"); + + let enabledCounts = Services.telemetry + .getHistogramById("TRACKING_PROTECTION_ENABLED") + .snapshot().values; + is(enabledCounts[0], 1, "TP was not enabled on start up"); +}); + +add_task(async function testShieldHistogram() { + Services.prefs.setBoolPref(PREF, true); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + // Reset these to make counting easier + getShieldHistogram().clear(); + + await promiseTabLoadEvent(tab, BENIGN_PAGE); + is(getShieldCounts()[0], 1, "Page loads without tracking"); + + await promiseTabLoadEvent(tab, TRACKING_PAGE); + is(getShieldCounts()[0], 2, "Adds one more page load"); + is(getShieldCounts()[2], 1, "Counts one instance of the shield being shown"); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + gProtectionsHandler.disableForCurrentPage(); + await tabReloadPromise; + is(getShieldCounts()[0], 3, "Adds one more page load"); + is( + getShieldCounts()[1], + 1, + "Counts one instance of the shield being crossed out" + ); + + info("Re-enable TP for the page (which reloads the page)"); + tabReloadPromise = promiseTabLoadEvent(tab); + gProtectionsHandler.enableForCurrentPage(); + await tabReloadPromise; + is(getShieldCounts()[0], 4, "Adds one more page load"); + is( + getShieldCounts()[2], + 2, + "Adds one more instance of the shield being shown" + ); + + gBrowser.removeCurrentTab(); + + // Reset these to make counting easier for the next test + getShieldHistogram().clear(); +}); diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js new file mode 100644 index 0000000000..b500d066f1 --- /dev/null +++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js @@ -0,0 +1,132 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.import( + "resource://testing-common/PermissionTestUtils.jsm" +); + +const TRACKING_PAGE = + "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html"; + +const TP_PREF = "privacy.trackingprotection.enabled"; + +add_setup(async function() { + await UrlClassifierTestUtils.addTestTrackers(); + + registerCleanupFunction(() => { + UrlClassifierTestUtils.cleanupTestTrackers(); + }); +}); + +async function assertSitesListed(blocked) { + let promise = BrowserTestUtils.openNewForegroundTab({ + url: TRACKING_PAGE, + gBrowser, + }); + + // Wait for 2 content blocking events - one for the load and one for the tracker. + let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]); + + await openProtectionsPanel(); + + let categoryItem = document.getElementById( + "protections-popup-category-trackers" + ); + + // Explicitly waiting for the category item becoming visible. + await TestUtils.waitForCondition(() => { + return BrowserTestUtils.is_visible(categoryItem); + }); + + ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible"); + let trackersView = document.getElementById("protections-popup-trackersView"); + let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Trackers view was shown"); + + let trackersViewShimHint = document.getElementById( + "protections-popup-trackersView-shim-allow-hint" + ); + ok(trackersViewShimHint.hidden, "Shim hint is hidden"); + let listItems = trackersView.querySelectorAll(".protections-popup-list-item"); + is(listItems.length, 1, "We have 1 tracker in the list"); + + let mainView = document.getElementById("protections-popup-mainView"); + viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + let backButton = trackersView.querySelector(".subviewbutton-back"); + backButton.click(); + await viewShown; + + ok(true, "Main view was shown"); + + let change = waitForSecurityChange(1); + let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + content.postMessage("more-tracking", "*"); + }); + + let result = await Promise.race([change, timeoutPromise]); + is(result, undefined, "No securityChange events should be received"); + + viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown"); + categoryItem.click(); + await viewShown; + + ok(true, "Trackers view was shown"); + + listItems = Array.from( + trackersView.querySelectorAll(".protections-popup-list-item") + ); + is(listItems.length, 2, "We have 2 trackers in the list"); + + let listItem = listItems.find( + item => item.querySelector("label").value == "http://trackertest.org" + ); + ok(listItem, "Has an item for trackertest.org"); + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is( + listItem.classList.contains("allowed"), + !blocked, + "Indicates whether the tracker was blocked or allowed" + ); + + listItem = listItems.find( + item => item.querySelector("label").value == "https://itisatracker.org" + ); + ok(listItem, "Has an item for itisatracker.org"); + ok(BrowserTestUtils.is_visible(listItem), "List item is visible"); + is( + listItem.classList.contains("allowed"), + !blocked, + "Indicates whether the tracker was blocked or allowed" + ); + BrowserTestUtils.removeTab(tab); +} + +add_task(async function testTrackersSubView() { + info("Testing trackers subview with TP disabled."); + Services.prefs.setBoolPref(TP_PREF, false); + await assertSitesListed(false); + info("Testing trackers subview with TP enabled."); + Services.prefs.setBoolPref(TP_PREF, true); + await assertSitesListed(true); + info("Testing trackers subview with TP enabled and a CB exception."); + let uri = Services.io.newURI("https://tracking.example.org"); + PermissionTestUtils.add( + uri, + "trackingprotection", + Services.perms.ALLOW_ACTION + ); + await assertSitesListed(false); + info("Testing trackers subview with TP enabled and a CB exception removed."); + PermissionTestUtils.remove(uri, "trackingprotection"); + await assertSitesListed(true); + + Services.prefs.clearUserPref(TP_PREF); +}); diff --git a/browser/base/content/test/protectionsUI/containerPage.html b/browser/base/content/test/protectionsUI/containerPage.html new file mode 100644 index 0000000000..f68f7325c1 --- /dev/null +++ b/browser/base/content/test/protectionsUI/containerPage.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> + <iframe src="http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/embeddedPage.html"></iframe> +</body> +</html> diff --git a/browser/base/content/test/protectionsUI/cookiePage.html b/browser/base/content/test/protectionsUI/cookiePage.html new file mode 100644 index 0000000000..e7ef2aafa1 --- /dev/null +++ b/browser/base/content/test/protectionsUI/cookiePage.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + <script src="trackingAPI.js" type="text/javascript"></script> + </head> + <body> + <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"></iframe> + </body> +</html> diff --git a/browser/base/content/test/protectionsUI/cookieServer.sjs b/browser/base/content/test/protectionsUI/cookieServer.sjs new file mode 100644 index 0000000000..44341b9a71 --- /dev/null +++ b/browser/base/content/test/protectionsUI/cookieServer.sjs @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// A 1x1 PNG image. +// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain) +const IMAGE = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" + + "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=" +); + +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 200); + if ( + request.queryString && + request.queryString.includes("type=image-no-cookie") + ) { + response.setHeader("Content-Type", "image/png", false); + response.write(IMAGE); + } else { + response.setHeader("Set-Cookie", "foopy=1"); + response.write("cookie served"); + } +} diff --git a/browser/base/content/test/protectionsUI/cookieSetterPage.html b/browser/base/content/test/protectionsUI/cookieSetterPage.html new file mode 100644 index 0000000000..aab18e0aff --- /dev/null +++ b/browser/base/content/test/protectionsUI/cookieSetterPage.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> + <script> document.cookie = "foo=bar"; </script> +</body> +</html> diff --git a/browser/base/content/test/protectionsUI/embeddedPage.html b/browser/base/content/test/protectionsUI/embeddedPage.html new file mode 100644 index 0000000000..6003d49300 --- /dev/null +++ b/browser/base/content/test/protectionsUI/embeddedPage.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> + <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieSetterPage.html"></iframe> +</body> +</html> diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html new file mode 100644 index 0000000000..ee5f998046 --- /dev/null +++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Testing the shield from fetch and XHR</title> +</head> +<body> + <p>Hello there!</p> + <script type="application/javascript"> + function test_fetch() { + let url = "http://trackertest.org/browser/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js"; + return fetch(url); + } + </script> +</body> +</html> diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js new file mode 100644 index 0000000000..f7ac687cfc --- /dev/null +++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js @@ -0,0 +1,2 @@ +/* Some code goes here! */ +void 0; diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^ new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/browser/base/content/test/protectionsUI/head.js b/browser/base/content/test/protectionsUI/head.js new file mode 100644 index 0000000000..de24b5197f --- /dev/null +++ b/browser/base/content/test/protectionsUI/head.js @@ -0,0 +1,222 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Sqlite } = ChromeUtils.importESModule( + "resource://gre/modules/Sqlite.sys.mjs" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "TrackingDBService", + "@mozilla.org/tracking-db-service;1", + "nsITrackingDBService" +); + +XPCOMUtils.defineLazyGetter(this, "TRACK_DB_PATH", function() { + return PathUtils.join(PathUtils.profileDir, "protections.sqlite"); +}); + +ChromeUtils.defineModuleGetter( + this, + "ContentBlockingAllowList", + "resource://gre/modules/ContentBlockingAllowList.jsm" +); + +var { UrlClassifierTestUtils } = ChromeUtils.import( + "resource://testing-common/UrlClassifierTestUtils.jsm" +); + +async function openProtectionsPanel(toast, win = window) { + let popupShownPromise = BrowserTestUtils.waitForEvent( + win, + "popupshown", + true, + e => e.target.id == "protections-popup" + ); + let shieldIconContainer = win.document.getElementById( + "tracking-protection-icon-container" + ); + + // Move out than move over the shield icon to trigger the hover event in + // order to fetch tracker count. + EventUtils.synthesizeMouseAtCenter( + win.gURLBar.textbox, + { + type: "mousemove", + }, + win + ); + EventUtils.synthesizeMouseAtCenter( + shieldIconContainer, + { + type: "mousemove", + }, + win + ); + + if (!toast) { + EventUtils.synthesizeMouseAtCenter(shieldIconContainer, {}, win); + } else { + win.gProtectionsHandler.showProtectionsPopup({ toast }); + } + + await popupShownPromise; +} + +async function openProtectionsPanelWithKeyNav() { + let popupShownPromise = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + e => e.target.id == "protections-popup" + ); + + gURLBar.focus(); + + // This will trigger the focus event for the shield icon for pre-fetching + // the tracker count. + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); + EventUtils.synthesizeKey("KEY_Enter", {}); + + await popupShownPromise; +} + +async function closeProtectionsPanel(win = window) { + let protectionsPopup = win.document.getElementById("protections-popup"); + if (!protectionsPopup) { + return; + } + let popuphiddenPromise = BrowserTestUtils.waitForEvent( + protectionsPopup, + "popuphidden" + ); + + PanelMultiView.hidePopup(protectionsPopup); + await popuphiddenPromise; +} + +function checkClickTelemetry(objectName, value, source = "protectionspopup") { + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + let buttonEvents = events.filter( + e => + e[1] == `security.ui.${source}` && + e[2] == "click" && + e[3] == objectName && + e[4] === value + ); + is(buttonEvents.length, 1, `recorded ${objectName} telemetry event`); +} + +async function addTrackerDataIntoDB(count) { + const insertSQL = + "INSERT INTO events (type, count, timestamp)" + + "VALUES (:type, :count, date(:timestamp));"; + + let db = await Sqlite.openConnection({ path: TRACK_DB_PATH }); + let date = new Date().toISOString(); + + await db.execute(insertSQL, { + type: TrackingDBService.TRACKERS_ID, + count, + timestamp: date, + }); + + await db.close(); +} + +async function waitForAboutProtectionsTab() { + let tab = await BrowserTestUtils.waitForNewTab( + gBrowser, + "about:protections", + true + ); + + // When the graph is built it means the messaging has finished, + // we can close the tab. + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + await ContentTaskUtils.waitForCondition(() => { + let bars = content.document.querySelectorAll(".graph-bar"); + return bars.length; + }, "The graph has been built"); + }); + + return tab; +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param tab + * The tab to load into. + * @param [optional] url + * The url to load, or the current url. + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url) { + info("Wait tab event: load"); + + function handle(loadedUrl) { + if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { + info(`Skipping spurious load event for ${loadedUrl}`); + return false; + } + + info("Tab event received: load"); + return true; + } + + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); + + if (url) { + BrowserTestUtils.loadURI(tab.linkedBrowser, url); + } + + return loaded; +} + +function waitForSecurityChange(numChanges = 1, win = null) { + if (!win) { + win = window; + } + return new Promise(resolve => { + let n = 0; + let listener = { + onSecurityChange() { + n = n + 1; + info("Received onSecurityChange event " + n + " of " + numChanges); + if (n >= numChanges) { + win.gBrowser.removeProgressListener(listener); + resolve(n); + } + }, + }; + win.gBrowser.addProgressListener(listener); + }); +} + +function waitForContentBlockingEvent(numChanges = 1, win = null) { + if (!win) { + win = window; + } + return new Promise(resolve => { + let n = 0; + let listener = { + onContentBlockingEvent(webProgress, request, event) { + n = n + 1; + info( + `Received onContentBlockingEvent event: ${event} (${n} of ${numChanges})` + ); + if (n >= numChanges) { + win.gBrowser.removeProgressListener(listener); + resolve(n); + } + }, + }; + win.gBrowser.addProgressListener(listener); + }); +} diff --git a/browser/base/content/test/protectionsUI/sandboxed.html b/browser/base/content/test/protectionsUI/sandboxed.html new file mode 100644 index 0000000000..661fb0b8e2 --- /dev/null +++ b/browser/base/content/test/protectionsUI/sandboxed.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"></iframe> + </body> +</html> diff --git a/browser/base/content/test/protectionsUI/sandboxed.html^headers^ b/browser/base/content/test/protectionsUI/sandboxed.html^headers^ new file mode 100644 index 0000000000..4705ce9ded --- /dev/null +++ b/browser/base/content/test/protectionsUI/sandboxed.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts; diff --git a/browser/base/content/test/protectionsUI/trackingAPI.js b/browser/base/content/test/protectionsUI/trackingAPI.js new file mode 100644 index 0000000000..b515358a32 --- /dev/null +++ b/browser/base/content/test/protectionsUI/trackingAPI.js @@ -0,0 +1,73 @@ +function createIframe(src) { + let ifr = document.createElement("iframe"); + ifr.src = src; + document.body.appendChild(ifr); +} + +function createImage(src) { + let img = document.createElement("img"); + img.src = src; + img.onload = () => { + parent.postMessage("done", "*"); + }; + document.body.appendChild(img); +} + +onmessage = event => { + switch (event.data) { + case "tracking": + createIframe("https://trackertest.org/"); + break; + case "socialtracking": + createIframe( + "https://social-tracking.example.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs" + ); + break; + case "cryptomining": + createIframe("http://cryptomining.example.com/"); + break; + case "fingerprinting": + createIframe("https://fingerprinting.example.com/"); + break; + case "more-tracking": + createIframe("https://itisatracker.org/"); + break; + case "more-tracking-2": + createIframe("https://tracking.example.com/"); + break; + case "cookie": + createIframe( + "https://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs" + ); + break; + case "first-party-cookie": + // Since the content blocking log doesn't seem to get updated for + // top-level cookies right now, we just create an iframe with the + // first party domain... + createIframe( + "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookieServer.sjs" + ); + break; + case "third-party-cookie": + createIframe( + "https://test1.example.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs" + ); + break; + case "image": + createImage( + "http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs?type=image-no-cookie" + ); + break; + case "window-open": + window.win = window.open( + "http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs", + "_blank", + "width=100,height=100" + ); + break; + case "window-close": + window.win.close(); + window.win = null; + break; + } +}; diff --git a/browser/base/content/test/protectionsUI/trackingPage.html b/browser/base/content/test/protectionsUI/trackingPage.html new file mode 100644 index 0000000000..60ee20203b --- /dev/null +++ b/browser/base/content/test/protectionsUI/trackingPage.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + <script src="trackingAPI.js" type="text/javascript"></script> + </head> + <body> + <iframe src="http://trackertest.org/"></iframe> + </body> +</html> |