summaryrefslogtreecommitdiffstats
path: root/toolkit/components/cookiebanners/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/cookiebanners/test')
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser.toml62
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking.js485
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_domainPref.js368
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_events.js98
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules.js234
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules_telemetry.js223
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_runContext.js115
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_slowLoad.js37
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_stopExecuteAfterSeveralAttempt.js352
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_telemetry_querySelector.js169
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_bannerClicking_visibilityOverride.js84
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js781
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebanner_webconsole.js101
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js635
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_domainPrefs.js403
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_getRules.js88
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_hasRuleForBCTree.js293
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_prefs.js213
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js625
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookieinjector_events.js56
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookieinjector_telemetry.js88
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_banner.html28
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_banner_b.html28
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_banner_invisible.html28
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_delayed_banner.html54
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_delayed_banner_load.html49
-rw-r--r--toolkit/components/cookiebanners/test/browser/file_iframe_banner.html8
-rw-r--r--toolkit/components/cookiebanners/test/browser/head.js658
-rw-r--r--toolkit/components/cookiebanners/test/browser/slowSubresource.sjs18
-rw-r--r--toolkit/components/cookiebanners/test/browser/testCookieHeader.sjs5
-rw-r--r--toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js611
-rw-r--r--toolkit/components/cookiebanners/test/unit/xpcshell.toml3
32 files changed, 7000 insertions, 0 deletions
diff --git a/toolkit/components/cookiebanners/test/browser/browser.toml b/toolkit/components/cookiebanners/test/browser/browser.toml
new file mode 100644
index 0000000000..69728540d9
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser.toml
@@ -0,0 +1,62 @@
+[DEFAULT]
+support-files = ["head.js"]
+
+["browser_bannerClicking.js"]
+support-files = [
+ "file_banner.html",
+ "file_banner_b.html",
+ "file_delayed_banner.html",
+]
+
+["browser_bannerClicking_domainPref.js"]
+
+["browser_bannerClicking_events.js"]
+support-files = ["file_banner.html"]
+
+["browser_bannerClicking_globalRules.js"]
+
+["browser_bannerClicking_globalRules_telemetry.js"]
+
+["browser_bannerClicking_runContext.js"]
+support-files = ["file_banner.html"]
+
+["browser_bannerClicking_slowLoad.js"]
+support-files = [
+ "file_delayed_banner_load.html",
+ "slowSubresource.sjs",
+]
+
+["browser_bannerClicking_stopExecuteAfterSeveralAttempt.js"]
+
+["browser_bannerClicking_telemetry_querySelector.js"]
+support-files = [
+ "file_banner.html",
+]
+
+["browser_bannerClicking_visibilityOverride.js"]
+support-files = ["file_banner_invisible.html"]
+
+["browser_cookiebanner_telemetry.js"]
+support-files = ["file_iframe_banner.html"]
+
+["browser_cookiebanner_webconsole.js"]
+support-files = [
+ "file_banner.html",
+]
+
+["browser_cookiebannerservice.js"]
+
+["browser_cookiebannerservice_domainPrefs.js"]
+
+["browser_cookiebannerservice_getRules.js"]
+
+["browser_cookiebannerservice_hasRuleForBCTree.js"]
+
+["browser_cookiebannerservice_prefs.js"]
+
+["browser_cookieinjector.js"]
+support-files = ["testCookieHeader.sjs"]
+
+["browser_cookieinjector_events.js"]
+
+["browser_cookieinjector_telemetry.js"]
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking.js
new file mode 100644
index 0000000000..4353505ab2
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking.js
@@ -0,0 +1,485 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+/**
+ * Test that the banner clicking won't click banner if the service is disabled or in detect-only mode.
+ */
+add_task(async function test_cookie_banner_service_disabled() {
+ for (let [serviceMode, detectOnly] of [
+ [Ci.nsICookieBannerService.MODE_DISABLED, false],
+ [Ci.nsICookieBannerService.MODE_DISABLED, true],
+ [Ci.nsICookieBannerService.MODE_REJECT, true],
+ [Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, true],
+ ]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", serviceMode],
+ ["cookiebanners.service.detectOnly", detectOnly],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ ],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ if (serviceMode != Ci.nsICookieBannerService.MODE_DISABLED) {
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ }
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ expectActorEnabled:
+ serviceMode != Ci.nsICookieBannerService.MODE_DISABLED,
+ });
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+/**
+ * Test that the banner clicking won't click banner if there is no rule.
+ */
+add_task(async function test_no_rules() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // No click telemetry reported.
+ await testClickResultTelemetry({});
+});
+
+/**
+ * Test the banner clicking with MODE_REJECT.
+ */
+add_task(async function test_clicking_mode_reject() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry(
+ { success: 1, success_dom_content_loaded: 1 },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // No opt out rule for the example.org, the banner shouldn't be clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // No matching rule means we don't record any telemetry for clicks.
+ await testClickResultTelemetry({
+ success: 1,
+ success_dom_content_loaded: 1,
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ });
+});
+
+/**
+ * Test the banner clicking with MODE_REJECT_OR_ACCEPT.
+ */
+add_task(async function test_clicking_mode_reject_or_accept() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 1,
+ success_dom_content_loaded: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ await testClickResultTelemetry({
+ success: 2,
+ success_dom_content_loaded: 2,
+ });
+});
+
+/**
+ * Test the banner clicking with the case where the banner is added after
+ * page loads and with a short amount of delay.
+ */
+add_task(async function test_clicking_with_delayed_banner() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.bannerClicking.timeoutAfterLoad", 10000],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ let TEST_PAGE =
+ TEST_ORIGIN_A + TEST_PATH + "file_delayed_banner.html?delay=100";
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry({
+ success: 1,
+ success_mutation_pre_load: 1,
+ });
+});
+
+/**
+ * Test that the banner clicking in an iframe.
+ */
+add_task(async function test_embedded_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry({
+ success: 1,
+ success_dom_content_loaded: 1,
+ });
+});
+
+/**
+ * Test banner clicking with the private browsing window.
+ */
+add_task(async function test_pbm() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+
+ await testClickResultTelemetry({
+ success: 1,
+ success_dom_content_loaded: 1,
+ });
+});
+
+/**
+ * Tests service mode pref combinations for normal and private browsing.
+ */
+add_task(async function test_pref_pbm_pref() {
+ info("Enable in normal browsing but disable in private browsing.");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ ],
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 1,
+ success_dom_content_loaded: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 1,
+ success_dom_content_loaded: 1,
+ },
+ false
+ );
+
+ info("Disable in normal browsing but enable in private browsing.");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 1,
+ success_dom_content_loaded: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 2,
+ success_dom_content_loaded: 2,
+ },
+ false
+ );
+
+ info(
+ "Set normal browsing to REJECT_OR_ACCEPT and private browsing to REJECT."
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info(
+ "The normal browsing window accepts the banner according to the opt-in rule."
+ );
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ await testClickResultTelemetry(
+ {
+ success: 3,
+ success_dom_content_loaded: 3,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info(
+ "The private browsing window should not perform any click, because there is only an opt-in rule."
+ );
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+
+ await testClickResultTelemetry({
+ success: 3,
+ success_dom_content_loaded: 3,
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ });
+});
+
+/**
+ * Test that the banner clicking in an iframe with the private browsing window.
+ */
+add_task(async function test_embedded_iframe_pbm() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ await openIframeAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+
+ await testClickResultTelemetry({
+ success: 1,
+ success_dom_content_loaded: 1,
+ });
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_domainPref.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_domainPref.js
new file mode 100644
index 0000000000..01986eb36b
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_domainPref.js
@@ -0,0 +1,368 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { ForgetAboutSite } = ChromeUtils.importESModule(
+ "resource://gre/modules/ForgetAboutSite.sys.mjs"
+);
+
+add_setup(clickTestSetup);
+
+/**
+ * Test that domain preference takes precedence over pref settings.
+ */
+add_task(async function test_domain_preference() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Clear executed records before testing for private and normal browsing.
+ Services.cookieBanners.removeAllExecutedRecords(true);
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ for (let testPBM of [false, true]) {
+ let testWin = window;
+ if (testPBM) {
+ testWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ }
+
+ await testClickResultTelemetry({});
+
+ info(
+ "Make sure the example.org follows the pref setting when there is no domain preference."
+ );
+ await openPageAndVerify({
+ win: testWin,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await testClickResultTelemetry(
+ {
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(testPBM);
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ let uri = Services.io.newURI(TEST_ORIGIN_B);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ testPBM
+ );
+
+ info(
+ "Verify if domain preference takes precedence over then the pref setting for example.org"
+ );
+ await openPageAndVerify({
+ win: testWin,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ Services.cookieBanners.removeAllDomainPrefs(testPBM);
+
+ if (testPBM) {
+ await BrowserTestUtils.closeWindow(testWin);
+ }
+
+ await testClickResultTelemetry({
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ success: 1,
+ success_dom_content_loaded: 1,
+ });
+ }
+});
+
+/**
+ * Test that domain preference works on the top-level domain.
+ */
+add_task(async function test_domain_preference_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Clear executed records before testing for private and normal browsing.
+ Services.cookieBanners.removeAllExecutedRecords(true);
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await testClickResultTelemetry({});
+
+ for (let testPBM of [false, true]) {
+ let testWin = window;
+ if (testPBM) {
+ testWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ }
+
+ info(
+ "Make sure the example.org follows the pref setting when there is no domain preference for the top-level example.net."
+ );
+ await openIframeAndVerify({
+ win: testWin,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await testClickResultTelemetry(
+ {
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(testPBM);
+
+ info(
+ "Set the domain preference of the top-level domain to MODE_REJECT_OR_ACCEPT"
+ );
+ let uri = Services.io.newURI(TEST_ORIGIN_C);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ testPBM
+ );
+
+ info(
+ "Verify if domain preference takes precedence over then the pref setting for top-level example.net"
+ );
+ await openIframeAndVerify({
+ win: testWin,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ Services.cookieBanners.removeAllDomainPrefs(testPBM);
+
+ if (testPBM) {
+ await BrowserTestUtils.closeWindow(testWin);
+ }
+
+ await testClickResultTelemetry({
+ fail: 1,
+ fail_no_rule_for_mode: 1,
+ success: 1,
+ success_dom_content_loaded: 1,
+ });
+ }
+});
+
+// Verify that the ForgetAboutSite clears the domain preference properly.
+add_task(async function test_domain_preference_forgetAboutSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Clear executed records before testing for private and normal browsing.
+ Services.cookieBanners.removeAllExecutedRecords(true);
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ let uri = Services.io.newURI(TEST_ORIGIN_B);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ info(
+ "Verify if domain preference takes precedence over then the pref setting for example.org"
+ );
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ // Call ForgetAboutSite for the domain.
+ await ForgetAboutSite.removeDataFromDomain(TEST_DOMAIN_B);
+
+ info("Ensure the domain preference is cleared.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+});
+
+add_task(async function test_domain_preference_clearDataService() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Clear executed records before testing or private and normal browsing.
+ Services.cookieBanners.removeAllExecutedRecords(true);
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ let uri = Services.io.newURI(TEST_ORIGIN_B);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ info(
+ "Verify if domain preference takes precedence over then the pref setting for example.org"
+ );
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptIn",
+ });
+
+ info("Call ClearDataService.deleteDataFromBaseDomain for the domain.");
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromBaseDomain(
+ TEST_DOMAIN_B,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION,
+ aResolve
+ );
+ });
+
+ info("Ensure the domain preference is cleared.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ info("Call ClearDataService.deleteDataFromHost for the domain.");
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromHost(
+ TEST_DOMAIN_B,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION,
+ aResolve
+ );
+ });
+
+ info("Ensure the domain preference is cleared.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ info("Call ClearDataService.deleteDataFromPrincipal for the domain.");
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://" + TEST_DOMAIN_B
+ );
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromPrincipal(
+ principal,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION,
+ aResolve
+ );
+ });
+
+ info("Ensure the domain preference is cleared.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ info("Set the domain preference of example.org to MODE_REJECT_OR_ACCEPT");
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ info("Call ClearDataService.deleteData for the domain.");
+ await new Promise(aResolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION,
+ aResolve
+ );
+ });
+
+ info("Ensure the domain preference is cleared.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_events.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_events.js
new file mode 100644
index 0000000000..de9a464446
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_events.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+/**
+ * Triggers cookie banner clicking and tests the events dispatched.
+ * @param {*} options - Test options.
+ * @param {nsICookieBannerService::Modes} options.mode - The cookie banner service mode to test with.
+ * @param {boolean} options.detectOnly - Whether the service should be enabled
+ * in detection only mode, where it does not handle banners.
+ * @param {*} options.openPageOptions - Options to overwrite for the openPageAndVerify call.
+ */
+async function runTest({ mode, detectOnly = false, openPageOptions = {} }) {
+ if (mode == null) {
+ throw new Error("Invalid cookie banner service mode.");
+ }
+ let initFn = () => {
+ // Insert rules only if the feature is enabled.
+ if (Services.cookieBanners.isEnabled) {
+ insertTestClickRules();
+
+ // Clear executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ }
+ };
+
+ let shouldHandleBanner =
+ mode == Ci.nsICookieBannerService.MODE_REJECT && !detectOnly;
+ let expectActorEnabled = mode != Ci.nsICookieBannerService.MODE_DISABLED;
+ let testURL = openPageOptions.testURL || TEST_PAGE_A;
+ let triggerFn = async () => {
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL,
+ visible: !shouldHandleBanner,
+ expected: shouldHandleBanner ? "OptOut" : "NoClick",
+ keepTabOpen: true,
+ expectActorEnabled,
+ ...openPageOptions, // Allow test callers to override any options for this method.
+ });
+ };
+
+ await runEventTest({ mode, detectOnly, initFn, triggerFn, testURL });
+
+ // Clean up the test tab opened by openPageAndVerify.
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+/**
+ * Test the banner clicking events with MODE_REJECT.
+ */
+add_task(async function test_events_mode_reject() {
+ await runTest({ mode: Ci.nsICookieBannerService.MODE_REJECT });
+});
+
+/**
+ * Test the banner clicking events with detect-only mode.
+ */
+add_task(async function test_events_mode_detect_only() {
+ await runTest({
+ mode: Ci.nsICookieBannerService.MODE_REJECT,
+ detectOnly: true,
+ });
+ await runTest({
+ mode: Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ detectOnly: true,
+ });
+});
+
+/**
+ * Test the banner clicking events with detect-only mode with a click rule that
+ * only supports opt-in.
+ */
+add_task(async function test_events_mode_detect_only_opt_in_rule() {
+ await runTest({
+ mode: Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ detectOnly: true,
+ openPageOptions: {
+ // We only have an opt-in rule for DOMAIN_B. This ensures we still fire
+ // detection events for that case.
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ shouldHandleBanner: true,
+ expected: "NoClick",
+ },
+ });
+});
+
+/**
+ * Test the banner clicking events in disabled mode.
+ */
+add_task(async function test_events_mode_disabled() {
+ await runTest({ mode: Ci.nsICookieBannerService.MODE_DISABLED });
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules.js
new file mode 100644
index 0000000000..a23194be95
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules.js
@@ -0,0 +1,234 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+/**
+ * Test the banner clicking with global rules and MODE_REJECT.
+ */
+add_task(async function test_clicking_global_rules() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.service.enableGlobalRules", true],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting global test rules.");
+
+ info(
+ "Add global ruleA which targets an existing banner (presence) with existing buttons. This rule should handle the banner."
+ );
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.id = genUUID();
+ ruleA.domains = [];
+ ruleA.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleA);
+
+ info(
+ "Add global ruleC which targets an existing banner (presence) but non-existing buttons."
+ );
+ let ruleC = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleC.id = genUUID();
+ ruleC.domains = [];
+ ruleC.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#nonExistingOptOut",
+ "button#nonExistingOptIn"
+ );
+ Services.cookieBanners.insertRule(ruleC);
+
+ info("Add global ruleD which targets a non-existing banner (presence).");
+ let ruleD = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleD.id = genUUID();
+ ruleD.domains = [];
+ ruleD.addClickRule(
+ "div#nonExistingBanner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ null,
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleD);
+
+ await testCMPResultTelemetry({});
+
+ info("The global rule ruleA should handle both test pages with div#banner.");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testCMPResultTelemetry(
+ {
+ success: 1,
+ success_dom_content_loaded: 1,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testCMPResultTelemetry(
+ {
+ success: 2,
+ success_dom_content_loaded: 2,
+ },
+ false
+ );
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("No global rule should handle TEST_PAGE_C with div#bannerB.");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_C,
+ testURL: TEST_PAGE_C,
+ visible: true,
+ expected: "NoClick",
+ bannerId: "bannerB",
+ });
+
+ await testCMPResultTelemetry(
+ {
+ success: 2,
+ success_dom_content_loaded: 2,
+ fail: 1,
+ fail_banner_not_found: 1,
+ },
+ false
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["cookiebanners.bannerClicking.timeoutAfterLoad", 10000]],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Test delayed banner handling with global rules.");
+ let TEST_PAGE =
+ TEST_ORIGIN_A + TEST_PATH + "file_delayed_banner.html?delay=100";
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await SpecialPowers.popPrefEnv();
+
+ await testCMPResultTelemetry({
+ success: 3,
+ success_dom_content_loaded: 2,
+ fail: 1,
+ fail_banner_not_found: 1,
+ success_mutation_pre_load: 1,
+ });
+});
+
+/**
+ * Test that domain-specific rules take precedence over global rules.
+ */
+add_task(async function test_clicking_global_rules_precedence() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.service.enableGlobalRules", true],
+ ],
+ });
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting global test rules.");
+
+ info(
+ "Add global ruleA which targets an existing banner (presence) with existing buttons."
+ );
+ let ruleGlobal = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleGlobal.id = genUUID();
+ ruleGlobal.domains = [];
+ ruleGlobal.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#optOut",
+ null
+ );
+ Services.cookieBanners.insertRule(ruleGlobal);
+
+ info("Add domain specific rule which also targets the existing banner.");
+ let ruleDomain = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleDomain.id = genUUID();
+ ruleDomain.domains = [TEST_DOMAIN_A];
+ ruleDomain.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ null,
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleDomain);
+
+ await testCMPResultTelemetry({});
+
+ info("Test that the domain-specific rule applies, not the global one.");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ // Because of the way the rules are setup OptOut would mean the global rule
+ // applies, opt-in means the domain specific rule applies.
+ expected: "OptIn",
+ });
+
+ // Ensure we don't accidentally collect CMP result telemetry when
+ // domain-specific rule applies.
+ await testCMPResultTelemetry({});
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules_telemetry.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules_telemetry.js
new file mode 100644
index 0000000000..6d9e96914d
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_globalRules_telemetry.js
@@ -0,0 +1,223 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+async function verifyDetectedCMPTelemetry(expected) {
+ // Ensure we have all data from the content process.
+ await Services.fog.testFlushAllChildren();
+
+ let LABELS = [
+ "trustarcbar",
+ "quantcast",
+ "borlabs",
+ "consentmanagernet",
+ "cookiebot",
+ "complianz",
+ "onetrust",
+ "didomi",
+ "sourcepoint",
+ ];
+
+ let testMetricState = doAssert => {
+ for (let label of LABELS) {
+ let expectedValue = expected[label] ?? null;
+ if (doAssert) {
+ is(
+ Glean.cookieBannersCmp.detectedCmp[label].testGetValue(),
+ expectedValue,
+ `Counter for label '${label}' has correct state.`
+ );
+ } else if (
+ Glean.cookieBannersCmp.detectedCmp[label].testGetValue() !==
+ expectedValue
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ // Wait for the labeled counter to match the expected state. Returns greedy on
+ // mismatch.
+ try {
+ await TestUtils.waitForCondition(
+ testMetricState,
+ "Waiting for cookieBannersClick.result metric to match."
+ );
+ } finally {
+ // Test again but this time with assertions and test all labels.
+ testMetricState(true);
+ }
+}
+
+async function verifyRatioTelemetry(expected) {
+ // Ensure we have all data from the content process.
+ await Services.fog.testFlushAllChildren();
+
+ let cmpMetric = Glean.cookieBannersCmp.ratioHandledByCmpRule;
+
+ let testMetricState = doAssert => {
+ let cmpValue = cmpMetric.testGetValue();
+
+ if (!cmpValue) {
+ return false;
+ }
+
+ if (!doAssert) {
+ return (
+ cmpValue.numerator == expected.cmp.numerator &&
+ cmpValue.denominator == expected.cmp.denominator
+ );
+ }
+
+ is(
+ cmpValue.numerator,
+ expected.cmp.numerator,
+ "The numerator of rateHandledByCmpRule is expected"
+ );
+ is(
+ cmpValue.denominator,
+ expected.cmp.denominator,
+ "The denominator of rateHandledByCmpRule is expected"
+ );
+
+ return true;
+ };
+
+ // Wait for matching the expected state.
+ try {
+ await TestUtils.waitForCondition(
+ testMetricState,
+ "Waiting for metric to match."
+ );
+ } finally {
+ // Test again but this time with assertions and test all labels.
+ testMetricState(true);
+ }
+}
+
+add_task(async function test_cmp_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.service.enableGlobalRules", true],
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 0],
+ ],
+ });
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting global test rules.");
+
+ info(
+ "Add global ruleA with a predefined identifier. This rule will handle the banner."
+ );
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.id = "trustarcbar";
+ ruleA.domains = [];
+ ruleA.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleA);
+
+ info("Add global ruleC with another predefined identifier.");
+ let ruleC = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleC.id = "quantcast";
+ ruleC.domains = [];
+ ruleC.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#nonExistingOptOut",
+ "button#nonExistingOptIn"
+ );
+ Services.cookieBanners.insertRule(ruleC);
+
+ info("Open a test page and verify the telemetry");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await verifyRatioTelemetry({
+ cmp: { numerator: 1, denominator: 1 },
+ });
+ await verifyDetectedCMPTelemetry({
+ trustarcbar: 1,
+ quantcast: 1,
+ });
+
+ info("Open the test page again and verify if the telemetry gets updated");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await verifyRatioTelemetry({
+ cmp: { numerator: 2, denominator: 2 },
+ });
+ await verifyDetectedCMPTelemetry({
+ trustarcbar: 2,
+ quantcast: 2,
+ });
+
+ info("Add domain specific rule which also targets the existing banner.");
+ let ruleDomain = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleDomain.id = genUUID();
+ ruleDomain.domains = [TEST_DOMAIN_A];
+ ruleDomain.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleDomain);
+
+ info("Open the test page and now the site-specific rule will handle it");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ info("Verify that the CMP telemetry doesn't get updated.");
+ await verifyDetectedCMPTelemetry({
+ trustarcbar: 2,
+ quantcast: 2,
+ });
+
+ info(
+ "Verify the handled ratio telemetry for site-specific rule gets updated."
+ );
+ await verifyRatioTelemetry({
+ cmp: { numerator: 2, denominator: 3 },
+ });
+
+ // Clear Telemetry
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_runContext.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_runContext.js
new file mode 100644
index 0000000000..e6bfad4765
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_runContext.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+/**
+ * Insert a test rule with the specified runContext.
+ * @param {RunContext} - The runContext to set for the rule. See nsIClickRule
+ * for documentation.
+ */
+function insertTestRules({ runContext }) {
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting test rules. " + JSON.stringify({ runContext }));
+
+ info("Add opt-out click rule for DOMAIN_A.");
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.id = genUUID();
+ ruleA.domains = [TEST_DOMAIN_A];
+
+ ruleA.addClickRule(
+ "div#banner",
+ false,
+ runContext,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleA);
+}
+
+/**
+ * Test that banner clicking only runs if the context matches the runContext
+ * specified in the click rule.
+ */
+add_task(async function test_embedded_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ insertTestRules({ runContext: Ci.nsIClickRule.RUN_TOP });
+
+ // Clear executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestRules({ runContext: Ci.nsIClickRule.RUN_CHILD });
+
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertTestRules({ runContext: Ci.nsIClickRule.RUN_ALL });
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_slowLoad.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_slowLoad.js
new file mode 100644
index 0000000000..d0d06f3324
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_slowLoad.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(clickTestSetup);
+
+/**
+ * Test the banner clicking with the case where the banner is added shortly after
+ * page load, but the page load itself is delayed.
+ */
+add_task(async function test_clicking_with_delayed_banner() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ insertTestClickRules();
+
+ await testClickResultTelemetry({});
+
+ // A test page that has a delayed load event.
+ let TEST_PAGE = TEST_ORIGIN_A + TEST_PATH + "file_delayed_banner_load.html";
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await testClickResultTelemetry({
+ success: 1,
+ success_mutation_post_load: 1,
+ });
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_stopExecuteAfterSeveralAttempt.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_stopExecuteAfterSeveralAttempt.js
new file mode 100644
index 0000000000..6e3a6f19fe
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_stopExecuteAfterSeveralAttempt.js
@@ -0,0 +1,352 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { ForgetAboutSite } = ChromeUtils.importESModule(
+ "resource://gre/modules/ForgetAboutSite.sys.mjs"
+);
+
+add_setup(async function () {
+ await clickTestSetup();
+
+ // Set the pref to enable stopping executing cookie banner handling after
+ // several tries.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 1],
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // Clean the records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+});
+
+// Ensure the banner clicking doesn't execute at the second load if the
+// stopExecuteAfterAttempt feature is enabled.
+add_task(async function testStopExecuteAfterOneAttempt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 1],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Open the domain and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain again and the banner shouldn't be hidden nor the banner is
+ // clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // Clear the record for the normal window.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // Open the domain again after clearing the record and ensure the banner is
+ // clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Test in the private browsing mode.
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Open the domain in PBM and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain in PBM again and the banner shouldn't be hidden nor the
+ // banner is clicked.
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // Clear the record for the private window.
+ Services.cookieBanners.removeAllExecutedRecords(true);
+
+ // Open the domain again in PBM after clearing the record, and ensure the
+ // banner is clicked.
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+
+ // Clean the records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+});
+
+// Ensure the banner clicking doesn't execute after several attempts if the
+// stopExecuteAfterAttempt feature is enabled.
+add_task(async function testStopExecuteAfterSeveralAttempts() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 2],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Open the domain and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain in the second time and the banner should be clicked,
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain in the third time, the the banner shouldn't be hidden nor
+ // the banner is clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // Test in the private browsing mode.
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Open the domain in PBM and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain in the second time and the banner should be clicked,
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain in PBM third time and the banner shouldn't be hidden nor
+ // the banner is clicked.
+ await openPageAndVerify({
+ win: pbmWindow,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+
+ // Clean the records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+});
+
+// Ensure that ForgetAboutSite clears the handled record properly.
+add_task(async function testForgetAboutSiteWithStopExecuteAfterOneAttempt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 1],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Open the domain and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Open the domain again and the banner shouldn't be hidden nor the banner is
+ // clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: true,
+ expected: "NoClick",
+ });
+
+ // Call ForgetAboutSite for the domain.
+ await ForgetAboutSite.removeDataFromDomain(TEST_DOMAIN_A);
+
+ // Open the domain again after ForgetAboutSite and the clicking should work
+ // again.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Clean the records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+});
+
+// Ensure the ClearDataService clears the handled record properly.
+add_task(async function testClearDataServiceWithStopExecuteAfterOneAttempt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.bannerClicking.maxTriesPerSiteAndSession", 1],
+ ],
+ });
+
+ insertTestClickRules();
+
+ // Open the domain initially and ensure the banner is clicked.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Invoke deleteDataFromBaseDomain.
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromBaseDomain(
+ TEST_DOMAIN_A,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
+ aResolve
+ );
+ });
+
+ // Ensure the record is cleared. The banner clicking should work after clean.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Invoke deleteDataFromHost.
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromHost(
+ TEST_DOMAIN_A,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
+ aResolve
+ );
+ });
+
+ // Ensure the record is cleared. The banner clicking should work after clean.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Invoke deleteDataFromPrincipal.
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://" + TEST_DOMAIN_A
+ );
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromPrincipal(
+ principal,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
+ aResolve
+ );
+ });
+
+ // Ensure the record is cleared. The banner clicking should work after clean.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Invoke deleteData.
+ await new Promise(aResolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
+ aResolve
+ );
+ });
+
+ // Ensure the record is cleared. The banner clicking should work after clean.
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ // Clean the records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_telemetry_querySelector.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_telemetry_querySelector.js
new file mode 100644
index 0000000000..0778c736a4
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_telemetry_querySelector.js
@@ -0,0 +1,169 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Wait for the result of checkFn to equal expectedValue and run an assertion
+ * once the value matches.
+ * @param {function} checkFn - Function which returns value to compare to
+ * expectedValue.
+ * @param {*} expectedValue - Value to compare against checkFn return value.
+ * @param {string} message - Assertion / test message.
+ */
+async function waitForAndAssertEqual(checkFn, expectedValue, message) {
+ let checkFnResult;
+ await BrowserTestUtils.waitForCondition(() => {
+ checkFnResult = checkFn();
+ return checkFnResult == expectedValue;
+ }, message);
+ Assert.equal(checkFnResult, expectedValue, message);
+}
+
+/**
+ * Test that query selector histogram data is set / unset.
+ * @param {*} options
+ * @param {boolean} options.hasTopLevelData - Whether there should be telemetry
+ * data for top level windows.
+ * @param {boolean} options.hasFrameData = Whether there should be telemetry
+ * data for sub-frames.
+ */
+async function assertQuerySelectorTelemetry({ hasTopLevelData, hasFrameData }) {
+ // Ensure we have all data from the content process.
+ await Services.fog.testFlushAllChildren();
+
+ let runDurationTopLevel = () =>
+ Glean.cookieBannersClick.querySelectorRunDurationPerWindowTopLevel.testGetValue() !=
+ null;
+ let runDurationFrame = () =>
+ Glean.cookieBannersClick.querySelectorRunDurationPerWindowFrame.testGetValue() !=
+ null;
+
+ let runCountTopLevel = () =>
+ Glean.cookieBannersClick.querySelectorRunCountPerWindowTopLevel.testGetValue() !=
+ null;
+ let runCountFrame = () =>
+ Glean.cookieBannersClick.querySelectorRunCountPerWindowFrame.testGetValue() !=
+ null;
+
+ let messagePrefix = hasData => `Should${hasData ? "" : " not"} have`;
+
+ await waitForAndAssertEqual(
+ runCountTopLevel,
+ hasTopLevelData,
+ `${messagePrefix(hasTopLevelData)} top-level run count data.`
+ );
+ await waitForAndAssertEqual(
+ runDurationTopLevel,
+ hasTopLevelData,
+ `${messagePrefix(hasTopLevelData)} top-level run duration data.`
+ );
+
+ await waitForAndAssertEqual(
+ runDurationFrame,
+ hasFrameData,
+ `${messagePrefix(hasFrameData)} sub-frame run duration data.`
+ );
+ await waitForAndAssertEqual(
+ runCountFrame,
+ hasFrameData,
+ `${messagePrefix(hasFrameData)} sub-frame run count data.`
+ );
+}
+
+add_setup(clickTestSetup);
+
+/**
+ * Tests that, when performing cookie banner clicking, query selector
+ * performance telemetry is collected.
+ */
+add_task(async function test_click_query_selector_telemetry() {
+ // Clear telemetry.
+ Services.fog.testResetFOG();
+
+ // Enable cookie banner handling.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ insertTestClickRules();
+
+ info("No telemetry recorded initially.");
+ await assertQuerySelectorTelemetry({
+ hasFrameData: false,
+ hasTopLevelData: false,
+ });
+
+ // No opt out rule for the example.org, the banner shouldn't be clicked.
+ info("Top level cookie banner with no matching rule.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_B,
+ testURL: TEST_PAGE_B,
+ visible: true,
+ expected: "NoClick",
+ });
+ await assertQuerySelectorTelemetry({
+ hasFrameData: false,
+ hasTopLevelData: true,
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Top level cookie banner with matching rule.");
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+ await assertQuerySelectorTelemetry({
+ hasFrameData: false,
+ hasTopLevelData: true,
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Iframe cookie banner with matching rule.");
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+ await assertQuerySelectorTelemetry({
+ hasFrameData: true,
+ hasTopLevelData: true,
+ });
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // Reset test rules and insert only site-specific rules so we can ensure the
+ // clicking mechanism is only running for the iframe, not the top level.
+ info("Insert test rules without global rules.");
+ insertTestClickRules(false);
+
+ // Clear telemetry.
+ info("Clear telemetry.");
+ Services.fog.testResetFOG();
+
+ info("Iframe cookie banner with matching rule.");
+ await openIframeAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+ await assertQuerySelectorTelemetry({
+ hasFrameData: true,
+ hasTopLevelData: false,
+ });
+
+ // Clear telemetry.
+ Services.fog.testResetFOG();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_visibilityOverride.js b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_visibilityOverride.js
new file mode 100644
index 0000000000..0846f92620
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_bannerClicking_visibilityOverride.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// A test page that has an invisible cookie banner. This simulates sites where
+// the banner is invisible whenever we test for it. See Bug 1793803.
+const TEST_PAGE = TEST_ORIGIN_A + TEST_PATH + "file_banner_invisible.html";
+
+add_setup(clickTestSetup);
+
+/**
+ * Insert a test rule with or without the skipPresenceVisibilityCheck flag.
+ * @param {boolean} skipPresenceVisibilityCheck - Whether to set the flag for
+ * the test rule.
+ */
+function insertVisibilityTestRules(skipPresenceVisibilityCheck) {
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info(
+ "Inserting test rules. " + JSON.stringify({ skipPresenceVisibilityCheck })
+ );
+
+ info("Add opt-out click rule for DOMAIN_A.");
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.id = genUUID();
+ ruleA.domains = [TEST_DOMAIN_A];
+
+ ruleA.addClickRule(
+ "div#banner",
+ skipPresenceVisibilityCheck,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleA);
+}
+
+/**
+ * Test that we click on an invisible banner element if
+ * skipPresenceVisibilityCheck is set.
+ */
+add_task(async function test_clicking_with_delayed_banner() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ for (let skipPresenceVisibilityCheck of [false, true]) {
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ insertVisibilityTestRules(skipPresenceVisibilityCheck);
+
+ await testClickResultTelemetry({});
+
+ await openPageAndVerify({
+ win: window,
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE,
+ visible: false,
+ expected: skipPresenceVisibilityCheck ? "OptOut" : "NoClick",
+ });
+
+ let expectedTelemetry;
+ if (skipPresenceVisibilityCheck) {
+ expectedTelemetry = {
+ success: 1,
+ success_dom_content_loaded: 1,
+ };
+ } else {
+ expectedTelemetry = {
+ fail: 1,
+ fail_banner_not_visible: 1,
+ };
+ }
+ await testClickResultTelemetry(expectedTelemetry);
+ }
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
new file mode 100644
index 0000000000..7366ae2ab6
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
@@ -0,0 +1,781 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+const { MODE_DISABLED, MODE_REJECT, MODE_REJECT_OR_ACCEPT, MODE_UNSET } =
+ Ci.nsICookieBannerService;
+
+const TEST_MODES = [
+ MODE_DISABLED,
+ MODE_REJECT,
+ MODE_REJECT_OR_ACCEPT,
+ MODE_UNSET, // Should be recorded as invalid.
+ 99, // Invalid
+ -1, // Invalid
+];
+
+function convertModeToTelemetryString(mode) {
+ switch (mode) {
+ case MODE_DISABLED:
+ return "disabled";
+ case MODE_REJECT:
+ return "reject";
+ case MODE_REJECT_OR_ACCEPT:
+ return "reject_or_accept";
+ }
+
+ return "invalid";
+}
+
+/**
+ * A helper function to verify the cookie rule look up telemetry.
+ *
+ * @param {String} probe The telemetry probe that we want to verify
+ * @param {Array} expected An array of objects that describe the expected value.
+ */
+function verifyLookUpTelemetry(probe, expected) {
+ for (let telemetry of expected) {
+ Assert.equal(
+ telemetry.count,
+ Glean.cookieBanners[probe][telemetry.label].testGetValue()
+ );
+ }
+}
+
+/**
+ * A helper function to verify the reload telemetry.
+ *
+ * @param {Number} length The expected length of the telemetry array.
+ * @param {Number} idx The index of the telemetry to be verified.
+ * @param {Object} expected An object that describe the expected value.
+ */
+function verifyReloadTelemetry(length, idx, expected) {
+ let events = Glean.cookieBanners.reload.testGetValue();
+
+ is(events.length, length, "There is a expected number of reload events.");
+
+ let event = events[idx];
+
+ let { noRule, hasCookieRule, hasClickRule } = expected;
+ is(event.name, "reload", "The reload event has the correct name");
+ is(event.extra.no_rule, noRule, "The extra field 'no_rule' is expected");
+ is(
+ event.extra.has_cookie_rule,
+ hasCookieRule,
+ "The extra field 'has_cookie_rule' is expected"
+ );
+ is(
+ event.extra.has_click_rule,
+ hasClickRule,
+ "The extra field 'has_click_rule' is expected"
+ );
+}
+
+/**
+ * A helper function to reload the browser and wait until it loads.
+ *
+ * @param {Browser} browser The browser object.
+ * @param {String} url The URL to be loaded.
+ */
+async function reloadBrowser(browser, url) {
+ let reloaded = BrowserTestUtils.browserLoaded(browser, false, url);
+
+ // Reload as a user.
+ window.BrowserReload();
+
+ await reloaded;
+}
+/**
+ * A helper function to open the testing page for look up telemetry.
+ *
+ * @param {browser} browser The browser element
+ * @param {boolean} testInTop To indicate the page should be opened in top level
+ * @param {String} page The url of the testing page
+ * @param {String} domain The domain of the testing page
+ */
+async function openLookUpTelemetryTestPage(browser, testInTop, page, domain) {
+ let clickFinishPromise = promiseBannerClickingFinish(domain);
+
+ if (testInTop) {
+ BrowserTestUtils.startLoadingURIString(browser, page);
+ } else {
+ BrowserTestUtils.startLoadingURIString(browser, TEST_ORIGIN_C);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [page], async testURL => {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = testURL;
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+ });
+ }
+
+ await clickFinishPromise;
+}
+
+add_setup(async function () {
+ // Clear telemetry before starting telemetry test.
+ Services.fog.testResetFOG();
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (
+ Services.prefs.getIntPref("cookiebanners.service.mode") !=
+ Ci.nsICookieBannerService.MODE_DISABLED ||
+ Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
+ Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+ }
+
+ // Clear cookies that have been set during testing.
+ await SiteDataTestUtils.clear();
+ });
+
+ await clickTestSetup();
+});
+
+add_task(async function test_service_mode_telemetry() {
+ let service = Cc["@mozilla.org/cookie-banner-service;1"].getService(
+ Ci.nsIObserver
+ );
+
+ for (let mode of TEST_MODES) {
+ for (let modePBM of TEST_MODES) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", mode],
+ ["cookiebanners.service.mode.privateBrowsing", modePBM],
+ ],
+ });
+
+ // Trigger the idle-daily on the cookie banner service.
+ service.observe(null, "idle-daily", null);
+
+ // Verify the telemetry value.
+ for (let label of ["disabled", "reject", "reject_or_accept", "invalid"]) {
+ let expected = convertModeToTelemetryString(mode) == label;
+ let expectedPBM = convertModeToTelemetryString(modePBM) == label;
+
+ is(
+ Glean.cookieBanners.normalWindowServiceMode[label].testGetValue(),
+ expected,
+ `Has set label ${label} to ${expected} for mode ${mode}.`
+ );
+ is(
+ Glean.cookieBanners.privateWindowServiceMode[label].testGetValue(),
+ expectedPBM,
+ `Has set label '${label}' to ${expected} for mode ${modePBM}.`
+ );
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+});
+
+add_task(async function test_rule_lookup_telemetry_no_rule() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (let context of ["top", "iframe"]) {
+ let isTop = context === "top";
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // Open a test domain. We should record a rule miss because there is no rule
+ // right now
+ info("Open a test domain.");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetryOnce = [
+ {
+ label: `${context}_miss`,
+ count: 1,
+ },
+ {
+ label: `${context}_cookie_miss`,
+ count: 1,
+ },
+ {
+ label: `${context}_click_miss`,
+ count: 1,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetryOnce);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetryOnce);
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Open the same domain again.");
+ // Load the same domain again, verify that the telemetry counts increases for
+ // load telemetry not not for domain telemetry.
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetryTwice = [
+ {
+ label: `${context}_miss`,
+ count: 2,
+ },
+ {
+ label: `${context}_cookie_miss`,
+ count: 2,
+ },
+ {
+ label: `${context}_click_miss`,
+ count: 2,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetryTwice);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetryOnce);
+ }
+
+ Services.fog.testResetFOG();
+ Services.cookieBanners.resetDomainTelemetryRecord();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_rule_lookup_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (let type of ["click", "cookie"]) {
+ info(`Running the test for lookup telemetry for ${type} rules.`);
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ info("Insert rules.");
+ if (type === "click") {
+ insertTestClickRules();
+ } else {
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.domains = [TEST_DOMAIN_A];
+
+ Services.cookieBanners.insertRule(ruleA);
+ ruleA.addCookie(
+ true,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optOut1",
+ null,
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ ruleA.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_A}_2`,
+ "optIn2",
+ null,
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ let ruleB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleB.domains = [TEST_DOMAIN_B];
+
+ Services.cookieBanners.insertRule(ruleB);
+ ruleB.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_B}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+ }
+
+ for (let context of ["top", "iframe"]) {
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info(`Test in a ${context} context.`);
+ let isTop = context === "top";
+
+ info("Load a domain with opt-in and opt-out clicking rules.");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 1,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 1,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 1,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Load a domain with only opt-in clicking rules");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_B,
+ TEST_DOMAIN_B
+ );
+
+ expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 2,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 2,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 1,
+ },
+ ];
+
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info(
+ "Load a domain again to verify that we don't collect domain telemetry for this time."
+ );
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ // The domain telemetry should't be changed.
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 3,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 3,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 3,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 3,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 2,
+ },
+ ];
+
+ // Verify that the load telemetry still increases.
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ }
+
+ Services.fog.testResetFOG();
+ Services.cookieBanners.resetDomainTelemetryRecord();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_reload_telemetry_no_rule() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Make sure there is no reload event at the beginning.
+ let events = Glean.cookieBanners.reload.testGetValue();
+ ok(!events, "No reload event at the beginning.");
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Make sure there is no reload event at the beginning.
+ let events = Glean.cookieBanners.reload.testGetValue();
+ ok(!events, "No reload event at the beginning.");
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ // Add a both click rule and cookie rule for another domain.
+ let cookieRule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ cookieRule.domains = [TEST_DOMAIN_B];
+
+ Services.cookieBanners.insertRule(cookieRule);
+ cookieRule.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_B}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+ cookieRule.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ null,
+ "button#optIn"
+ );
+
+ // Load the page with another origin.
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, TEST_ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_B + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(2, 1, {
+ noRule: "false",
+ hasCookieRule: "true",
+ hasClickRule: "true",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_mode_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Disable the cookie banner service in normal browsing.
+ // Keep it enabled in PBM so the service stays alive and can still collect telemetry.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry. The reload telemetry should report no rule given that
+ // the service is disabled.
+ verifyReloadTelemetry(1, 0, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_mode_reject() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+ insertTestClickRules();
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry. The reload telemetry should report there is click rule
+ // for the domain has opt-out rule.
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ // Load the page with the domain only has opt-in click rule.
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, TEST_ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_B + "/");
+
+ // Check the telemetry. It should report there is no rule because the domain
+ // only has an opt-in click rule.
+ verifyReloadTelemetry(2, 1, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Insert a click rule for an iframe case. And add a cookie rule for the same
+ // domain. We shouldn't report there is a cookie rule for iframe because
+ // cookie rules are top-level only.
+ let cookieRule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ cookieRule.domains = [TEST_DOMAIN_A];
+ Services.cookieBanners.insertRule(cookieRule);
+
+ cookieRule.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_CHILD,
+ null,
+ null,
+ "button#optIn"
+ );
+ cookieRule.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_C + TEST_PATH + "file_iframe_banner.html"
+ );
+
+ // Trigger the reload
+ await reloadBrowser(
+ tab.linkedBrowser,
+ TEST_ORIGIN_C + TEST_PATH + "file_iframe_banner.html"
+ );
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_service_detectOnly_telemetry() {
+ let service = Cc["@mozilla.org/cookie-banner-service;1"].getService(
+ Ci.nsIObserver
+ );
+
+ for (let detectOnly of [true, false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["cookiebanners.service.detectOnly", detectOnly]],
+ });
+
+ // Trigger the idle-daily on the cookie banner service.
+ service.observe(null, "idle-daily", null);
+
+ is(
+ Glean.cookieBanners.serviceDetectOnly.testGetValue(),
+ detectOnly,
+ `Has set detect-only metric to ${detectOnly}.`
+ );
+
+ await SpecialPowers.popPrefEnv();
+ }
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_webconsole.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_webconsole.js
new file mode 100644
index 0000000000..20b1d490a8
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_webconsole.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+/**
+ * Registers a console listener and waits for the cookie banner handled message
+ * to be logged.
+ * @returns {Promise} - Promise which resolves once the message has been logged.
+ */
+async function waitForCookieBannerHandledConsoleMsg() {
+ let msg;
+ let checkFn = msg =>
+ msg && msg.match(/handled a cookie banner on behalf of the user./);
+ await new Promise(resolve => {
+ SpecialPowers.registerConsoleListener(consoleMsg => {
+ msg = consoleMsg.message;
+ if (checkFn(msg)) {
+ resolve();
+ }
+ });
+ });
+ SpecialPowers.postConsoleSentinel();
+
+ ok(checkFn(msg), "Observed cookie banner handled console message.");
+}
+
+add_setup(clickTestSetup);
+
+/**
+ * Tests that when we handle a banner via clicking a message is logged to the
+ * website console.
+ */
+add_task(async function test_banner_clicking_log_web_console() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ insertTestClickRules();
+
+ let consoleMsgPromise = waitForCookieBannerHandledConsoleMsg();
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info("Handle the banner via click and wait for console message to appear.");
+ await openPageAndVerify({
+ domain: TEST_DOMAIN_A,
+ testURL: TEST_PAGE_A,
+ visible: false,
+ expected: "OptOut",
+ });
+
+ await consoleMsgPromise;
+});
+
+/**
+ * Tests that when we handle a banner via cookie injection a message is logged
+ * to the website console.
+ */
+add_task(async function test_cookie_injection_log_web_console() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ let consoleMsgPromise = waitForCookieBannerHandledConsoleMsg();
+
+ // Clear the executed records before testing.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ info(
+ "Handle the banner via cookie injection and wait for console message to appear."
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ await consoleMsgPromise;
+
+ // Clear injected cookies.
+ await SiteDataTestUtils.clear();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js
new file mode 100644
index 0000000000..361d6cb189
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js
@@ -0,0 +1,635 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (
+ Services.prefs.getIntPref("cookiebanners.service.mode") !=
+ Ci.nsICookieBannerService.MODE_DISABLED ||
+ Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
+ Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+ }
+ });
+});
+
+add_task(async function test_insertAndGetRule() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Clear any preexisting rules");
+ Services.cookieBanners.resetRules(false);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 0,
+ "Cookie banner service has no rules initially."
+ );
+
+ info("Test that we can't import rules with empty domain field.");
+ let ruleInvalid = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.insertRule(ruleInvalid);
+ },
+ /NS_ERROR_FAILURE/,
+ "Inserting an invalid rule missing a domain should throw."
+ );
+
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.domains = ["example.com"];
+
+ Services.cookieBanners.insertRule(rule);
+
+ is(
+ rule.cookiesOptOut.length,
+ 0,
+ "Should not have any opt-out cookies initially"
+ );
+ is(
+ rule.cookiesOptIn.length,
+ 0,
+ "Should not have any opt-in cookies initially"
+ );
+
+ info("Clearing preexisting cookies rules for example.com.");
+ rule.clearCookies();
+
+ info("Adding cookies to the rule for example.com.");
+ rule.addCookie(
+ true,
+ "foo",
+ "bar",
+ "example.com",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ rule.addCookie(
+ true,
+ "foobar",
+ "barfoo",
+ "example.com",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ rule.addCookie(
+ false,
+ "foo",
+ "bar",
+ "foo.example.com",
+ "/myPath",
+ 3600,
+ "",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+
+ info("Adding a click rule to the rule for example.com.");
+ rule.addClickRule(
+ "div#presence",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ "div#hide",
+ "div#optOut",
+ "div#optIn"
+ );
+
+ is(rule.cookiesOptOut.length, 2, "Should have two opt-out cookies.");
+ is(rule.cookiesOptIn.length, 1, "Should have one opt-in cookie.");
+
+ is(
+ Services.cookieBanners.rules.length,
+ 1,
+ "Cookie Banner Service has one rule."
+ );
+
+ let rule2 = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule2.domains = ["example.org"];
+
+ Services.cookieBanners.insertRule(rule2);
+ info("Clearing preexisting cookies rules for example.org.");
+ rule2.clearCookies();
+
+ info("Adding a cookie to the rule for example.org.");
+ rule2.addCookie(
+ false,
+ "foo2",
+ "bar2",
+ "example.org",
+ "/",
+ 0,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ info("Adding a click rule to the rule for example.org.");
+ rule2.addClickRule(
+ "div#presence",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ null,
+ null,
+ "div#optIn"
+ );
+
+ is(
+ Services.cookieBanners.rules.length,
+ 2,
+ "Cookie Banner Service has two rules."
+ );
+
+ info("Getting cookies by URI for example.com.");
+ let ruleArray = Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://example.com"),
+ false
+ );
+ ok(
+ ruleArray && Array.isArray(ruleArray),
+ "getCookiesForURI should return a rule array."
+ );
+ is(ruleArray.length, 2, "rule array should contain 2 rules.");
+ ruleArray.every(rule => {
+ ok(rule instanceof Ci.nsICookieRule, "Rule should have correct type.");
+ is(rule.cookie.host, "example.com", "Rule should have correct host.");
+ });
+
+ info("Clearing cookies of rule.");
+ rule.clearCookies();
+ is(rule.cookiesOptOut.length, 0, "Should have no opt-out cookies.");
+ is(rule.cookiesOptIn.length, 0, "Should have no opt-in cookies.");
+
+ info("Getting the click rule for example.com.");
+ let clickRules = Services.cookieBanners.getClickRulesForDomain(
+ "example.com",
+ true
+ );
+ is(
+ clickRules.length,
+ 1,
+ "There should be one domain-specific click rule for example.com"
+ );
+ let [clickRule] = clickRules;
+
+ is(
+ clickRule.presence,
+ "div#presence",
+ "Should have the correct presence selector."
+ );
+ is(clickRule.hide, "div#hide", "Should have the correct hide selector.");
+ is(
+ clickRule.optOut,
+ "div#optOut",
+ "Should have the correct optOut selector."
+ );
+ is(clickRule.optIn, "div#optIn", "Should have the correct optIn selector.");
+
+ info("Getting cookies by URI for example.org.");
+ let ruleArray2 = Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://example.org"),
+ false
+ );
+ ok(
+ ruleArray2 && Array.isArray(ruleArray2),
+ "getCookiesForURI should return a rule array."
+ );
+ is(
+ ruleArray2.length,
+ 0,
+ "rule array should contain no rules in MODE_REJECT (opt-out only)"
+ );
+
+ info("Getting the click rule for example.org.");
+ let clickRules2 = Services.cookieBanners.getClickRulesForDomain(
+ "example.org",
+ true
+ );
+ is(
+ clickRules2.length,
+ 1,
+ "There should be one domain-specific click rule for example.org"
+ );
+ let [clickRule2] = clickRules2;
+ is(
+ clickRule2.presence,
+ "div#presence",
+ "Should have the correct presence selector."
+ );
+ ok(!clickRule2.hide, "Should have no hide selector.");
+ ok(!clickRule2.optOut, "Should have no target selector.");
+ is(clickRule.optIn, "div#optIn", "Should have the correct optIn selector.");
+
+ info("Switching cookiebanners.service.mode to MODE_REJECT_OR_ACCEPT.");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ ruleArray2 = Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://example.org"),
+ false
+ );
+ ok(
+ ruleArray2 && Array.isArray(ruleArray2),
+ "getCookiesForURI should return a rule array."
+ );
+ is(
+ ruleArray2.length,
+ 1,
+ "rule array should contain one rule in mode MODE_REJECT_OR_ACCEPT (opt-out or opt-in)"
+ );
+
+ info("Calling getCookiesForURI for unknown domain.");
+ let ruleArrayUnknown = Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://example.net"),
+ false
+ );
+ ok(
+ ruleArrayUnknown && Array.isArray(ruleArrayUnknown),
+ "getCookiesForURI should return a rule array."
+ );
+ is(ruleArrayUnknown.length, 0, "rule array should contain no rules.");
+
+ // Cleanup.
+ Services.cookieBanners.resetRules(false);
+});
+
+add_task(async function test_removeRule() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Clear any preexisting rules");
+ Services.cookieBanners.resetRules(false);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 0,
+ "Cookie banner service has no rules initially."
+ );
+
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.id = genUUID();
+ rule.domains = ["example.com"];
+
+ Services.cookieBanners.insertRule(rule);
+
+ let rule2 = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule2.id = genUUID();
+ rule2.domains = ["example.org"];
+
+ Services.cookieBanners.insertRule(rule2);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 2,
+ "Cookie banner service two rules after insert."
+ );
+
+ info("Removing rule for non existent example.net");
+ let ruleExampleNet = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleExampleNet.id = genUUID();
+ ruleExampleNet.domains = ["example.net"];
+ Services.cookieBanners.removeRule(ruleExampleNet);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 2,
+ "Cookie banner service still has two rules."
+ );
+
+ info("Removing rule for non existent global rule.");
+ let ruleGlobal = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleGlobal.id = genUUID();
+ ruleGlobal.domains = [];
+ Services.cookieBanners.removeRule(ruleGlobal);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 2,
+ "Cookie banner service still has two rules."
+ );
+
+ info("Removing rule for example.com");
+ Services.cookieBanners.removeRule(rule);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 1,
+ "Cookie banner service should have one rule left after remove."
+ );
+
+ is(
+ Services.cookieBanners.rules[0].domains[0],
+ "example.org",
+ "It should be the example.org rule."
+ );
+
+ // Cleanup.
+ Services.cookieBanners.resetRules(false);
+});
+
+add_task(async function test_overwriteRule() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Clear any preexisting rules");
+ Services.cookieBanners.resetRules(false);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 0,
+ "Cookie banner service has no rules initially."
+ );
+
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.domains = ["example.com"];
+
+ info("Adding a cookie so we can detect if the rule updates.");
+ rule.addCookie(
+ true,
+ "foo",
+ "original",
+ "example.com",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ info("Adding a click rule so we can detect if the rule updates.");
+ rule.addClickRule("div#original");
+
+ Services.cookieBanners.insertRule(rule);
+
+ let { cookie } = Services.cookieBanners.rules[0].cookiesOptOut[0];
+
+ is(cookie.name, "foo", "Should have set the correct cookie name.");
+ is(cookie.value, "original", "Should have set the correct cookie value.");
+
+ info("Add a new rule with the same domain. It should be overwritten.");
+
+ let ruleNew = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleNew.domains = ["example.com"];
+
+ ruleNew.addCookie(
+ true,
+ "foo",
+ "new",
+ "example.com",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ ruleNew.addClickRule("div#new");
+
+ Services.cookieBanners.insertRule(ruleNew);
+
+ let { cookie: cookieNew } = Services.cookieBanners.rules[0].cookiesOptOut[0];
+ is(cookieNew.name, "foo", "Should have set the original cookie name.");
+ is(cookieNew.value, "new", "Should have set the updated cookie value.");
+
+ let { presence: presenceNew } = Services.cookieBanners.rules[0].clickRule;
+ is(presenceNew, "div#new", "Should have set the updated presence value");
+
+ // Cleanup.
+ Services.cookieBanners.resetRules(false);
+});
+
+add_task(async function test_globalRules() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.service.enableGlobalRules", true],
+ ],
+ });
+
+ info("Clear any preexisting rules");
+ Services.cookieBanners.resetRules(false);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 0,
+ "Cookie banner service has no rules initially."
+ );
+
+ info("Insert a site-specific rule for example.com");
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.id = genUUID();
+ rule.domains = ["example.com"];
+ rule.addCookie(
+ true,
+ "foo",
+ "new",
+ "example.com",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ rule.addClickRule(
+ "#cookieBannerExample",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ "#btnOptOut",
+ "#btnOptIn"
+ );
+ Services.cookieBanners.insertRule(rule);
+
+ info(
+ "Insert a global rule with a cookie and a click rule. The cookie rule shouldn't be used."
+ );
+ let ruleGlobalA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleGlobalA.id = genUUID();
+ ruleGlobalA.domains = [];
+ ruleGlobalA.addCookie(
+ true,
+ "foo",
+ "new",
+ "example.net",
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ ruleGlobalA.addClickRule(
+ "#globalCookieBanner",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ "#btnOptOut",
+ "#btnOptIn"
+ );
+ Services.cookieBanners.insertRule(ruleGlobalA);
+
+ info("Insert a second global rule");
+ let ruleGlobalB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleGlobalB.id = genUUID();
+ ruleGlobalB.domains = [];
+ ruleGlobalB.addClickRule(
+ "#globalCookieBannerB",
+ false,
+ Ci.nsIClickRule.RUN_TOP,
+ "#btnOptOutB",
+ "#btnOptIn"
+ );
+ Services.cookieBanners.insertRule(ruleGlobalB);
+
+ is(
+ Services.cookieBanners.rules.length,
+ 3,
+ "Cookie Banner Service has three rules."
+ );
+
+ is(
+ Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://example.com"),
+ false
+ ).length,
+ 1,
+ "There should be a cookie rule for example.com"
+ );
+
+ is(
+ Services.cookieBanners.getClickRulesForDomain("example.com", true).length,
+ 1,
+ "There should be a a click rule for example.com"
+ );
+
+ is(
+ Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://thishasnorule.com"),
+ false
+ ).length,
+ 0,
+ "There should be no cookie rule for thishasnorule.com"
+ );
+
+ let clickRules = Services.cookieBanners.getClickRulesForDomain(
+ "thishasnorule.com",
+ true
+ );
+ is(
+ clickRules.length,
+ 2,
+ "There should be two click rules for thishasnorule.com"
+ );
+ ok(
+ clickRules.every(rule => rule.presence.startsWith("#globalCookieBanner")),
+ "The returned click rules should be global rules."
+ );
+
+ info("Disabling global rules");
+ await SpecialPowers.pushPrefEnv({
+ set: [["cookiebanners.service.enableGlobalRules", false]],
+ });
+
+ is(
+ Services.cookieBanners.rules.length,
+ 1,
+ "Cookie Banner Service has 1 rule."
+ );
+
+ is(
+ Services.cookieBanners.rules[0].id,
+ rule.id,
+ "It should be the domain specific rule"
+ );
+
+ is(
+ Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("http://thishasnorule.com"),
+ false
+ ).length,
+ 0,
+ "There should be no cookie rule for thishasnorule.com"
+ );
+
+ is(
+ Services.cookieBanners.getClickRulesForDomain("thishasnorule.com", true)
+ .length,
+ 0,
+ "There should be no click rules for thishasnorule.com since global rules are disabled"
+ );
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_domainPrefs.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_domainPrefs.js
new file mode 100644
index 0000000000..700155ada3
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_domainPrefs.js
@@ -0,0 +1,403 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (
+ Services.prefs.getIntPref("cookiebanners.service.mode") !=
+ Ci.nsICookieBannerService.MODE_DISABLED ||
+ Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
+ Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+ }
+ });
+});
+
+add_task(async function test_domain_preference() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let uri = Services.io.newURI("http://example.com");
+
+ // Check no site preference at the beginning
+ is(
+ Services.cookieBanners.getDomainPref(uri, false),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "There should be no per site preference at the beginning."
+ );
+
+ // Check setting and getting a site preference.
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ false
+ );
+
+ is(
+ Services.cookieBanners.getDomainPref(uri, false),
+ Ci.nsICookieBannerService.MODE_REJECT,
+ "Can get site preference for example.com with the correct value."
+ );
+
+ // Check site preference is shared between http and https.
+ let uriHttps = Services.io.newURI("https://example.com");
+ is(
+ Services.cookieBanners.getDomainPref(uriHttps, false),
+ Ci.nsICookieBannerService.MODE_REJECT,
+ "Can get site preference for example.com in secure context."
+ );
+
+ // Check site preference in the other domain, example.org.
+ let uriOther = Services.io.newURI("https://example.org");
+ is(
+ Services.cookieBanners.getDomainPref(uriOther, false),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "There should be no domain preference for example.org."
+ );
+
+ // Check setting site preference won't affect the other domain.
+ Services.cookieBanners.setDomainPref(
+ uriOther,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ is(
+ Services.cookieBanners.getDomainPref(uriOther, false),
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ "Can get domain preference for example.org with the correct value."
+ );
+ is(
+ Services.cookieBanners.getDomainPref(uri, false),
+ Ci.nsICookieBannerService.MODE_REJECT,
+ "Can get site preference for example.com"
+ );
+
+ // Check nsICookieBannerService.setDomainPrefAndPersistInPrivateBrowsing().
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT
+ );
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ "Can get site preference for example.com"
+ );
+
+ // Check removing the site preference.
+ Services.cookieBanners.removeDomainPref(uri, false);
+ is(
+ Services.cookieBanners.getDomainPref(uri, false),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "There should be no site preference for example.com."
+ );
+
+ // Check remove all site preferences.
+ Services.cookieBanners.removeAllDomainPrefs(false);
+ is(
+ Services.cookieBanners.getDomainPref(uri, false),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "There should be no site preference for example.com."
+ );
+ is(
+ Services.cookieBanners.getDomainPref(uriOther, false),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "There should be no site preference for example.org."
+ );
+});
+
+add_task(async function test_domain_preference_dont_override_disable_pref() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Adding a domain preference for example.com");
+ let uri = Services.io.newURI("https://example.com");
+
+ // Set a domain preference.
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ false
+ );
+
+ info("Disabling the cookie banner service.");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_DISABLED],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ ],
+ ],
+ });
+
+ info("Verifying if the cookie banner service is disabled.");
+ Assert.throws(
+ () => {
+ Services.cookieBanners.getDomainPref(uri, false);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for getDomainPref."
+ );
+
+ info("Enable the service again in order to clear the domain prefs.");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+ Services.cookieBanners.removeAllDomainPrefs(false);
+});
+
+/**
+ * Test that domain preference is properly cleared when private browsing session
+ * ends.
+ */
+add_task(async function test_domain_preference_cleared_PBM_ends() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Adding a domain preference for example.com in PBM");
+ let uri = Services.io.newURI("https://example.com");
+
+ info("Open a private browsing window.");
+ let PBMWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Set a domain preference for PBM.
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ true
+ );
+
+ info("Verifying if the cookie banner domain pref is set for PBM.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ "The domain pref is properly set for PBM."
+ );
+
+ info("Trigger an ending of a private browsing window session");
+ let PBMSessionEndsObserved = TestUtils.topicObserved(
+ "last-pb-context-exited"
+ );
+
+ // Close the PBM window and wait until it finishes.
+ await BrowserTestUtils.closeWindow(PBMWin);
+ await PBMSessionEndsObserved;
+
+ info("Verify if the private domain pref is cleared.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "The domain pref is properly set for PBM."
+ );
+});
+
+/**
+ * Test that the persistent domain preference won't be cleared when private
+ * browsing session ends.
+ */
+add_task(async function test_persistent_domain_preference_remain_PBM_ends() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Adding a domain preference for example.com in PBM");
+ let uri = Services.io.newURI("https://example.com");
+
+ info("Open a private browsing window.");
+ let PBMWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Set a domain preference for PBM.
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ true
+ );
+
+ info("Verifying if the cookie banner domain pref is set for PBM.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ "The domain pref is properly set for PBM."
+ );
+
+ info("Adding a persistent domain preference for example.org in PBM");
+ let uriPersistent = Services.io.newURI("https://example.org");
+
+ // Set a persistent domain preference for PBM.
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uriPersistent,
+ Ci.nsICookieBannerService.MODE_DISABLED
+ );
+
+ info("Trigger an ending of a private browsing window session");
+ let PBMSessionEndsObserved = TestUtils.topicObserved(
+ "last-pb-context-exited"
+ );
+
+ // Close the PBM window and wait until it finishes.
+ await BrowserTestUtils.closeWindow(PBMWin);
+ await PBMSessionEndsObserved;
+
+ info("Verify if the private domain pref is cleared.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "The domain pref is properly set for PBM."
+ );
+
+ info("Verify if the persistent private domain pref remains.");
+ is(
+ Services.cookieBanners.getDomainPref(uriPersistent, true),
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ "The persistent domain pref remains for PBM after private session ends."
+ );
+});
+
+add_task(async function test_remove_persistent_domain_pref_in_PBM() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Adding a domain preference for example.com in PBM");
+ let uri = Services.io.newURI("https://example.com");
+
+ info("Open a private browsing window.");
+ let PBMWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Set a persistent domain preference for PBM.
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uri,
+ Ci.nsICookieBannerService.MODE_DISABLED
+ );
+
+ info("Verifying if the cookie banner domain pref is set for PBM.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ "The domain pref is properly set for PBM."
+ );
+
+ info("Remove the persistent domain pref.");
+ Services.cookieBanners.removeDomainPref(uri, true);
+
+ info("Trigger an ending of a private browsing window session");
+ let PBMSessionEndsObserved = TestUtils.topicObserved(
+ "last-pb-context-exited"
+ );
+
+ // Close the PBM window and wait until it finishes.
+ await BrowserTestUtils.closeWindow(PBMWin);
+ await PBMSessionEndsObserved;
+
+ info(
+ "Verify if the private domain pref is no longer persistent and cleared."
+ );
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "The domain pref is properly set for PBM."
+ );
+});
+
+/**
+ * Test that the persistent state of a domain pref in PMB can be override by new
+ * call without persistent state.
+ */
+add_task(async function test_override_persistent_state_in_PBM() {
+ info("Enabling cookie banner service with MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Adding a domain preference for example.com in PBM");
+ let uri = Services.io.newURI("https://example.com");
+
+ info("Open a private browsing window.");
+ let PBMWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ // Set a persistent domain preference for PBM.
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uri,
+ Ci.nsICookieBannerService.MODE_DISABLED
+ );
+
+ info("Trigger an ending of a private browsing window session");
+ let PBMSessionEndsObserved = TestUtils.topicObserved(
+ "last-pb-context-exited"
+ );
+
+ // Close the PBM window and wait until it finishes.
+ await BrowserTestUtils.closeWindow(PBMWin);
+ await PBMSessionEndsObserved;
+
+ info("Verify if the persistent private domain pref remains.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ "The persistent domain pref remains for PBM after private session ends."
+ );
+
+ info("Open a private browsing window again.");
+ PBMWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ info("Override the persistent domain pref with non-persistent domain pref.");
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ true
+ );
+
+ info("Trigger an ending of a private browsing window session again");
+ PBMSessionEndsObserved = TestUtils.topicObserved("last-pb-context-exited");
+
+ // Close the PBM window and wait until it finishes.
+ await BrowserTestUtils.closeWindow(PBMWin);
+ await PBMSessionEndsObserved;
+
+ info("Verify if the private domain pref is cleared.");
+ is(
+ Services.cookieBanners.getDomainPref(uri, true),
+ Ci.nsICookieBannerService.MODE_UNSET,
+ "The domain pref is properly set for PBM."
+ );
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_getRules.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_getRules.js
new file mode 100644
index 0000000000..fcc6078e28
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_getRules.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let testRules = [
+ // Cookie rule with multiple domains.
+ {
+ id: "0e4cdbb8-b688-47e0-9c8b-4db620398dbd",
+ click: {},
+ cookies: {
+ optIn: [
+ {
+ name: "foo",
+ value: "bar",
+ },
+ ],
+ },
+ domains: [TEST_DOMAIN_A, TEST_DOMAIN_B],
+ },
+ // Click rule with single domain.
+ {
+ id: "0560e02c-a50f-4e7b-86e0-d6b7d258eb5f",
+ click: {
+ optOut: "#optOutBtn",
+ presence: "#cookieBanner",
+ },
+ cookies: {},
+ domains: [TEST_DOMAIN_C],
+ },
+];
+
+add_setup(async function () {
+ // Enable the service and insert the test rules.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.listService.testSkipRemoteSettings", true],
+ ["cookiebanners.listService.testRules", JSON.stringify(testRules)],
+ ["cookiebanners.listService.logLevel", "Debug"],
+ ],
+ });
+
+ Services.cookieBanners.resetRules(true);
+});
+
+function ruleCountForDomain(domain) {
+ return Services.cookieBanners.rules.filter(rule =>
+ rule.domains.includes(domain)
+ ).length;
+}
+
+/**
+ * Tests that the rules getter does not return duplicate rules for rules with
+ * multiple domains.
+ */
+add_task(async function test_rules_getter_no_duplicates() {
+ // The rule import is async because it needs to fetch rules from
+ // RemoteSettings. Wait for the test rules to be applied.
+ // See CookieBannerListService#importAllRules.
+ await BrowserTestUtils.waitForCondition(
+ () => Services.cookieBanners.rules.length,
+ "Waiting for test rules to be imported."
+ );
+ is(
+ Services.cookieBanners.rules.length,
+ 2,
+ "Rules getter should only return the two test rules."
+ );
+ is(
+ ruleCountForDomain(TEST_DOMAIN_A),
+ 1,
+ "There should only be one rule with TEST_DOMAIN_A."
+ );
+ is(
+ ruleCountForDomain(TEST_DOMAIN_B),
+ 1,
+ "There should only be one rule with TEST_DOMAIN_B."
+ );
+ is(
+ ruleCountForDomain(TEST_DOMAIN_C),
+ 1,
+ "There should only be one rule with TEST_DOMAIN_C."
+ );
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_hasRuleForBCTree.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_hasRuleForBCTree.js
new file mode 100644
index 0000000000..815fd115e9
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_hasRuleForBCTree.js
@@ -0,0 +1,293 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+let testRules = [
+ // Top-level cookie rule.
+ {
+ id: "87815b2d-a840-4155-8713-f8a26d1f483a",
+ click: {},
+ cookies: {
+ optIn: [
+ {
+ name: "foo",
+ value: "bar",
+ },
+ ],
+ },
+ domains: [TEST_DOMAIN_B],
+ },
+ // Child click rule.
+ {
+ id: "d42bbaee-f96e-47e7-8e81-efc642518e97",
+ click: {
+ optOut: "#optOutBtn",
+ presence: "#cookieBanner",
+ runContext: "child",
+ },
+ cookies: {},
+ domains: [TEST_DOMAIN_C],
+ },
+ // Top level click rule.
+ {
+ id: "19dd1f52-f3e6-4a24-a926-d77f553d1b15",
+ click: {
+ optOut: "#optOutBtn",
+ presence: "#cookieBanner",
+ },
+ cookies: {},
+ domains: [TEST_DOMAIN_A],
+ },
+];
+
+/**
+ * Insert an iframe and wait for it to load.
+ * @param {BrowsingContext} parentBC - The BC the frame to insert under.
+ * @param {string} uri - The URI to load in the frame.
+ * @returns {Promise} - A Promise which resolves once the frame has loaded.
+ */
+function insertIframe(parentBC, uri) {
+ return SpecialPowers.spawn(parentBC, [uri], async testURL => {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = testURL;
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+ return iframe.browsingContext;
+ });
+}
+
+add_setup(async function () {
+ // Enable the service and insert the test rules. We only test
+ // MODE_REJECT_OR_ACCEPT here as the other modes are covered by other tests
+ // already and hasRuleForBrowsingContextTree mostly shares logic with other
+ // service getters.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.listService.testSkipRemoteSettings", true],
+ ["cookiebanners.listService.testRules", JSON.stringify(testRules)],
+ ],
+ });
+
+ // Ensure the test rules have been applied before the first test starts.
+ Services.cookieBanners.resetRules();
+
+ // Visiting sites in this test can set cookies. Clean them up on test exit.
+ registerCleanupFunction(async () => {
+ await SiteDataTestUtils.clear();
+ });
+});
+
+add_task(async function test_unsupported() {
+ let unsupportedURIs = {
+ "about:preferences": /NS_ERROR_FAILURE/,
+ "about:blank": false,
+ };
+
+ for (let [key, value] of Object.entries(unsupportedURIs)) {
+ await BrowserTestUtils.withNewTab(key, async browser => {
+ if (typeof value == "object") {
+ // It's an error code.
+ Assert.throws(
+ () => {
+ Services.cookieBanners.hasRuleForBrowsingContextTree(
+ browser.browsingContext
+ );
+ },
+ value,
+ `Should throw ${value} for hasRuleForBrowsingContextTree call for '${key}'.`
+ );
+ } else {
+ is(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(
+ browser.browsingContext
+ ),
+ value,
+ `Should return expected value for hasRuleForBrowsingContextTree for '${key}'`
+ );
+ }
+ });
+ }
+});
+
+add_task(async function test_hasRuleForBCTree() {
+ info("Test with top level A");
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_A, async browser => {
+ let bcTop = browser.browsingContext;
+
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top BC for A"
+ );
+
+ info("inserting frame with TEST_ORIGIN_A");
+ let bcChildA = await insertIframe(bcTop, TEST_ORIGIN_A);
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should still have rule when called with top BC for A."
+ );
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildA),
+ "Should not have rule when called with child BC for A, because A has no child click-rule."
+ );
+ });
+
+ info("Test with top level C");
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_C, async browser => {
+ let bcTop = browser.browsingContext;
+
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have no rule when called with top BC for C, because C only has a child click rule."
+ );
+
+ info("inserting frame with TEST_ORIGIN_C");
+ let bcChildC = await insertIframe(bcTop, TEST_ORIGIN_C);
+
+ info("inserting unrelated frames");
+ await insertIframe(bcTop, "https://itisatracker.org");
+ await insertIframe(bcChildC, "https://itisatracker.org");
+
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top BC for C, because frame C has a child click rule."
+ );
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildC),
+ "Should have rule when called with child BC for C, because it has a child click rule."
+ );
+ });
+
+ info("Test with unrelated top level");
+ await BrowserTestUtils.withNewTab("http://mochi.test:8888", async browser => {
+ let bcTop = browser.browsingContext;
+
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should not have rule for unrelated site."
+ );
+
+ info("inserting frame with TEST_ORIGIN_A");
+ let bcChildA = await insertIframe(bcTop, TEST_ORIGIN_A);
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should still have no rule when called with top BC for A, because click rule for A only applies top-level."
+ );
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildA),
+ "Should have no rule when called with child BC for A."
+ );
+
+ info("inserting frame with TEST_ORIGIN_B");
+ let bcChildB = await insertIframe(bcTop, TEST_ORIGIN_B);
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should still have no rule when called with top BC for A, because cookie rule for B only applies top-level."
+ );
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildA),
+ "Should have no rule when called with child BC for A."
+ );
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildB),
+ "Should have no rule when called with child BC for B."
+ );
+
+ info("inserting nested frame with TEST_ORIGIN_C");
+ let bcChildC = await insertIframe(bcChildB, TEST_ORIGIN_C);
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top level BC because rule for nested iframe C applies."
+ );
+ ok(
+ !Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildA),
+ "Should have no rule when called with child BC for A."
+ );
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildB),
+ "Should have rule when called with child BC for B, because C rule for nested iframe C applies."
+ );
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcChildC),
+ "Should have rule when called with child BC for C, because C rule for nested iframe C applies."
+ );
+ });
+});
+
+/**
+ * Tests that domain prefs are not considered when evaluating whether the
+ * service has an applicable rule for the given BrowsingContext.
+ */
+add_task(async function test_hasRuleForBCTree_ignoreDomainPrefs() {
+ info("Test with top level A");
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_A, async browser => {
+ let bcTop = browser.browsingContext;
+
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top BC for A"
+ );
+
+ // Disable for current site per domain pref.
+ Services.cookieBanners.setDomainPref(
+ browser.currentURI,
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ false
+ );
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top BC for A, even if mechanism is disabled for A."
+ );
+
+ // Change mode via domain pref.
+ Services.cookieBanners.setDomainPref(
+ browser.currentURI,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ false
+ );
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should still have rule when called with top BC for A, even with custom mode for A"
+ );
+
+ // Cleanup.
+ Services.cookieBanners.removeAllDomainPrefs(false);
+ });
+
+ info("Test with top level B");
+ await BrowserTestUtils.withNewTab(TEST_ORIGIN_B, async browser => {
+ let bcTop = browser.browsingContext;
+
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should have rule when called with top BC for B"
+ );
+
+ // Change mode via domain pref.
+ Services.cookieBanners.setDomainPref(
+ browser.currentURI,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ false
+ );
+ // Rule for B has no opt-out option. Since the mode is overridden to
+ // MODE_REJECT for B we don't have any applicable rule for it. This should
+ // however not be considered for the hasRule getter, it should ignore
+ // per-domain preferences and evaluate based on the global service mode
+ // instead.
+ ok(
+ Services.cookieBanners.hasRuleForBrowsingContextTree(bcTop),
+ "Should still have rule when called with top BC for B, even with custom mode for B"
+ );
+
+ // Cleanup.
+ Services.cookieBanners.removeAllDomainPrefs(false);
+ });
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_prefs.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_prefs.js
new file mode 100644
index 0000000000..6e9197281c
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice_prefs.js
@@ -0,0 +1,213 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (
+ Services.prefs.getIntPref("cookiebanners.service.mode") !=
+ Ci.nsICookieBannerService.MODE_DISABLED ||
+ Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
+ Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+ }
+ });
+});
+
+add_task(async function test_enabled_pref() {
+ info("Disabling cookie banner service.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_DISABLED],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ ],
+ ],
+ });
+
+ ok(Services.cookieBanners, "Services.cookieBanners is defined.");
+ ok(
+ Services.cookieBanners instanceof Ci.nsICookieBannerService,
+ "Services.cookieBanners is nsICookieBannerService"
+ );
+
+ info(
+ "Testing that methods throw NS_ERROR_NOT_AVAILABLE if the service is disabled."
+ );
+
+ Assert.throws(
+ () => {
+ Services.cookieBanners.rules;
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for rules getter."
+ );
+
+ // Create a test rule to attempt to insert.
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.id = genUUID();
+ rule.domains = ["example.com"];
+
+ Assert.throws(
+ () => {
+ Services.cookieBanners.insertRule(rule);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for insertRule."
+ );
+
+ Assert.throws(
+ () => {
+ Services.cookieBanners.removeRule(rule);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for removeRule."
+ );
+
+ Assert.throws(
+ () => {
+ Services.cookieBanners.getCookiesForURI(
+ Services.io.newURI("https://example.com"),
+ false
+ );
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for rules getCookiesForURI."
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.getClickRulesForDomain("example.com", true);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for rules getClickRuleForDomain."
+ );
+ let uri = Services.io.newURI("https://example.com");
+ Assert.throws(
+ () => {
+ Services.cookieBanners.getDomainPref(uri, false);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for getDomainPref."
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ false
+ );
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for setDomainPref."
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT
+ );
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for setDomainPrefAndPersistInPrivateBrowsing."
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.removeDomainPref(uri, false);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for removeDomainPref."
+ );
+ Assert.throws(
+ () => {
+ Services.cookieBanners.removeAllDomainPrefs(false);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should have thrown NS_ERROR_NOT_AVAILABLE for removeAllSitePref."
+ );
+
+ info("Enabling cookie banner service. MODE_REJECT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ let rules = Services.cookieBanners.rules;
+ ok(
+ Array.isArray(rules),
+ "Rules getter should not throw but return an array."
+ );
+
+ info("Enabling cookie banner service. MODE_REJECT_OR_ACCEPT");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ rules = Services.cookieBanners.rules;
+ ok(
+ Array.isArray(rules),
+ "Rules getter should not throw but return an array."
+ );
+});
+
+/**
+ * Test both service mode pref combinations to ensure the cookie banner service
+ * is (un-)initialized correctly.
+ */
+add_task(async function test_enabled_pref_pbm_combinations() {
+ const MODES = [
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ Ci.nsICookieBannerService.MODE_REJECT,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ];
+
+ // Test all pref combinations
+ MODES.forEach(modeNormal => {
+ MODES.forEach(modePrivate => {
+ info(
+ `cookiebanners.service.mode=${modeNormal}; cookiebanners.service.mode.privateBrowsing=${modePrivate}`
+ );
+ Services.prefs.setIntPref("cookiebanners.service.mode", modeNormal);
+ Services.prefs.setIntPref(
+ "cookiebanners.service.mode.privateBrowsing",
+ modePrivate
+ );
+
+ if (
+ modeNormal == Ci.nsICookieBannerService.MODE_DISABLED &&
+ modePrivate == Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ Assert.throws(
+ () => {
+ Services.cookieBanners.rules;
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Cookie banner service should be disabled. Should throw NS_ERROR_NOT_AVAILABLE for rules getter."
+ );
+ } else {
+ ok(
+ Services.cookieBanners.rules,
+ "Cookie banner service should be enabled, rules getter should not throw."
+ );
+ }
+ });
+ });
+
+ // Cleanup.
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js
new file mode 100644
index 0000000000..d341355f20
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js
@@ -0,0 +1,625 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+const DOMAIN_A = "example.com";
+const DOMAIN_B = "example.org";
+const DOMAIN_C = "example.net";
+
+const ORIGIN_A = "https://" + DOMAIN_A;
+const ORIGIN_A_SUB = `https://test1.${DOMAIN_A}`;
+const ORIGIN_B = "https://" + DOMAIN_B;
+const ORIGIN_C = "https://" + DOMAIN_C;
+
+const TEST_COOKIE_HEADER_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "testCookieHeader.sjs";
+
+/**
+ * Tests that the test domains have no cookies set.
+ */
+function assertNoCookies() {
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_A),
+ "Should not set any cookies for ORIGIN_A"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_C),
+ "Should not set any cookies for ORIGIN_C"
+ );
+}
+
+/**
+ * Loads a list of urls consecutively from the same tab.
+ * @param {string[]} urls - List of urls to load.
+ */
+async function visitTestSites(urls = [ORIGIN_A, ORIGIN_B, ORIGIN_C]) {
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+
+ for (let url of urls) {
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(cookieInjectorTestSetup);
+
+/**
+ * Tests that no cookies are set if the cookie injection component is disabled
+ * by pref, but the cookie banner service is enabled.
+ */
+add_task(async function test_cookie_injector_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", false],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ await visitTestSites();
+ assertNoCookies();
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Tests that no cookies are set if the cookie injection component is enabled
+ * by pref, but the cookie banner service is disabled or in detect-only mode.
+ */
+add_task(async function test_cookie_banner_service_disabled() {
+ // Enable in PBM so the service is always initialized.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ],
+ });
+
+ for (let [serviceMode, detectOnly] of [
+ [Ci.nsICookieBannerService.MODE_DISABLED, false],
+ [Ci.nsICookieBannerService.MODE_DISABLED, true],
+ [Ci.nsICookieBannerService.MODE_REJECT, true],
+ [Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, true],
+ ]) {
+ info(`Testing with serviceMode=${serviceMode}; detectOnly=${detectOnly}`);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", serviceMode],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ["cookiebanners.service.detectOnly", detectOnly],
+ ],
+ });
+
+ await visitTestSites();
+ assertNoCookies();
+
+ await SiteDataTestUtils.clear();
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+/**
+ * Tests that we don't inject cookies if there are no matching rules.
+ */
+add_task(async function test_no_rules() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ await visitTestSites();
+ assertNoCookies();
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Tests that inject the correct cookies for matching rules and MODE_REJECT.
+ */
+add_task(async function test_mode_reject() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ await visitTestSites();
+
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_A, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_A"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+ // RULE_A also includes DOMAIN_C.
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_C, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_C"
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Tests that inject the correct cookies for matching rules and
+ * MODE_REJECT_OR_ACCEPT.
+ */
+add_task(async function test_mode_reject_or_accept() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ await visitTestSites();
+
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_A, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_A"
+ );
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_B, [
+ {
+ key: `cookieConsent_${DOMAIN_B}_1`,
+ value: "optIn1",
+ },
+ ]),
+ "Should set opt-in cookies for ORIGIN_B"
+ );
+ // Rule a also includes DOMAIN_C
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_C, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_C"
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Test that embedded third-parties do not trigger cookie injection.
+ */
+add_task(async function test_embedded_third_party() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ info("Loading example.com with an iframe for example.org.");
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ await SpecialPowers.spawn(browser, [], async () => {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = "https://example.org";
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+ });
+ });
+
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_A, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for top-level ORIGIN_A"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for embedded ORIGIN_B"
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Test that the injected cookies are present in the cookie header for the
+ * initial top level document request.
+ */
+add_task(async function test_cookie_header_and_document() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+ insertTestCookieRules();
+
+ await BrowserTestUtils.withNewTab(TEST_COOKIE_HEADER_URL, async browser => {
+ await SpecialPowers.spawn(browser, [], async () => {
+ const EXPECTED_COOKIE_STR =
+ "cookieConsent_example.com_1=optOut1; cookieConsent_example.com_2=optOut2";
+ is(
+ content.document.body.innerText,
+ EXPECTED_COOKIE_STR,
+ "Sent the correct cookie header."
+ );
+ is(
+ content.document.cookie,
+ EXPECTED_COOKIE_STR,
+ "document.cookie has the correct cookie string."
+ );
+ });
+ });
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Test that cookies get properly injected for private browsing mode.
+ */
+add_task(async function test_pbm() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+ insertTestCookieRules();
+
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let tab = BrowserTestUtils.addTab(pbmWindow.gBrowser, "about:blank");
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_A);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ ok(
+ SiteDataTestUtils.hasCookies(
+ tab.linkedBrowser.contentPrincipal.origin,
+ [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ],
+ true
+ ),
+ "Should set opt-out cookies for top-level ORIGIN_A in private browsing."
+ );
+
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_A),
+ "Should not set any cookies for ORIGIN_A without PBM origin attribute."
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+
+ await BrowserTestUtils.closeWindow(pbmWindow);
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Test that cookies get properly injected for container tabs.
+ */
+add_task(async function test_container_tab() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+ insertTestCookieRules();
+
+ info("Loading ORIGIN_B in a container tab.");
+ let tab = BrowserTestUtils.addTab(gBrowser, ORIGIN_B, {
+ userContextId: 1,
+ });
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_A),
+ "Should not set any cookies for ORIGIN_A"
+ );
+ ok(
+ SiteDataTestUtils.hasCookies(tab.linkedBrowser.contentPrincipal.origin, [
+ {
+ key: `cookieConsent_${DOMAIN_B}_1`,
+ value: "optIn1",
+ },
+ ]),
+ "Should set opt-out cookies for top-level ORIGIN_B in user context 1."
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B in default user context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Test that if there is already a cookie with the given key, we don't overwrite
+ * it. If the rule sets the unsetValue field, this cookie may be overwritten if
+ * the value matches.
+ */
+add_task(async function test_no_overwrite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ info("Pre-setting a cookie that should not be overwritten.");
+ SiteDataTestUtils.addToCookies({
+ origin: ORIGIN_A,
+ host: `.${DOMAIN_A}`,
+ path: "/",
+ name: `cookieConsent_${DOMAIN_A}_1`,
+ value: "KEEPME",
+ });
+
+ info("Pre-setting a cookie that should be overwritten, based on its value");
+ SiteDataTestUtils.addToCookies({
+ origin: ORIGIN_B,
+ host: `.${DOMAIN_B}`,
+ path: "/",
+ name: `cookieConsent_${DOMAIN_B}_1`,
+ value: "UNSET",
+ });
+
+ await visitTestSites();
+
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_A, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "KEEPME",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should retain pre-set opt-in cookies for ORIGIN_A, but write new secondary cookie from rules."
+ );
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_B, [
+ {
+ key: `cookieConsent_${DOMAIN_B}_1`,
+ value: "optIn1",
+ },
+ ]),
+ "Should have overwritten cookie for ORIGIN_B, based on its value and the unsetValue rule property."
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+/**
+ * Tests that cookies are injected for the base domain when visiting a
+ * subdomain.
+ */
+add_task(async function test_subdomain() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ await visitTestSites([ORIGIN_A_SUB, ORIGIN_B]);
+
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_A, [
+ {
+ key: `cookieConsent_${DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_A when visiting subdomain."
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+add_task(async function test_site_preference() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ await visitTestSites();
+
+ ok(
+ !SiteDataTestUtils.hasCookies(ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+
+ info("Set the site preference of example.org to MODE_REJECT_OR_ACCEPT.");
+ let uri = Services.io.newURI(ORIGIN_B);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ false
+ );
+
+ await visitTestSites();
+ ok(
+ SiteDataTestUtils.hasCookies(ORIGIN_B, [
+ {
+ key: `cookieConsent_${DOMAIN_B}_1`,
+ value: "optIn1",
+ },
+ ]),
+ "Should set opt-in cookies for ORIGIN_B"
+ );
+
+ Services.cookieBanners.removeAllDomainPrefs(false);
+ await SiteDataTestUtils.clear();
+});
+
+add_task(async function test_site_preference_pbm() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT,
+ ],
+ ["cookiebanners.cookieInjector.enabled", true],
+ ],
+ });
+
+ insertTestCookieRules();
+
+ let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let tab = BrowserTestUtils.addTab(pbmWindow.gBrowser, "about:blank");
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ ok(
+ !SiteDataTestUtils.hasCookies(
+ tab.linkedBrowser.contentPrincipal.origin,
+ null,
+ true
+ ),
+ "Should not set any cookies for ORIGIN_B in the private window"
+ );
+
+ info(
+ "Set the site preference of example.org to MODE_REJECT_OR_ACCEPT. in the private window."
+ );
+ let uri = Services.io.newURI(ORIGIN_B);
+ Services.cookieBanners.setDomainPref(
+ uri,
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ true
+ );
+
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ ok(
+ SiteDataTestUtils.hasCookies(
+ tab.linkedBrowser.contentPrincipal.origin,
+ [
+ {
+ key: `cookieConsent_${DOMAIN_B}_1`,
+ value: "optIn1",
+ },
+ ],
+ true
+ ),
+ "Should set opt-in cookies for ORIGIN_B"
+ );
+
+ Services.cookieBanners.removeAllDomainPrefs(true);
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(pbmWindow);
+ await SiteDataTestUtils.clear();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_events.js b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_events.js
new file mode 100644
index 0000000000..0459731a46
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_events.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+add_setup(cookieInjectorTestSetup);
+
+/**
+ * Tests that we dispatch cookiebannerhandled and cookiebannerdetected events
+ * for cookie injection.
+ */
+add_task(async function test_events() {
+ let tab;
+
+ let triggerFn = async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_ORIGIN_A);
+ };
+
+ await runEventTest({
+ mode: Ci.nsICookieBannerService.MODE_REJECT,
+ initFn: insertTestCookieRules,
+ triggerFn,
+ testURL: `${TEST_ORIGIN_A}/`,
+ });
+
+ // Clean up the test tab opened by triggerFn.
+ BrowserTestUtils.removeTab(tab);
+
+ ok(
+ SiteDataTestUtils.hasCookies(TEST_ORIGIN_A, [
+ {
+ key: `cookieConsent_${TEST_DOMAIN_A}_1`,
+ value: "optOut1",
+ },
+ {
+ key: `cookieConsent_${TEST_DOMAIN_A}_2`,
+ value: "optOut2",
+ },
+ ]),
+ "Should set opt-out cookies for ORIGIN_A"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(TEST_ORIGIN_B),
+ "Should not set any cookies for ORIGIN_B"
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(TEST_ORIGIN_C),
+ "Should not set any cookies for ORIGIN_C"
+ );
+
+ await SiteDataTestUtils.clear();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_telemetry.js b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_telemetry.js
new file mode 100644
index 0000000000..89a882814f
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector_telemetry.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+add_setup(async function () {
+ await cookieInjectorTestSetup();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.bannerClicking.logLevel", "Debug"],
+ ["cookiebanners.bannerClicking.testing", true],
+ ],
+ });
+});
+
+add_task(async function testCookieInjectionFailTelemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Creating a rule contains both cookie and click rules.");
+ let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ rule.id = genUUID();
+ rule.domains = [TEST_DOMAIN_A];
+
+ rule.addCookie(
+ true,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optOut1",
+ null, // empty host to fall back to .<domain>
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ rule.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(rule);
+
+ Services.cookieBanners.removeAllExecutedRecords(false);
+
+ // Open the test page with the banner to trigger the cookie injection failure.
+ let promise = promiseBannerClickingFinish(TEST_DOMAIN_A);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_A);
+ await promise;
+
+ await Services.fog.testFlushAllChildren();
+
+ BrowserTestUtils.removeTab(tab);
+
+ await TestUtils.waitForCondition(_ => {
+ return Glean.cookieBanners.cookieInjectionFail.testGetValue() != null;
+ }, "Waiting for metric to be ready.");
+
+ is(
+ Glean.cookieBanners.cookieInjectionFail.testGetValue(),
+ 1,
+ "Counter for cookie_injection_fail has correct state"
+ );
+
+ // Clear Telemetry
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+
+ await SiteDataTestUtils.clear();
+});
diff --git a/toolkit/components/cookiebanners/test/browser/file_banner.html b/toolkit/components/cookiebanners/test/browser/file_banner.html
new file mode 100644
index 0000000000..d2af994f39
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_banner.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+ <title>A top-level page with cookie banner</title>
+ <script>
+ function hideBanner() {
+ document.getElementById("banner").style.display = "none";
+ }
+
+ function clickOptOut() {
+ document.getElementById("result").textContent = "OptOut";
+ hideBanner();
+ }
+
+ function clickOptIn() {
+ document.getElementById("result").textContent = "OptIn";
+ hideBanner();
+ }
+ </script>
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+ <div id="banner">
+ <button id="optOut" onclick="clickOptOut()">OptOut</button>
+ <button id="optIn" onclick="clickOptIn()">OptIn</button>
+ </div>
+ <p id="result">NoClick</p>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/file_banner_b.html b/toolkit/components/cookiebanners/test/browser/file_banner_b.html
new file mode 100644
index 0000000000..b71faa0ab4
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_banner_b.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+ <title>A top-level page with cookie banner</title>
+ <script>
+ function hideBanner() {
+ document.getElementById("banner").style.display = "none";
+ }
+
+ function clickOptOut() {
+ document.getElementById("result").textContent = "OptOut";
+ hideBanner();
+ }
+
+ function clickOptIn() {
+ document.getElementById("result").textContent = "OptIn";
+ hideBanner();
+ }
+ </script>
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+ <div id="bannerB">
+ <button id="optOut" onclick="clickOptOut()">OptOut</button>
+ <button id="optIn" onclick="clickOptIn()">OptIn</button>
+ </div>
+ <p id="result">NoClick</p>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/file_banner_invisible.html b/toolkit/components/cookiebanners/test/browser/file_banner_invisible.html
new file mode 100644
index 0000000000..9bde593b4d
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_banner_invisible.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+ <title>A top-level page with cookie banner</title>
+ <script>
+ function hideBanner() {
+ document.getElementById("banner").style.display = "none";
+ }
+
+ function clickOptOut() {
+ document.getElementById("result").textContent = "OptOut";
+ hideBanner();
+ }
+
+ function clickOptIn() {
+ document.getElementById("result").textContent = "OptIn";
+ hideBanner();
+ }
+ </script>
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+ <div id="banner" style="display: none;">
+ <button id="optOut" onclick="clickOptOut()">OptOut</button>
+ <button id="optIn" onclick="clickOptIn()">OptIn</button>
+ </div>
+ <p id="result">NoClick</p>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/file_delayed_banner.html b/toolkit/components/cookiebanners/test/browser/file_delayed_banner.html
new file mode 100644
index 0000000000..e528064434
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_delayed_banner.html
@@ -0,0 +1,54 @@
+<html>
+<head>
+ <title>A top-level page with delayed cookie banner</title>
+ <script>
+ function hideBanner() {
+ document.getElementById("banner").style.display = "none";
+ }
+
+ function clickOptOut() {
+ document.getElementById("result").textContent = "OptOut";
+ hideBanner();
+ }
+
+ function clickOptIn() {
+ document.getElementById("result").textContent = "OptIn";
+ hideBanner();
+ }
+
+ function generateBanner() {
+ let banner = document.createElement("div");
+ banner.id = "banner";
+
+ let buttonOptOut = document.createElement("button");
+ buttonOptOut.id = "OptOut";
+ buttonOptOut.onclick = () => {clickOptOut();};
+
+ let buttonOptIn = document.createElement("button");
+ buttonOptIn.id = "OptIn";
+ buttonOptIn.onclick = () => {clickOptIn();};
+
+ banner.appendChild(buttonOptOut);
+ banner.appendChild(buttonOptIn);
+ document.body.appendChild(banner);
+ }
+
+ window.onload = () => {
+ const params = (new URL(document.location)).searchParams;
+ let delay = 0;
+
+ if (params.has("delay")) {
+ delay = parseInt(params.get("delay"));
+ }
+
+ window.setTimeout(() => {
+ generateBanner();
+ }, delay);
+ };
+ </script>
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+ <p id="result">NoClick</p>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/file_delayed_banner_load.html b/toolkit/components/cookiebanners/test/browser/file_delayed_banner_load.html
new file mode 100644
index 0000000000..0209ad76cd
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_delayed_banner_load.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+ <title>A top-level page with delayed cookie banner</title>
+ <script>
+ function hideBanner() {
+ document.getElementById("banner").style.display = "none";
+ }
+
+ function clickOptOut() {
+ document.getElementById("result").textContent = "OptOut";
+ hideBanner();
+ }
+
+ function clickOptIn() {
+ document.getElementById("result").textContent = "OptIn";
+ hideBanner();
+ }
+
+ function generateBanner() {
+ let banner = document.createElement("div");
+ banner.id = "banner";
+
+ let buttonOptOut = document.createElement("button");
+ buttonOptOut.id = "OptOut";
+ buttonOptOut.onclick = () => {clickOptOut();};
+
+ let buttonOptIn = document.createElement("button");
+ buttonOptIn.id = "OptIn";
+ buttonOptIn.onclick = () => {clickOptIn();};
+
+ banner.appendChild(buttonOptOut);
+ banner.appendChild(buttonOptIn);
+ document.body.appendChild(banner);
+ }
+
+ window.onload = () => {
+ generateBanner();
+ };
+ </script>
+ <!-- This will cause DOMContentLoaded and load to be further apart which is
+ required for certain test cases. slowSubresource.sjs will delay the page load
+ event.-->
+ <link rel="stylesheet" href="slowSubresource.sjs">
+</head>
+<body>
+ <h1>This is the top-level page</h1>
+ <p id="result">NoClick</p>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/file_iframe_banner.html b/toolkit/components/cookiebanners/test/browser/file_iframe_banner.html
new file mode 100644
index 0000000000..8bf81a9222
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/file_iframe_banner.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>A top-level page with iframe cookie banner</title>
+</head>
+<body>
+<iframe src="https://example.com/browser/toolkit/components/cookiebanners/test/browser/file_banner.html"></iframe>
+</body>
+</html>
diff --git a/toolkit/components/cookiebanners/test/browser/head.js b/toolkit/components/cookiebanners/test/browser/head.js
new file mode 100644
index 0000000000..e67ac49b9f
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/head.js
@@ -0,0 +1,658 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_DOMAIN_A = "example.com";
+const TEST_DOMAIN_B = "example.org";
+const TEST_DOMAIN_C = "example.net";
+
+const TEST_ORIGIN_A = "https://" + TEST_DOMAIN_A;
+const TEST_ORIGIN_B = "https://" + TEST_DOMAIN_B;
+const TEST_ORIGIN_C = "https://" + TEST_DOMAIN_C;
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ ""
+);
+
+const TEST_PAGE_A = TEST_ORIGIN_A + TEST_PATH + "file_banner.html";
+const TEST_PAGE_B = TEST_ORIGIN_B + TEST_PATH + "file_banner.html";
+// Page C has a different banner element ID than A and B.
+const TEST_PAGE_C = TEST_ORIGIN_C + TEST_PATH + "file_banner_b.html";
+
+function genUUID() {
+ return Services.uuid.generateUUID().number.slice(1, -1);
+}
+
+/**
+ * Common setup function for cookie banner handling tests.
+ */
+async function testSetup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enable debug logging.
+ ["cookiebanners.listService.logLevel", "Debug"],
+ // Avoid importing rules from RemoteSettings. They may interfere with test
+ // rules / assertions.
+ ["cookiebanners.listService.testSkipRemoteSettings", true],
+ ],
+ });
+
+ // Reset GLEAN (FOG) telemetry to avoid data bleeding over from other tests.
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (Services.cookieBanners.isEnabled) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+
+ // Clear executed records.
+ Services.cookieBanners.removeAllExecutedRecords(false);
+ Services.cookieBanners.removeAllExecutedRecords(true);
+ }
+ });
+}
+
+/**
+ * Setup function for click tests.
+ */
+async function clickTestSetup() {
+ await testSetup();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enable debug logging.
+ ["cookiebanners.bannerClicking.logLevel", "Debug"],
+ ["cookiebanners.bannerClicking.testing", true],
+ ["cookiebanners.bannerClicking.timeoutAfterLoad", 500],
+ ["cookiebanners.bannerClicking.enabled", true],
+ ["cookiebanners.cookieInjector.enabled", false],
+ ],
+ });
+}
+
+/**
+ * Setup function for cookie injector tests.
+ */
+async function cookieInjectorTestSetup() {
+ await testSetup();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.cookieInjector.enabled", true],
+ // Required to dispatch cookiebanner events.
+ ["cookiebanners.bannerClicking.enabled", true],
+ ],
+ });
+}
+
+/**
+ * A helper function returns a promise which resolves when the banner clicking
+ * is finished for the given domain.
+ *
+ * @param {String} domain the domain that should run the banner clicking.
+ */
+function promiseBannerClickingFinish(domain) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject, topic, data) {
+ if (data != domain) {
+ return;
+ }
+
+ Services.obs.removeObserver(
+ observer,
+ "cookie-banner-test-clicking-finish"
+ );
+ resolve();
+ }, "cookie-banner-test-clicking-finish");
+ });
+}
+
+/**
+ * A helper function to verify the banner state of the given browsingContext.
+ *
+ * @param {BrowsingContext} bc - the browsing context
+ * @param {boolean} visible - if the banner should be visible.
+ * @param {boolean} expected - the expected banner click state.
+ * @param {string} [bannerId] - id of the cookie banner element.
+ */
+async function verifyBannerState(bc, visible, expected, bannerId = "banner") {
+ info("Verify the cookie banner state.");
+
+ await SpecialPowers.spawn(
+ bc,
+ [visible, expected, bannerId],
+ (visible, expected, bannerId) => {
+ let banner = content.document.getElementById(bannerId);
+
+ is(
+ banner.checkVisibility({
+ checkOpacity: true,
+ checkVisibilityCSS: true,
+ }),
+ visible,
+ `The banner element should be ${visible ? "visible" : "hidden"}`
+ );
+
+ let result = content.document.getElementById("result");
+
+ is(result.textContent, expected, "The build click state is correct.");
+ }
+ );
+}
+
+/**
+ * A helper function to open the test page and verify the banner state.
+ *
+ * @param {Window} [win] - the chrome window object.
+ * @param {String} domain - the domain of the testing page.
+ * @param {String} testURL - the url of the testing page.
+ * @param {boolean} visible - if the banner should be visible.
+ * @param {boolean} expected - the expected banner click state.
+ * @param {string} [bannerId] - id of the cookie banner element.
+ * @param {boolean} [keepTabOpen] - whether to leave the tab open after the test
+ * function completed.
+ */
+async function openPageAndVerify({
+ win = window,
+ domain,
+ testURL,
+ visible,
+ expected,
+ bannerId = "banner",
+ keepTabOpen = false,
+ expectActorEnabled = true,
+}) {
+ info(`Opening ${testURL}`);
+
+ // If the actor isn't enabled there won't be a "finished" observer message.
+ let promise = expectActorEnabled
+ ? promiseBannerClickingFinish(domain)
+ : new Promise(resolve => setTimeout(resolve, 0));
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, testURL);
+
+ await promise;
+
+ await verifyBannerState(tab.linkedBrowser, visible, expected, bannerId);
+
+ if (!keepTabOpen) {
+ BrowserTestUtils.removeTab(tab);
+ }
+}
+
+/**
+ * A helper function to open the test page in an iframe and verify the banner
+ * state in the iframe.
+ *
+ * @param {Window} win - the chrome window object.
+ * @param {String} domain - the domain of the testing iframe page.
+ * @param {String} testURL - the url of the testing iframe page.
+ * @param {boolean} visible - if the banner should be visible.
+ * @param {boolean} expected - the expected banner click state.
+ */
+async function openIframeAndVerify({
+ win,
+ domain,
+ testURL,
+ visible,
+ expected,
+}) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ TEST_ORIGIN_C
+ );
+
+ let promise = promiseBannerClickingFinish(domain);
+
+ let iframeBC = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [testURL],
+ async testURL => {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = testURL;
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+
+ return iframe.browsingContext;
+ }
+ );
+
+ await promise;
+ await verifyBannerState(iframeBC, visible, expected);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+/**
+ * A helper function to insert testing rules.
+ */
+function insertTestClickRules(insertGlobalRules = true) {
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting test rules.");
+
+ info("Add opt-out click rule for DOMAIN_A.");
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.id = genUUID();
+ ruleA.domains = [TEST_DOMAIN_A];
+
+ ruleA.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleA);
+
+ info("Add opt-in click rule for DOMAIN_B.");
+ let ruleB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleB.id = genUUID();
+ ruleB.domains = [TEST_DOMAIN_B];
+
+ ruleB.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ null,
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleB);
+
+ if (insertGlobalRules) {
+ info("Add global ruleC which targets a non-existing banner (presence).");
+ let ruleC = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleC.id = genUUID();
+ ruleC.domains = [];
+ ruleC.addClickRule(
+ "div#nonExistingBanner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ null,
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleC);
+
+ info("Add global ruleD which targets a non-existing banner (presence).");
+ let ruleD = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleD.id = genUUID();
+ ruleD.domains = [];
+ ruleD.addClickRule(
+ "div#nonExistingBanner2",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ "button#optOut",
+ "button#optIn"
+ );
+ Services.cookieBanners.insertRule(ruleD);
+ }
+}
+
+/**
+ * Inserts cookie injection test rules for TEST_DOMAIN_A and TEST_DOMAIN_B.
+ */
+function insertTestCookieRules() {
+ info("Clearing existing rules");
+ Services.cookieBanners.resetRules(false);
+
+ info("Inserting test rules.");
+
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.domains = [TEST_DOMAIN_A, TEST_DOMAIN_C];
+
+ Services.cookieBanners.insertRule(ruleA);
+ ruleA.addCookie(
+ true,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optOut1",
+ null, // empty host to fall back to .<domain>
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ ruleA.addCookie(
+ true,
+ `cookieConsent_${TEST_DOMAIN_A}_2`,
+ "optOut2",
+ null,
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ // An opt-in cookie rule for DOMAIN_B.
+ let ruleB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleB.domains = [TEST_DOMAIN_B];
+
+ Services.cookieBanners.insertRule(ruleB);
+ ruleB.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_B}_1`,
+ "optIn1",
+ TEST_DOMAIN_B,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+}
+
+/**
+ * Test the Glean.cookieBannersClick.result metric.
+ *
+ * @param {*} expected - Object mapping labels to counters. Omitted labels are
+ * asserted to be in initial state (undefined =^ 0)
+ * @param {boolean} [resetFOG] - Whether to reset all FOG telemetry after the
+ * method has finished.
+ */
+function testClickResultTelemetry(expected, resetFOG = true) {
+ return testClickResultTelemetryInternal(
+ Glean.cookieBannersClick.result,
+ expected,
+ resetFOG
+ );
+}
+
+/**
+ * Test the Glean.cookieBannersCmp.result metric.
+ *
+ * @param {*} expected - Object mapping labels to counters. Omitted labels are
+ * asserted to be in initial state (undefined =^ 0)
+ * @param {boolean} [resetFOG] - Whether to reset all FOG telemetry after the
+ * method has finished.
+ */
+function testCMPResultTelemetry(expected, resetFOG = true) {
+ return testClickResultTelemetryInternal(
+ Glean.cookieBannersCmp.result,
+ expected,
+ resetFOG
+ );
+}
+
+/**
+ * Test the result metric.
+ *
+ * @param {Object} targetTelemetry - The target glean interface to run the
+ * checks
+ * @param {*} expected - Object mapping labels to counters. Omitted labels are
+ * asserted to be in initial state (undefined =^ 0)
+ * @param {boolean} [resetFOG] - Whether to reset all FOG telemetry after the
+ * method has finished.
+ */
+async function testClickResultTelemetryInternal(
+ targetTelemetry,
+ expected,
+ resetFOG
+) {
+ // TODO: Bug 1805653: Enable tests for Linux.
+ if (AppConstants.platform == "linux") {
+ ok(true, "Skip click telemetry tests on linux.");
+ return;
+ }
+
+ // Ensure we have all data from the content process.
+ await Services.fog.testFlushAllChildren();
+
+ let labels = [
+ "success",
+ "success_cookie_injected",
+ "success_dom_content_loaded",
+ "success_mutation_pre_load",
+ "success_mutation_post_load",
+ "fail",
+ "fail_banner_not_found",
+ "fail_banner_not_visible",
+ "fail_button_not_found",
+ "fail_no_rule_for_mode",
+ "fail_actor_destroyed",
+ ];
+
+ let testMetricState = doAssert => {
+ for (let label of labels) {
+ let expectedValue = expected[label] ?? null;
+ if (doAssert) {
+ is(
+ targetTelemetry[label].testGetValue(),
+ expectedValue,
+ `Counter for label '${label}' has correct state.`
+ );
+ } else if (targetTelemetry[label].testGetValue() !== expectedValue) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ // Wait for the labeled counter to match the expected state. Returns greedy on
+ // mismatch.
+ try {
+ await TestUtils.waitForCondition(
+ testMetricState,
+ "Waiting for cookieBannersClick.result metric to match."
+ );
+ } finally {
+ // Test again but this time with assertions and test all labels.
+ testMetricState(true);
+
+ // Reset telemetry, even if the test condition above throws. This is to
+ // avoid failing subsequent tests in case of a test failure.
+ if (resetFOG) {
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+ }
+ }
+}
+
+/**
+ * Triggers a cookie banner handling feature and tests the events dispatched.
+ * @param {*} options - Test options.
+ * @param {nsICookieBannerService::Modes} options.mode - The cookie banner
+ * service mode to test with.
+ * @param {boolean} options.detectOnly - Whether the service should be enabled
+ * in detection only mode, where it does not handle banners.
+ * @param {function} options.initFn - Function to call for test initialization.
+ * @param {function} options.triggerFn - Function to call to trigger the banner
+ * handling feature.
+ * @param {string} options.testURL - URL where the test will trigger the banner
+ * handling feature.
+ * @returns {Promise} Resolves when the test completes, after cookie banner
+ * events.
+ */
+async function runEventTest({ mode, detectOnly, initFn, triggerFn, testURL }) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", mode],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED,
+ ],
+ ["cookiebanners.service.detectOnly", detectOnly],
+ ],
+ });
+
+ await initFn();
+
+ let expectEventDetected = mode != Ci.nsICookieBannerService.MODE_DISABLED;
+ let expectEventHandled =
+ !detectOnly &&
+ (mode == Ci.nsICookieBannerService.MODE_REJECT ||
+ mode == Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT);
+
+ let eventObservedDetected = false;
+ let eventObservedHandled = false;
+
+ // This is a bit hacky, we use side effects caused by the checkFn we pass into
+ // waitForEvent to keep track of whether an event fired.
+ let promiseEventDetected = BrowserTestUtils.waitForEvent(
+ window,
+ "cookiebannerdetected",
+ false,
+ () => {
+ eventObservedDetected = true;
+ return true;
+ }
+ );
+ let promiseEventHandled = BrowserTestUtils.waitForEvent(
+ window,
+ "cookiebannerhandled",
+ false,
+ () => {
+ eventObservedHandled = true;
+ return true;
+ }
+ );
+
+ // If we expect any events check which one comes first.
+ let firstEventPromise;
+ if (expectEventDetected || expectEventHandled) {
+ firstEventPromise = Promise.race([
+ promiseEventHandled,
+ promiseEventDetected,
+ ]);
+ }
+
+ await triggerFn();
+
+ let eventDetected;
+ if (expectEventDetected) {
+ eventDetected = await promiseEventDetected;
+
+ is(
+ eventDetected.type,
+ "cookiebannerdetected",
+ "Should dispatch cookiebannerdetected event."
+ );
+ }
+
+ let eventHandled;
+ if (expectEventHandled) {
+ eventHandled = await promiseEventHandled;
+ is(
+ eventHandled.type,
+ "cookiebannerhandled",
+ "Should dispatch cookiebannerhandled event."
+ );
+ }
+
+ // For MODE_DISABLED this array will be empty, because we don't expect any
+ // events to be dispatched.
+ let eventsToTest = [eventDetected, eventHandled].filter(event => !!event);
+
+ for (let event of eventsToTest) {
+ info(`Testing properties of event ${event.type}`);
+
+ let { windowContext } = event.detail;
+ ok(
+ windowContext,
+ `Event ${event.type} detail should contain a WindowContext`
+ );
+
+ let browser = windowContext.browsingContext.top.embedderElement;
+ ok(
+ browser,
+ "WindowContext should have an associated top embedder element."
+ );
+ is(
+ browser.tagName,
+ "browser",
+ "The top embedder element should be a browser"
+ );
+ let chromeWin = browser.ownerGlobal;
+ is(
+ chromeWin,
+ window,
+ "The chrome window associated with the browser should match the window where the cookie banner was handled."
+ );
+ is(
+ chromeWin.gBrowser.selectedBrowser,
+ browser,
+ "The browser associated with the event should be the selected browser."
+ );
+ is(
+ browser.currentURI.spec,
+ testURL,
+ "The browser's URI spec should match the cookie banner test page."
+ );
+ }
+
+ let firstEvent = await firstEventPromise;
+ is(
+ expectEventDetected || expectEventHandled,
+ !!firstEvent,
+ "Should have observed the first event if banner clicking is enabled."
+ );
+
+ if (expectEventDetected || expectEventHandled) {
+ is(
+ firstEvent.type,
+ "cookiebannerdetected",
+ "Detected event should be dispatched first"
+ );
+ }
+
+ is(
+ eventObservedDetected,
+ expectEventDetected,
+ `Should ${
+ expectEventDetected ? "" : "not "
+ }have observed 'cookiebannerdetected' event for mode ${mode}`
+ );
+ is(
+ eventObservedHandled,
+ expectEventHandled,
+ `Should ${
+ expectEventHandled ? "" : "not "
+ }have observed 'cookiebannerhandled' event for mode ${mode}`
+ );
+
+ // Clean up pending promises by dispatching artificial cookiebanner events.
+ // Otherwise the test fails because of pending event listeners which
+ // BrowserTestUtils.waitForEvent registered.
+ for (let eventType of ["cookiebannerdetected", "cookiebannerhandled"]) {
+ let event = new CustomEvent(eventType, {
+ bubbles: true,
+ cancelable: false,
+ });
+ window.windowUtils.dispatchEventToChromeOnly(window, event);
+ }
+
+ await promiseEventDetected;
+ await promiseEventHandled;
+}
diff --git a/toolkit/components/cookiebanners/test/browser/slowSubresource.sjs b/toolkit/components/cookiebanners/test/browser/slowSubresource.sjs
new file mode 100644
index 0000000000..1f14c2e9cf
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/slowSubresource.sjs
@@ -0,0 +1,18 @@
+"use strict";
+
+let timer;
+
+const DELAY_MS = 5000;
+function handleRequest(request, response) {
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(
+ () => {
+ response.setHeader("Content-Type", "text/css ", false);
+ response.write("body { background-color: red; }");
+ response.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/toolkit/components/cookiebanners/test/browser/testCookieHeader.sjs b/toolkit/components/cookiebanners/test/browser/testCookieHeader.sjs
new file mode 100644
index 0000000000..2d7cac5e45
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/testCookieHeader.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200);
+ // Write the cookie header value to the body for the test to inspect.
+ response.write(request.getHeader("Cookie"));
+}
diff --git a/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js b/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js
new file mode 100644
index 0000000000..bc69a49d0f
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js
@@ -0,0 +1,611 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+let { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+// Name of the RemoteSettings collection containing the rules.
+const COLLECTION_NAME = "cookie-banner-rules-list";
+
+// Name of pref used to import test rules.
+const PREF_TEST_RULES = "cookiebanners.listService.testRules";
+
+let rulesInserted = [];
+let rulesRemoved = [];
+let insertCallback = null;
+
+function genUUID() {
+ return Services.uuid.generateUUID().number.slice(1, -1);
+}
+
+const RULE_A_ORIGINAL = {
+ id: genUUID(),
+ click: {
+ optOut: "#fooOut",
+ presence: "#foobar",
+ },
+ domains: ["example.com"],
+ cookies: {
+ optOut: [
+ {
+ name: "doOptOut",
+ value: "true",
+ isSecure: true,
+ isSession: false,
+ },
+ ],
+ },
+};
+
+const RULE_B = {
+ id: genUUID(),
+ click: {
+ optOut: "#fooOutB",
+ presence: "#foobarB",
+ },
+ domains: ["example.org"],
+ cookies: {
+ optOut: [
+ {
+ name: "doOptOutB",
+ value: "true",
+ isSecure: true,
+ },
+ ],
+ },
+};
+
+const RULE_C = {
+ id: genUUID(),
+ click: {
+ optOut: "#fooOutC",
+ presence: "#foobarC",
+ },
+ domains: ["example.net"],
+ cookies: {
+ optIn: [
+ {
+ name: "gdpr",
+ value: "1",
+ path: "/myPath",
+ host: "foo.example.net",
+ },
+ ],
+ },
+};
+
+const RULE_A_UPDATED = {
+ id: RULE_A_ORIGINAL.id,
+ click: {
+ optOut: "#fooOut",
+ optIn: "#barIn",
+ presence: "#foobar",
+ },
+ domains: ["example.com"],
+ cookies: {
+ optOut: [
+ {
+ name: "doOptOutUpdated",
+ value: "true",
+ isSecure: true,
+ },
+ {
+ name: "hideBanner",
+ value: "1",
+ },
+ ],
+ },
+};
+
+const INVALID_RULE_CLICK = {
+ id: genUUID(),
+ domains: ["foobar.com"],
+ click: {
+ presence: 1,
+ optIn: "#foo",
+ },
+};
+
+const INVALID_RULE_EMPTY = {};
+
+const RULE_D_GLOBAL = {
+ id: genUUID(),
+ click: {
+ optOut: "#globalOptOutD",
+ presence: "#globalBannerD",
+ },
+ domains: [],
+ cookies: {},
+};
+
+const RULE_E_GLOBAL = {
+ id: genUUID(),
+ click: {
+ optOut: "#globalOptOutE",
+ presence: "#globalBannerE",
+ },
+ domains: [],
+ cookies: {},
+};
+
+const RULE_F_EMPTY_CLICK = {
+ id: genUUID(),
+ click: {},
+ domains: ["example.com"],
+ cookies: {
+ optOut: [
+ {
+ name: "doOptOut",
+ value: "true",
+ isSecure: true,
+ },
+ {
+ name: "hideBanner",
+ value: "1",
+ },
+ ],
+ },
+};
+
+// Testing with RemoteSettings requires a profile.
+do_get_profile();
+
+add_setup(async () => {
+ // Enable debug logging.
+ Services.prefs.setStringPref("cookiebanners.listService.logLevel", "Debug");
+
+ // Stub some nsICookieBannerService methods for easy testing.
+ let removeRule = sinon.stub().callsFake(rule => {
+ rulesRemoved.push(rule);
+ });
+
+ let insertRule = sinon.stub().callsFake(rule => {
+ rulesInserted.push(rule);
+ insertCallback?.();
+ });
+
+ let oldCookieBanners = Services.cookieBanners;
+ Services.cookieBanners = {
+ isEnabled: true,
+ insertRule,
+ removeRule,
+ resetRules() {},
+ resetDomainTelemetryRecord() {},
+ };
+
+ // Remove stubs on test end.
+ registerCleanupFunction(() => {
+ Services.cookieBanners = oldCookieBanners;
+ Services.prefs.clearUserPref("cookiebanners.listService.logLevel");
+ });
+});
+
+/**
+ * Promise wrapper to listen for Services.cookieBanners.insertRule calls from
+ * the CookieBannerListService.
+ * @param {function} checkFn - Function which returns true or false to indicate
+ * if this is the insert the caller is looking for.
+ * @returns {Promise} - Promise which resolves when checkFn matches after
+ * insertRule call.
+ */
+function waitForInsert(checkFn) {
+ return new Promise(resolve => {
+ insertCallback = () => {
+ if (checkFn()) {
+ insertCallback = null;
+ resolve();
+ }
+ };
+ });
+}
+
+/**
+ * Tests that the cookie banner list service imports all rules on init.
+ */
+add_task(async function test_initial_import() {
+ info("Initializing RemoteSettings collection " + COLLECTION_NAME);
+
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ db.clear();
+ await db.create(RULE_A_ORIGINAL);
+ await db.create(RULE_C);
+ await db.importChanges({}, Date.now());
+
+ Assert.equal(rulesInserted.length, 0, "No inserted rules initially.");
+ Assert.equal(rulesRemoved.length, 0, "No removed rules initially.");
+
+ let insertPromise = waitForInsert(() => rulesInserted.length >= 2);
+
+ info(
+ "Initializing the cookie banner list service which triggers a collection get call"
+ );
+ let cookieBannerListService = Cc[
+ "@mozilla.org/cookie-banner-list-service;1"
+ ].getService(Ci.nsICookieBannerListService);
+ await cookieBannerListService.initForTest();
+
+ await insertPromise;
+
+ Assert.equal(rulesInserted.length, 2, "Two inserted rules after init.");
+ Assert.equal(rulesRemoved.length, 0, "No removed rules after init.");
+
+ let ruleA = rulesInserted.find(rule => rule.id == RULE_A_UPDATED.id);
+ let cookieRuleA = ruleA.cookiesOptOut[0].cookie;
+ let ruleC = rulesInserted.find(rule => rule.id == RULE_C.id);
+ let cookieRuleC = ruleC.cookiesOptIn[0].cookie;
+
+ Assert.ok(ruleA, "Has rule A.");
+ Assert.deepEqual(
+ ruleA.domains,
+ RULE_A_UPDATED.domains,
+ "Domains for ruleA should match."
+ );
+ // Test the defaults which CookieBannerListService sets when the rule does
+ // not.
+ Assert.equal(
+ cookieRuleA.isSession,
+ false,
+ "Cookie for rule A should not be a session cookie."
+ );
+ Assert.equal(
+ cookieRuleA.host,
+ null,
+ "Cookie host for rule A should be default."
+ );
+ Assert.equal(
+ cookieRuleA.path,
+ "/",
+ "Cookie path for rule A should be default."
+ );
+
+ Assert.ok(ruleC, "Has rule C.");
+ Assert.deepEqual(
+ ruleC.domains,
+ RULE_C.domains,
+ "Domains for ruleA should match."
+ );
+ Assert.equal(
+ ruleC.cookiesOptIn[0].cookie.isSession,
+ true,
+ "Cookie for rule C should should be a session cookie."
+ );
+ Assert.equal(
+ cookieRuleC.host,
+ "foo.example.net",
+ "Cookie host for rule C should be custom."
+ );
+ Assert.equal(
+ cookieRuleC.path,
+ "/myPath",
+ "Cookie path for rule C should be custom."
+ );
+
+ // Cleanup
+ cookieBannerListService.shutdown();
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ db.clear();
+ await db.importChanges({}, Date.now());
+});
+
+/**
+ * Tests that the cookie banner list service updates rules on sync.
+ */
+add_task(async function test_remotesettings_sync() {
+ // Initialize the cookie banner list service so it subscribes to
+ // RemoteSettings updates.
+ let cookieBannerListService = Cc[
+ "@mozilla.org/cookie-banner-list-service;1"
+ ].getService(Ci.nsICookieBannerListService);
+ await cookieBannerListService.initForTest();
+
+ const payload = {
+ current: [RULE_A_ORIGINAL, RULE_C, RULE_D_GLOBAL],
+ created: [RULE_B, RULE_E_GLOBAL],
+ updated: [{ old: RULE_A_ORIGINAL, new: RULE_A_UPDATED }],
+ deleted: [RULE_C, RULE_D_GLOBAL],
+ };
+
+ Assert.equal(rulesInserted.length, 0, "No inserted rules initially.");
+ Assert.equal(rulesRemoved.length, 0, "No removed rules initially.");
+
+ info("Dispatching artificial RemoteSettings sync event");
+ await RemoteSettings(COLLECTION_NAME).emit("sync", { data: payload });
+
+ Assert.equal(rulesInserted.length, 3, "Three inserted rules after sync.");
+ Assert.equal(rulesRemoved.length, 2, "Two removed rules after sync.");
+
+ let ruleA = rulesInserted.find(rule => rule.id == RULE_A_UPDATED.id);
+ let ruleB = rulesInserted.find(rule => rule.id == RULE_B.id);
+ let ruleE = rulesInserted.find(rule => rule.id == RULE_E_GLOBAL.id);
+ let ruleC = rulesRemoved[0];
+
+ info("Testing that service inserted updated version of RULE_A.");
+ Assert.deepEqual(
+ ruleA.domains,
+ RULE_A_UPDATED.domains,
+ "Domains should match RULE_A."
+ );
+ Assert.equal(
+ ruleA.cookiesOptOut.length,
+ RULE_A_UPDATED.cookies.optOut.length,
+ "cookiesOptOut length should match RULE_A."
+ );
+
+ info("Testing opt-out cookies of RULE_A");
+ for (let i = 0; i < RULE_A_UPDATED.cookies.optOut.length; i += 1) {
+ Assert.equal(
+ ruleA.cookiesOptOut[i].cookie.name,
+ RULE_A_UPDATED.cookies.optOut[i].name,
+ "cookiesOptOut cookie name should match RULE_A."
+ );
+ Assert.equal(
+ ruleA.cookiesOptOut[i].cookie.value,
+ RULE_A_UPDATED.cookies.optOut[i].value,
+ "cookiesOptOut cookie value should match RULE_A."
+ );
+ }
+
+ Assert.equal(ruleB.id, RULE_B.id, "Should have inserted RULE_B");
+ Assert.equal(ruleC.id, RULE_C.id, "Should have removed RULE_C");
+ Assert.equal(
+ ruleE.id,
+ RULE_E_GLOBAL.id,
+ "Should have inserted RULE_E_GLOBAL"
+ );
+
+ // Cleanup
+ cookieBannerListService.shutdown();
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ let { db } = RemoteSettings(COLLECTION_NAME);
+ db.clear();
+ await db.importChanges({}, Date.now());
+});
+
+/**
+ * Tests the cookie banner rule test pref.
+ */
+add_task(async function test_rule_test_pref() {
+ Services.prefs.setStringPref(
+ PREF_TEST_RULES,
+ JSON.stringify([RULE_A_ORIGINAL, RULE_B])
+ );
+
+ Assert.equal(rulesInserted.length, 0, "No inserted rules initially.");
+ Assert.equal(rulesRemoved.length, 0, "No removed rules initially.");
+
+ let insertPromise = waitForInsert(() => rulesInserted.length >= 2);
+
+ // Initialize the cookie banner list service so it imports test rules and listens for pref changes.
+ let cookieBannerListService = Cc[
+ "@mozilla.org/cookie-banner-list-service;1"
+ ].getService(Ci.nsICookieBannerListService);
+ await cookieBannerListService.initForTest();
+
+ info("Wait for rules to be inserted");
+ await insertPromise;
+
+ Assert.equal(rulesInserted.length, 2, "Should have inserted two rules.");
+ Assert.equal(rulesRemoved.length, 0, "Should not have removed any rules.");
+
+ Assert.ok(
+ rulesInserted.some(rule => rule.id == RULE_A_ORIGINAL.id),
+ "Should have inserted RULE_A"
+ );
+ Assert.ok(
+ rulesInserted.some(rule => rule.id == RULE_B.id),
+ "Should have inserted RULE_B"
+ );
+
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ let insertPromise2 = waitForInsert(() => rulesInserted.length >= 3);
+
+ info(
+ "Updating test rules via pref. The list service should detect the pref change."
+ );
+ // This includes some invalid rules, they should be skipped.
+ Services.prefs.setStringPref(
+ PREF_TEST_RULES,
+ JSON.stringify([
+ RULE_A_ORIGINAL,
+ RULE_B,
+ INVALID_RULE_EMPTY,
+ RULE_C,
+ INVALID_RULE_CLICK,
+ ])
+ );
+
+ info("Wait for rules to be inserted");
+ await insertPromise2;
+
+ Assert.equal(rulesInserted.length, 3, "Should have inserted three rules.");
+ Assert.equal(rulesRemoved.length, 0, "Should not have removed any rules.");
+
+ Assert.ok(
+ rulesInserted.some(rule => rule.id == RULE_A_ORIGINAL.id),
+ "Should have inserted RULE_A"
+ );
+ Assert.ok(
+ rulesInserted.some(rule => rule.id == RULE_B.id),
+ "Should have inserted RULE_B"
+ );
+ Assert.ok(
+ rulesInserted.some(rule => rule.id == RULE_C.id),
+ "Should have inserted RULE_C"
+ );
+
+ // Cleanup
+ cookieBannerListService.shutdown();
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ Services.prefs.clearUserPref(PREF_TEST_RULES);
+});
+
+/**
+ * Tests that runContext string values get properly translated into nsIClickRule::RunContext.
+ */
+add_task(async function test_runContext_conversion() {
+ info("Initializing RemoteSettings collection " + COLLECTION_NAME);
+
+ let ruleA = {
+ id: genUUID(),
+ click: {
+ presence: "#foobar",
+ runContext: "child",
+ },
+ domains: ["a.com"],
+ };
+ let ruleB = {
+ id: genUUID(),
+ click: {
+ presence: "#foobar",
+ },
+ domains: ["b.com"],
+ };
+ let ruleC = {
+ id: genUUID(),
+ click: {
+ presence: "#foobar",
+ runContext: "all",
+ },
+ domains: ["c.com"],
+ };
+ let ruleD = {
+ id: genUUID(),
+ click: {
+ presence: "#foobar",
+ runContext: "top",
+ },
+ domains: ["d.com"],
+ };
+ let ruleE = {
+ id: genUUID(),
+ click: {
+ presence: "#foobar",
+ runContext: "thisIsNotValid",
+ },
+ domains: ["e.com"],
+ };
+
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ db.clear();
+ await db.create(ruleA);
+ await db.create(ruleB);
+ await db.create(ruleC);
+ await db.create(ruleD);
+ await db.create(ruleE);
+ await db.importChanges({}, Date.now());
+
+ let insertPromise = waitForInsert(() => rulesInserted.length >= 4);
+
+ info(
+ "Initializing the cookie banner list service which triggers a collection get call"
+ );
+ let cookieBannerListService = Cc[
+ "@mozilla.org/cookie-banner-list-service;1"
+ ].getService(Ci.nsICookieBannerListService);
+ await cookieBannerListService.initForTest();
+
+ await insertPromise;
+
+ let resultA = rulesInserted.find(rule =>
+ rule.domains.includes("a.com")
+ ).clickRule;
+ let resultB = rulesInserted.find(rule =>
+ rule.domains.includes("b.com")
+ ).clickRule;
+ let resultC = rulesInserted.find(rule =>
+ rule.domains.includes("c.com")
+ ).clickRule;
+ let resultD = rulesInserted.find(rule =>
+ rule.domains.includes("d.com")
+ ).clickRule;
+ let resultE = rulesInserted.find(rule =>
+ rule.domains.includes("e.com")
+ ).clickRule;
+
+ Assert.equal(
+ resultA.runContext,
+ Ci.nsIClickRule.RUN_CHILD,
+ "Rule A should have been imported with the correct runContext"
+ );
+ Assert.equal(
+ resultB.runContext,
+ Ci.nsIClickRule.RUN_TOP,
+ "Rule B should have fallen back to default runContext"
+ );
+ Assert.equal(
+ resultC.runContext,
+ Ci.nsIClickRule.RUN_ALL,
+ "Rule C should have been imported with the correct runContext"
+ );
+ Assert.equal(
+ resultD.runContext,
+ Ci.nsIClickRule.RUN_TOP,
+ "Rule D should have been imported with the correct runContext"
+ );
+ Assert.equal(
+ resultE.runContext,
+ Ci.nsIClickRule.RUN_TOP,
+ "Rule E should have fallen back to default runContext"
+ );
+
+ // Cleanup
+ cookieBannerListService.shutdown();
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ db.clear();
+ await db.importChanges({}, Date.now());
+});
+
+/**
+ * Tests empty click rules don't get imported.
+ */
+add_task(async function test_empty_click_rule() {
+ info("Initializing RemoteSettings collection " + COLLECTION_NAME);
+
+ let db = RemoteSettings(COLLECTION_NAME).db;
+ db.clear();
+ await db.create(RULE_F_EMPTY_CLICK);
+ await db.importChanges({}, Date.now());
+
+ let insertPromise = waitForInsert(() => rulesInserted.length >= 1);
+
+ info(
+ "Initializing the cookie banner list service which triggers a collection get call"
+ );
+ let cookieBannerListService = Cc[
+ "@mozilla.org/cookie-banner-list-service;1"
+ ].getService(Ci.nsICookieBannerListService);
+ await cookieBannerListService.initForTest();
+
+ await insertPromise;
+
+ let ruleF = rulesInserted.find(rule => rule.id == RULE_F_EMPTY_CLICK.id);
+
+ Assert.ok(ruleF, "Has rule F.");
+ Assert.ok(ruleF.cookiesOptOut?.length, "Should have imported a cookie rule.");
+ Assert.equal(ruleF.clickRule, null, "Should not have imported a click rule.");
+
+ // Cleanup
+ cookieBannerListService.shutdown();
+ rulesInserted = [];
+ rulesRemoved = [];
+
+ db.clear();
+ await db.importChanges({}, Date.now());
+});
diff --git a/toolkit/components/cookiebanners/test/unit/xpcshell.toml b/toolkit/components/cookiebanners/test/unit/xpcshell.toml
new file mode 100644
index 0000000000..cfa2723627
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/unit/xpcshell.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["test_cookiebannerlistservice.js"]