diff options
Diffstat (limited to 'toolkit/components/cookiebanners/test/browser/head.js')
-rw-r--r-- | toolkit/components/cookiebanners/test/browser/head.js | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/toolkit/components/cookiebanners/test/browser/head.js b/toolkit/components/cookiebanners/test/browser/head.js new file mode 100644 index 0000000000..d88de7e20b --- /dev/null +++ b/toolkit/components/cookiebanners/test/browser/head.js @@ -0,0 +1,605 @@ +/* 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. + 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); + } + }); +} + +/** + * 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.timeout", 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, +}) { + info(`Opening ${testURL}`); + + let promise = promiseBannerClickingFinish(domain); + + 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() { + 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); + + 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. + */ +async function testClickResultTelemetry(expected, resetFOG = true) { + // 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) { + if (doAssert) { + is( + Glean.cookieBannersClick.result[label].testGetValue(), + expected[label], + `Counter for label '${label}' has correct state.` + ); + } else if ( + Glean.cookieBannersClick.result[label].testGetValue() !== + expected[label] + ) { + 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) { + 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.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; +} |