549 lines
14 KiB
JavaScript
549 lines
14 KiB
JavaScript
/* 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() {
|
|
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
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|