/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const TRACKING_PAGE = "https://tracking.example.org"; const TRACKING_PAGE2 = "https://tracking.example.org^partitionKey=(https,example.com)"; const BENIGN_PAGE = "https://example.com"; const FOREIGN_PAGE = "https://example.net"; const FOREIGN_PAGE2 = "https://example.net^partitionKey=(https,example.com)"; const FOREIGN_PAGE3 = "https://example.net^partitionKey=(https,example.org)"; const { UrlClassifierTestUtils } = ChromeUtils.importESModule( "resource://testing-common/UrlClassifierTestUtils.sys.mjs" ); const { SiteDataTestUtils } = ChromeUtils.importESModule( "resource://testing-common/SiteDataTestUtils.sys.mjs" ); const { PermissionTestUtils } = ChromeUtils.importESModule( "resource://testing-common/PermissionTestUtils.sys.mjs" ); const { setTimeout } = ChromeUtils.importESModule( "resource://gre/modules/Timer.sys.mjs" ); XPCOMUtils.defineLazyServiceGetter( this, "PurgeTrackerService", "@mozilla.org/purge-tracker-service;1", "nsIPurgeTrackerService" ); async function setupTest(aCookieBehavior) { Services.prefs.setIntPref("network.cookie.cookieBehavior", aCookieBehavior); Services.prefs.setBoolPref("privacy.purge_trackers.enabled", true); Services.prefs.setCharPref("privacy.purge_trackers.logging.level", "Debug"); Services.prefs.setStringPref( "urlclassifier.trackingAnnotationTable.testEntries", "tracking.example.org" ); // Enables us to test localStorage in xpcshell. Services.prefs.setBoolPref("dom.storage.client_validation", false); } /** * Test that purging doesn't happen when it shouldn't happen. */ add_task(async function testNotPurging() { await UrlClassifierTestUtils.addTestTrackers(); setupTest(Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN); SiteDataTestUtils.addToCookies({ origin: TRACKING_PAGE }); Services.prefs.setIntPref( "network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT ); await PurgeTrackerService.purgeTrackingCookieJars(); ok(SiteDataTestUtils.hasCookies(TRACKING_PAGE), "cookie remains."); Services.prefs.setIntPref( "network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN ); Services.prefs.setBoolPref("privacy.purge_trackers.enabled", false); await PurgeTrackerService.purgeTrackingCookieJars(); ok(SiteDataTestUtils.hasCookies(TRACKING_PAGE), "cookie remains."); Services.prefs.setBoolPref("privacy.purge_trackers.enabled", true); Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true); Services.prefs.setBoolPref("privacy.clearOnShutdown.history", true); await PurgeTrackerService.purgeTrackingCookieJars(); ok(SiteDataTestUtils.hasCookies(TRACKING_PAGE), "cookie remains."); Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown"); Services.prefs.clearUserPref("privacy.clearOnShutdown.history"); await PurgeTrackerService.purgeTrackingCookieJars(); ok(!SiteDataTestUtils.hasCookies(TRACKING_PAGE), "cookie cleared."); UrlClassifierTestUtils.cleanupTestTrackers(); }); /** * Test that cookies indexedDB and localStorage are purged if the cookie is found * on the tracking list and does not have an Interaction Permission. */ async function testIndexedDBAndLocalStorage() { await UrlClassifierTestUtils.addTestTrackers(); PermissionTestUtils.add( TRACKING_PAGE, "storageAccessAPI", Services.perms.ALLOW_ACTION ); SiteDataTestUtils.addToCookies({ origin: BENIGN_PAGE }); for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { SiteDataTestUtils.addToLocalStorage(url); SiteDataTestUtils.addToCookies({ origin: url }); await SiteDataTestUtils.addToIndexedDB(url); } // Purge while storage access permission exists. await PurgeTrackerService.purgeTrackingCookieJars(); for (let url of [TRACKING_PAGE, TRACKING_PAGE2]) { ok( SiteDataTestUtils.hasCookies(url), "cookie remains while storage access permission exists." ); ok( SiteDataTestUtils.hasLocalStorage(url), "localStorage should not have been removed while storage access permission exists." ); Assert.greater( await SiteDataTestUtils.getQuotaUsage(url), 0, `We have data for ${url}` ); } // Run purge after storage access permission has been removed. PermissionTestUtils.remove(TRACKING_PAGE, "storageAccessAPI"); await PurgeTrackerService.purgeTrackingCookieJars(); ok( SiteDataTestUtils.hasCookies(BENIGN_PAGE), "A non-tracking page should retain cookies after purging" ); for (let url of [FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3]) { ok( SiteDataTestUtils.hasCookies(url), `A non-tracking foreign page should retain cookies after purging` ); ok( SiteDataTestUtils.hasLocalStorage(url), `localStorage for ${url} should not have been removed.` ); Assert.greater( await SiteDataTestUtils.getQuotaUsage(url), 0, `We have data for ${url}` ); } // Cookie should have been removed. for (let url of [TRACKING_PAGE, TRACKING_PAGE2]) { ok( !SiteDataTestUtils.hasCookies(url), "cookie is removed after purge with no storage access permission." ); ok( !SiteDataTestUtils.hasLocalStorage(url), "localStorage should have been removed" ); Assert.equal( await SiteDataTestUtils.getQuotaUsage(url), 0, "quota storage was deleted" ); } UrlClassifierTestUtils.cleanupTestTrackers(); } /** * Test that trackers are treated based on their base domain, not origin. */ async function testBaseDomain() { await UrlClassifierTestUtils.addTestTrackers(); let associatedOrigins = [ "https://itisatracker.org", "https://sub.itisatracker.org", "https://www.itisatracker.org", "https://sub.sub.sub.itisatracker.org", "http://itisatracker.org", "http://sub.itisatracker.org", ]; for (let permissionOrigin of associatedOrigins) { // Only one of the associated origins gets permission, but // all should be exempt from purging. PermissionTestUtils.add( permissionOrigin, "storageAccessAPI", Services.perms.ALLOW_ACTION ); for (let origin of associatedOrigins) { SiteDataTestUtils.addToCookies({ origin }); } // Add another tracker to verify we're actually purging. SiteDataTestUtils.addToCookies({ origin: TRACKING_PAGE }); await PurgeTrackerService.purgeTrackingCookieJars(); for (let origin of associatedOrigins) { ok( SiteDataTestUtils.hasCookies(origin), `${origin} should have retained its cookies when permission is set for ${permissionOrigin}.` ); } ok( !SiteDataTestUtils.hasCookies(TRACKING_PAGE), "cookie is removed after purge with no storage access permission." ); PermissionTestUtils.remove(permissionOrigin, "storageAccessAPI"); await SiteDataTestUtils.clear(); } UrlClassifierTestUtils.cleanupTestTrackers(); } /** * Test that trackers are not cleared if they are associated * with an entry on the entity list that has user interaction. */ async function testUserInteraction(ownerPage) { Services.prefs.setBoolPref( "privacy.purge_trackers.consider_entity_list", true ); // The test URL for the entity list for annotation is // itisatrap.org/?resource=example.org, so we need to // add example.org as a tracker. Services.prefs.setCharPref( "urlclassifier.trackingAnnotationTable.testEntries", "example.org" ); await UrlClassifierTestUtils.addTestTrackers(); // example.org and itisatrap.org are hard coded test values on the entity list. const RESOURCE_PAGE = "https://example.org"; PermissionTestUtils.add( ownerPage, "storageAccessAPI", Services.perms.ALLOW_ACTION ); SiteDataTestUtils.addToCookies({ origin: RESOURCE_PAGE }); // Add another tracker to verify we're actually purging. SiteDataTestUtils.addToCookies({ origin: "https://another-tracking.example.net", }); await PurgeTrackerService.purgeTrackingCookieJars(); ok( SiteDataTestUtils.hasCookies(RESOURCE_PAGE), `${RESOURCE_PAGE} should have retained its cookies when permission is set for ${ownerPage}.` ); ok( !SiteDataTestUtils.hasCookies("https://another-tracking.example.net"), "cookie is removed after purge with no storage access permission." ); Services.prefs.setBoolPref( "privacy.purge_trackers.consider_entity_list", false ); await PurgeTrackerService.purgeTrackingCookieJars(); ok( !SiteDataTestUtils.hasCookies(RESOURCE_PAGE), `${RESOURCE_PAGE} should not have retained its cookies when permission is set for ${ownerPage} and the entity list pref is off.` ); PermissionTestUtils.remove(ownerPage, "storageAccessAPI"); await SiteDataTestUtils.clear(); Services.prefs.clearUserPref("privacy.purge_trackers.consider_entity_list"); UrlClassifierTestUtils.cleanupTestTrackers(); } /** * Test that quota storage (even without cookies) is considered when purging trackers. */ async function testQuotaStorage() { await UrlClassifierTestUtils.addTestTrackers(); let testCases = [ { localStorage: true, indexedDB: true }, { localStorage: false, indexedDB: true }, { localStorage: true, indexedDB: false }, ]; for (let { localStorage, indexedDB } of testCases) { PermissionTestUtils.add( TRACKING_PAGE, "storageAccessAPI", Services.perms.ALLOW_ACTION ); if (localStorage) { for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, BENIGN_PAGE, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { SiteDataTestUtils.addToLocalStorage(url); } } if (indexedDB) { for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, BENIGN_PAGE, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { await SiteDataTestUtils.addToIndexedDB(url); } } // Purge while storage access permission exists. await PurgeTrackerService.purgeTrackingCookieJars(); if (localStorage) { ok( SiteDataTestUtils.hasLocalStorage(TRACKING_PAGE), "localStorage should not have been removed while storage access permission exists." ); } if (indexedDB) { for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { Assert.greater( await SiteDataTestUtils.getQuotaUsage(url), 0, `We have data for ${url}` ); } } // Run purge after storage access permission has been removed. PermissionTestUtils.remove(TRACKING_PAGE, "storageAccessAPI"); await PurgeTrackerService.purgeTrackingCookieJars(); if (localStorage) { for (let url of [ BENIGN_PAGE, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { ok( SiteDataTestUtils.hasLocalStorage(url), "localStorage should not have been removed for non-tracking page." ); } for (let url of [TRACKING_PAGE, TRACKING_PAGE2]) { ok( !SiteDataTestUtils.hasLocalStorage(url), "localStorage should have been removed." ); } } if (indexedDB) { for (let url of [ BENIGN_PAGE, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { Assert.greater( await SiteDataTestUtils.getQuotaUsage(url), 0, "quota storage for non-tracking page was not deleted" ); } for (let url of [TRACKING_PAGE, TRACKING_PAGE2]) { Assert.equal( await SiteDataTestUtils.getQuotaUsage(url), 0, "quota storage was deleted" ); } } await SiteDataTestUtils.clear(); } UrlClassifierTestUtils.cleanupTestTrackers(); } /** * Test that we correctly delete cookies and storage for sites * with an expired interaction permission. */ async function testExpiredInteractionPermission() { await UrlClassifierTestUtils.addTestTrackers(); PermissionTestUtils.add( TRACKING_PAGE, "storageAccessAPI", Services.perms.ALLOW_ACTION, Services.perms.EXPIRE_TIME, Date.now() + 500 ); for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { SiteDataTestUtils.addToLocalStorage(url); SiteDataTestUtils.addToCookies({ origin: url }); await SiteDataTestUtils.addToIndexedDB(url); } // Purge while storage access permission exists. await PurgeTrackerService.purgeTrackingCookieJars(); for (let url of [ TRACKING_PAGE, TRACKING_PAGE2, FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3, ]) { ok( SiteDataTestUtils.hasCookies(url), "cookie remains while storage access permission exists." ); ok( SiteDataTestUtils.hasLocalStorage(url), "localStorage should not have been removed while storage access permission exists." ); Assert.greater( await SiteDataTestUtils.getQuotaUsage(url), 0, `We have data for ${url}` ); } // Run purge after storage access permission has been removed. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(c => setTimeout(c, 500)); await PurgeTrackerService.purgeTrackingCookieJars(); // Cookie should have been removed. for (let url of [TRACKING_PAGE, TRACKING_PAGE2]) { ok( !SiteDataTestUtils.hasCookies(url), "cookie is removed after purge with no storage access permission." ); ok( !SiteDataTestUtils.hasLocalStorage(url), "localStorage should not have been removed while storage access permission exists." ); Assert.equal( await SiteDataTestUtils.getQuotaUsage(url), 0, "quota storage was deleted" ); } // Cookie should not have been removed. for (let url of [FOREIGN_PAGE, FOREIGN_PAGE2, FOREIGN_PAGE3]) { ok( SiteDataTestUtils.hasCookies(url), "cookie remains while storage access permission exists." ); ok( SiteDataTestUtils.hasLocalStorage(url), "localStorage should not have been removed while storage access permission exists." ); } UrlClassifierTestUtils.cleanupTestTrackers(); } add_task(async function () { const cookieBehaviors = [ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN, Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, ]; for (let cookieBehavior of cookieBehaviors) { await setupTest(cookieBehavior); await testIndexedDBAndLocalStorage(); await testBaseDomain(); // example.org and itisatrap.org are hard coded test values on the entity list. await testUserInteraction("https://itisatrap.org"); await testUserInteraction( "https://itisatrap.org^firstPartyDomain=example.net" ); await testQuotaStorage(); await testExpiredInteractionPermission(); } });