diff --git a/browser/components/preferences/cookieBannerPreferences.ftl b/browser/components/preferences/cookieBannerPreferences.ftl
new file mode 100644
index 000000000000..abcf47134e90
--- /dev/null
+++ b/browser/components/preferences/cookieBannerPreferences.ftl
@@ -0,0 +1,11 @@
+# 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/.
+
+## Privacy Section - Cookie Banner Handling
+
+cookie-banner-handling-header = Cookie Banner Reduction
+cookie-banner-reject-accept = { -brand-short-name } automatically tries to reject cookie requests on cookie banners. If a reject option isn’t available, { -brand-short-name } may accept all cookies to dismiss the banner.
+cookie-banner-learn-more = Learn More
+forms-handle-cookie-banners =
+ .label = Reduce Cookie Banners
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 9921e0307b4a..c04427420966 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -55,6 +55,7 @@
+
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index 481661f4a38a..ccebff968d02 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -383,6 +383,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 34dd736f1c44..4a629a8e12c2 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -193,6 +193,10 @@ Preferences.addAll([
// Quick Actions
{ id: "browser.urlbar.quickactions.showPrefs", type: "bool" },
{ id: "browser.urlbar.suggest.quickactions", type: "bool" },
+
+ // Cookie Banner Handling
+ { id: "cookiebanners.ui.desktop.enabled", type: "bool" },
+ { id: "cookiebanners.service.mode", type: "int" },
]);
// Study opt out
@@ -754,6 +758,8 @@ var gPrivacyPane = {
"storage-permissions";
document.getElementById("siteDataLearnMoreLink").setAttribute("href", url);
+ this.initCookieBannerHandling();
+
let notificationInfoURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
document
@@ -1982,6 +1988,92 @@ var gPrivacyPane = {
);
},
+ /**
+ * Initializes the cookie banner handling subgroup on the privacy pane.
+ *
+ * This UI is shown if the "cookiebanners.ui.desktop.enabled" pref is true.
+ *
+ * The cookie banner handling checkbox tracks the state of the integer-valued
+ * "cookiebanners.service.mode" pref: unchecked if the value is either
+ * nsICookieBannerService.MODE_DISABLED, meaning the feature is turned off, or
+ * nsICookieBannerService.MODE_DETECT_ONLY, which is used to allow us to
+ * advertise the feature to the user via an onboarding doorhanger.
+ *
+ * If the user checks the checkbox, the pref value is set to
+ * nsICookieBannerService.MODE_REJECT_OR_ACCEPT.
+ *
+ * If the user unchecks the checkbox, the mode pref value is set to
+ * nsICookieBannerService.MODE_DISABLED.
+ *
+ * Advanced users can choose other int-valued modes via about:config.
+ */
+ initCookieBannerHandling() {
+ this._initCookieBannerHandlingLearnMore();
+
+ setSyncFromPrefListener("handleCookieBanners", () =>
+ this.readCookieBannerMode()
+ );
+ setSyncToPrefListener("handleCookieBanners", () =>
+ this.writeCookieBannerMode()
+ );
+
+ let preference = Preferences.get("cookiebanners.ui.desktop.enabled");
+ preference.on("change", () => this.updateCookieBannerHandlingVisibility());
+
+ this.updateCookieBannerHandlingVisibility();
+ },
+
+ _initCookieBannerHandlingLearnMore() {
+ let url =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "cookie-banner-reduction";
+ let learnMore = document.getElementById("cookieBannerHandlingLearnMore");
+ learnMore.setAttribute("href", url);
+ },
+
+ /**
+ * Reads the cookiebanners.service.mode preference value and updates
+ * the cookie banner handling checkbox accordingly.
+ */
+ readCookieBannerMode() {
+ let mode = Preferences.get("cookiebanners.service.mode").value;
+ let disabledModes = [
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ Ci.nsICookieBannerService.MODE_DETECT_ONLY,
+ ];
+ let isEnabled = !disabledModes.includes(mode);
+ return isEnabled;
+ },
+
+ /**
+ * Translates user clicks on the cookie banner handling checkbox to the
+ * corresponding integer-valued cookie banner mode preference.
+ */
+ writeCookieBannerMode() {
+ let checkbox = document.getElementById("handleCookieBanners");
+ let mode = checkbox.checked
+ ? Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT
+ : Ci.nsICookieBannerService.MODE_DISABLED;
+ return mode;
+ },
+
+ /**
+ * Shows or hides the cookie banner handling section based on the value of
+ * the "cookiebanners.ui.desktop.enabled" pref.
+ */
+ updateCookieBannerHandlingVisibility() {
+ let groupbox = document.getElementById("cookieBannerHandlingGroup");
+ let isEnabled = Preferences.get("cookiebanners.ui.desktop.enabled").value;
+
+ // Because the top-level pane showing code unsets the hidden attribute, we
+ // manually hide the section when cookie banner handling is preffed off.
+ if (isEnabled) {
+ groupbox.removeAttribute("style");
+ } else {
+ groupbox.setAttribute("style", "display: none !important");
+ }
+ },
+
// ADDRESS BAR
/**
diff --git a/browser/components/preferences/tests/browser.ini b/browser/components/preferences/tests/browser.ini
index 2599b05a3365..373a668b6655 100644
--- a/browser/components/preferences/tests/browser.ini
+++ b/browser/components/preferences/tests/browser.ini
@@ -101,6 +101,7 @@ skip-if = true
[browser_proxy_backup.js]
[browser_privacypane_2.js]
[browser_privacypane_3.js]
+[browser_privacy_cookieBannerHandling.js]
[browser_privacy_firefoxSuggest.js]
[browser_privacy_passwordGenerationAndAutofill.js]
[browser_privacy_relayIntegration.js]
diff --git a/browser/components/preferences/tests/browser_bug731866.js b/browser/components/preferences/tests/browser_bug731866.js
index 0ef04382cc80..ab2966685ccf 100644
--- a/browser/components/preferences/tests/browser_bug731866.js
+++ b/browser/components/preferences/tests/browser_bug731866.js
@@ -36,6 +36,15 @@ function checkElements(expectedPane) {
continue;
}
+ // Cookie Banner Handling is currently disabled by default (bug 1800679)
+ if (element.id == "cookieBannerHandlingGroup") {
+ is_element_hidden(
+ element,
+ "Disabled cookieBannerHandlingGroup should be hidden"
+ );
+ continue;
+ }
+
// Update prefs are hidden when running an MSIX build
if (
updatePrefContainers.includes(element.id) &&
diff --git a/browser/components/preferences/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/tests/browser_bug795764_cachedisabled.js
index f702818d4af0..4dd788b5c95f 100644
--- a/browser/components/preferences/tests/browser_bug795764_cachedisabled.js
+++ b/browser/components/preferences/tests/browser_bug795764_cachedisabled.js
@@ -33,6 +33,13 @@ async function runTest(win) {
await win.gotoPref("panePrivacy");
for (let element of elements) {
let attributeValue = element.getAttribute("data-category");
+
+ // Ignore the cookie banner handling section, as it is currently preffed
+ // off by default (bug 1800679).
+ if (element.id === "cookieBannerHandlingGroup") {
+ continue;
+ }
+
if (attributeValue == "panePrivacy") {
is_element_visible(element, "HTTPSOnly should be visible");
diff --git a/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js b/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js
new file mode 100644
index 000000000000..95c906a950e6
--- /dev/null
+++ b/browser/components/preferences/tests/browser_privacy_cookieBannerHandling.js
@@ -0,0 +1,181 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file tests the Privacy pane's Cookie Banner Handling UI.
+
+"use strict";
+
+const FEATURE_PREF = "cookiebanners.ui.desktop.enabled";
+const MODE_PREF = "cookiebanners.service.mode";
+
+const GROUPBOX_ID = "cookieBannerHandlingGroup";
+const CHECKBOX_ID = "handleCookieBanners";
+
+// Test the section is hidden on page load if the feature pref is disabled.
+add_task(async function test_section_hidden_when_feature_flag_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, false],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let groupbox = browser.contentDocument.getElementById(GROUPBOX_ID);
+ is_element_hidden(groupbox, "#cookieBannerHandlingGroup is hidden");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the section is shown on page load if the feature pref is enabled.
+add_task(async function test_section_shown_when_feature_flag_enabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let groupbox = browser.contentDocument.getElementById(GROUPBOX_ID);
+ is_element_visible(groupbox, "#cookieBannerHandlingGroup is visible");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is unchecked in DISABLED mode.
+add_task(async function test_checkbox_unchecked_disabled_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(!checkbox.checked, "checkbox is not checked in DISABLED mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is unchecked in DETECT_ONLY mode.
+add_task(async function test_checkbox_unchecked_detect_only_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_DETECT_ONLY],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(!checkbox.checked, "checkbox is not checked in DETECT_ONLY mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is checked in REJECT_OR_ACCEPT mode.
+add_task(async function test_checkbox_checked_reject_or_accept_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(checkbox.checked, "checkbox is checked in REJECT_OR_ACCEPT mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the checkbox is checked in REJECT mode.
+add_task(async function test_checkbox_checked_reject_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let checkbox = browser.contentDocument.getElementById(CHECKBOX_ID);
+ ok(checkbox.checked, "checkbox is checked in REJECT mode");
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test that toggling the checkbox toggles the mode pref value as expected.
+add_task(async function test_checkbox_modifies_mode_pref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, true],
+ [MODE_PREF, Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function(browser) {
+ let checkboxSelector = "#" + CHECKBOX_ID;
+ let checkbox = browser.contentDocument.querySelector(checkboxSelector);
+ let section = browser.contentDocument.getElementById(GROUPBOX_ID);
+
+ section.scrollIntoView();
+
+ Assert.ok(checkbox.checked, "initially, the checkbox should be checked");
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ checkboxSelector,
+ {},
+ browser
+ );
+ Assert.ok(!checkbox.checked, "checkbox should be unchecked");
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ Services.prefs.getIntPref(MODE_PREF),
+ "cookie banner handling mode should be set to DISABLED mode after unchecking the checkbox"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ checkboxSelector,
+ {},
+ browser
+ );
+ Assert.ok(checkbox.checked, "checkbox should be checked");
+ Assert.equal(
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ Services.prefs.getIntPref(MODE_PREF),
+ "cookie banner handling mode should be set to REJECT_OR_ACCEPT mode after checking the checkbox"
+ );
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/preferences/tests/head.js b/browser/components/preferences/tests/head.js
index 1ca450ff145c..219829a13cb8 100644
--- a/browser/components/preferences/tests/head.js
+++ b/browser/components/preferences/tests/head.js
@@ -131,11 +131,13 @@ async function runSearchInput(input) {
async function evaluateSearchResults(
keyword,
- searchReults,
+ searchResults,
includeExperiments = false
) {
- searchReults = Array.isArray(searchReults) ? searchReults : [searchReults];
- searchReults.push("header-searchResults");
+ searchResults = Array.isArray(searchResults)
+ ? searchResults
+ : [searchResults];
+ searchResults.push("header-searchResults");
await runSearchInput(keyword);
@@ -145,7 +147,7 @@ async function evaluateSearchResults(
if (!includeExperiments && child.id?.startsWith("pane-experimental")) {
continue;
}
- if (searchReults.includes(child.id)) {
+ if (searchResults.includes(child.id)) {
is_element_visible(child, `${child.id} should be in search results`);
} else if (child.id) {
is_element_hidden(child, `${child.id} should not be in search results`);
diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn
index f5c0efcb0bf3..72a346e61cf5 100644
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -14,6 +14,7 @@
preview/firefoxSuggest.ftl (../components/urlbar/content/firefoxSuggest.ftl)
preview/identityCredentialNotification.ftl (../components/credentialmanager/identityCredentialNotification.ftl)
preview/protectionsPanel.ftl (../base/content/protectionsPanel.ftl)
+ preview/cookieBannerPreferences.ftl (../components/preferences/cookieBannerPreferences.ftl)
locales-preview/migrationWizard.ftl (../locales-preview/migrationWizard.ftl)
browser (%browser/**/*.ftl)